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