123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- /**
- * @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);
|