comparison core/modules/quickedit/js/models/FieldModel.es6.js @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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));