quickedit.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /**
  2. * DO NOT EDIT THIS FILE.
  3. * See the following change record for more information,
  4. * https://www.drupal.org/node/2815083
  5. * @preserve
  6. **/
  7. (function ($, _, Backbone, Drupal, drupalSettings, JSON, storage) {
  8. var options = $.extend(drupalSettings.quickedit, {
  9. strings: {
  10. quickEdit: Drupal.t('Quick edit')
  11. }
  12. });
  13. var fieldsMetadataQueue = [];
  14. var fieldsAvailableQueue = [];
  15. var contextualLinksQueue = [];
  16. var entityInstancesTracker = {};
  17. Drupal.behaviors.quickedit = {
  18. attach: function attach(context) {
  19. $('body').once('quickedit-init').each(initQuickEdit);
  20. var $fields = $(context).find('[data-quickedit-field-id]').once('quickedit');
  21. if ($fields.length === 0) {
  22. return;
  23. }
  24. $(context).find('[data-quickedit-entity-id]').once('quickedit').each(function (index, entityElement) {
  25. processEntity(entityElement);
  26. });
  27. $fields.each(function (index, fieldElement) {
  28. processField(fieldElement);
  29. });
  30. contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
  31. return !initializeEntityContextualLink(contextualLink);
  32. });
  33. fetchMissingMetadata(function (fieldElementsWithFreshMetadata) {
  34. _.each(fieldElementsWithFreshMetadata, processField);
  35. contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) {
  36. return !initializeEntityContextualLink(contextualLink);
  37. });
  38. });
  39. },
  40. detach: function detach(context, settings, trigger) {
  41. if (trigger === 'unload') {
  42. deleteContainedModelsAndQueues($(context));
  43. }
  44. }
  45. };
  46. Drupal.quickedit = {
  47. app: null,
  48. collections: {
  49. entities: null,
  50. fields: null
  51. },
  52. editors: {},
  53. metadata: {
  54. has: function has(fieldID) {
  55. return storage.getItem(this._prefixFieldID(fieldID)) !== null;
  56. },
  57. add: function add(fieldID, metadata) {
  58. storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata));
  59. },
  60. get: function get(fieldID, key) {
  61. var metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID)));
  62. return typeof key === 'undefined' ? metadata : metadata[key];
  63. },
  64. _prefixFieldID: function _prefixFieldID(fieldID) {
  65. return 'Drupal.quickedit.metadata.' + fieldID;
  66. },
  67. _unprefixFieldID: function _unprefixFieldID(fieldID) {
  68. return fieldID.substring(26);
  69. },
  70. intersection: function intersection(fieldIDs) {
  71. var prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID);
  72. var intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage));
  73. return _.map(intersection, this._unprefixFieldID);
  74. }
  75. }
  76. };
  77. var permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash');
  78. var permissionsHashValue = storage.getItem(permissionsHashKey);
  79. var permissionsHash = drupalSettings.user.permissionsHash;
  80. if (permissionsHashValue !== permissionsHash) {
  81. if (typeof permissionsHash === 'string') {
  82. _.chain(storage).keys().each(function (key) {
  83. if (key.substring(0, 26) === 'Drupal.quickedit.metadata.') {
  84. storage.removeItem(key);
  85. }
  86. });
  87. }
  88. storage.setItem(permissionsHashKey, permissionsHash);
  89. }
  90. $(document).on('drupalContextualLinkAdded', function (event, data) {
  91. if (data.$region.is('[data-quickedit-entity-id]')) {
  92. if (!data.$region.is('[data-quickedit-entity-instance-id]')) {
  93. data.$region.once('quickedit');
  94. processEntity(data.$region.get(0));
  95. }
  96. var contextualLink = {
  97. entityID: data.$region.attr('data-quickedit-entity-id'),
  98. entityInstanceID: data.$region.attr('data-quickedit-entity-instance-id'),
  99. el: data.$el[0],
  100. region: data.$region[0]
  101. };
  102. if (!initializeEntityContextualLink(contextualLink)) {
  103. contextualLinksQueue.push(contextualLink);
  104. }
  105. }
  106. });
  107. function extractEntityID(fieldID) {
  108. return fieldID.split('/').slice(0, 2).join('/');
  109. }
  110. function initQuickEdit(bodyElement) {
  111. Drupal.quickedit.collections.entities = new Drupal.quickedit.EntityCollection();
  112. Drupal.quickedit.collections.fields = new Drupal.quickedit.FieldCollection();
  113. Drupal.quickedit.app = new Drupal.quickedit.AppView({
  114. el: bodyElement,
  115. model: new Drupal.quickedit.AppModel(),
  116. entitiesCollection: Drupal.quickedit.collections.entities,
  117. fieldsCollection: Drupal.quickedit.collections.fields
  118. });
  119. }
  120. function processEntity(entityElement) {
  121. var entityID = entityElement.getAttribute('data-quickedit-entity-id');
  122. if (!entityInstancesTracker.hasOwnProperty(entityID)) {
  123. entityInstancesTracker[entityID] = 0;
  124. } else {
  125. entityInstancesTracker[entityID]++;
  126. }
  127. var entityInstanceID = entityInstancesTracker[entityID];
  128. entityElement.setAttribute('data-quickedit-entity-instance-id', entityInstanceID);
  129. }
  130. function processField(fieldElement) {
  131. var metadata = Drupal.quickedit.metadata;
  132. var fieldID = fieldElement.getAttribute('data-quickedit-field-id');
  133. var entityID = extractEntityID(fieldID);
  134. var entityElementSelector = '[data-quickedit-entity-id="' + entityID + '"]';
  135. var $entityElement = $(entityElementSelector);
  136. if (!$entityElement.length) {
  137. throw new Error('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.');
  138. }
  139. var entityElement = $(fieldElement).closest($entityElement);
  140. if (entityElement.length === 0) {
  141. var $lowestCommonParent = $entityElement.parents().has(fieldElement).first();
  142. entityElement = $lowestCommonParent.find($entityElement);
  143. }
  144. var entityInstanceID = entityElement.get(0).getAttribute('data-quickedit-entity-instance-id');
  145. if (!metadata.has(fieldID)) {
  146. fieldsMetadataQueue.push({
  147. el: fieldElement,
  148. fieldID: fieldID,
  149. entityID: entityID,
  150. entityInstanceID: entityInstanceID
  151. });
  152. return;
  153. }
  154. if (metadata.get(fieldID, 'access') !== true) {
  155. return;
  156. }
  157. if (Drupal.quickedit.collections.entities.findWhere({ entityID: entityID, entityInstanceID: entityInstanceID })) {
  158. initializeField(fieldElement, fieldID, entityID, entityInstanceID);
  159. } else {
  160. fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID });
  161. }
  162. }
  163. function initializeField(fieldElement, fieldID, entityID, entityInstanceID) {
  164. var entity = Drupal.quickedit.collections.entities.findWhere({
  165. entityID: entityID,
  166. entityInstanceID: entityInstanceID
  167. });
  168. $(fieldElement).addClass('quickedit-field');
  169. var field = new Drupal.quickedit.FieldModel({
  170. el: fieldElement,
  171. fieldID: fieldID,
  172. id: fieldID + '[' + entity.get('entityInstanceID') + ']',
  173. entity: entity,
  174. metadata: Drupal.quickedit.metadata.get(fieldID),
  175. acceptStateChange: _.bind(Drupal.quickedit.app.acceptEditorStateChange, Drupal.quickedit.app)
  176. });
  177. Drupal.quickedit.collections.fields.add(field);
  178. }
  179. function fetchMissingMetadata(callback) {
  180. if (fieldsMetadataQueue.length) {
  181. var fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID');
  182. var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
  183. var entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true);
  184. entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs));
  185. fieldsMetadataQueue = [];
  186. $.ajax({
  187. url: Drupal.url('quickedit/metadata'),
  188. type: 'POST',
  189. data: {
  190. 'fields[]': fieldIDs,
  191. 'entities[]': entityIDs
  192. },
  193. dataType: 'json',
  194. success: function success(results) {
  195. _.each(results, function (fieldMetadata, fieldID) {
  196. Drupal.quickedit.metadata.add(fieldID, fieldMetadata);
  197. });
  198. callback(fieldElementsWithoutMetadata);
  199. }
  200. });
  201. }
  202. }
  203. function loadMissingEditors(callback) {
  204. var loadedEditors = _.keys(Drupal.quickedit.editors);
  205. var missingEditors = [];
  206. Drupal.quickedit.collections.fields.each(function (fieldModel) {
  207. var metadata = Drupal.quickedit.metadata.get(fieldModel.get('fieldID'));
  208. if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
  209. missingEditors.push(metadata.editor);
  210. Drupal.quickedit.editors[metadata.editor] = false;
  211. }
  212. });
  213. missingEditors = _.uniq(missingEditors);
  214. if (missingEditors.length === 0) {
  215. callback();
  216. return;
  217. }
  218. var loadEditorsAjax = Drupal.ajax({
  219. url: Drupal.url('quickedit/attachments'),
  220. submit: { 'editors[]': missingEditors }
  221. });
  222. var realInsert = Drupal.AjaxCommands.prototype.insert;
  223. loadEditorsAjax.commands.insert = function (ajax, response, status) {
  224. _.defer(callback);
  225. realInsert(ajax, response, status);
  226. };
  227. loadEditorsAjax.execute();
  228. }
  229. function initializeEntityContextualLink(contextualLink) {
  230. var metadata = Drupal.quickedit.metadata;
  231. function hasFieldWithPermission(fieldIDs) {
  232. for (var i = 0; i < fieldIDs.length; i++) {
  233. var fieldID = fieldIDs[i];
  234. if (metadata.get(fieldID, 'access') === true) {
  235. return true;
  236. }
  237. }
  238. return false;
  239. }
  240. function allMetadataExists(fieldIDs) {
  241. return fieldIDs.length === metadata.intersection(fieldIDs).length;
  242. }
  243. var fields = _.where(fieldsAvailableQueue, {
  244. entityID: contextualLink.entityID,
  245. entityInstanceID: contextualLink.entityInstanceID
  246. });
  247. var fieldIDs = _.pluck(fields, 'fieldID');
  248. if (fieldIDs.length === 0) {
  249. return false;
  250. } else if (hasFieldWithPermission(fieldIDs)) {
  251. var entityModel = new Drupal.quickedit.EntityModel({
  252. el: contextualLink.region,
  253. entityID: contextualLink.entityID,
  254. entityInstanceID: contextualLink.entityInstanceID,
  255. id: contextualLink.entityID + '[' + contextualLink.entityInstanceID + ']',
  256. label: Drupal.quickedit.metadata.get(contextualLink.entityID, 'label')
  257. });
  258. Drupal.quickedit.collections.entities.add(entityModel);
  259. var entityDecorationView = new Drupal.quickedit.EntityDecorationView({
  260. el: contextualLink.region,
  261. model: entityModel
  262. });
  263. entityModel.set('entityDecorationView', entityDecorationView);
  264. _.each(fields, function (field) {
  265. initializeField(field.el, field.fieldID, contextualLink.entityID, contextualLink.entityInstanceID);
  266. });
  267. fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
  268. var initContextualLink = _.once(function () {
  269. var $links = $(contextualLink.el).find('.contextual-links');
  270. var contextualLinkView = new Drupal.quickedit.ContextualLinkView($.extend({
  271. el: $('<li class="quickedit"><a href="" role="button" aria-pressed="false"></a></li>').prependTo($links),
  272. model: entityModel,
  273. appModel: Drupal.quickedit.app.model
  274. }, options));
  275. entityModel.set('contextualLinkView', contextualLinkView);
  276. });
  277. loadMissingEditors(initContextualLink);
  278. return true;
  279. } else if (allMetadataExists(fieldIDs)) {
  280. return true;
  281. }
  282. return false;
  283. }
  284. function deleteContainedModelsAndQueues($context) {
  285. $context.find('[data-quickedit-entity-id]').addBack('[data-quickedit-entity-id]').each(function (index, entityElement) {
  286. var entityModel = Drupal.quickedit.collections.entities.findWhere({ el: entityElement });
  287. if (entityModel) {
  288. var contextualLinkView = entityModel.get('contextualLinkView');
  289. contextualLinkView.undelegateEvents();
  290. contextualLinkView.remove();
  291. entityModel.get('entityDecorationView').remove();
  292. entityModel.destroy();
  293. }
  294. function hasOtherRegion(contextualLink) {
  295. return contextualLink.region !== entityElement;
  296. }
  297. contextualLinksQueue = _.filter(contextualLinksQueue, hasOtherRegion);
  298. });
  299. $context.find('[data-quickedit-field-id]').addBack('[data-quickedit-field-id]').each(function (index, fieldElement) {
  300. Drupal.quickedit.collections.fields.chain().filter(function (fieldModel) {
  301. return fieldModel.get('el') === fieldElement;
  302. }).invoke('destroy');
  303. function hasOtherFieldElement(field) {
  304. return field.el !== fieldElement;
  305. }
  306. fieldsMetadataQueue = _.filter(fieldsMetadataQueue, hasOtherFieldElement);
  307. fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement);
  308. });
  309. }
  310. })(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage);