PageRenderTime 78ms CodeModel.GetById 23ms RepoModel.GetById 5ms app.codeStats 1ms

/js/models/FieldModel.js

https://gitlab.com/Drulenium-bot/quickedit
JavaScript | 260 lines | 88 code | 22 blank | 150 comment | 8 complexity | 975655b1b4d7bf55cd3f5eaa1f570394 MD5 | raw file
  1. /**
  2. * @file
  3. * A Backbone Model for the state of an in-place editable field in the DOM.
  4. */
  5. (function (_, Backbone, Drupal) {
  6. "use strict";
  7. /**
  8. * State of an in-place editable field in the DOM.
  9. */
  10. Drupal.quickedit.FieldModel = Drupal.quickedit.BaseModel.extend({
  11. defaults: {
  12. // The DOM element that represents this field. It may seem bizarre to have
  13. // a DOM element in a Backbone Model, but we need to be able to map fields
  14. // in the DOM to FieldModels in memory.
  15. el: null,
  16. // A field ID, of the form
  17. // "<entity type>/<id>/<field name>/<language>/<view mode>", e.g.
  18. // "node/1/field_tags/und/full".
  19. fieldID: null,
  20. // The unique ID of this field within its entity instance on the page, of
  21. // the form "<entity type>/<id>/<field name>/<language>/<view mode>[entity instance ID]",
  22. // e.g. "node/1/field_tags/und/full[0]".
  23. id: null,
  24. // A Drupal.quickedit.EntityModel. Its "fields" attribute, which is a
  25. // FieldCollection, is automatically updated to include this FieldModel.
  26. entity: null,
  27. // This field's metadata as returned by the QuickEditController::metadata().
  28. metadata: null,
  29. // Callback function for validating changes between states. Receives the
  30. // previous state, new state, context, and a callback
  31. acceptStateChange: null,
  32. // A logical field ID, of the form
  33. // "<entity type>/<id>/<field name>/<language>", i.e. the fieldID without
  34. // the view mode, to be able to identify other instances of the same field
  35. // on the page but rendered in a different view mode. e.g. "node/1/field_tags/und".
  36. logicalFieldID: null,
  37. // The attributes below are stateful. The ones above will never change
  38. // during the life of a FieldModel instance.
  39. // In-place editing state of this field. Defaults to the initial state.
  40. // Possible values: @see Drupal.quickedit.FieldModel.states.
  41. state: 'inactive',
  42. // The field is currently in the 'changed' state or one of the following
  43. // states in which the field is still changed.
  44. isChanged: false,
  45. // Is tracked by the EntityModel, is mirrored here solely for decorative
  46. // purposes: so that FieldDecorationView.renderChanged() can react to it.
  47. inTempStore: false,
  48. // The full HTML representation of this field (with the element that has
  49. // the data-quickedit-field-id as the outer element). Used to propagate
  50. // changes from this field instance to other instances of the same field.
  51. html: null,
  52. // An object containing the full HTML representations (values) of other view
  53. // modes (keys) of this field, for other instances of this field displayed
  54. // in a different view mode.
  55. htmlForOtherViewModes: null
  56. },
  57. /**
  58. * {@inheritdoc}
  59. */
  60. initialize: function (options) {
  61. // Store the original full HTML representation of this field.
  62. this.set('html', options.el.outerHTML);
  63. // Enlist field automatically in the associated entity's field collection.
  64. this.get('entity').get('fields').add(this);
  65. // Automatically generate the logical field ID.
  66. this.set('logicalFieldID', this.get('fieldID').split('/').slice(0, 4).join('/'));
  67. // Call Drupal.quickedit.BaseModel's initialize() method.
  68. Drupal.quickedit.BaseModel.prototype.initialize.call(this, options);
  69. },
  70. /**
  71. * {@inheritdoc}
  72. */
  73. destroy: function (options) {
  74. if (this.get('state') !== 'inactive') {
  75. throw new Error("FieldModel cannot be destroyed if it is not inactive state.");
  76. }
  77. Drupal.quickedit.BaseModel.prototype.destroy.call(this, options);
  78. },
  79. /**
  80. * {@inheritdoc}
  81. */
  82. sync: function () {
  83. // We don't use REST updates to sync.
  84. return;
  85. },
  86. /**
  87. * {@inheritdoc}
  88. */
  89. validate: function (attrs, options) {
  90. var current = this.get('state');
  91. var next = attrs.state;
  92. if (current !== next) {
  93. // Ensure it's a valid state.
  94. if (_.indexOf(this.constructor.states, next) === -1) {
  95. return '"' + next + '" is an invalid state';
  96. }
  97. // Check if the acceptStateChange callback accepts it.
  98. if (!this.get('acceptStateChange')(current, next, options, this)) {
  99. return 'state change not accepted';
  100. }
  101. }
  102. },
  103. /**
  104. * Extracts the entity ID from this field's ID.
  105. *
  106. * @return String
  107. * An entity ID: a string of the format `<entity type>/<id>`.
  108. */
  109. getEntityID: function () {
  110. return this.get('fieldID').split('/').slice(0, 2).join('/');
  111. },
  112. /**
  113. * Extracts the view mode ID from this field's ID.
  114. *
  115. * @return String
  116. * A view mode ID.
  117. */
  118. getViewMode: function () {
  119. return this.get('fieldID').split('/').pop();
  120. },
  121. /**
  122. * Find other instances of this field with different view modes.
  123. *
  124. * @return Array
  125. * An array containing view mode IDs.
  126. */
  127. findOtherViewModes: function () {
  128. var currentField = this;
  129. var otherViewModes = [];
  130. Drupal.quickedit.collections.fields
  131. // Find all instances of fields that display the same logical field (same
  132. // entity, same field, just a different instance and maybe a different
  133. // view mode).
  134. .where({ logicalFieldID: currentField.get('logicalFieldID') })
  135. .forEach(function (field) {
  136. // Ignore the current field.
  137. if (field === currentField) {
  138. return;
  139. }
  140. // Also ignore other fields with the same view mode.
  141. else if (field.get('fieldID') === currentField.get('fieldID')) {
  142. return;
  143. }
  144. else {
  145. otherViewModes.push(field.getViewMode());
  146. }
  147. });
  148. return otherViewModes;
  149. }
  150. }, {
  151. /**
  152. * A list (sequence) of all possible states a field can be in during in-place
  153. * editing.
  154. */
  155. states: [
  156. // The field associated with this FieldModel is linked to an EntityModel;
  157. // the user can choose to start in-place editing that entity (and
  158. // consequently this field). No in-place editor (EditorView) is associated
  159. // with this field, because this field is not being in-place edited.
  160. // This is both the initial (not yet in-place editing) and the end state (
  161. // finished in-place editing).
  162. 'inactive',
  163. // The user is in-place editing this entity, and this field is a candidate
  164. // for in-place editing. In-place editor should not
  165. // - Trigger: user.
  166. // - Guarantees: entity is ready, in-place editor (EditorView) is associated
  167. // with the field.
  168. // - Expected behavior: visual indicators around the field indicate it is
  169. // available for in-place editing, no in-place editor presented yet.
  170. 'candidate',
  171. // User is highlighting this field.
  172. // - Trigger: user.
  173. // - Guarantees: see 'candidate'.
  174. // - Expected behavior: visual indicators to convey highlighting, in-place
  175. // editing toolbar shows field's label.
  176. 'highlighted',
  177. // User has activated the in-place editing of this field; in-place editor is
  178. // activating.
  179. // - Trigger: user.
  180. // - Guarantees: see 'candidate'.
  181. // - Expected behavior: loading indicator, in-place editor is loading remote
  182. // data (e.g. retrieve form from back-end). Upon retrieval of remote data,
  183. // the in-place editor transitions the field's state to 'active'.
  184. 'activating',
  185. // In-place editor has finished loading remote data; ready for use.
  186. // - Trigger: in-place editor.
  187. // - Guarantees: see 'candidate'.
  188. // - Expected behavior: in-place editor for the field is ready for use.
  189. 'active',
  190. // User has modified values in the in-place editor.
  191. // - Trigger: user.
  192. // - Guarantees: see 'candidate', plus in-place editor is ready for use.
  193. // - Expected behavior: visual indicator of change.
  194. 'changed',
  195. // User is saving changed field data in in-place editor to TempStore. The
  196. // save mechanism of the in-place editor is called.
  197. // - Trigger: user.
  198. // - Guarantees: see 'candidate' and 'active'.
  199. // - Expected behavior: saving indicator, in-place editor is saving field
  200. // data into TempStore. Upon successful saving (without validation
  201. // errors), the in-place editor transitions the field's state to 'saved',
  202. // but to 'invalid' upon failed saving (with validation errors).
  203. 'saving',
  204. // In-place editor has successfully saved the changed field.
  205. // - Trigger: in-place editor.
  206. // - Guarantees: see 'candidate' and 'active'.
  207. // - Expected behavior: transition back to 'candidate' state because the
  208. // deed is done. Then: 1) transition to 'inactive' to allow the field to
  209. // be rerendered, 2) destroy the FieldModel (which also destroys attached
  210. // views like the EditorView), 3) replace the existing field HTML with the
  211. // existing HTML and 4) attach behaviors again so that the field becomes
  212. // available again for in-place editing.
  213. 'saved',
  214. // In-place editor has failed to saved the changed field: there were
  215. // validation errors.
  216. // - Trigger: in-place editor.
  217. // - Guarantees: see 'candidate' and 'active'.
  218. // - Expected behavior: remain in 'invalid' state, let the user make more
  219. // changes so that he can save it again, without validation errors.
  220. 'invalid'
  221. ],
  222. /**
  223. * Indicates whether the 'from' state comes before the 'to' state.
  224. *
  225. * @param String from
  226. * One of Drupal.quickedit.FieldModel.states.
  227. * @param String to
  228. * One of Drupal.quickedit.FieldModel.states.
  229. * @return Boolean
  230. */
  231. followsStateSequence: function (from, to) {
  232. return _.indexOf(this.states, from) < _.indexOf(this.states, to);
  233. }
  234. });
  235. Drupal.quickedit.FieldCollection = Backbone.Collection.extend({
  236. model: Drupal.quickedit.FieldModel
  237. });
  238. }(_, Backbone, Drupal));