3 * A Backbone View that decorates the in-place edited element.
6 (function($, Backbone, Drupal) {
7 Drupal.quickedit.FieldDecorationView = Backbone.View.extend(
8 /** @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);
41 'change:isChanged change:inTempStore',
50 // The el property is the field, which should not be removed. Remove the
51 // pointer to it, then call Backbone.View.prototype.remove().
53 Backbone.View.prototype.remove.call(this);
57 * Determines the actions to take given a change of state.
59 * @param {Drupal.quickedit.FieldModel} model
60 * The `FieldModel` model.
61 * @param {string} state
62 * The state of the associated field. One of
63 * {@link Drupal.quickedit.FieldModel.states}.
65 stateChange(model, state) {
66 const from = model.previous('state');
75 if (from !== 'inactive') {
77 if (from !== 'highlighted') {
78 this.model.set('isChanged', false);
86 this.startHighlight();
90 // NOTE: this state is not used by every editor! It's only used by
91 // those that need to interact with the server.
96 if (from !== 'activating') {
99 if (this.editorView.getQuickEditUISettings().padding) {
105 this.model.set('isChanged', true);
120 * Adds a class to the edited element that indicates whether the field has
121 * been changed by the user (i.e. locally) or the field has already been
122 * changed and stored before by the user (i.e. remotely, stored in
126 this.$el.toggleClass(
128 this.model.get('isChanged') || this.model.get('inTempStore'),
133 * Starts hover; transitions to 'highlight' state.
135 * @param {jQuery.Event} event
138 onMouseEnter(event) {
140 that.model.set('state', 'highlighted');
141 event.stopPropagation();
145 * Stops hover; transitions to 'candidate' state.
147 * @param {jQuery.Event} event
150 onMouseLeave(event) {
152 that.model.set('state', 'candidate', { reason: 'mouseleave' });
153 event.stopPropagation();
157 * Transition to 'activating' stage.
159 * @param {jQuery.Event} event
163 this.model.set('state', 'activating');
164 event.preventDefault();
165 event.stopPropagation();
169 * Adds classes used to indicate an elements editable state.
172 this.$el.addClass('quickedit-candidate quickedit-editable');
176 * Removes classes used to indicate an elements editable state.
179 this.$el.removeClass(
180 'quickedit-candidate quickedit-editable quickedit-highlighted quickedit-editing',
185 * Adds that class that indicates that an element is highlighted.
190 // Use a timeout to grab the next available animation frame.
191 that.$el.addClass('quickedit-highlighted');
195 * Removes the class that indicates that an element is highlighted.
198 this.$el.removeClass('quickedit-highlighted');
202 * Removes the class that indicates that an element as editable.
205 this.$el.addClass('quickedit-editing');
207 // Allow the field to be styled differently while editing in a pop-up
209 if (this.editorView.getQuickEditUISettings().popup) {
210 this.$el.addClass('quickedit-editor-is-popup');
215 * Removes the class that indicates that an element is being edited.
217 * Reapplies the class that indicates that a candidate editable element is
218 * again available to be edited.
221 this.$el.removeClass('quickedit-highlighted quickedit-editing');
223 // Done editing in a pop-up in-place editor; remove the class.
224 if (this.editorView.getQuickEditUISettings().popup) {
225 this.$el.removeClass('quickedit-editor-is-popup');
228 // Make the other editors show up again.
229 $('.quickedit-candidate').addClass('quickedit-editable');
233 * Adds padding around the editable element to make it pop visually.
236 // Early return if the element has already been padded.
237 if (this.$el.data('quickedit-padded')) {
242 // Add 5px padding for readability. This means we'll freeze the current
243 // width and *then* add 5px padding, hence ensuring the padding is added
245 // 1) Freeze the width (if it's not already set); don't use animations.
246 if (this.$el[0].style.width === '') {
247 this._widthAttributeIsEmpty = true;
249 .addClass('quickedit-animate-disable-width')
250 .css('width', this.$el.width());
253 // 2) Add padding; use animations.
254 const posProp = this._getPositionProperties(this.$el);
256 // Re-enable width animations (padding changes affect width too!).
257 self.$el.removeClass('quickedit-animate-disable-width');
262 position: 'relative',
263 top: `${posProp.top - 5}px`,
264 left: `${posProp.left - 5}px`,
265 'padding-top': `${posProp['padding-top'] + 5}px`,
266 'padding-left': `${posProp['padding-left'] + 5}px`,
267 'padding-right': `${posProp['padding-right'] + 5}px`,
268 'padding-bottom': `${posProp['padding-bottom'] + 5}px`,
269 'margin-bottom': `${posProp['margin-bottom'] - 10}px`,
271 .data('quickedit-padded', true);
276 * Removes the padding around the element being edited when editing ceases.
279 // Early return if the element has not been padded.
280 if (!this.$el.data('quickedit-padded')) {
285 // 1) Set the empty width again.
286 if (this._widthAttributeIsEmpty) {
287 this.$el.addClass('quickedit-animate-disable-width').css('width', '');
290 // 2) Remove padding; use animations (these will run simultaneously with)
291 // the fading out of the toolbar as its gets removed).
292 const posProp = this._getPositionProperties(this.$el);
294 // Re-enable width animations (padding changes affect width too!).
295 self.$el.removeClass('quickedit-animate-disable-width');
297 // Unpad the editable.
299 position: 'relative',
300 top: `${posProp.top + 5}px`,
301 left: `${posProp.left + 5}px`,
302 'padding-top': `${posProp['padding-top'] - 5}px`,
303 'padding-left': `${posProp['padding-left'] - 5}px`,
304 'padding-right': `${posProp['padding-right'] - 5}px`,
305 'padding-bottom': `${posProp['padding-bottom'] - 5}px`,
306 'margin-bottom': `${posProp['margin-bottom'] + 10}px`,
309 // Remove the marker that indicates that this field has padding. This is
310 // done outside the timed out function above so that we don't get numerous
311 // queued functions that will remove padding before the data marker has
313 this.$el.removeData('quickedit-padded');
317 * Gets the top and left properties of an element.
319 * Convert extraneous values and information into numbers ready for
323 * The element to get position properties from.
326 * An object containing css values for the needed properties.
328 _getPositionProperties($e) {
343 const propCount = props.length;
344 for (let i = 0; i < propCount; i++) {
346 r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
352 * Replaces blank or 'auto' CSS `position: <value>` values with "0px".
354 * @param {string} [pos]
355 * The value for a CSS position declaration.
358 * A CSS value that is valid for `position`.
360 _replaceBlankPosition(pos) {
361 if (pos === 'auto' || !pos) {
368 })(jQuery, Backbone, Drupal);