Mercurial > hg > cmmr2012-drupal-site
diff core/modules/quickedit/js/views/FieldDecorationView.es6.js @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/modules/quickedit/js/views/FieldDecorationView.es6.js Thu Jul 05 14:24:15 2018 +0000 @@ -0,0 +1,356 @@ +/** + * @file + * A Backbone View that decorates the in-place edited element. + */ + +(function ($, Backbone, Drupal) { + Drupal.quickedit.FieldDecorationView = Backbone.View.extend(/** @lends Drupal.quickedit.FieldDecorationView# */{ + + /** + * @type {null} + */ + _widthAttributeIsEmpty: null, + + /** + * @type {object} + */ + events: { + 'mouseenter.quickedit': 'onMouseEnter', + 'mouseleave.quickedit': 'onMouseLeave', + click: 'onClick', + 'tabIn.quickedit': 'onMouseEnter', + 'tabOut.quickedit': 'onMouseLeave', + }, + + /** + * @constructs + * + * @augments Backbone.View + * + * @param {object} options + * An object with the following keys: + * @param {Drupal.quickedit.EditorView} options.editorView + * The editor object view. + */ + initialize(options) { + this.editorView = options.editorView; + + this.listenTo(this.model, 'change:state', this.stateChange); + this.listenTo(this.model, 'change:isChanged change:inTempStore', this.renderChanged); + }, + + /** + * @inheritdoc + */ + remove() { + // The el property is the field, which should not be removed. Remove the + // pointer to it, then call Backbone.View.prototype.remove(). + this.setElement(); + Backbone.View.prototype.remove.call(this); + }, + + /** + * Determines the actions to take given a change of state. + * + * @param {Drupal.quickedit.FieldModel} model + * The `FieldModel` model. + * @param {string} state + * The state of the associated field. One of + * {@link Drupal.quickedit.FieldModel.states}. + */ + stateChange(model, state) { + const from = model.previous('state'); + const to = state; + switch (to) { + case 'inactive': + this.undecorate(); + break; + + case 'candidate': + this.decorate(); + if (from !== 'inactive') { + this.stopHighlight(); + if (from !== 'highlighted') { + this.model.set('isChanged', false); + this.stopEdit(); + } + } + this._unpad(); + break; + + case 'highlighted': + this.startHighlight(); + break; + + case 'activating': + // NOTE: this state is not used by every editor! It's only used by + // those that need to interact with the server. + this.prepareEdit(); + break; + + case 'active': + if (from !== 'activating') { + this.prepareEdit(); + } + if (this.editorView.getQuickEditUISettings().padding) { + this._pad(); + } + break; + + case 'changed': + this.model.set('isChanged', true); + break; + + case 'saving': + break; + + case 'saved': + break; + + case 'invalid': + break; + } + }, + + /** + * Adds a class to the edited element that indicates whether the field has + * been changed by the user (i.e. locally) or the field has already been + * changed and stored before by the user (i.e. remotely, stored in + * PrivateTempStore). + */ + renderChanged() { + this.$el.toggleClass('quickedit-changed', this.model.get('isChanged') || this.model.get('inTempStore')); + }, + + /** + * Starts hover; transitions to 'highlight' state. + * + * @param {jQuery.Event} event + * The mouse event. + */ + onMouseEnter(event) { + const that = this; + that.model.set('state', 'highlighted'); + event.stopPropagation(); + }, + + /** + * Stops hover; transitions to 'candidate' state. + * + * @param {jQuery.Event} event + * The mouse event. + */ + onMouseLeave(event) { + const that = this; + that.model.set('state', 'candidate', { reason: 'mouseleave' }); + event.stopPropagation(); + }, + + /** + * Transition to 'activating' stage. + * + * @param {jQuery.Event} event + * The click event. + */ + onClick(event) { + this.model.set('state', 'activating'); + event.preventDefault(); + event.stopPropagation(); + }, + + /** + * Adds classes used to indicate an elements editable state. + */ + decorate() { + this.$el.addClass('quickedit-candidate quickedit-editable'); + }, + + /** + * Removes classes used to indicate an elements editable state. + */ + undecorate() { + this.$el.removeClass('quickedit-candidate quickedit-editable quickedit-highlighted quickedit-editing'); + }, + + /** + * Adds that class that indicates that an element is highlighted. + */ + startHighlight() { + // Animations. + const that = this; + // Use a timeout to grab the next available animation frame. + that.$el.addClass('quickedit-highlighted'); + }, + + /** + * Removes the class that indicates that an element is highlighted. + */ + stopHighlight() { + this.$el.removeClass('quickedit-highlighted'); + }, + + /** + * Removes the class that indicates that an element as editable. + */ + prepareEdit() { + this.$el.addClass('quickedit-editing'); + + // Allow the field to be styled differently while editing in a pop-up + // in-place editor. + if (this.editorView.getQuickEditUISettings().popup) { + this.$el.addClass('quickedit-editor-is-popup'); + } + }, + + /** + * Removes the class that indicates that an element is being edited. + * + * Reapplies the class that indicates that a candidate editable element is + * again available to be edited. + */ + stopEdit() { + this.$el.removeClass('quickedit-highlighted quickedit-editing'); + + // Done editing in a pop-up in-place editor; remove the class. + if (this.editorView.getQuickEditUISettings().popup) { + this.$el.removeClass('quickedit-editor-is-popup'); + } + + // Make the other editors show up again. + $('.quickedit-candidate').addClass('quickedit-editable'); + }, + + /** + * Adds padding around the editable element to make it pop visually. + */ + _pad() { + // Early return if the element has already been padded. + if (this.$el.data('quickedit-padded')) { + return; + } + const self = this; + + // Add 5px padding for readability. This means we'll freeze the current + // width and *then* add 5px padding, hence ensuring the padding is added + // "on the outside". + // 1) Freeze the width (if it's not already set); don't use animations. + if (this.$el[0].style.width === '') { + this._widthAttributeIsEmpty = true; + this.$el + .addClass('quickedit-animate-disable-width') + .css('width', this.$el.width()); + } + + // 2) Add padding; use animations. + const posProp = this._getPositionProperties(this.$el); + setTimeout(() => { + // Re-enable width animations (padding changes affect width too!). + self.$el.removeClass('quickedit-animate-disable-width'); + + // Pad the editable. + self.$el + .css({ + position: 'relative', + top: `${posProp.top - 5}px`, + left: `${posProp.left - 5}px`, + 'padding-top': `${posProp['padding-top'] + 5}px`, + 'padding-left': `${posProp['padding-left'] + 5}px`, + 'padding-right': `${posProp['padding-right'] + 5}px`, + 'padding-bottom': `${posProp['padding-bottom'] + 5}px`, + 'margin-bottom': `${posProp['margin-bottom'] - 10}px`, + }) + .data('quickedit-padded', true); + }, 0); + }, + + /** + * Removes the padding around the element being edited when editing ceases. + */ + _unpad() { + // Early return if the element has not been padded. + if (!this.$el.data('quickedit-padded')) { + return; + } + const self = this; + + // 1) Set the empty width again. + if (this._widthAttributeIsEmpty) { + this.$el + .addClass('quickedit-animate-disable-width') + .css('width', ''); + } + + // 2) Remove padding; use animations (these will run simultaneously with) + // the fading out of the toolbar as its gets removed). + const posProp = this._getPositionProperties(this.$el); + setTimeout(() => { + // Re-enable width animations (padding changes affect width too!). + self.$el.removeClass('quickedit-animate-disable-width'); + + // Unpad the editable. + self.$el + .css({ + position: 'relative', + top: `${posProp.top + 5}px`, + left: `${posProp.left + 5}px`, + 'padding-top': `${posProp['padding-top'] - 5}px`, + 'padding-left': `${posProp['padding-left'] - 5}px`, + 'padding-right': `${posProp['padding-right'] - 5}px`, + 'padding-bottom': `${posProp['padding-bottom'] - 5}px`, + 'margin-bottom': `${posProp['margin-bottom'] + 10}px`, + }); + }, 0); + // Remove the marker that indicates that this field has padding. This is + // done outside the timed out function above so that we don't get numerous + // queued functions that will remove padding before the data marker has + // been removed. + this.$el.removeData('quickedit-padded'); + }, + + /** + * Gets the top and left properties of an element. + * + * Convert extraneous values and information into numbers ready for + * subtraction. + * + * @param {jQuery} $e + * The element to get position properties from. + * + * @return {object} + * An object containing css values for the needed properties. + */ + _getPositionProperties($e) { + let p; + const r = {}; + const props = [ + 'top', 'left', 'bottom', 'right', + 'padding-top', 'padding-left', 'padding-right', 'padding-bottom', + 'margin-bottom', + ]; + + const propCount = props.length; + for (let i = 0; i < propCount; i++) { + p = props[i]; + r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10); + } + return r; + }, + + /** + * Replaces blank or 'auto' CSS `position: <value>` values with "0px". + * + * @param {string} [pos] + * The value for a CSS position declaration. + * + * @return {string} + * A CSS value that is valid for `position`. + */ + _replaceBlankPosition(pos) { + if (pos === 'auto' || !pos) { + pos = '0px'; + } + return pos; + }, + + }); +}(jQuery, Backbone, Drupal));