quickedit.js 13 KB

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