Version 1
[yaffs-website] / web / core / modules / quickedit / js / editors / formEditor.js
1 /**
2  * @file
3  * Form-based in-place editor. Works for any field type.
4  */
5
6 (function ($, Drupal, _) {
7
8   'use strict';
9
10   /**
11    * @constructor
12    *
13    * @augments Drupal.quickedit.EditorView
14    */
15   Drupal.quickedit.editors.form = Drupal.quickedit.EditorView.extend(/** @lends Drupal.quickedit.editors.form# */{
16
17     /**
18      * Tracks form container DOM element that is used while in-place editing.
19      *
20      * @type {jQuery}
21      */
22     $formContainer: null,
23
24     /**
25      * Holds the {@link Drupal.Ajax} object.
26      *
27      * @type {Drupal.Ajax}
28      */
29     formSaveAjax: null,
30
31     /**
32      * @inheritdoc
33      *
34      * @param {object} fieldModel
35      *   The field model that holds the state.
36      * @param {string} state
37      *   The state to change to.
38      */
39     stateChange: function (fieldModel, state) {
40       var from = fieldModel.previous('state');
41       var to = state;
42       switch (to) {
43         case 'inactive':
44           break;
45
46         case 'candidate':
47           if (from !== 'inactive') {
48             this.removeForm();
49           }
50           break;
51
52         case 'highlighted':
53           break;
54
55         case 'activating':
56           // If coming from an invalid state, then the form is already loaded.
57           if (from !== 'invalid') {
58             this.loadForm();
59           }
60           break;
61
62         case 'active':
63           break;
64
65         case 'changed':
66           break;
67
68         case 'saving':
69           this.save();
70           break;
71
72         case 'saved':
73           break;
74
75         case 'invalid':
76           this.showValidationErrors();
77           break;
78       }
79     },
80
81     /**
82      * @inheritdoc
83      *
84      * @return {object}
85      *   A settings object for the quick edit UI.
86      */
87     getQuickEditUISettings: function () {
88       return {padding: true, unifiedToolbar: true, fullWidthToolbar: true, popup: true};
89     },
90
91     /**
92      * Loads the form for this field, displays it on top of the actual field.
93      */
94     loadForm: function () {
95       var fieldModel = this.fieldModel;
96
97       // Generate a DOM-compatible ID for the form container DOM element.
98       var id = 'quickedit-form-for-' + fieldModel.id.replace(/[\/\[\]]/g, '_');
99
100       // Render form container.
101       var $formContainer = this.$formContainer = $(Drupal.theme('quickeditFormContainer', {
102         id: id,
103         loadingMsg: Drupal.t('Loading…')
104       }));
105       $formContainer
106         .find('.quickedit-form')
107         .addClass('quickedit-editable quickedit-highlighted quickedit-editing')
108         .attr('role', 'dialog');
109
110       // Insert form container in DOM.
111       if (this.$el.css('display') === 'inline') {
112         $formContainer.prependTo(this.$el.offsetParent());
113         // Position the form container to render on top of the field's element.
114         var pos = this.$el.position();
115         $formContainer.css('left', pos.left).css('top', pos.top);
116       }
117       else {
118         $formContainer.insertBefore(this.$el);
119       }
120
121       // Load form, insert it into the form container and attach event handlers.
122       var formOptions = {
123         fieldID: fieldModel.get('fieldID'),
124         $el: this.$el,
125         nocssjs: false,
126         // Reset an existing entry for this entity in the PrivateTempStore (if
127         // any) when loading the field. Logically speaking, this should happen
128         // in a separate request because this is an entity-level operation, not
129         // a field-level operation. But that would require an additional
130         // request, that might not even be necessary: it is only when a user
131         // loads a first changed field for an entity that this needs to happen:
132         // precisely now!
133         reset: !fieldModel.get('entity').get('inTempStore')
134       };
135       Drupal.quickedit.util.form.load(formOptions, function (form, ajax) {
136         Drupal.AjaxCommands.prototype.insert(ajax, {
137           data: form,
138           selector: '#' + id + ' .placeholder'
139         });
140
141         $formContainer
142           .on('formUpdated.quickedit', ':input', function (event) {
143             var state = fieldModel.get('state');
144             // If the form is in an invalid state, it will persist on the page.
145             // Set the field to activating so that the user can correct the
146             // invalid value.
147             if (state === 'invalid') {
148               fieldModel.set('state', 'activating');
149             }
150             // Otherwise assume that the fieldModel is in a candidate state and
151             // set it to changed on formUpdate.
152             else {
153               fieldModel.set('state', 'changed');
154             }
155           })
156           .on('keypress.quickedit', 'input', function (event) {
157             if (event.keyCode === 13) {
158               return false;
159             }
160           });
161
162         // The in-place editor has loaded; change state to 'active'.
163         fieldModel.set('state', 'active');
164       });
165     },
166
167     /**
168      * Removes the form for this field, detaches behaviors and event handlers.
169      */
170     removeForm: function () {
171       if (this.$formContainer === null) {
172         return;
173       }
174
175       delete this.formSaveAjax;
176       // Allow form widgets to detach properly.
177       Drupal.detachBehaviors(this.$formContainer.get(0), null, 'unload');
178       this.$formContainer
179         .off('change.quickedit', ':input')
180         .off('keypress.quickedit', 'input')
181         .remove();
182       this.$formContainer = null;
183     },
184
185     /**
186      * @inheritdoc
187      */
188     save: function () {
189       var $formContainer = this.$formContainer;
190       var $submit = $formContainer.find('.quickedit-form-submit');
191       var editorModel = this.model;
192       var fieldModel = this.fieldModel;
193
194       function cleanUpAjax() {
195         Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax);
196         formSaveAjax = null;
197       }
198
199       // Create an AJAX object for the form associated with the field.
200       var formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({
201         nocssjs: false,
202         other_view_modes: fieldModel.findOtherViewModes()
203       }, $submit);
204
205       // Successfully saved.
206       formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) {
207         cleanUpAjax();
208         // First, transition the state to 'saved'.
209         fieldModel.set('state', 'saved');
210         // Second, set the 'htmlForOtherViewModes' attribute, so that when this
211         // field is rerendered, the change can be propagated to other instances
212         // of this field, which may be displayed in different view modes.
213         fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
214         // Finally, set the 'html' attribute on the field model. This will cause
215         // the field to be rerendered.
216         _.defer(function () {
217           fieldModel.set('html', response.data);
218         });
219       };
220
221       // Unsuccessfully saved; validation errors.
222       formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) {
223         editorModel.set('validationErrors', response.data);
224         fieldModel.set('state', 'invalid');
225       };
226
227       // The quickeditFieldForm AJAX command is called upon attempting to save
228       // the form; Form API will mark which form items have errors, if any. This
229       // command is invoked only if validation errors exist and then it runs
230       // before editFieldFormValidationErrors().
231       formSaveAjax.commands.quickeditFieldForm = function (ajax, response, status) {
232         Drupal.AjaxCommands.prototype.insert(ajax, {
233           data: response.data,
234           selector: '#' + $formContainer.attr('id') + ' form'
235         });
236       };
237
238       // Click the form's submit button; the scoped AJAX commands above will
239       // handle the server's response.
240       $submit.trigger('click.quickedit');
241     },
242
243     /**
244      * @inheritdoc
245      */
246     showValidationErrors: function () {
247       this.$formContainer
248         .find('.quickedit-form')
249         .addClass('quickedit-validation-error')
250         .find('form')
251         .prepend(this.model.get('validationErrors'));
252     }
253   });
254
255 })(jQuery, Drupal, _);