annotate core/modules/quickedit/js/models/FieldModel.es6.js @ 15:e200cb7efeb3

Update Drupal core to 8.5.3 via Composer
author Chris Cannam
date Thu, 26 Apr 2018 11:26:54 +0100
parents 1fec387a4317
children 129ea1e6d783
rev   line source
Chris@0 1 /**
Chris@0 2 * @file
Chris@0 3 * A Backbone Model for the state of an in-place editable field in the DOM.
Chris@0 4 */
Chris@0 5
Chris@0 6 (function (_, Backbone, Drupal) {
Chris@0 7 Drupal.quickedit.FieldModel = Drupal.quickedit.BaseModel.extend(/** @lends Drupal.quickedit.FieldModel# */{
Chris@0 8
Chris@0 9 /**
Chris@0 10 * @type {object}
Chris@0 11 */
Chris@0 12 defaults: /** @lends Drupal.quickedit.FieldModel# */{
Chris@0 13
Chris@0 14 /**
Chris@0 15 * The DOM element that represents this field. It may seem bizarre to have
Chris@0 16 * a DOM element in a Backbone Model, but we need to be able to map fields
Chris@0 17 * in the DOM to FieldModels in memory.
Chris@0 18 */
Chris@0 19 el: null,
Chris@0 20
Chris@0 21 /**
Chris@0 22 * A field ID, of the form
Chris@0 23 * `<entity type>/<id>/<field name>/<language>/<view mode>`
Chris@0 24 *
Chris@0 25 * @example
Chris@0 26 * "node/1/field_tags/und/full"
Chris@0 27 */
Chris@0 28 fieldID: null,
Chris@0 29
Chris@0 30 /**
Chris@0 31 * The unique ID of this field within its entity instance on the page, of
Chris@0 32 * the form `<entity type>/<id>/<field name>/<language>/<view
Chris@0 33 * mode>[entity instance ID]`.
Chris@0 34 *
Chris@0 35 * @example
Chris@0 36 * "node/1/field_tags/und/full[0]"
Chris@0 37 */
Chris@0 38 id: null,
Chris@0 39
Chris@0 40 /**
Chris@0 41 * A {@link Drupal.quickedit.EntityModel}. Its "fields" attribute, which
Chris@0 42 * is a FieldCollection, is automatically updated to include this
Chris@0 43 * FieldModel.
Chris@0 44 */
Chris@0 45 entity: null,
Chris@0 46
Chris@0 47 /**
Chris@0 48 * This field's metadata as returned by the
Chris@0 49 * QuickEditController::metadata().
Chris@0 50 */
Chris@0 51 metadata: null,
Chris@0 52
Chris@0 53 /**
Chris@0 54 * Callback function for validating changes between states. Receives the
Chris@0 55 * previous state, new state, context, and a callback.
Chris@0 56 */
Chris@0 57 acceptStateChange: null,
Chris@0 58
Chris@0 59 /**
Chris@0 60 * A logical field ID, of the form
Chris@0 61 * `<entity type>/<id>/<field name>/<language>`, i.e. the fieldID without
Chris@0 62 * the view mode, to be able to identify other instances of the same
Chris@0 63 * field on the page but rendered in a different view mode.
Chris@0 64 *
Chris@0 65 * @example
Chris@0 66 * "node/1/field_tags/und".
Chris@0 67 */
Chris@0 68 logicalFieldID: null,
Chris@0 69
Chris@0 70 // The attributes below are stateful. The ones above will never change
Chris@0 71 // during the life of a FieldModel instance.
Chris@0 72
Chris@0 73 /**
Chris@0 74 * In-place editing state of this field. Defaults to the initial state.
Chris@0 75 * Possible values: {@link Drupal.quickedit.FieldModel.states}.
Chris@0 76 */
Chris@0 77 state: 'inactive',
Chris@0 78
Chris@0 79 /**
Chris@0 80 * The field is currently in the 'changed' state or one of the following
Chris@0 81 * states in which the field is still changed.
Chris@0 82 */
Chris@0 83 isChanged: false,
Chris@0 84
Chris@0 85 /**
Chris@0 86 * Is tracked by the EntityModel, is mirrored here solely for decorative
Chris@0 87 * purposes: so that FieldDecorationView.renderChanged() can react to it.
Chris@0 88 */
Chris@0 89 inTempStore: false,
Chris@0 90
Chris@0 91 /**
Chris@0 92 * The full HTML representation of this field (with the element that has
Chris@0 93 * the data-quickedit-field-id as the outer element). Used to propagate
Chris@0 94 * changes from this field to other instances of the same field storage.
Chris@0 95 */
Chris@0 96 html: null,
Chris@0 97
Chris@0 98 /**
Chris@0 99 * An object containing the full HTML representations (values) of other
Chris@0 100 * view modes (keys) of this field, for other instances of this field
Chris@0 101 * displayed in a different view mode.
Chris@0 102 */
Chris@0 103 htmlForOtherViewModes: null,
Chris@0 104 },
Chris@0 105
Chris@0 106 /**
Chris@0 107 * State of an in-place editable field in the DOM.
Chris@0 108 *
Chris@0 109 * @constructs
Chris@0 110 *
Chris@0 111 * @augments Drupal.quickedit.BaseModel
Chris@0 112 *
Chris@0 113 * @param {object} options
Chris@0 114 * Options for the field model.
Chris@0 115 */
Chris@0 116 initialize(options) {
Chris@0 117 // Store the original full HTML representation of this field.
Chris@0 118 this.set('html', options.el.outerHTML);
Chris@0 119
Chris@0 120 // Enlist field automatically in the associated entity's field collection.
Chris@0 121 this.get('entity').get('fields').add(this);
Chris@0 122
Chris@0 123 // Automatically generate the logical field ID.
Chris@0 124 this.set('logicalFieldID', this.get('fieldID').split('/').slice(0, 4).join('/'));
Chris@0 125
Chris@0 126 // Call Drupal.quickedit.BaseModel's initialize() method.
Chris@0 127 Drupal.quickedit.BaseModel.prototype.initialize.call(this, options);
Chris@0 128 },
Chris@0 129
Chris@0 130 /**
Chris@0 131 * Destroys the field model.
Chris@0 132 *
Chris@0 133 * @param {object} options
Chris@0 134 * Options for the field model.
Chris@0 135 */
Chris@0 136 destroy(options) {
Chris@0 137 if (this.get('state') !== 'inactive') {
Chris@0 138 throw new Error('FieldModel cannot be destroyed if it is not inactive state.');
Chris@0 139 }
Chris@0 140 Drupal.quickedit.BaseModel.prototype.destroy.call(this, options);
Chris@0 141 },
Chris@0 142
Chris@0 143 /**
Chris@0 144 * @inheritdoc
Chris@0 145 */
Chris@0 146 sync() {
Chris@0 147 // We don't use REST updates to sync.
Chris@0 148
Chris@0 149 },
Chris@0 150
Chris@0 151 /**
Chris@0 152 * Validate function for the field model.
Chris@0 153 *
Chris@0 154 * @param {object} attrs
Chris@0 155 * The attributes changes in the save or set call.
Chris@0 156 * @param {object} options
Chris@0 157 * An object with the following option:
Chris@0 158 * @param {string} [options.reason]
Chris@0 159 * A string that conveys a particular reason to allow for an exceptional
Chris@0 160 * state change.
Chris@0 161 * @param {Array} options.accept-field-states
Chris@0 162 * An array of strings that represent field states that the entities must
Chris@0 163 * be in to validate. For example, if `accept-field-states` is
Chris@0 164 * `['candidate', 'highlighted']`, then all the fields of the entity must
Chris@0 165 * be in either of these two states for the save or set call to
Chris@0 166 * validate and proceed.
Chris@0 167 *
Chris@0 168 * @return {string}
Chris@0 169 * A string to say something about the state of the field model.
Chris@0 170 */
Chris@0 171 validate(attrs, options) {
Chris@0 172 const current = this.get('state');
Chris@0 173 const next = attrs.state;
Chris@0 174 if (current !== next) {
Chris@0 175 // Ensure it's a valid state.
Chris@0 176 if (_.indexOf(this.constructor.states, next) === -1) {
Chris@0 177 return `"${next}" is an invalid state`;
Chris@0 178 }
Chris@0 179 // Check if the acceptStateChange callback accepts it.
Chris@0 180 if (!this.get('acceptStateChange')(current, next, options, this)) {
Chris@0 181 return 'state change not accepted';
Chris@0 182 }
Chris@0 183 }
Chris@0 184 },
Chris@0 185
Chris@0 186 /**
Chris@0 187 * Extracts the entity ID from this field's ID.
Chris@0 188 *
Chris@0 189 * @return {string}
Chris@0 190 * An entity ID: a string of the format `<entity type>/<id>`.
Chris@0 191 */
Chris@0 192 getEntityID() {
Chris@0 193 return this.get('fieldID').split('/').slice(0, 2).join('/');
Chris@0 194 },
Chris@0 195
Chris@0 196 /**
Chris@0 197 * Extracts the view mode ID from this field's ID.
Chris@0 198 *
Chris@0 199 * @return {string}
Chris@0 200 * A view mode ID.
Chris@0 201 */
Chris@0 202 getViewMode() {
Chris@0 203 return this.get('fieldID').split('/').pop();
Chris@0 204 },
Chris@0 205
Chris@0 206 /**
Chris@0 207 * Find other instances of this field with different view modes.
Chris@0 208 *
Chris@0 209 * @return {Array}
Chris@0 210 * An array containing view mode IDs.
Chris@0 211 */
Chris@0 212 findOtherViewModes() {
Chris@0 213 const currentField = this;
Chris@0 214 const otherViewModes = [];
Chris@0 215 Drupal.quickedit.collections.fields
Chris@0 216 // Find all instances of fields that display the same logical field
Chris@0 217 // (same entity, same field, just a different instance and maybe a
Chris@0 218 // different view mode).
Chris@0 219 .where({ logicalFieldID: currentField.get('logicalFieldID') })
Chris@0 220 .forEach((field) => {
Chris@14 221 // Ignore the current field and other fields with the same view mode.
Chris@14 222 if (field !== currentField && field.get('fieldID') !== currentField.get('fieldID')) {
Chris@0 223 otherViewModes.push(field.getViewMode());
Chris@0 224 }
Chris@0 225 });
Chris@0 226 return otherViewModes;
Chris@0 227 },
Chris@0 228
Chris@0 229 }, /** @lends Drupal.quickedit.FieldModel */{
Chris@0 230
Chris@0 231 /**
Chris@0 232 * Sequence of all possible states a field can be in during quickediting.
Chris@0 233 *
Chris@0 234 * @type {Array.<string>}
Chris@0 235 */
Chris@0 236 states: [
Chris@0 237 // The field associated with this FieldModel is linked to an EntityModel;
Chris@0 238 // the user can choose to start in-place editing that entity (and
Chris@0 239 // consequently this field). No in-place editor (EditorView) is associated
Chris@0 240 // with this field, because this field is not being in-place edited.
Chris@0 241 // This is both the initial (not yet in-place editing) and the end state
Chris@0 242 // (finished in-place editing).
Chris@0 243 'inactive',
Chris@0 244 // The user is in-place editing this entity, and this field is a
Chris@0 245 // candidate
Chris@0 246 // for in-place editing. In-place editor should not
Chris@0 247 // - Trigger: user.
Chris@0 248 // - Guarantees: entity is ready, in-place editor (EditorView) is
Chris@0 249 // associated with the field.
Chris@0 250 // - Expected behavior: visual indicators
Chris@0 251 // around the field indicate it is available for in-place editing, no
Chris@0 252 // in-place editor presented yet.
Chris@0 253 'candidate',
Chris@0 254 // User is highlighting this field.
Chris@0 255 // - Trigger: user.
Chris@0 256 // - Guarantees: see 'candidate'.
Chris@0 257 // - Expected behavior: visual indicators to convey highlighting, in-place
Chris@0 258 // editing toolbar shows field's label.
Chris@0 259 'highlighted',
Chris@0 260 // User has activated the in-place editing of this field; in-place editor
Chris@0 261 // is activating.
Chris@0 262 // - Trigger: user.
Chris@0 263 // - Guarantees: see 'candidate'.
Chris@0 264 // - Expected behavior: loading indicator, in-place editor is loading
Chris@0 265 // remote data (e.g. retrieve form from back-end). Upon retrieval of
Chris@0 266 // remote data, the in-place editor transitions the field's state to
Chris@0 267 // 'active'.
Chris@0 268 'activating',
Chris@0 269 // In-place editor has finished loading remote data; ready for use.
Chris@0 270 // - Trigger: in-place editor.
Chris@0 271 // - Guarantees: see 'candidate'.
Chris@0 272 // - Expected behavior: in-place editor for the field is ready for use.
Chris@0 273 'active',
Chris@0 274 // User has modified values in the in-place editor.
Chris@0 275 // - Trigger: user.
Chris@0 276 // - Guarantees: see 'candidate', plus in-place editor is ready for use.
Chris@0 277 // - Expected behavior: visual indicator of change.
Chris@0 278 'changed',
Chris@0 279 // User is saving changed field data in in-place editor to
Chris@0 280 // PrivateTempStore. The save mechanism of the in-place editor is called.
Chris@0 281 // - Trigger: user.
Chris@0 282 // - Guarantees: see 'candidate' and 'active'.
Chris@0 283 // - Expected behavior: saving indicator, in-place editor is saving field
Chris@0 284 // data into PrivateTempStore. Upon successful saving (without
Chris@0 285 // validation errors), the in-place editor transitions the field's state
Chris@0 286 // to 'saved', but to 'invalid' upon failed saving (with validation
Chris@0 287 // errors).
Chris@0 288 'saving',
Chris@0 289 // In-place editor has successfully saved the changed field.
Chris@0 290 // - Trigger: in-place editor.
Chris@0 291 // - Guarantees: see 'candidate' and 'active'.
Chris@0 292 // - Expected behavior: transition back to 'candidate' state because the
Chris@0 293 // deed is done. Then: 1) transition to 'inactive' to allow the field
Chris@0 294 // to be rerendered, 2) destroy the FieldModel (which also destroys
Chris@0 295 // attached views like the EditorView), 3) replace the existing field
Chris@0 296 // HTML with the existing HTML and 4) attach behaviors again so that the
Chris@0 297 // field becomes available again for in-place editing.
Chris@0 298 'saved',
Chris@0 299 // In-place editor has failed to saved the changed field: there were
Chris@0 300 // validation errors.
Chris@0 301 // - Trigger: in-place editor.
Chris@0 302 // - Guarantees: see 'candidate' and 'active'.
Chris@0 303 // - Expected behavior: remain in 'invalid' state, let the user make more
Chris@0 304 // changes so that he can save it again, without validation errors.
Chris@0 305 'invalid',
Chris@0 306 ],
Chris@0 307
Chris@0 308 /**
Chris@0 309 * Indicates whether the 'from' state comes before the 'to' state.
Chris@0 310 *
Chris@0 311 * @param {string} from
Chris@0 312 * One of {@link Drupal.quickedit.FieldModel.states}.
Chris@0 313 * @param {string} to
Chris@0 314 * One of {@link Drupal.quickedit.FieldModel.states}.
Chris@0 315 *
Chris@0 316 * @return {bool}
Chris@0 317 * Whether the 'from' state comes before the 'to' state.
Chris@0 318 */
Chris@0 319 followsStateSequence(from, to) {
Chris@0 320 return _.indexOf(this.states, from) < _.indexOf(this.states, to);
Chris@0 321 },
Chris@0 322
Chris@0 323 });
Chris@0 324
Chris@0 325 /**
Chris@0 326 * @constructor
Chris@0 327 *
Chris@0 328 * @augments Backbone.Collection
Chris@0 329 */
Chris@0 330 Drupal.quickedit.FieldCollection = Backbone.Collection.extend(/** @lends Drupal.quickedit.FieldCollection */{
Chris@0 331
Chris@0 332 /**
Chris@0 333 * @type {Drupal.quickedit.FieldModel}
Chris@0 334 */
Chris@0 335 model: Drupal.quickedit.FieldModel,
Chris@0 336 });
Chris@0 337 }(_, Backbone, Drupal));