Chris@0
|
1 /**
|
Chris@0
|
2 * @file
|
Chris@0
|
3 * A Backbone View that provides an entity level toolbar.
|
Chris@0
|
4 */
|
Chris@0
|
5
|
Chris@17
|
6 (function($, _, Backbone, Drupal, debounce) {
|
Chris@17
|
7 Drupal.quickedit.EntityToolbarView = Backbone.View.extend(
|
Chris@17
|
8 /** @lends Drupal.quickedit.EntityToolbarView# */ {
|
Chris@17
|
9 /**
|
Chris@17
|
10 * @type {jQuery}
|
Chris@17
|
11 */
|
Chris@17
|
12 _fieldToolbarRoot: null,
|
Chris@0
|
13
|
Chris@17
|
14 /**
|
Chris@17
|
15 * @return {object}
|
Chris@17
|
16 * A map of events.
|
Chris@17
|
17 */
|
Chris@17
|
18 events() {
|
Chris@17
|
19 const map = {
|
Chris@17
|
20 'click button.action-save': 'onClickSave',
|
Chris@17
|
21 'click button.action-cancel': 'onClickCancel',
|
Chris@17
|
22 mouseenter: 'onMouseenter',
|
Chris@17
|
23 };
|
Chris@17
|
24 return map;
|
Chris@17
|
25 },
|
Chris@0
|
26
|
Chris@17
|
27 /**
|
Chris@17
|
28 * @constructs
|
Chris@17
|
29 *
|
Chris@17
|
30 * @augments Backbone.View
|
Chris@17
|
31 *
|
Chris@17
|
32 * @param {object} options
|
Chris@17
|
33 * Options to construct the view.
|
Chris@17
|
34 * @param {Drupal.quickedit.AppModel} options.appModel
|
Chris@17
|
35 * A quickedit `AppModel` to use in the view.
|
Chris@17
|
36 */
|
Chris@17
|
37 initialize(options) {
|
Chris@17
|
38 const that = this;
|
Chris@17
|
39 this.appModel = options.appModel;
|
Chris@17
|
40 this.$entity = $(this.model.get('el'));
|
Chris@0
|
41
|
Chris@17
|
42 // Rerender whenever the entity state changes.
|
Chris@17
|
43 this.listenTo(
|
Chris@17
|
44 this.model,
|
Chris@17
|
45 'change:isActive change:isDirty change:state',
|
Chris@17
|
46 this.render,
|
Chris@17
|
47 );
|
Chris@17
|
48 // Also rerender whenever a different field is highlighted or activated.
|
Chris@17
|
49 this.listenTo(
|
Chris@17
|
50 this.appModel,
|
Chris@17
|
51 'change:highlightedField change:activeField',
|
Chris@17
|
52 this.render,
|
Chris@17
|
53 );
|
Chris@17
|
54 // Rerender when a field of the entity changes state.
|
Chris@17
|
55 this.listenTo(
|
Chris@17
|
56 this.model.get('fields'),
|
Chris@17
|
57 'change:state',
|
Chris@17
|
58 this.fieldStateChange,
|
Chris@17
|
59 );
|
Chris@0
|
60
|
Chris@17
|
61 // Reposition the entity toolbar as the viewport and the position within
|
Chris@17
|
62 // the viewport changes.
|
Chris@17
|
63 $(window).on(
|
Chris@17
|
64 'resize.quickedit scroll.quickedit drupalViewportOffsetChange.quickedit',
|
Chris@17
|
65 debounce($.proxy(this.windowChangeHandler, this), 150),
|
Chris@17
|
66 );
|
Chris@0
|
67
|
Chris@17
|
68 // Adjust the fence placement within which the entity toolbar may be
|
Chris@17
|
69 // positioned.
|
Chris@17
|
70 $(document).on(
|
Chris@17
|
71 'drupalViewportOffsetChange.quickedit',
|
Chris@17
|
72 (event, offsets) => {
|
Chris@17
|
73 if (that.$fence) {
|
Chris@17
|
74 that.$fence.css(offsets);
|
Chris@17
|
75 }
|
Chris@17
|
76 },
|
Chris@17
|
77 );
|
Chris@0
|
78
|
Chris@17
|
79 // Set the entity toolbar DOM element as the el for this view.
|
Chris@17
|
80 const $toolbar = this.buildToolbarEl();
|
Chris@17
|
81 this.setElement($toolbar);
|
Chris@17
|
82 this._fieldToolbarRoot = $toolbar
|
Chris@17
|
83 .find('.quickedit-toolbar-field')
|
Chris@17
|
84 .get(0);
|
Chris@17
|
85
|
Chris@17
|
86 // Initial render.
|
Chris@17
|
87 this.render();
|
Chris@17
|
88 },
|
Chris@17
|
89
|
Chris@17
|
90 /**
|
Chris@17
|
91 * @inheritdoc
|
Chris@17
|
92 *
|
Chris@17
|
93 * @return {Drupal.quickedit.EntityToolbarView}
|
Chris@17
|
94 * The entity toolbar view.
|
Chris@17
|
95 */
|
Chris@17
|
96 render() {
|
Chris@17
|
97 if (this.model.get('isActive')) {
|
Chris@17
|
98 // If the toolbar container doesn't exist, create it.
|
Chris@17
|
99 const $body = $('body');
|
Chris@17
|
100 if ($body.children('#quickedit-entity-toolbar').length === 0) {
|
Chris@17
|
101 $body.append(this.$el);
|
Chris@17
|
102 }
|
Chris@17
|
103 // The fence will define a area on the screen that the entity toolbar
|
Chris@17
|
104 // will be position within.
|
Chris@17
|
105 if ($body.children('#quickedit-toolbar-fence').length === 0) {
|
Chris@17
|
106 this.$fence = $(Drupal.theme('quickeditEntityToolbarFence'))
|
Chris@17
|
107 .css(Drupal.displace())
|
Chris@17
|
108 .appendTo($body);
|
Chris@17
|
109 }
|
Chris@17
|
110 // Adds the entity title to the toolbar.
|
Chris@17
|
111 this.label();
|
Chris@17
|
112
|
Chris@17
|
113 // Show the save and cancel buttons.
|
Chris@17
|
114 this.show('ops');
|
Chris@17
|
115 // If render is being called and the toolbar is already visible, just
|
Chris@17
|
116 // reposition it.
|
Chris@17
|
117 this.position();
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@17
|
120 // The save button text and state varies with the state of the entity
|
Chris@17
|
121 // model.
|
Chris@17
|
122 const $button = this.$el.find('.quickedit-button.action-save');
|
Chris@17
|
123 const isDirty = this.model.get('isDirty');
|
Chris@17
|
124 // Adjust the save button according to the state of the model.
|
Chris@17
|
125 switch (this.model.get('state')) {
|
Chris@17
|
126 // Quick editing is active, but no field is being edited.
|
Chris@17
|
127 case 'opened':
|
Chris@17
|
128 // The saving throbber is not managed by AJAX system. The
|
Chris@17
|
129 // EntityToolbarView manages this visual element.
|
Chris@17
|
130 $button
|
Chris@17
|
131 .removeClass('action-saving icon-throbber icon-end')
|
Chris@17
|
132 .text(Drupal.t('Save'))
|
Chris@17
|
133 .removeAttr('disabled')
|
Chris@17
|
134 .attr('aria-hidden', !isDirty);
|
Chris@0
|
135 break;
|
Chris@0
|
136
|
Chris@17
|
137 // The changes to the fields of the entity are being committed.
|
Chris@17
|
138 case 'committing':
|
Chris@17
|
139 $button
|
Chris@17
|
140 .addClass('action-saving icon-throbber icon-end')
|
Chris@17
|
141 .text(Drupal.t('Saving'))
|
Chris@17
|
142 .attr('disabled', 'disabled');
|
Chris@0
|
143 break;
|
Chris@0
|
144
|
Chris@17
|
145 default:
|
Chris@17
|
146 $button.attr('aria-hidden', true);
|
Chris@17
|
147 break;
|
Chris@17
|
148 }
|
Chris@17
|
149
|
Chris@17
|
150 return this;
|
Chris@17
|
151 },
|
Chris@17
|
152
|
Chris@17
|
153 /**
|
Chris@17
|
154 * @inheritdoc
|
Chris@17
|
155 */
|
Chris@17
|
156 remove() {
|
Chris@17
|
157 // Remove additional DOM elements controlled by this View.
|
Chris@17
|
158 this.$fence.remove();
|
Chris@17
|
159
|
Chris@17
|
160 // Stop listening to additional events.
|
Chris@17
|
161 $(window).off(
|
Chris@17
|
162 'resize.quickedit scroll.quickedit drupalViewportOffsetChange.quickedit',
|
Chris@17
|
163 );
|
Chris@17
|
164 $(document).off('drupalViewportOffsetChange.quickedit');
|
Chris@17
|
165
|
Chris@17
|
166 Backbone.View.prototype.remove.call(this);
|
Chris@17
|
167 },
|
Chris@17
|
168
|
Chris@17
|
169 /**
|
Chris@17
|
170 * Repositions the entity toolbar on window scroll and resize.
|
Chris@17
|
171 *
|
Chris@17
|
172 * @param {jQuery.Event} event
|
Chris@17
|
173 * The scroll or resize event.
|
Chris@17
|
174 */
|
Chris@17
|
175 windowChangeHandler(event) {
|
Chris@17
|
176 this.position();
|
Chris@17
|
177 },
|
Chris@17
|
178
|
Chris@17
|
179 /**
|
Chris@17
|
180 * Determines the actions to take given a change of state.
|
Chris@17
|
181 *
|
Chris@17
|
182 * @param {Drupal.quickedit.FieldModel} model
|
Chris@17
|
183 * The `FieldModel` model.
|
Chris@17
|
184 * @param {string} state
|
Chris@17
|
185 * The state of the associated field. One of
|
Chris@17
|
186 * {@link Drupal.quickedit.FieldModel.states}.
|
Chris@17
|
187 */
|
Chris@17
|
188 fieldStateChange(model, state) {
|
Chris@17
|
189 switch (state) {
|
Chris@17
|
190 case 'active':
|
Chris@17
|
191 this.render();
|
Chris@0
|
192 break;
|
Chris@0
|
193
|
Chris@17
|
194 case 'invalid':
|
Chris@17
|
195 this.render();
|
Chris@0
|
196 break;
|
Chris@0
|
197 }
|
Chris@17
|
198 },
|
Chris@0
|
199
|
Chris@0
|
200 /**
|
Chris@17
|
201 * Uses the jQuery.ui.position() method to position the entity toolbar.
|
Chris@0
|
202 *
|
Chris@17
|
203 * @param {HTMLElement} [element]
|
Chris@17
|
204 * The element against which the entity toolbar is positioned.
|
Chris@0
|
205 */
|
Chris@17
|
206 position(element) {
|
Chris@17
|
207 clearTimeout(this.timer);
|
Chris@17
|
208
|
Chris@17
|
209 const that = this;
|
Chris@17
|
210 // Vary the edge of the positioning according to the direction of language
|
Chris@17
|
211 // in the document.
|
Chris@17
|
212 const edge = document.documentElement.dir === 'rtl' ? 'right' : 'left';
|
Chris@17
|
213 // A time unit to wait until the entity toolbar is repositioned.
|
Chris@17
|
214 let delay = 0;
|
Chris@17
|
215 // Determines what check in the series of checks below should be
|
Chris@17
|
216 // evaluated.
|
Chris@17
|
217 let check = 0;
|
Chris@17
|
218 // When positioned against an active field that has padding, we should
|
Chris@17
|
219 // ignore that padding when positioning the toolbar, to not unnecessarily
|
Chris@17
|
220 // move the toolbar horizontally, which feels annoying.
|
Chris@17
|
221 let horizontalPadding = 0;
|
Chris@17
|
222 let of;
|
Chris@17
|
223 let activeField;
|
Chris@17
|
224 let highlightedField;
|
Chris@17
|
225 // There are several elements in the page that the entity toolbar might be
|
Chris@17
|
226 // positioned against. They are considered below in a priority order.
|
Chris@17
|
227 do {
|
Chris@17
|
228 switch (check) {
|
Chris@17
|
229 case 0:
|
Chris@17
|
230 // Position against a specific element.
|
Chris@17
|
231 of = element;
|
Chris@17
|
232 break;
|
Chris@17
|
233
|
Chris@17
|
234 case 1:
|
Chris@17
|
235 // Position against a form container.
|
Chris@17
|
236 activeField = Drupal.quickedit.app.model.get('activeField');
|
Chris@17
|
237 of =
|
Chris@17
|
238 activeField &&
|
Chris@17
|
239 activeField.editorView &&
|
Chris@17
|
240 activeField.editorView.$formContainer &&
|
Chris@17
|
241 activeField.editorView.$formContainer.find('.quickedit-form');
|
Chris@17
|
242 break;
|
Chris@17
|
243
|
Chris@17
|
244 case 2:
|
Chris@17
|
245 // Position against an active field.
|
Chris@17
|
246 of =
|
Chris@17
|
247 activeField &&
|
Chris@17
|
248 activeField.editorView &&
|
Chris@17
|
249 activeField.editorView.getEditedElement();
|
Chris@17
|
250 if (
|
Chris@17
|
251 activeField &&
|
Chris@17
|
252 activeField.editorView &&
|
Chris@17
|
253 activeField.editorView.getQuickEditUISettings().padding
|
Chris@17
|
254 ) {
|
Chris@17
|
255 horizontalPadding = 5;
|
Chris@17
|
256 }
|
Chris@17
|
257 break;
|
Chris@17
|
258
|
Chris@17
|
259 case 3:
|
Chris@17
|
260 // Position against a highlighted field.
|
Chris@17
|
261 highlightedField = Drupal.quickedit.app.model.get(
|
Chris@17
|
262 'highlightedField',
|
Chris@17
|
263 );
|
Chris@17
|
264 of =
|
Chris@17
|
265 highlightedField &&
|
Chris@17
|
266 highlightedField.editorView &&
|
Chris@17
|
267 highlightedField.editorView.getEditedElement();
|
Chris@17
|
268 delay = 250;
|
Chris@17
|
269 break;
|
Chris@17
|
270
|
Chris@17
|
271 default: {
|
Chris@17
|
272 const fieldModels = this.model.get('fields').models;
|
Chris@17
|
273 let topMostPosition = 1000000;
|
Chris@17
|
274 let topMostField = null;
|
Chris@17
|
275 // Position against the topmost field.
|
Chris@17
|
276 for (let i = 0; i < fieldModels.length; i++) {
|
Chris@17
|
277 const pos = fieldModels[i].get('el').getBoundingClientRect()
|
Chris@17
|
278 .top;
|
Chris@17
|
279 if (pos < topMostPosition) {
|
Chris@17
|
280 topMostPosition = pos;
|
Chris@17
|
281 topMostField = fieldModels[i];
|
Chris@17
|
282 }
|
Chris@17
|
283 }
|
Chris@17
|
284 of = topMostField.get('el');
|
Chris@17
|
285 delay = 50;
|
Chris@17
|
286 break;
|
Chris@17
|
287 }
|
Chris@0
|
288 }
|
Chris@17
|
289 // Prepare to check the next possible element to position against.
|
Chris@17
|
290 check++;
|
Chris@17
|
291 } while (!of);
|
Chris@17
|
292
|
Chris@17
|
293 /**
|
Chris@17
|
294 * Refines the positioning algorithm of jquery.ui.position().
|
Chris@17
|
295 *
|
Chris@17
|
296 * Invoked as the 'using' callback of jquery.ui.position() in
|
Chris@17
|
297 * positionToolbar().
|
Chris@17
|
298 *
|
Chris@17
|
299 * @param {*} view
|
Chris@17
|
300 * The view the positions will be calculated from.
|
Chris@17
|
301 * @param {object} suggested
|
Chris@17
|
302 * A hash of top and left values for the position that should be set. It
|
Chris@17
|
303 * can be forwarded to .css() or .animate().
|
Chris@17
|
304 * @param {object} info
|
Chris@17
|
305 * The position and dimensions of both the 'my' element and the 'of'
|
Chris@17
|
306 * elements, as well as calculations to their relative position. This
|
Chris@17
|
307 * object contains the following properties:
|
Chris@17
|
308 * @param {object} info.element
|
Chris@17
|
309 * A hash that contains information about the HTML element that will be
|
Chris@17
|
310 * positioned. Also known as the 'my' element.
|
Chris@17
|
311 * @param {object} info.target
|
Chris@17
|
312 * A hash that contains information about the HTML element that the
|
Chris@17
|
313 * 'my' element will be positioned against. Also known as the 'of'
|
Chris@17
|
314 * element.
|
Chris@17
|
315 */
|
Chris@17
|
316 function refinePosition(view, suggested, info) {
|
Chris@17
|
317 // Determine if the pointer should be on the top or bottom.
|
Chris@17
|
318 const isBelow = suggested.top > info.target.top;
|
Chris@17
|
319 info.element.element.toggleClass(
|
Chris@17
|
320 'quickedit-toolbar-pointer-top',
|
Chris@17
|
321 isBelow,
|
Chris@17
|
322 );
|
Chris@17
|
323 // Don't position the toolbar past the first or last editable field if
|
Chris@17
|
324 // the entity is the target.
|
Chris@17
|
325 if (view.$entity[0] === info.target.element[0]) {
|
Chris@17
|
326 // Get the first or last field according to whether the toolbar is
|
Chris@17
|
327 // above or below the entity.
|
Chris@17
|
328 const $field = view.$entity
|
Chris@17
|
329 .find('.quickedit-editable')
|
Chris@17
|
330 .eq(isBelow ? -1 : 0);
|
Chris@17
|
331 if ($field.length > 0) {
|
Chris@17
|
332 suggested.top = isBelow
|
Chris@17
|
333 ? $field.offset().top + $field.outerHeight(true)
|
Chris@17
|
334 : $field.offset().top - info.element.element.outerHeight(true);
|
Chris@17
|
335 }
|
Chris@17
|
336 }
|
Chris@17
|
337 // Don't let the toolbar go outside the fence.
|
Chris@17
|
338 const fenceTop = view.$fence.offset().top;
|
Chris@17
|
339 const fenceHeight = view.$fence.height();
|
Chris@17
|
340 const toolbarHeight = info.element.element.outerHeight(true);
|
Chris@17
|
341 if (suggested.top < fenceTop) {
|
Chris@17
|
342 suggested.top = fenceTop;
|
Chris@17
|
343 } else if (suggested.top + toolbarHeight > fenceTop + fenceHeight) {
|
Chris@17
|
344 suggested.top = fenceTop + fenceHeight - toolbarHeight;
|
Chris@17
|
345 }
|
Chris@17
|
346 // Position the toolbar.
|
Chris@17
|
347 info.element.element.css({
|
Chris@17
|
348 left: Math.floor(suggested.left),
|
Chris@17
|
349 top: Math.floor(suggested.top),
|
Chris@17
|
350 });
|
Chris@0
|
351 }
|
Chris@17
|
352
|
Chris@17
|
353 /**
|
Chris@17
|
354 * Calls the jquery.ui.position() method on the $el of this view.
|
Chris@17
|
355 */
|
Chris@17
|
356 function positionToolbar() {
|
Chris@17
|
357 that.$el
|
Chris@17
|
358 .position({
|
Chris@17
|
359 my: `${edge} bottom`,
|
Chris@17
|
360 // Move the toolbar 1px towards the start edge of the 'of' element,
|
Chris@17
|
361 // plus any horizontal padding that may have been added to the
|
Chris@17
|
362 // element that is being added, to prevent unwanted horizontal
|
Chris@17
|
363 // movement.
|
Chris@17
|
364 at: `${edge}+${1 + horizontalPadding} top`,
|
Chris@17
|
365 of,
|
Chris@17
|
366 collision: 'flipfit',
|
Chris@17
|
367 using: refinePosition.bind(null, that),
|
Chris@17
|
368 within: that.$fence,
|
Chris@17
|
369 })
|
Chris@17
|
370 // Resize the toolbar to match the dimensions of the field, up to a
|
Chris@17
|
371 // maximum width that is equal to 90% of the field's width.
|
Chris@17
|
372 .css({
|
Chris@17
|
373 'max-width':
|
Chris@17
|
374 document.documentElement.clientWidth < 450
|
Chris@17
|
375 ? document.documentElement.clientWidth
|
Chris@17
|
376 : 450,
|
Chris@17
|
377 // Set a minimum width of 240px for the entity toolbar, or the width
|
Chris@17
|
378 // of the client if it is less than 240px, so that the toolbar
|
Chris@17
|
379 // never folds up into a squashed and jumbled mess.
|
Chris@17
|
380 'min-width':
|
Chris@17
|
381 document.documentElement.clientWidth < 240
|
Chris@17
|
382 ? document.documentElement.clientWidth
|
Chris@17
|
383 : 240,
|
Chris@17
|
384 width: '100%',
|
Chris@17
|
385 });
|
Chris@0
|
386 }
|
Chris@17
|
387
|
Chris@17
|
388 // Uses the jQuery.ui.position() method. Use a timeout to move the toolbar
|
Chris@17
|
389 // only after the user has focused on an editable for 250ms. This prevents
|
Chris@17
|
390 // the toolbar from jumping around the screen.
|
Chris@17
|
391 this.timer = setTimeout(() => {
|
Chris@17
|
392 // Render the position in the next execution cycle, so that animations
|
Chris@17
|
393 // on the field have time to process. This is not strictly speaking, a
|
Chris@17
|
394 // guarantee that all animations will be finished, but it's a simple
|
Chris@17
|
395 // way to get better positioning without too much additional code.
|
Chris@17
|
396 _.defer(positionToolbar);
|
Chris@17
|
397 }, delay);
|
Chris@17
|
398 },
|
Chris@0
|
399
|
Chris@0
|
400 /**
|
Chris@17
|
401 * Set the model state to 'saving' when the save button is clicked.
|
Chris@17
|
402 *
|
Chris@17
|
403 * @param {jQuery.Event} event
|
Chris@17
|
404 * The click event.
|
Chris@0
|
405 */
|
Chris@17
|
406 onClickSave(event) {
|
Chris@17
|
407 event.stopPropagation();
|
Chris@17
|
408 event.preventDefault();
|
Chris@17
|
409 // Save the model.
|
Chris@17
|
410 this.model.set('state', 'committing');
|
Chris@17
|
411 },
|
Chris@0
|
412
|
Chris@17
|
413 /**
|
Chris@17
|
414 * Sets the model state to candidate when the cancel button is clicked.
|
Chris@17
|
415 *
|
Chris@17
|
416 * @param {jQuery.Event} event
|
Chris@17
|
417 * The click event.
|
Chris@17
|
418 */
|
Chris@17
|
419 onClickCancel(event) {
|
Chris@17
|
420 event.preventDefault();
|
Chris@17
|
421 this.model.set('state', 'deactivating');
|
Chris@17
|
422 },
|
Chris@0
|
423
|
Chris@17
|
424 /**
|
Chris@17
|
425 * Clears the timeout that will eventually reposition the entity toolbar.
|
Chris@17
|
426 *
|
Chris@17
|
427 * Without this, it may reposition itself, away from the user's cursor!
|
Chris@17
|
428 *
|
Chris@17
|
429 * @param {jQuery.Event} event
|
Chris@17
|
430 * The mouse event.
|
Chris@17
|
431 */
|
Chris@17
|
432 onMouseenter(event) {
|
Chris@17
|
433 clearTimeout(this.timer);
|
Chris@17
|
434 },
|
Chris@0
|
435
|
Chris@17
|
436 /**
|
Chris@17
|
437 * Builds the entity toolbar HTML; attaches to DOM; sets starting position.
|
Chris@17
|
438 *
|
Chris@17
|
439 * @return {jQuery}
|
Chris@17
|
440 * The toolbar element.
|
Chris@17
|
441 */
|
Chris@17
|
442 buildToolbarEl() {
|
Chris@17
|
443 const $toolbar = $(
|
Chris@17
|
444 Drupal.theme('quickeditEntityToolbar', {
|
Chris@17
|
445 id: 'quickedit-entity-toolbar',
|
Chris@17
|
446 }),
|
Chris@17
|
447 );
|
Chris@0
|
448
|
Chris@17
|
449 $toolbar
|
Chris@17
|
450 .find('.quickedit-toolbar-entity')
|
Chris@17
|
451 // Append the "ops" toolgroup into the toolbar.
|
Chris@17
|
452 .prepend(
|
Chris@17
|
453 Drupal.theme('quickeditToolgroup', {
|
Chris@17
|
454 classes: ['ops'],
|
Chris@17
|
455 buttons: [
|
Chris@17
|
456 {
|
Chris@17
|
457 label: Drupal.t('Save'),
|
Chris@17
|
458 type: 'submit',
|
Chris@17
|
459 classes: 'action-save quickedit-button icon',
|
Chris@17
|
460 attributes: {
|
Chris@17
|
461 'aria-hidden': true,
|
Chris@17
|
462 },
|
Chris@17
|
463 },
|
Chris@17
|
464 {
|
Chris@17
|
465 label: Drupal.t('Close'),
|
Chris@17
|
466 classes:
|
Chris@17
|
467 'action-cancel quickedit-button icon icon-close icon-only',
|
Chris@17
|
468 },
|
Chris@17
|
469 ],
|
Chris@17
|
470 }),
|
Chris@17
|
471 );
|
Chris@0
|
472
|
Chris@17
|
473 // Give the toolbar a sensible starting position so that it doesn't
|
Chris@17
|
474 // animate on to the screen from a far off corner.
|
Chris@17
|
475 $toolbar.css({
|
Chris@0
|
476 left: this.$entity.offset().left,
|
Chris@0
|
477 top: this.$entity.offset().top,
|
Chris@0
|
478 });
|
Chris@0
|
479
|
Chris@17
|
480 return $toolbar;
|
Chris@17
|
481 },
|
Chris@17
|
482
|
Chris@17
|
483 /**
|
Chris@17
|
484 * Returns the DOM element that fields will attach their toolbars to.
|
Chris@17
|
485 *
|
Chris@17
|
486 * @return {jQuery}
|
Chris@17
|
487 * The DOM element that fields will attach their toolbars to.
|
Chris@17
|
488 */
|
Chris@17
|
489 getToolbarRoot() {
|
Chris@17
|
490 return this._fieldToolbarRoot;
|
Chris@17
|
491 },
|
Chris@17
|
492
|
Chris@17
|
493 /**
|
Chris@17
|
494 * Generates a state-dependent label for the entity toolbar.
|
Chris@17
|
495 */
|
Chris@17
|
496 label() {
|
Chris@17
|
497 // The entity label.
|
Chris@17
|
498 let label = '';
|
Chris@17
|
499 const entityLabel = this.model.get('label');
|
Chris@17
|
500
|
Chris@17
|
501 // Label of an active field, if it exists.
|
Chris@17
|
502 const activeField = Drupal.quickedit.app.model.get('activeField');
|
Chris@17
|
503 const activeFieldLabel =
|
Chris@17
|
504 activeField && activeField.get('metadata').label;
|
Chris@17
|
505 // Label of a highlighted field, if it exists.
|
Chris@17
|
506 const highlightedField = Drupal.quickedit.app.model.get(
|
Chris@17
|
507 'highlightedField',
|
Chris@17
|
508 );
|
Chris@17
|
509 const highlightedFieldLabel =
|
Chris@17
|
510 highlightedField && highlightedField.get('metadata').label;
|
Chris@17
|
511 // The label is constructed in a priority order.
|
Chris@17
|
512 if (activeFieldLabel) {
|
Chris@17
|
513 label = Drupal.theme('quickeditEntityToolbarLabel', {
|
Chris@17
|
514 entityLabel,
|
Chris@17
|
515 fieldLabel: activeFieldLabel,
|
Chris@17
|
516 });
|
Chris@17
|
517 } else if (highlightedFieldLabel) {
|
Chris@17
|
518 label = Drupal.theme('quickeditEntityToolbarLabel', {
|
Chris@17
|
519 entityLabel,
|
Chris@17
|
520 fieldLabel: highlightedFieldLabel,
|
Chris@17
|
521 });
|
Chris@17
|
522 } else {
|
Chris@17
|
523 // @todo Add XSS regression test coverage in https://www.drupal.org/node/2547437
|
Chris@17
|
524 label = Drupal.checkPlain(entityLabel);
|
Chris@17
|
525 }
|
Chris@17
|
526
|
Chris@17
|
527 this.$el.find('.quickedit-toolbar-label').html(label);
|
Chris@17
|
528 },
|
Chris@17
|
529
|
Chris@17
|
530 /**
|
Chris@17
|
531 * Adds classes to a toolgroup.
|
Chris@17
|
532 *
|
Chris@17
|
533 * @param {string} toolgroup
|
Chris@17
|
534 * A toolgroup name.
|
Chris@17
|
535 * @param {string} classes
|
Chris@17
|
536 * A string of space-delimited class names that will be applied to the
|
Chris@17
|
537 * wrapping element of the toolbar group.
|
Chris@17
|
538 */
|
Chris@17
|
539 addClass(toolgroup, classes) {
|
Chris@17
|
540 this._find(toolgroup).addClass(classes);
|
Chris@17
|
541 },
|
Chris@17
|
542
|
Chris@17
|
543 /**
|
Chris@17
|
544 * Removes classes from a toolgroup.
|
Chris@17
|
545 *
|
Chris@17
|
546 * @param {string} toolgroup
|
Chris@17
|
547 * A toolgroup name.
|
Chris@17
|
548 * @param {string} classes
|
Chris@17
|
549 * A string of space-delimited class names that will be removed from the
|
Chris@17
|
550 * wrapping element of the toolbar group.
|
Chris@17
|
551 */
|
Chris@17
|
552 removeClass(toolgroup, classes) {
|
Chris@17
|
553 this._find(toolgroup).removeClass(classes);
|
Chris@17
|
554 },
|
Chris@17
|
555
|
Chris@17
|
556 /**
|
Chris@17
|
557 * Finds a toolgroup.
|
Chris@17
|
558 *
|
Chris@17
|
559 * @param {string} toolgroup
|
Chris@17
|
560 * A toolgroup name.
|
Chris@17
|
561 *
|
Chris@17
|
562 * @return {jQuery}
|
Chris@17
|
563 * The toolgroup DOM element.
|
Chris@17
|
564 */
|
Chris@17
|
565 _find(toolgroup) {
|
Chris@17
|
566 return this.$el.find(
|
Chris@17
|
567 `.quickedit-toolbar .quickedit-toolgroup.${toolgroup}`,
|
Chris@17
|
568 );
|
Chris@17
|
569 },
|
Chris@17
|
570
|
Chris@17
|
571 /**
|
Chris@17
|
572 * Shows a toolgroup.
|
Chris@17
|
573 *
|
Chris@17
|
574 * @param {string} toolgroup
|
Chris@17
|
575 * A toolgroup name.
|
Chris@17
|
576 */
|
Chris@17
|
577 show(toolgroup) {
|
Chris@17
|
578 this.$el.removeClass('quickedit-animate-invisible');
|
Chris@17
|
579 },
|
Chris@0
|
580 },
|
Chris@17
|
581 );
|
Chris@17
|
582 })(jQuery, _, Backbone, Drupal, Drupal.debounce);
|