annotate core/modules/quickedit/js/views/FieldDecorationView.es6.js @ 5:12f9dff5fda9 tip

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