annotate 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
rev   line source
Chris@0 1 /**
Chris@0 2 * @file
Chris@0 3 * A Backbone View that decorates the in-place edited element.
Chris@0 4 */
Chris@0 5
Chris@0 6 (function ($, Backbone, Drupal) {
Chris@0 7 Drupal.quickedit.FieldDecorationView = Backbone.View.extend(/** @lends Drupal.quickedit.FieldDecorationView# */{
Chris@0 8
Chris@0 9 /**
Chris@0 10 * @type {null}
Chris@0 11 */
Chris@0 12 _widthAttributeIsEmpty: null,
Chris@0 13
Chris@0 14 /**
Chris@0 15 * @type {object}
Chris@0 16 */
Chris@0 17 events: {
Chris@0 18 'mouseenter.quickedit': 'onMouseEnter',
Chris@0 19 'mouseleave.quickedit': 'onMouseLeave',
Chris@0 20 click: 'onClick',
Chris@0 21 'tabIn.quickedit': 'onMouseEnter',
Chris@0 22 'tabOut.quickedit': 'onMouseLeave',
Chris@0 23 },
Chris@0 24
Chris@0 25 /**
Chris@0 26 * @constructs
Chris@0 27 *
Chris@0 28 * @augments Backbone.View
Chris@0 29 *
Chris@0 30 * @param {object} options
Chris@0 31 * An object with the following keys:
Chris@0 32 * @param {Drupal.quickedit.EditorView} options.editorView
Chris@0 33 * The editor object view.
Chris@0 34 */
Chris@0 35 initialize(options) {
Chris@0 36 this.editorView = options.editorView;
Chris@0 37
Chris@0 38 this.listenTo(this.model, 'change:state', this.stateChange);
Chris@0 39 this.listenTo(this.model, 'change:isChanged change:inTempStore', this.renderChanged);
Chris@0 40 },
Chris@0 41
Chris@0 42 /**
Chris@0 43 * @inheritdoc
Chris@0 44 */
Chris@0 45 remove() {
Chris@0 46 // The el property is the field, which should not be removed. Remove the
Chris@0 47 // pointer to it, then call Backbone.View.prototype.remove().
Chris@0 48 this.setElement();
Chris@0 49 Backbone.View.prototype.remove.call(this);
Chris@0 50 },
Chris@0 51
Chris@0 52 /**
Chris@0 53 * Determines the actions to take given a change of state.
Chris@0 54 *
Chris@0 55 * @param {Drupal.quickedit.FieldModel} model
Chris@0 56 * The `FieldModel` model.
Chris@0 57 * @param {string} state
Chris@0 58 * The state of the associated field. One of
Chris@0 59 * {@link Drupal.quickedit.FieldModel.states}.
Chris@0 60 */
Chris@0 61 stateChange(model, state) {
Chris@0 62 const from = model.previous('state');
Chris@0 63 const to = state;
Chris@0 64 switch (to) {
Chris@0 65 case 'inactive':
Chris@0 66 this.undecorate();
Chris@0 67 break;
Chris@0 68
Chris@0 69 case 'candidate':
Chris@0 70 this.decorate();
Chris@0 71 if (from !== 'inactive') {
Chris@0 72 this.stopHighlight();
Chris@0 73 if (from !== 'highlighted') {
Chris@0 74 this.model.set('isChanged', false);
Chris@0 75 this.stopEdit();
Chris@0 76 }
Chris@0 77 }
Chris@0 78 this._unpad();
Chris@0 79 break;
Chris@0 80
Chris@0 81 case 'highlighted':
Chris@0 82 this.startHighlight();
Chris@0 83 break;
Chris@0 84
Chris@0 85 case 'activating':
Chris@0 86 // NOTE: this state is not used by every editor! It's only used by
Chris@0 87 // those that need to interact with the server.
Chris@0 88 this.prepareEdit();
Chris@0 89 break;
Chris@0 90
Chris@0 91 case 'active':
Chris@0 92 if (from !== 'activating') {
Chris@0 93 this.prepareEdit();
Chris@0 94 }
Chris@0 95 if (this.editorView.getQuickEditUISettings().padding) {
Chris@0 96 this._pad();
Chris@0 97 }
Chris@0 98 break;
Chris@0 99
Chris@0 100 case 'changed':
Chris@0 101 this.model.set('isChanged', true);
Chris@0 102 break;
Chris@0 103
Chris@0 104 case 'saving':
Chris@0 105 break;
Chris@0 106
Chris@0 107 case 'saved':
Chris@0 108 break;
Chris@0 109
Chris@0 110 case 'invalid':
Chris@0 111 break;
Chris@0 112 }
Chris@0 113 },
Chris@0 114
Chris@0 115 /**
Chris@0 116 * Adds a class to the edited element that indicates whether the field has
Chris@0 117 * been changed by the user (i.e. locally) or the field has already been
Chris@0 118 * changed and stored before by the user (i.e. remotely, stored in
Chris@0 119 * PrivateTempStore).
Chris@0 120 */
Chris@0 121 renderChanged() {
Chris@0 122 this.$el.toggleClass('quickedit-changed', this.model.get('isChanged') || this.model.get('inTempStore'));
Chris@0 123 },
Chris@0 124
Chris@0 125 /**
Chris@0 126 * Starts hover; transitions to 'highlight' state.
Chris@0 127 *
Chris@0 128 * @param {jQuery.Event} event
Chris@0 129 * The mouse event.
Chris@0 130 */
Chris@0 131 onMouseEnter(event) {
Chris@0 132 const that = this;
Chris@0 133 that.model.set('state', 'highlighted');
Chris@0 134 event.stopPropagation();
Chris@0 135 },
Chris@0 136
Chris@0 137 /**
Chris@0 138 * Stops hover; transitions to 'candidate' state.
Chris@0 139 *
Chris@0 140 * @param {jQuery.Event} event
Chris@0 141 * The mouse event.
Chris@0 142 */
Chris@0 143 onMouseLeave(event) {
Chris@0 144 const that = this;
Chris@0 145 that.model.set('state', 'candidate', { reason: 'mouseleave' });
Chris@0 146 event.stopPropagation();
Chris@0 147 },
Chris@0 148
Chris@0 149 /**
Chris@0 150 * Transition to 'activating' stage.
Chris@0 151 *
Chris@0 152 * @param {jQuery.Event} event
Chris@0 153 * The click event.
Chris@0 154 */
Chris@0 155 onClick(event) {
Chris@0 156 this.model.set('state', 'activating');
Chris@0 157 event.preventDefault();
Chris@0 158 event.stopPropagation();
Chris@0 159 },
Chris@0 160
Chris@0 161 /**
Chris@0 162 * Adds classes used to indicate an elements editable state.
Chris@0 163 */
Chris@0 164 decorate() {
Chris@0 165 this.$el.addClass('quickedit-candidate quickedit-editable');
Chris@0 166 },
Chris@0 167
Chris@0 168 /**
Chris@0 169 * Removes classes used to indicate an elements editable state.
Chris@0 170 */
Chris@0 171 undecorate() {
Chris@0 172 this.$el.removeClass('quickedit-candidate quickedit-editable quickedit-highlighted quickedit-editing');
Chris@0 173 },
Chris@0 174
Chris@0 175 /**
Chris@0 176 * Adds that class that indicates that an element is highlighted.
Chris@0 177 */
Chris@0 178 startHighlight() {
Chris@0 179 // Animations.
Chris@0 180 const that = this;
Chris@0 181 // Use a timeout to grab the next available animation frame.
Chris@0 182 that.$el.addClass('quickedit-highlighted');
Chris@0 183 },
Chris@0 184
Chris@0 185 /**
Chris@0 186 * Removes the class that indicates that an element is highlighted.
Chris@0 187 */
Chris@0 188 stopHighlight() {
Chris@0 189 this.$el.removeClass('quickedit-highlighted');
Chris@0 190 },
Chris@0 191
Chris@0 192 /**
Chris@0 193 * Removes the class that indicates that an element as editable.
Chris@0 194 */
Chris@0 195 prepareEdit() {
Chris@0 196 this.$el.addClass('quickedit-editing');
Chris@0 197
Chris@0 198 // Allow the field to be styled differently while editing in a pop-up
Chris@0 199 // in-place editor.
Chris@0 200 if (this.editorView.getQuickEditUISettings().popup) {
Chris@0 201 this.$el.addClass('quickedit-editor-is-popup');
Chris@0 202 }
Chris@0 203 },
Chris@0 204
Chris@0 205 /**
Chris@0 206 * Removes the class that indicates that an element is being edited.
Chris@0 207 *
Chris@0 208 * Reapplies the class that indicates that a candidate editable element is
Chris@0 209 * again available to be edited.
Chris@0 210 */
Chris@0 211 stopEdit() {
Chris@0 212 this.$el.removeClass('quickedit-highlighted quickedit-editing');
Chris@0 213
Chris@0 214 // Done editing in a pop-up in-place editor; remove the class.
Chris@0 215 if (this.editorView.getQuickEditUISettings().popup) {
Chris@0 216 this.$el.removeClass('quickedit-editor-is-popup');
Chris@0 217 }
Chris@0 218
Chris@0 219 // Make the other editors show up again.
Chris@0 220 $('.quickedit-candidate').addClass('quickedit-editable');
Chris@0 221 },
Chris@0 222
Chris@0 223 /**
Chris@0 224 * Adds padding around the editable element to make it pop visually.
Chris@0 225 */
Chris@0 226 _pad() {
Chris@0 227 // Early return if the element has already been padded.
Chris@0 228 if (this.$el.data('quickedit-padded')) {
Chris@0 229 return;
Chris@0 230 }
Chris@0 231 const self = this;
Chris@0 232
Chris@0 233 // Add 5px padding for readability. This means we'll freeze the current
Chris@0 234 // width and *then* add 5px padding, hence ensuring the padding is added
Chris@0 235 // "on the outside".
Chris@0 236 // 1) Freeze the width (if it's not already set); don't use animations.
Chris@0 237 if (this.$el[0].style.width === '') {
Chris@0 238 this._widthAttributeIsEmpty = true;
Chris@0 239 this.$el
Chris@0 240 .addClass('quickedit-animate-disable-width')
Chris@0 241 .css('width', this.$el.width());
Chris@0 242 }
Chris@0 243
Chris@0 244 // 2) Add padding; use animations.
Chris@0 245 const posProp = this._getPositionProperties(this.$el);
Chris@0 246 setTimeout(() => {
Chris@0 247 // Re-enable width animations (padding changes affect width too!).
Chris@0 248 self.$el.removeClass('quickedit-animate-disable-width');
Chris@0 249
Chris@0 250 // Pad the editable.
Chris@0 251 self.$el
Chris@0 252 .css({
Chris@0 253 position: 'relative',
Chris@0 254 top: `${posProp.top - 5}px`,
Chris@0 255 left: `${posProp.left - 5}px`,
Chris@0 256 'padding-top': `${posProp['padding-top'] + 5}px`,
Chris@0 257 'padding-left': `${posProp['padding-left'] + 5}px`,
Chris@0 258 'padding-right': `${posProp['padding-right'] + 5}px`,
Chris@0 259 'padding-bottom': `${posProp['padding-bottom'] + 5}px`,
Chris@0 260 'margin-bottom': `${posProp['margin-bottom'] - 10}px`,
Chris@0 261 })
Chris@0 262 .data('quickedit-padded', true);
Chris@0 263 }, 0);
Chris@0 264 },
Chris@0 265
Chris@0 266 /**
Chris@0 267 * Removes the padding around the element being edited when editing ceases.
Chris@0 268 */
Chris@0 269 _unpad() {
Chris@0 270 // Early return if the element has not been padded.
Chris@0 271 if (!this.$el.data('quickedit-padded')) {
Chris@0 272 return;
Chris@0 273 }
Chris@0 274 const self = this;
Chris@0 275
Chris@0 276 // 1) Set the empty width again.
Chris@0 277 if (this._widthAttributeIsEmpty) {
Chris@0 278 this.$el
Chris@0 279 .addClass('quickedit-animate-disable-width')
Chris@0 280 .css('width', '');
Chris@0 281 }
Chris@0 282
Chris@0 283 // 2) Remove padding; use animations (these will run simultaneously with)
Chris@0 284 // the fading out of the toolbar as its gets removed).
Chris@0 285 const posProp = this._getPositionProperties(this.$el);
Chris@0 286 setTimeout(() => {
Chris@0 287 // Re-enable width animations (padding changes affect width too!).
Chris@0 288 self.$el.removeClass('quickedit-animate-disable-width');
Chris@0 289
Chris@0 290 // Unpad the editable.
Chris@0 291 self.$el
Chris@0 292 .css({
Chris@0 293 position: 'relative',
Chris@0 294 top: `${posProp.top + 5}px`,
Chris@0 295 left: `${posProp.left + 5}px`,
Chris@0 296 'padding-top': `${posProp['padding-top'] - 5}px`,
Chris@0 297 'padding-left': `${posProp['padding-left'] - 5}px`,
Chris@0 298 'padding-right': `${posProp['padding-right'] - 5}px`,
Chris@0 299 'padding-bottom': `${posProp['padding-bottom'] - 5}px`,
Chris@0 300 'margin-bottom': `${posProp['margin-bottom'] + 10}px`,
Chris@0 301 });
Chris@0 302 }, 0);
Chris@0 303 // Remove the marker that indicates that this field has padding. This is
Chris@0 304 // done outside the timed out function above so that we don't get numerous
Chris@0 305 // queued functions that will remove padding before the data marker has
Chris@0 306 // been removed.
Chris@0 307 this.$el.removeData('quickedit-padded');
Chris@0 308 },
Chris@0 309
Chris@0 310 /**
Chris@0 311 * Gets the top and left properties of an element.
Chris@0 312 *
Chris@0 313 * Convert extraneous values and information into numbers ready for
Chris@0 314 * subtraction.
Chris@0 315 *
Chris@0 316 * @param {jQuery} $e
Chris@0 317 * The element to get position properties from.
Chris@0 318 *
Chris@0 319 * @return {object}
Chris@0 320 * An object containing css values for the needed properties.
Chris@0 321 */
Chris@0 322 _getPositionProperties($e) {
Chris@0 323 let p;
Chris@0 324 const r = {};
Chris@0 325 const props = [
Chris@0 326 'top', 'left', 'bottom', 'right',
Chris@0 327 'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
Chris@0 328 'margin-bottom',
Chris@0 329 ];
Chris@0 330
Chris@0 331 const propCount = props.length;
Chris@0 332 for (let i = 0; i < propCount; i++) {
Chris@0 333 p = props[i];
Chris@0 334 r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
Chris@0 335 }
Chris@0 336 return r;
Chris@0 337 },
Chris@0 338
Chris@0 339 /**
Chris@0 340 * Replaces blank or 'auto' CSS `position: <value>` values with "0px".
Chris@0 341 *
Chris@0 342 * @param {string} [pos]
Chris@0 343 * The value for a CSS position declaration.
Chris@0 344 *
Chris@0 345 * @return {string}
Chris@0 346 * A CSS value that is valid for `position`.
Chris@0 347 */
Chris@0 348 _replaceBlankPosition(pos) {
Chris@0 349 if (pos === 'auto' || !pos) {
Chris@0 350 pos = '0px';
Chris@0 351 }
Chris@0 352 return pos;
Chris@0 353 },
Chris@0 354
Chris@0 355 });
Chris@0 356 }(jQuery, Backbone, Drupal));