PageRenderTime 56ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/web/core/modules/quickedit/js/models/FieldModel.es6.js

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