3 * A Backbone View that decorates the in-place edited element.
6 (function ($, Backbone, Drupal) {
7 Drupal.quickedit.FieldDecorationView = Backbone.View.extend(/** @lends Drupal.quickedit.FieldDecorationView# */{
12 _widthAttributeIsEmpty: null,
18 'mouseenter.quickedit': 'onMouseEnter',
19 'mouseleave.quickedit': 'onMouseLeave',
21 'tabIn.quickedit': 'onMouseEnter',
22 'tabOut.quickedit': 'onMouseLeave',
28 * @augments Backbone.View
30 * @param {object} options
31 * An object with the following keys:
32 * @param {Drupal.quickedit.EditorView} options.editorView
33 * The editor object view.
36 this.editorView = options.editorView;
38 this.listenTo(this.model, 'change:state', this.stateChange);
39 this.listenTo(this.model, 'change:isChanged change:inTempStore', this.renderChanged);
46 // The el property is the field, which should not be removed. Remove the
47 // pointer to it, then call Backbone.View.prototype.remove().
49 Backbone.View.prototype.remove.call(this);
53 * Determines the actions to take given a change of state.
55 * @param {Drupal.quickedit.FieldModel} model
56 * The `FieldModel` model.
57 * @param {string} state
58 * The state of the associated field. One of
59 * {@link Drupal.quickedit.FieldModel.states}.
61 stateChange(model, state) {
62 const from = model.previous('state');
71 if (from !== 'inactive') {
73 if (from !== 'highlighted') {
74 this.model.set('isChanged', false);
82 this.startHighlight();
86 // NOTE: this state is not used by every editor! It's only used by
87 // those that need to interact with the server.
92 if (from !== 'activating') {
95 if (this.editorView.getQuickEditUISettings().padding) {
101 this.model.set('isChanged', true);
116 * Adds a class to the edited element that indicates whether the field has
117 * been changed by the user (i.e. locally) or the field has already been
118 * changed and stored before by the user (i.e. remotely, stored in
122 this.$el.toggleClass('quickedit-changed', this.model.get('isChanged') || this.model.get('inTempStore'));
126 * Starts hover; transitions to 'highlight' state.
128 * @param {jQuery.Event} event
131 onMouseEnter(event) {
133 that.model.set('state', 'highlighted');
134 event.stopPropagation();
138 * Stops hover; transitions to 'candidate' state.
140 * @param {jQuery.Event} event
143 onMouseLeave(event) {
145 that.model.set('state', 'candidate', { reason: 'mouseleave' });
146 event.stopPropagation();
150 * Transition to 'activating' stage.
152 * @param {jQuery.Event} event
156 this.model.set('state', 'activating');
157 event.preventDefault();
158 event.stopPropagation();
162 * Adds classes used to indicate an elements editable state.
165 this.$el.addClass('quickedit-candidate quickedit-editable');
169 * Removes classes used to indicate an elements editable state.
172 this.$el.removeClass('quickedit-candidate quickedit-editable quickedit-highlighted quickedit-editing');
176 * Adds that class that indicates that an element is highlighted.
181 // Use a timeout to grab the next available animation frame.
182 that.$el.addClass('quickedit-highlighted');
186 * Removes the class that indicates that an element is highlighted.
189 this.$el.removeClass('quickedit-highlighted');
193 * Removes the class that indicates that an element as editable.
196 this.$el.addClass('quickedit-editing');
198 // Allow the field to be styled differently while editing in a pop-up
200 if (this.editorView.getQuickEditUISettings().popup) {
201 this.$el.addClass('quickedit-editor-is-popup');
206 * Removes the class that indicates that an element is being edited.
208 * Reapplies the class that indicates that a candidate editable element is
209 * again available to be edited.
212 this.$el.removeClass('quickedit-highlighted quickedit-editing');
214 // Done editing in a pop-up in-place editor; remove the class.
215 if (this.editorView.getQuickEditUISettings().popup) {
216 this.$el.removeClass('quickedit-editor-is-popup');
219 // Make the other editors show up again.
220 $('.quickedit-candidate').addClass('quickedit-editable');
224 * Adds padding around the editable element to make it pop visually.
227 // Early return if the element has already been padded.
228 if (this.$el.data('quickedit-padded')) {
233 // Add 5px padding for readability. This means we'll freeze the current
234 // width and *then* add 5px padding, hence ensuring the padding is added
236 // 1) Freeze the width (if it's not already set); don't use animations.
237 if (this.$el[0].style.width === '') {
238 this._widthAttributeIsEmpty = true;
240 .addClass('quickedit-animate-disable-width')
241 .css('width', this.$el.width());
244 // 2) Add padding; use animations.
245 const posProp = this._getPositionProperties(this.$el);
247 // Re-enable width animations (padding changes affect width too!).
248 self.$el.removeClass('quickedit-animate-disable-width');
253 position: 'relative',
254 top: `${posProp.top - 5}px`,
255 left: `${posProp.left - 5}px`,
256 'padding-top': `${posProp['padding-top'] + 5}px`,
257 'padding-left': `${posProp['padding-left'] + 5}px`,
258 'padding-right': `${posProp['padding-right'] + 5}px`,
259 'padding-bottom': `${posProp['padding-bottom'] + 5}px`,
260 'margin-bottom': `${posProp['margin-bottom'] - 10}px`,
262 .data('quickedit-padded', true);
267 * Removes the padding around the element being edited when editing ceases.
270 // Early return if the element has not been padded.
271 if (!this.$el.data('quickedit-padded')) {
276 // 1) Set the empty width again.
277 if (this._widthAttributeIsEmpty) {
279 .addClass('quickedit-animate-disable-width')
283 // 2) Remove padding; use animations (these will run simultaneously with)
284 // the fading out of the toolbar as its gets removed).
285 const posProp = this._getPositionProperties(this.$el);
287 // Re-enable width animations (padding changes affect width too!).
288 self.$el.removeClass('quickedit-animate-disable-width');
290 // Unpad the editable.
293 position: 'relative',
294 top: `${posProp.top + 5}px`,
295 left: `${posProp.left + 5}px`,
296 'padding-top': `${posProp['padding-top'] - 5}px`,
297 'padding-left': `${posProp['padding-left'] - 5}px`,
298 'padding-right': `${posProp['padding-right'] - 5}px`,
299 'padding-bottom': `${posProp['padding-bottom'] - 5}px`,
300 'margin-bottom': `${posProp['margin-bottom'] + 10}px`,
303 // Remove the marker that indicates that this field has padding. This is
304 // done outside the timed out function above so that we don't get numerous
305 // queued functions that will remove padding before the data marker has
307 this.$el.removeData('quickedit-padded');
311 * Gets the top and left properties of an element.
313 * Convert extraneous values and information into numbers ready for
317 * The element to get position properties from.
320 * An object containing css values for the needed properties.
322 _getPositionProperties($e) {
326 'top', 'left', 'bottom', 'right',
327 'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
331 const propCount = props.length;
332 for (let i = 0; i < propCount; i++) {
334 r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
340 * Replaces blank or 'auto' CSS `position: <value>` values with "0px".
342 * @param {string} [pos]
343 * The value for a CSS position declaration.
346 * A CSS value that is valid for `position`.
348 _replaceBlankPosition(pos) {
349 if (pos === 'auto' || !pos) {
356 }(jQuery, Backbone, Drupal));