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