Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/image/js/editors/image.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 * Drag+drop based in-place editor for images. | |
4 */ | |
5 | |
6 (function ($, _, Drupal) { | |
7 Drupal.quickedit.editors.image = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.image# */{ | |
8 | |
9 /** | |
10 * @constructs | |
11 * | |
12 * @augments Drupal.quickedit.EditorView | |
13 * | |
14 * @param {object} options | |
15 * Options for the image editor. | |
16 */ | |
17 initialize(options) { | |
18 Drupal.quickedit.EditorView.prototype.initialize.call(this, options); | |
19 // Set our original value to our current HTML (for reverting). | |
20 this.model.set('originalValue', this.$el.html().trim()); | |
21 // $.val() callback function for copying input from our custom form to | |
22 // the Quick Edit Field Form. | |
23 this.model.set('currentValue', function (index, value) { | |
24 const matches = $(this).attr('name').match(/(alt|title)]$/); | |
25 if (matches) { | |
26 const name = matches[1]; | |
27 const $toolgroup = $(`#${options.fieldModel.toolbarView.getMainWysiwygToolgroupId()}`); | |
28 const $input = $toolgroup.find(`.quickedit-image-field-info input[name="${name}"]`); | |
29 if ($input.length) { | |
30 return $input.val(); | |
31 } | |
32 } | |
33 }); | |
34 }, | |
35 | |
36 /** | |
37 * @inheritdoc | |
38 * | |
39 * @param {Drupal.quickedit.FieldModel} fieldModel | |
40 * The field model that holds the state. | |
41 * @param {string} state | |
42 * The state to change to. | |
43 * @param {object} options | |
44 * State options, if needed by the state change. | |
45 */ | |
46 stateChange(fieldModel, state, options) { | |
47 const from = fieldModel.previous('state'); | |
48 switch (state) { | |
49 case 'inactive': | |
50 break; | |
51 | |
52 case 'candidate': | |
53 if (from !== 'inactive') { | |
54 this.$el.find('.quickedit-image-dropzone').remove(); | |
55 this.$el.removeClass('quickedit-image-element'); | |
56 } | |
57 if (from === 'invalid') { | |
58 this.removeValidationErrors(); | |
59 } | |
60 break; | |
61 | |
62 case 'highlighted': | |
63 break; | |
64 | |
65 case 'activating': | |
66 // Defer updating the field model until the current state change has | |
67 // propagated, to not trigger a nested state change event. | |
68 _.defer(() => { | |
69 fieldModel.set('state', 'active'); | |
70 }); | |
71 break; | |
72 | |
73 case 'active': { | |
74 const self = this; | |
75 | |
76 // Indicate that this element is being edited by Quick Edit Image. | |
77 this.$el.addClass('quickedit-image-element'); | |
78 | |
79 // Render our initial dropzone element. Once the user reverts changes | |
80 // or saves a new image, this element is removed. | |
81 const $dropzone = this.renderDropzone('upload', Drupal.t('Drop file here or click to upload')); | |
82 | |
83 $dropzone.on('dragenter', function (e) { | |
84 $(this).addClass('hover'); | |
85 }); | |
86 $dropzone.on('dragleave', function (e) { | |
87 $(this).removeClass('hover'); | |
88 }); | |
89 | |
90 $dropzone.on('drop', function (e) { | |
91 // Only respond when a file is dropped (could be another element). | |
92 if (e.originalEvent.dataTransfer && e.originalEvent.dataTransfer.files.length) { | |
93 $(this).removeClass('hover'); | |
94 self.uploadImage(e.originalEvent.dataTransfer.files[0]); | |
95 } | |
96 }); | |
97 | |
98 $dropzone.on('click', (e) => { | |
99 // Create an <input> element without appending it to the DOM, and | |
100 // trigger a click event. This is the easiest way to arbitrarily | |
101 // open the browser's upload dialog. | |
102 $('<input type="file">') | |
103 .trigger('click') | |
104 .on('change', function () { | |
105 if (this.files.length) { | |
106 self.uploadImage(this.files[0]); | |
107 } | |
108 }); | |
109 }); | |
110 | |
111 // Prevent the browser's default behavior when dragging files onto | |
112 // the document (usually opens them in the same tab). | |
113 $dropzone.on('dragover dragenter dragleave drop click', (e) => { | |
114 e.preventDefault(); | |
115 e.stopPropagation(); | |
116 }); | |
117 | |
118 this.renderToolbar(fieldModel); | |
119 break; | |
120 } | |
121 | |
122 case 'changed': | |
123 break; | |
124 | |
125 case 'saving': | |
126 if (from === 'invalid') { | |
127 this.removeValidationErrors(); | |
128 } | |
129 | |
130 this.save(options); | |
131 break; | |
132 | |
133 case 'saved': | |
134 break; | |
135 | |
136 case 'invalid': | |
137 this.showValidationErrors(); | |
138 break; | |
139 } | |
140 }, | |
141 | |
142 /** | |
143 * Validates/uploads a given file. | |
144 * | |
145 * @param {File} file | |
146 * The file to upload. | |
147 */ | |
148 uploadImage(file) { | |
149 // Indicate loading by adding a special class to our icon. | |
150 this.renderDropzone('upload loading', Drupal.t('Uploading <i>@file</i>…', { '@file': file.name })); | |
151 | |
152 // Build a valid URL for our endpoint. | |
153 const fieldID = this.fieldModel.get('fieldID'); | |
154 const url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/upload/!entity_type/!id/!field_name/!langcode/!view_mode')); | |
155 | |
156 // Construct form data that our endpoint can consume. | |
157 const data = new FormData(); | |
158 data.append('files[image]', file); | |
159 | |
160 // Construct a POST request to our endpoint. | |
161 const self = this; | |
162 this.ajax({ | |
163 type: 'POST', | |
164 url, | |
165 data, | |
166 success(response) { | |
167 const $el = $(self.fieldModel.get('el')); | |
168 // Indicate that the field has changed - this enables the | |
169 // "Save" button. | |
170 self.fieldModel.set('state', 'changed'); | |
171 self.fieldModel.get('entity').set('inTempStore', true); | |
172 self.removeValidationErrors(); | |
173 | |
174 // Replace our html with the new image. If we replaced our entire | |
175 // element with data.html, we would have to implement complicated logic | |
176 // like what's in Drupal.quickedit.AppView.renderUpdatedField. | |
177 const $content = $(response.html).closest('[data-quickedit-field-id]').children(); | |
178 $el.empty().append($content); | |
179 }, | |
180 }); | |
181 }, | |
182 | |
183 /** | |
184 * Utility function to make an AJAX request to the server. | |
185 * | |
186 * In addition to formatting the correct request, this also handles error | |
187 * codes and messages by displaying them visually inline with the image. | |
188 * | |
189 * Drupal.ajax is not called here as the Form API is unused by this | |
190 * in-place editor, and our JSON requests/responses try to be | |
191 * editor-agnostic. Ideally similar logic and routes could be used by | |
192 * modules like CKEditor for drag+drop file uploads as well. | |
193 * | |
194 * @param {object} options | |
195 * Ajax options. | |
196 * @param {string} options.type | |
197 * The type of request (i.e. GET, POST, PUT, DELETE, etc.) | |
198 * @param {string} options.url | |
199 * The URL for the request. | |
200 * @param {*} options.data | |
201 * The data to send to the server. | |
202 * @param {function} options.success | |
203 * A callback function used when a request is successful, without errors. | |
204 */ | |
205 ajax(options) { | |
206 const defaultOptions = { | |
207 context: this, | |
208 dataType: 'json', | |
209 cache: false, | |
210 contentType: false, | |
211 processData: false, | |
212 error() { | |
213 this.renderDropzone('error', Drupal.t('A server error has occurred.')); | |
214 }, | |
215 }; | |
216 | |
217 const ajaxOptions = $.extend(defaultOptions, options); | |
218 const successCallback = ajaxOptions.success; | |
219 | |
220 // Handle the success callback. | |
221 ajaxOptions.success = function (response) { | |
222 if (response.main_error) { | |
223 this.renderDropzone('error', response.main_error); | |
224 if (response.errors.length) { | |
225 this.model.set('validationErrors', response.errors); | |
226 } | |
227 this.showValidationErrors(); | |
228 } | |
229 else { | |
230 successCallback(response); | |
231 } | |
232 }; | |
233 | |
234 $.ajax(ajaxOptions); | |
235 }, | |
236 | |
237 /** | |
238 * Renders our toolbar form for editing metadata. | |
239 * | |
240 * @param {Drupal.quickedit.FieldModel} fieldModel | |
241 * The current Field Model. | |
242 */ | |
243 renderToolbar(fieldModel) { | |
244 const $toolgroup = $(`#${fieldModel.toolbarView.getMainWysiwygToolgroupId()}`); | |
245 let $toolbar = $toolgroup.find('.quickedit-image-field-info'); | |
246 if ($toolbar.length === 0) { | |
247 // Perform an AJAX request for extra image info (alt/title). | |
248 const fieldID = fieldModel.get('fieldID'); | |
249 const url = Drupal.quickedit.util.buildUrl(fieldID, Drupal.url('quickedit/image/info/!entity_type/!id/!field_name/!langcode/!view_mode')); | |
250 const self = this; | |
251 self.ajax({ | |
252 type: 'GET', | |
253 url, | |
254 success(response) { | |
255 $toolbar = $(Drupal.theme.quickeditImageToolbar(response)); | |
256 $toolgroup.append($toolbar); | |
257 $toolbar.on('keyup paste', () => { | |
258 fieldModel.set('state', 'changed'); | |
259 }); | |
260 // Re-position the toolbar, which could have changed size. | |
261 fieldModel.get('entity').toolbarView.position(); | |
262 }, | |
263 }); | |
264 } | |
265 }, | |
266 | |
267 /** | |
268 * Renders our dropzone element. | |
269 * | |
270 * @param {string} state | |
271 * The current state of our editor. Only used for visual styling. | |
272 * @param {string} text | |
273 * The text to display in the dropzone area. | |
274 * | |
275 * @return {jQuery} | |
276 * The rendered dropzone. | |
277 */ | |
278 renderDropzone(state, text) { | |
279 let $dropzone = this.$el.find('.quickedit-image-dropzone'); | |
280 // If the element already exists, modify its contents. | |
281 if ($dropzone.length) { | |
282 $dropzone | |
283 .removeClass('upload error hover loading') | |
284 .addClass(`.quickedit-image-dropzone ${state}`) | |
285 .children('.quickedit-image-text') | |
286 .html(text); | |
287 } | |
288 else { | |
289 $dropzone = $(Drupal.theme('quickeditImageDropzone', { | |
290 state, | |
291 text, | |
292 })); | |
293 this.$el.append($dropzone); | |
294 } | |
295 | |
296 return $dropzone; | |
297 }, | |
298 | |
299 /** | |
300 * @inheritdoc | |
301 */ | |
302 revert() { | |
303 this.$el.html(this.model.get('originalValue')); | |
304 }, | |
305 | |
306 /** | |
307 * @inheritdoc | |
308 */ | |
309 getQuickEditUISettings() { | |
310 return { padding: false, unifiedToolbar: true, fullWidthToolbar: true, popup: false }; | |
311 }, | |
312 | |
313 /** | |
314 * @inheritdoc | |
315 */ | |
316 showValidationErrors() { | |
317 const errors = Drupal.theme('quickeditImageErrors', { | |
318 errors: this.model.get('validationErrors'), | |
319 }); | |
320 $(`#${this.fieldModel.toolbarView.getMainWysiwygToolgroupId()}`) | |
321 .append(errors); | |
322 this.getEditedElement() | |
323 .addClass('quickedit-validation-error'); | |
324 // Re-position the toolbar, which could have changed size. | |
325 this.fieldModel.get('entity').toolbarView.position(); | |
326 }, | |
327 | |
328 /** | |
329 * @inheritdoc | |
330 */ | |
331 removeValidationErrors() { | |
332 $(`#${this.fieldModel.toolbarView.getMainWysiwygToolgroupId()}`) | |
333 .find('.quickedit-image-errors').remove(); | |
334 this.getEditedElement() | |
335 .removeClass('quickedit-validation-error'); | |
336 }, | |
337 | |
338 }); | |
339 }(jQuery, _, Drupal)); |