comparison core/modules/quickedit/js/quickedit.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 * Attaches behavior for the Quick Edit module.
4 *
5 * Everything happens asynchronously, to allow for:
6 * - dynamically rendered contextual links
7 * - asynchronously retrieved (and cached) per-field in-place editing metadata
8 * - asynchronous setup of in-place editable field and "Quick edit" link.
9 *
10 * To achieve this, there are several queues:
11 * - fieldsMetadataQueue: fields whose metadata still needs to be fetched.
12 * - fieldsAvailableQueue: queue of fields whose metadata is known, and for
13 * which it has been confirmed that the user has permission to edit them.
14 * However, FieldModels will only be created for them once there's a
15 * contextual link for their entity: when it's possible to initiate editing.
16 * - contextualLinksQueue: queue of contextual links on entities for which it
17 * is not yet known whether the user has permission to edit at >=1 of them.
18 */
19
20 (function ($, _, Backbone, Drupal, drupalSettings, JSON, storage) {
21 const options = $.extend(drupalSettings.quickedit,
22 // Merge strings on top of drupalSettings so that they are not mutable.
23 {
24 strings: {
25 quickEdit: Drupal.t('Quick edit'),
26 },
27 },
28 );
29
30 /**
31 * Tracks fields without metadata. Contains objects with the following keys:
32 * - DOM el
33 * - String fieldID
34 * - String entityID
35 */
36 let fieldsMetadataQueue = [];
37
38 /**
39 * Tracks fields ready for use. Contains objects with the following keys:
40 * - DOM el
41 * - String fieldID
42 * - String entityID
43 */
44 let fieldsAvailableQueue = [];
45
46 /**
47 * Tracks contextual links on entities. Contains objects with the following
48 * keys:
49 * - String entityID
50 * - DOM el
51 * - DOM region
52 */
53 let contextualLinksQueue = [];
54
55 /**
56 * Tracks how many instances exist for each unique entity. Contains key-value
57 * pairs:
58 * - String entityID
59 * - Number count
60 */
61 const entityInstancesTracker = {};
62
63 /**
64 *
65 * @type {Drupal~behavior}
66 */
67 Drupal.behaviors.quickedit = {
68 attach(context) {
69 // Initialize the Quick Edit app once per page load.
70 $('body').once('quickedit-init').each(initQuickEdit);
71
72 // Find all in-place editable fields, if any.
73 const $fields = $(context).find('[data-quickedit-field-id]').once('quickedit');
74 if ($fields.length === 0) {
75 return;
76 }
77
78 // Process each entity element: identical entities that appear multiple
79 // times will get a numeric identifier, starting at 0.
80 $(context).find('[data-quickedit-entity-id]').once('quickedit').each((index, entityElement) => {
81 processEntity(entityElement);
82 });
83
84 // Process each field element: queue to be used or to fetch metadata.
85 // When a field is being rerendered after editing, it will be processed
86 // immediately. New fields will be unable to be processed immediately,
87 // but will instead be queued to have their metadata fetched, which occurs
88 // below in fetchMissingMetaData().
89 $fields.each((index, fieldElement) => {
90 processField(fieldElement);
91 });
92
93 // Entities and fields on the page have been detected, try to set up the
94 // contextual links for those entities that already have the necessary
95 // meta- data in the client-side cache.
96 contextualLinksQueue = _.filter(contextualLinksQueue, contextualLink => !initializeEntityContextualLink(contextualLink));
97
98 // Fetch metadata for any fields that are queued to retrieve it.
99 fetchMissingMetadata((fieldElementsWithFreshMetadata) => {
100 // Metadata has been fetched, reprocess fields whose metadata was
101 // missing.
102 _.each(fieldElementsWithFreshMetadata, processField);
103
104 // Metadata has been fetched, try to set up more contextual links now.
105 contextualLinksQueue = _.filter(contextualLinksQueue, contextualLink => !initializeEntityContextualLink(contextualLink));
106 });
107 },
108 detach(context, settings, trigger) {
109 if (trigger === 'unload') {
110 deleteContainedModelsAndQueues($(context));
111 }
112 },
113 };
114
115 /**
116 *
117 * @namespace
118 */
119 Drupal.quickedit = {
120
121 /**
122 * A {@link Drupal.quickedit.AppView} instance.
123 */
124 app: null,
125
126 /**
127 * @type {object}
128 *
129 * @prop {Array.<Drupal.quickedit.EntityModel>} entities
130 * @prop {Array.<Drupal.quickedit.FieldModel>} fields
131 */
132 collections: {
133 // All in-place editable entities (Drupal.quickedit.EntityModel) on the
134 // page.
135 entities: null,
136 // All in-place editable fields (Drupal.quickedit.FieldModel) on the page.
137 fields: null,
138 },
139
140 /**
141 * In-place editors will register themselves in this object.
142 *
143 * @namespace
144 */
145 editors: {},
146
147 /**
148 * Per-field metadata that indicates whether in-place editing is allowed,
149 * which in-place editor should be used, etc.
150 *
151 * @namespace
152 */
153 metadata: {
154
155 /**
156 * Check if a field exists in storage.
157 *
158 * @param {string} fieldID
159 * The field id to check.
160 *
161 * @return {bool}
162 * Whether it was found or not.
163 */
164 has(fieldID) {
165 return storage.getItem(this._prefixFieldID(fieldID)) !== null;
166 },
167
168 /**
169 * Add metadata to a field id.
170 *
171 * @param {string} fieldID
172 * The field ID to add data to.
173 * @param {object} metadata
174 * Metadata to add.
175 */
176 add(fieldID, metadata) {
177 storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata));
178 },
179
180 /**
181 * Get a key from a field id.
182 *
183 * @param {string} fieldID
184 * The field ID to check.
185 * @param {string} [key]
186 * The key to check. If empty, will return all metadata.
187 *
188 * @return {object|*}
189 * The value for the key, if defined. Otherwise will return all metadata
190 * for the specified field id.
191 *
192 */
193 get(fieldID, key) {
194 const metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID)));
195 return (typeof key === 'undefined') ? metadata : metadata[key];
196 },
197
198 /**
199 * Prefix the field id.
200 *
201 * @param {string} fieldID
202 * The field id to prefix.
203 *
204 * @return {string}
205 * A prefixed field id.
206 */
207 _prefixFieldID(fieldID) {
208 return `Drupal.quickedit.metadata.${fieldID}`;
209 },
210
211 /**
212 * Unprefix the field id.
213 *
214 * @param {string} fieldID
215 * The field id to unprefix.
216 *
217 * @return {string}
218 * An unprefixed field id.
219 */
220 _unprefixFieldID(fieldID) {
221 // Strip "Drupal.quickedit.metadata.", which is 26 characters long.
222 return fieldID.substring(26);
223 },
224
225 /**
226 * Intersection calculation.
227 *
228 * @param {Array} fieldIDs
229 * An array of field ids to compare to prefix field id.
230 *
231 * @return {Array}
232 * The intersection found.
233 */
234 intersection(fieldIDs) {
235 const prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID);
236 const intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage));
237 return _.map(intersection, this._unprefixFieldID);
238 },
239 },
240 };
241
242 // Clear the Quick Edit metadata cache whenever the current user's set of
243 // permissions changes.
244 const permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash');
245 const permissionsHashValue = storage.getItem(permissionsHashKey);
246 const permissionsHash = drupalSettings.user.permissionsHash;
247 if (permissionsHashValue !== permissionsHash) {
248 if (typeof permissionsHash === 'string') {
249 _.chain(storage).keys().each((key) => {
250 if (key.substring(0, 26) === 'Drupal.quickedit.metadata.') {
251 storage.removeItem(key);
252 }
253 });
254 }
255 storage.setItem(permissionsHashKey, permissionsHash);
256 }
257
258 /**
259 * Detect contextual links on entities annotated by quickedit.
260 *
261 * Queue contextual links to be processed.
262 *
263 * @param {jQuery.Event} event
264 * The `drupalContextualLinkAdded` event.
265 * @param {object} data
266 * An object containing the data relevant to the event.
267 *
268 * @listens event:drupalContextualLinkAdded
269 */
270 $(document).on('drupalContextualLinkAdded', (event, data) => {
271 if (data.$region.is('[data-quickedit-entity-id]')) {
272 // If the contextual link is cached on the client side, an entity instance
273 // will not yet have been assigned. So assign one.
274 if (!data.$region.is('[data-quickedit-entity-instance-id]')) {
275 data.$region.once('quickedit');
276 processEntity(data.$region.get(0));
277 }
278 const contextualLink = {
279 entityID: data.$region.attr('data-quickedit-entity-id'),
280 entityInstanceID: data.$region.attr('data-quickedit-entity-instance-id'),
281 el: data.$el[0],
282 region: data.$region[0],
283 };
284 // Set up contextual links for this, otherwise queue it to be set up
285 // later.
286 if (!initializeEntityContextualLink(contextualLink)) {
287 contextualLinksQueue.push(contextualLink);
288 }
289 }
290 });
291
292 /**
293 * Extracts the entity ID from a field ID.
294 *
295 * @param {string} fieldID
296 * A field ID: a string of the format
297 * `<entity type>/<id>/<field name>/<language>/<view mode>`.
298 *
299 * @return {string}
300 * An entity ID: a string of the format `<entity type>/<id>`.
301 */
302 function extractEntityID(fieldID) {
303 return fieldID.split('/').slice(0, 2).join('/');
304 }
305
306 /**
307 * Initialize the Quick Edit app.
308 *
309 * @param {HTMLElement} bodyElement
310 * This document's body element.
311 */
312 function initQuickEdit(bodyElement) {
313 Drupal.quickedit.collections.entities = new Drupal.quickedit.EntityCollection();
314 Drupal.quickedit.collections.fields = new Drupal.quickedit.FieldCollection();
315
316 // Instantiate AppModel (application state) and AppView, which is the
317 // controller of the whole in-place editing experience.
318 Drupal.quickedit.app = new Drupal.quickedit.AppView({
319 el: bodyElement,
320 model: new Drupal.quickedit.AppModel(),
321 entitiesCollection: Drupal.quickedit.collections.entities,
322 fieldsCollection: Drupal.quickedit.collections.fields,
323 });
324 }
325
326 /**
327 * Assigns the entity an instance ID.
328 *
329 * @param {HTMLElement} entityElement
330 * A Drupal Entity API entity's DOM element with a data-quickedit-entity-id
331 * attribute.
332 */
333 function processEntity(entityElement) {
334 const entityID = entityElement.getAttribute('data-quickedit-entity-id');
335 if (!entityInstancesTracker.hasOwnProperty(entityID)) {
336 entityInstancesTracker[entityID] = 0;
337 }
338 else {
339 entityInstancesTracker[entityID]++;
340 }
341
342 // Set the calculated entity instance ID for this element.
343 const entityInstanceID = entityInstancesTracker[entityID];
344 entityElement.setAttribute('data-quickedit-entity-instance-id', entityInstanceID);
345 }
346
347 /**
348 * Fetch the field's metadata; queue or initialize it (if EntityModel exists).
349 *
350 * @param {HTMLElement} fieldElement
351 * A Drupal Field API field's DOM element with a data-quickedit-field-id
352 * attribute.
353 */
354 function processField(fieldElement) {
355 const metadata = Drupal.quickedit.metadata;
356 const fieldID = fieldElement.getAttribute('data-quickedit-field-id');
357 const entityID = extractEntityID(fieldID);
358 // Figure out the instance ID by looking at the ancestor
359 // [data-quickedit-entity-id] element's data-quickedit-entity-instance-id
360 // attribute.
361 const entityElementSelector = `[data-quickedit-entity-id="${entityID}"]`;
362 const $entityElement = $(entityElementSelector);
363
364 // If there are no elements returned from `entityElementSelector`
365 // throw an error. Check the browser console for this message.
366 if (!$entityElement.length) {
367 throw `Quick Edit could not associate the rendered entity field markup (with [data-quickedit-field-id="${fieldID}"]) with the corresponding rendered entity markup: no parent DOM node found with [data-quickedit-entity-id="${entityID}"]. This is typically caused by the theme's template for this entity type forgetting to print the attributes.`;
368 }
369 let entityElement = $(fieldElement).closest($entityElement);
370
371 // In the case of a full entity view page, the entity title is rendered
372 // outside of "the entity DOM node": it's rendered as the page title. So in
373 // this case, we find the lowest common parent element (deepest in the tree)
374 // and consider that the entity element.
375 if (entityElement.length === 0) {
376 const $lowestCommonParent = $entityElement.parents().has(fieldElement).first();
377 entityElement = $lowestCommonParent.find($entityElement);
378 }
379 const entityInstanceID = entityElement
380 .get(0)
381 .getAttribute('data-quickedit-entity-instance-id');
382
383 // Early-return if metadata for this field is missing.
384 if (!metadata.has(fieldID)) {
385 fieldsMetadataQueue.push({
386 el: fieldElement,
387 fieldID,
388 entityID,
389 entityInstanceID,
390 });
391 return;
392 }
393 // Early-return if the user is not allowed to in-place edit this field.
394 if (metadata.get(fieldID, 'access') !== true) {
395 return;
396 }
397
398 // If an EntityModel for this field already exists (and hence also a "Quick
399 // edit" contextual link), then initialize it immediately.
400 if (Drupal.quickedit.collections.entities.findWhere({ entityID, entityInstanceID })) {
401 initializeField(fieldElement, fieldID, entityID, entityInstanceID);
402 }
403 // Otherwise: queue the field. It is now available to be set up when its
404 // corresponding entity becomes in-place editable.
405 else {
406 fieldsAvailableQueue.push({ el: fieldElement, fieldID, entityID, entityInstanceID });
407 }
408 }
409
410 /**
411 * Initialize a field; create FieldModel.
412 *
413 * @param {HTMLElement} fieldElement
414 * The field's DOM element.
415 * @param {string} fieldID
416 * The field's ID.
417 * @param {string} entityID
418 * The field's entity's ID.
419 * @param {string} entityInstanceID
420 * The field's entity's instance ID.
421 */
422 function initializeField(fieldElement, fieldID, entityID, entityInstanceID) {
423 const entity = Drupal.quickedit.collections.entities.findWhere({
424 entityID,
425 entityInstanceID,
426 });
427
428 $(fieldElement).addClass('quickedit-field');
429
430 // The FieldModel stores the state of an in-place editable entity field.
431 const field = new Drupal.quickedit.FieldModel({
432 el: fieldElement,
433 fieldID,
434 id: `${fieldID}[${entity.get('entityInstanceID')}]`,
435 entity,
436 metadata: Drupal.quickedit.metadata.get(fieldID),
437 acceptStateChange: _.bind(Drupal.quickedit.app.acceptEditorStateChange, Drupal.quickedit.app),
438 });
439
440 // Track all fields on the page.
441 Drupal.quickedit.collections.fields.add(field);
442 }
443
444 /**
445 * Fetches metadata for fields whose metadata is missing.
446 *
447 * Fields whose metadata is missing are tracked at fieldsMetadataQueue.
448 *
449 * @param {function} callback
450 * A callback function that receives field elements whose metadata will just
451 * have been fetched.
452 */
453 function fetchMissingMetadata(callback) {
454 if (fieldsMetadataQueue.length) {
455 const fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID');
456 const fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
457 let entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true);
458 // Ensure we only request entityIDs for which we don't have metadata yet.
459 entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs));
460 fieldsMetadataQueue = [];
461
462 $.ajax({
463 url: Drupal.url('quickedit/metadata'),
464 type: 'POST',
465 data: {
466 'fields[]': fieldIDs,
467 'entities[]': entityIDs,
468 },
469 dataType: 'json',
470 success(results) {
471 // Store the metadata.
472 _.each(results, (fieldMetadata, fieldID) => {
473 Drupal.quickedit.metadata.add(fieldID, fieldMetadata);
474 });
475
476 callback(fieldElementsWithoutMetadata);
477 },
478 });
479 }
480 }
481
482 /**
483 * Loads missing in-place editor's attachments (JavaScript and CSS files).
484 *
485 * Missing in-place editors are those whose fields are actively being used on
486 * the page but don't have.
487 *
488 * @param {function} callback
489 * Callback function to be called when the missing in-place editors (if any)
490 * have been inserted into the DOM. i.e. they may still be loading.
491 */
492 function loadMissingEditors(callback) {
493 const loadedEditors = _.keys(Drupal.quickedit.editors);
494 let missingEditors = [];
495 Drupal.quickedit.collections.fields.each((fieldModel) => {
496 const metadata = Drupal.quickedit.metadata.get(fieldModel.get('fieldID'));
497 if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
498 missingEditors.push(metadata.editor);
499 // Set a stub, to prevent subsequent calls to loadMissingEditors() from
500 // loading the same in-place editor again. Loading an in-place editor
501 // requires talking to a server, to download its JavaScript, then
502 // executing its JavaScript, and only then its Drupal.quickedit.editors
503 // entry will be set.
504 Drupal.quickedit.editors[metadata.editor] = false;
505 }
506 });
507 missingEditors = _.uniq(missingEditors);
508 if (missingEditors.length === 0) {
509 callback();
510 return;
511 }
512
513 // @see https://www.drupal.org/node/2029999.
514 // Create a Drupal.Ajax instance to load the form.
515 const loadEditorsAjax = Drupal.ajax({
516 url: Drupal.url('quickedit/attachments'),
517 submit: { 'editors[]': missingEditors },
518 });
519 // Implement a scoped insert AJAX command: calls the callback after all AJAX
520 // command functions have been executed (hence the deferred calling).
521 const realInsert = Drupal.AjaxCommands.prototype.insert;
522 loadEditorsAjax.commands.insert = function (ajax, response, status) {
523 _.defer(callback);
524 realInsert(ajax, response, status);
525 };
526 // Trigger the AJAX request, which will should return AJAX commands to
527 // insert any missing attachments.
528 loadEditorsAjax.execute();
529 }
530
531 /**
532 * Attempts to set up a "Quick edit" link and corresponding EntityModel.
533 *
534 * @param {object} contextualLink
535 * An object with the following properties:
536 * - String entityID: a Quick Edit entity identifier, e.g. "node/1" or
537 * "block_content/5".
538 * - String entityInstanceID: a Quick Edit entity instance identifier,
539 * e.g. 0, 1 or n (depending on whether it's the first, second, or n+1st
540 * instance of this entity).
541 * - DOM el: element pointing to the contextual links placeholder for this
542 * entity.
543 * - DOM region: element pointing to the contextual region of this entity.
544 *
545 * @return {bool}
546 * Returns true when a contextual the given contextual link metadata can be
547 * removed from the queue (either because the contextual link has been set
548 * up or because it is certain that in-place editing is not allowed for any
549 * of its fields). Returns false otherwise.
550 */
551 function initializeEntityContextualLink(contextualLink) {
552 const metadata = Drupal.quickedit.metadata;
553 // Check if the user has permission to edit at least one of them.
554 function hasFieldWithPermission(fieldIDs) {
555 for (let i = 0; i < fieldIDs.length; i++) {
556 const fieldID = fieldIDs[i];
557 if (metadata.get(fieldID, 'access') === true) {
558 return true;
559 }
560 }
561 return false;
562 }
563
564 // Checks if the metadata for all given field IDs exists.
565 function allMetadataExists(fieldIDs) {
566 return fieldIDs.length === metadata.intersection(fieldIDs).length;
567 }
568
569 // Find all fields for this entity instance and collect their field IDs.
570 const fields = _.where(fieldsAvailableQueue, {
571 entityID: contextualLink.entityID,
572 entityInstanceID: contextualLink.entityInstanceID,
573 });
574 const fieldIDs = _.pluck(fields, 'fieldID');
575
576 // No fields found yet.
577 if (fieldIDs.length === 0) {
578 return false;
579 }
580 // The entity for the given contextual link contains at least one field that
581 // the current user may edit in-place; instantiate EntityModel,
582 // EntityDecorationView and ContextualLinkView.
583 else if (hasFieldWithPermission(fieldIDs)) {
584 const entityModel = new Drupal.quickedit.EntityModel({
585 el: contextualLink.region,
586 entityID: contextualLink.entityID,
587 entityInstanceID: contextualLink.entityInstanceID,
588 id: `${contextualLink.entityID}[${contextualLink.entityInstanceID}]`,
589 label: Drupal.quickedit.metadata.get(contextualLink.entityID, 'label'),
590 });
591 Drupal.quickedit.collections.entities.add(entityModel);
592 // Create an EntityDecorationView associated with the root DOM node of the
593 // entity.
594 const entityDecorationView = new Drupal.quickedit.EntityDecorationView({
595 el: contextualLink.region,
596 model: entityModel,
597 });
598 entityModel.set('entityDecorationView', entityDecorationView);
599
600 // Initialize all queued fields within this entity (creates FieldModels).
601 _.each(fields, (field) => {
602 initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
603 });
604 fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
605
606 // Initialization should only be called once. Use Underscore's once method
607 // to get a one-time use version of the function.
608 const initContextualLink = _.once(() => {
609 const $links = $(contextualLink.el).find('.contextual-links');
610 const contextualLinkView = new Drupal.quickedit.ContextualLinkView($.extend({
611 el: $('<li class="quickedit"><a href="" role="button" aria-pressed="false"></a></li>').prependTo($links),
612 model: entityModel,
613 appModel: Drupal.quickedit.app.model,
614 }, options));
615 entityModel.set('contextualLinkView', contextualLinkView);
616 });
617
618 // Set up ContextualLinkView after loading any missing in-place editors.
619 loadMissingEditors(initContextualLink);
620
621 return true;
622 }
623 // There was not at least one field that the current user may edit in-place,
624 // even though the metadata for all fields within this entity is available.
625 else if (allMetadataExists(fieldIDs)) {
626 return true;
627 }
628
629 return false;
630 }
631
632 /**
633 * Delete models and queue items that are contained within a given context.
634 *
635 * Deletes any contained EntityModels (plus their associated FieldModels and
636 * ContextualLinkView) and FieldModels, as well as the corresponding queues.
637 *
638 * After EntityModels, FieldModels must also be deleted, because it is
639 * possible in Drupal for a field DOM element to exist outside of the entity
640 * DOM element, e.g. when viewing the full node, the title of the node is not
641 * rendered within the node (the entity) but as the page title.
642 *
643 * Note: this will not delete an entity that is actively being in-place
644 * edited.
645 *
646 * @param {jQuery} $context
647 * The context within which to delete.
648 */
649 function deleteContainedModelsAndQueues($context) {
650 $context.find('[data-quickedit-entity-id]').addBack('[data-quickedit-entity-id]').each((index, entityElement) => {
651 // Delete entity model.
652 const entityModel = Drupal.quickedit.collections.entities.findWhere({ el: entityElement });
653 if (entityModel) {
654 const contextualLinkView = entityModel.get('contextualLinkView');
655 contextualLinkView.undelegateEvents();
656 contextualLinkView.remove();
657 // Remove the EntityDecorationView.
658 entityModel.get('entityDecorationView').remove();
659 // Destroy the EntityModel; this will also destroy its FieldModels.
660 entityModel.destroy();
661 }
662
663 // Filter queue.
664 function hasOtherRegion(contextualLink) {
665 return contextualLink.region !== entityElement;
666 }
667
668 contextualLinksQueue = _.filter(contextualLinksQueue, hasOtherRegion);
669 });
670
671 $context.find('[data-quickedit-field-id]').addBack('[data-quickedit-field-id]').each((index, fieldElement) => {
672 // Delete field models.
673 Drupal.quickedit.collections.fields.chain()
674 .filter(fieldModel => fieldModel.get('el') === fieldElement)
675 .invoke('destroy');
676
677 // Filter queues.
678 function hasOtherFieldElement(field) {
679 return field.el !== fieldElement;
680 }
681
682 fieldsMetadataQueue = _.filter(fieldsMetadataQueue, hasOtherFieldElement);
683 fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement);
684 });
685 }
686 }(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage));