Mercurial > hg > cmmr2012-drupal-site
diff core/modules/quickedit/js/views/EditorView.es6.js @ 4:a9cd425dd02b
Update, including to Drupal core 8.6.10
author | Chris Cannam |
---|---|
date | Thu, 28 Feb 2019 13:11:55 +0000 |
parents | c75dbcec494b |
children |
line wrap: on
line diff
--- a/core/modules/quickedit/js/views/EditorView.es6.js Thu Feb 28 11:14:44 2019 +0000 +++ b/core/modules/quickedit/js/views/EditorView.es6.js Thu Feb 28 13:11:55 2019 +0000 @@ -3,299 +3,323 @@ * An abstract Backbone View that controls an in-place editor. */ -(function ($, Backbone, Drupal) { - Drupal.quickedit.EditorView = Backbone.View.extend(/** @lends Drupal.quickedit.EditorView# */{ +(function($, Backbone, Drupal) { + Drupal.quickedit.EditorView = Backbone.View.extend( + /** @lends Drupal.quickedit.EditorView# */ { + /** + * A base implementation that outlines the structure for in-place editors. + * + * Specific in-place editor implementations should subclass (extend) this + * View and override whichever method they deem necessary to override. + * + * Typically you would want to override this method to set the + * originalValue attribute in the FieldModel to such a value that your + * in-place editor can revert to the original value when necessary. + * + * @example + * <caption>If you override this method, you should call this + * method (the parent class' initialize()) first.</caption> + * Drupal.quickedit.EditorView.prototype.initialize.call(this, options); + * + * @constructs + * + * @augments Backbone.View + * + * @param {object} options + * An object with the following keys: + * @param {Drupal.quickedit.EditorModel} options.model + * The in-place editor state model. + * @param {Drupal.quickedit.FieldModel} options.fieldModel + * The field model. + * + * @see Drupal.quickedit.EditorModel + * @see Drupal.quickedit.editors.plain_text + */ + initialize(options) { + this.fieldModel = options.fieldModel; + this.listenTo(this.fieldModel, 'change:state', this.stateChange); + }, - /** - * A base implementation that outlines the structure for in-place editors. - * - * Specific in-place editor implementations should subclass (extend) this - * View and override whichever method they deem necessary to override. - * - * Typically you would want to override this method to set the - * originalValue attribute in the FieldModel to such a value that your - * in-place editor can revert to the original value when necessary. - * - * @example - * <caption>If you override this method, you should call this - * method (the parent class' initialize()) first.</caption> - * Drupal.quickedit.EditorView.prototype.initialize.call(this, options); - * - * @constructs - * - * @augments Backbone.View - * - * @param {object} options - * An object with the following keys: - * @param {Drupal.quickedit.EditorModel} options.model - * The in-place editor state model. - * @param {Drupal.quickedit.FieldModel} options.fieldModel - * The field model. - * - * @see Drupal.quickedit.EditorModel - * @see Drupal.quickedit.editors.plain_text - */ - initialize(options) { - this.fieldModel = options.fieldModel; - this.listenTo(this.fieldModel, 'change:state', this.stateChange); - }, + /** + * @inheritdoc + */ + remove() { + // The el property is the field, which should not be removed. Remove the + // pointer to it, then call Backbone.View.prototype.remove(). + this.setElement(); + Backbone.View.prototype.remove.call(this); + }, - /** - * @inheritdoc - */ - remove() { - // The el property is the field, which should not be removed. Remove the - // pointer to it, then call Backbone.View.prototype.remove(). - this.setElement(); - Backbone.View.prototype.remove.call(this); - }, + /** + * Returns the edited element. + * + * For some single cardinality fields, it may be necessary or useful to + * not in-place edit (and hence decorate) the DOM element with the + * data-quickedit-field-id attribute (which is the field's wrapper), but a + * specific element within the field's wrapper. + * e.g. using a WYSIWYG editor on a body field should happen on the DOM + * element containing the text itself, not on the field wrapper. + * + * @return {jQuery} + * A jQuery-wrapped DOM element. + * + * @see Drupal.quickedit.editors.plain_text + */ + getEditedElement() { + return this.$el; + }, - /** - * Returns the edited element. - * - * For some single cardinality fields, it may be necessary or useful to - * not in-place edit (and hence decorate) the DOM element with the - * data-quickedit-field-id attribute (which is the field's wrapper), but a - * specific element within the field's wrapper. - * e.g. using a WYSIWYG editor on a body field should happen on the DOM - * element containing the text itself, not on the field wrapper. - * - * @return {jQuery} - * A jQuery-wrapped DOM element. - * - * @see Drupal.quickedit.editors.plain_text - */ - getEditedElement() { - return this.$el; - }, + /** + * + * @return {object} + * Returns 3 Quick Edit UI settings that depend on the in-place editor: + * - Boolean padding: indicates whether padding should be applied to the + * edited element, to guarantee legibility of text. + * - Boolean unifiedToolbar: provides the in-place editor with the ability + * to insert its own toolbar UI into Quick Edit's tightly integrated + * toolbar. + * - Boolean fullWidthToolbar: indicates whether Quick Edit's tightly + * integrated toolbar should consume the full width of the element, + * rather than being just long enough to accommodate a label. + */ + getQuickEditUISettings() { + return { + padding: false, + unifiedToolbar: false, + fullWidthToolbar: false, + popup: false, + }; + }, - /** - * - * @return {object} - * Returns 3 Quick Edit UI settings that depend on the in-place editor: - * - Boolean padding: indicates whether padding should be applied to the - * edited element, to guarantee legibility of text. - * - Boolean unifiedToolbar: provides the in-place editor with the ability - * to insert its own toolbar UI into Quick Edit's tightly integrated - * toolbar. - * - Boolean fullWidthToolbar: indicates whether Quick Edit's tightly - * integrated toolbar should consume the full width of the element, - * rather than being just long enough to accommodate a label. - */ - getQuickEditUISettings() { - return { padding: false, unifiedToolbar: false, fullWidthToolbar: false, popup: false }; - }, + /** + * Determines the actions to take given a change of state. + * + * @param {Drupal.quickedit.FieldModel} fieldModel + * The quickedit `FieldModel` that holds the state. + * @param {string} state + * The state of the associated field. One of + * {@link Drupal.quickedit.FieldModel.states}. + */ + stateChange(fieldModel, state) { + const from = fieldModel.previous('state'); + const to = state; + switch (to) { + case 'inactive': + // An in-place editor view will not yet exist in this state, hence + // this will never be reached. Listed for sake of completeness. + break; - /** - * Determines the actions to take given a change of state. - * - * @param {Drupal.quickedit.FieldModel} fieldModel - * The quickedit `FieldModel` that holds the state. - * @param {string} state - * The state of the associated field. One of - * {@link Drupal.quickedit.FieldModel.states}. - */ - stateChange(fieldModel, state) { - const from = fieldModel.previous('state'); - const to = state; - switch (to) { - case 'inactive': - // An in-place editor view will not yet exist in this state, hence - // this will never be reached. Listed for sake of completeness. - break; + case 'candidate': + // Nothing to do for the typical in-place editor: it should not be + // visible yet. Except when we come from the 'invalid' state, then we + // clean up. + if (from === 'invalid') { + this.removeValidationErrors(); + } + break; - case 'candidate': - // Nothing to do for the typical in-place editor: it should not be - // visible yet. Except when we come from the 'invalid' state, then we - // clean up. - if (from === 'invalid') { - this.removeValidationErrors(); + case 'highlighted': + // Nothing to do for the typical in-place editor: it should not be + // visible yet. + break; + + case 'activating': { + // The user has indicated he wants to do in-place editing: if + // something needs to be loaded (CSS/JavaScript/server data/…), then + // do so at this stage, and once the in-place editor is ready, + // set the 'active' state. A "loading" indicator will be shown in the + // UI for as long as the field remains in this state. + const loadDependencies = function(callback) { + // Do the loading here. + callback(); + }; + loadDependencies(() => { + fieldModel.set('state', 'active'); + }); + break; } - break; - case 'highlighted': - // Nothing to do for the typical in-place editor: it should not be - // visible yet. - break; + case 'active': + // The user can now actually use the in-place editor. + break; - case 'activating': { - // The user has indicated he wants to do in-place editing: if - // something needs to be loaded (CSS/JavaScript/server data/…), then - // do so at this stage, and once the in-place editor is ready, - // set the 'active' state. A "loading" indicator will be shown in the - // UI for as long as the field remains in this state. - const loadDependencies = function (callback) { - // Do the loading here. - callback(); - }; - loadDependencies(() => { - fieldModel.set('state', 'active'); - }); - break; + case 'changed': + // Nothing to do for the typical in-place editor. The UI will show an + // indicator that the field has changed. + break; + + case 'saving': + // When the user has indicated he wants to save his changes to this + // field, this state will be entered. If the previous saving attempt + // resulted in validation errors, the previous state will be + // 'invalid'. Clean up those validation errors while the user is + // saving. + if (from === 'invalid') { + this.removeValidationErrors(); + } + this.save(); + break; + + case 'saved': + // Nothing to do for the typical in-place editor. Immediately after + // being saved, a field will go to the 'candidate' state, where it + // should no longer be visible (after all, the field will then again + // just be a *candidate* to be in-place edited). + break; + + case 'invalid': + // The modified field value was attempted to be saved, but there were + // validation errors. + this.showValidationErrors(); + break; + } + }, + + /** + * Reverts the modified value to the original, before editing started. + */ + revert() { + // A no-op by default; each editor should implement reverting itself. + // Note that if the in-place editor does not cause the FieldModel's + // element to be modified, then nothing needs to happen. + }, + + /** + * Saves the modified value in the in-place editor for this field. + */ + save() { + const fieldModel = this.fieldModel; + const editorModel = this.model; + const backstageId = `quickedit_backstage-${this.fieldModel.id.replace( + /[/[\]_\s]/g, + '-', + )}`; + + function fillAndSubmitForm(value) { + const $form = $(`#${backstageId}`).find('form'); + // Fill in the value in any <input> that isn't hidden or a submit + // button. + $form + .find(':input[type!="hidden"][type!="submit"]:not(select)') + // Don't mess with the node summary. + .not('[name$="\\[summary\\]"]') + .val(value); + // Submit the form. + $form.find('.quickedit-form-submit').trigger('click.quickedit'); } - case 'active': - // The user can now actually use the in-place editor. - break; - - case 'changed': - // Nothing to do for the typical in-place editor. The UI will show an - // indicator that the field has changed. - break; - - case 'saving': - // When the user has indicated he wants to save his changes to this - // field, this state will be entered. If the previous saving attempt - // resulted in validation errors, the previous state will be - // 'invalid'. Clean up those validation errors while the user is - // saving. - if (from === 'invalid') { - this.removeValidationErrors(); - } - this.save(); - break; - - case 'saved': - // Nothing to do for the typical in-place editor. Immediately after - // being saved, a field will go to the 'candidate' state, where it - // should no longer be visible (after all, the field will then again - // just be a *candidate* to be in-place edited). - break; - - case 'invalid': - // The modified field value was attempted to be saved, but there were - // validation errors. - this.showValidationErrors(); - break; - } - }, - - /** - * Reverts the modified value to the original, before editing started. - */ - revert() { - // A no-op by default; each editor should implement reverting itself. - // Note that if the in-place editor does not cause the FieldModel's - // element to be modified, then nothing needs to happen. - }, - - /** - * Saves the modified value in the in-place editor for this field. - */ - save() { - const fieldModel = this.fieldModel; - const editorModel = this.model; - const backstageId = `quickedit_backstage-${this.fieldModel.id.replace(/[/[\]_\s]/g, '-')}`; - - function fillAndSubmitForm(value) { - const $form = $(`#${backstageId}`).find('form'); - // Fill in the value in any <input> that isn't hidden or a submit - // button. - $form.find(':input[type!="hidden"][type!="submit"]:not(select)') - // Don't mess with the node summary. - .not('[name$="\\[summary\\]"]').val(value); - // Submit the form. - $form.find('.quickedit-form-submit').trigger('click.quickedit'); - } - - const formOptions = { - fieldID: this.fieldModel.get('fieldID'), - $el: this.$el, - nocssjs: true, - other_view_modes: fieldModel.findOtherViewModes(), - // Reset an existing entry for this entity in the PrivateTempStore (if - // any) when saving the field. Logically speaking, this should happen in - // a separate request because this is an entity-level operation, not a - // field-level operation. But that would require an additional request, - // that might not even be necessary: it is only when a user saves a - // first changed field for an entity that this needs to happen: - // precisely now! - reset: !this.fieldModel.get('entity').get('inTempStore'), - }; - - const self = this; - Drupal.quickedit.util.form.load(formOptions, (form, ajax) => { - // Create a backstage area for storing forms that are hidden from view - // (hence "backstage" — since the editing doesn't happen in the form, it - // happens "directly" in the content, the form is only used for saving). - const $backstage = $(Drupal.theme('quickeditBackstage', { id: backstageId })).appendTo('body'); - // Hidden forms are stuffed into the backstage container for this field. - const $form = $(form).appendTo($backstage); - // Disable the browser's HTML5 validation; we only care about server- - // side validation. (Not disabling this will actually cause problems - // because browsers don't like to set HTML5 validation errors on hidden - // forms.) - $form.prop('novalidate', true); - const $submit = $form.find('.quickedit-form-submit'); - self.formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving(formOptions, $submit); - - function removeHiddenForm() { - Drupal.quickedit.util.form.unajaxifySaving(self.formSaveAjax); - delete self.formSaveAjax; - $backstage.remove(); - } - - // Successfully saved. - self.formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) { - removeHiddenForm(); - // First, transition the state to 'saved'. - fieldModel.set('state', 'saved'); - // Second, set the 'htmlForOtherViewModes' attribute, so that when - // this field is rerendered, the change can be propagated to other - // instances of this field, which may be displayed in different view - // modes. - fieldModel.set('htmlForOtherViewModes', response.other_view_modes); - // Finally, set the 'html' attribute on the field model. This will - // cause the field to be rerendered. - fieldModel.set('html', response.data); + const formOptions = { + fieldID: this.fieldModel.get('fieldID'), + $el: this.$el, + nocssjs: true, + other_view_modes: fieldModel.findOtherViewModes(), + // Reset an existing entry for this entity in the PrivateTempStore (if + // any) when saving the field. Logically speaking, this should happen in + // a separate request because this is an entity-level operation, not a + // field-level operation. But that would require an additional request, + // that might not even be necessary: it is only when a user saves a + // first changed field for an entity that this needs to happen: + // precisely now! + reset: !this.fieldModel.get('entity').get('inTempStore'), }; - // Unsuccessfully saved; validation errors. - self.formSaveAjax.commands.quickeditFieldFormValidationErrors = function (ajax, response, status) { - removeHiddenForm(); - editorModel.set('validationErrors', response.data); - fieldModel.set('state', 'invalid'); - }; + const self = this; + Drupal.quickedit.util.form.load(formOptions, (form, ajax) => { + // Create a backstage area for storing forms that are hidden from view + // (hence "backstage" — since the editing doesn't happen in the form, it + // happens "directly" in the content, the form is only used for saving). + const $backstage = $( + Drupal.theme('quickeditBackstage', { id: backstageId }), + ).appendTo('body'); + // Hidden forms are stuffed into the backstage container for this field. + const $form = $(form).appendTo($backstage); + // Disable the browser's HTML5 validation; we only care about server- + // side validation. (Not disabling this will actually cause problems + // because browsers don't like to set HTML5 validation errors on hidden + // forms.) + $form.prop('novalidate', true); + const $submit = $form.find('.quickedit-form-submit'); + self.formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving( + formOptions, + $submit, + ); - // The quickeditFieldForm AJAX command is only called upon loading the - // form for the first time, and when there are validation errors in the - // form; Form API then marks which form items have errors. This is - // useful for the form-based in-place editor, but pointless for any - // other: the form itself won't be visible at all anyway! So, we just - // ignore it. - self.formSaveAjax.commands.quickeditFieldForm = function () {}; + function removeHiddenForm() { + Drupal.quickedit.util.form.unajaxifySaving(self.formSaveAjax); + delete self.formSaveAjax; + $backstage.remove(); + } - fillAndSubmitForm(editorModel.get('currentValue')); - }); + // Successfully saved. + self.formSaveAjax.commands.quickeditFieldFormSaved = function( + ajax, + response, + status, + ) { + removeHiddenForm(); + // First, transition the state to 'saved'. + fieldModel.set('state', 'saved'); + // Second, set the 'htmlForOtherViewModes' attribute, so that when + // this field is rerendered, the change can be propagated to other + // instances of this field, which may be displayed in different view + // modes. + fieldModel.set('htmlForOtherViewModes', response.other_view_modes); + // Finally, set the 'html' attribute on the field model. This will + // cause the field to be rerendered. + fieldModel.set('html', response.data); + }; + + // Unsuccessfully saved; validation errors. + self.formSaveAjax.commands.quickeditFieldFormValidationErrors = function( + ajax, + response, + status, + ) { + removeHiddenForm(); + editorModel.set('validationErrors', response.data); + fieldModel.set('state', 'invalid'); + }; + + // The quickeditFieldForm AJAX command is only called upon loading the + // form for the first time, and when there are validation errors in the + // form; Form API then marks which form items have errors. This is + // useful for the form-based in-place editor, but pointless for any + // other: the form itself won't be visible at all anyway! So, we just + // ignore it. + self.formSaveAjax.commands.quickeditFieldForm = function() {}; + + fillAndSubmitForm(editorModel.get('currentValue')); + }); + }, + + /** + * Shows validation error messages. + * + * Should be called when the state is changed to 'invalid'. + */ + showValidationErrors() { + const $errors = $( + '<div class="quickedit-validation-errors"></div>', + ).append(this.model.get('validationErrors')); + this.getEditedElement() + .addClass('quickedit-validation-error') + .after($errors); + }, + + /** + * Cleans up validation error messages. + * + * Should be called when the state is changed to 'candidate' or 'saving'. In + * the case of the latter: the user has modified the value in the in-place + * editor again to attempt to save again. In the case of the latter: the + * invalid value was discarded. + */ + removeValidationErrors() { + this.getEditedElement() + .removeClass('quickedit-validation-error') + .next('.quickedit-validation-errors') + .remove(); + }, }, - - /** - * Shows validation error messages. - * - * Should be called when the state is changed to 'invalid'. - */ - showValidationErrors() { - const $errors = $('<div class="quickedit-validation-errors"></div>') - .append(this.model.get('validationErrors')); - this.getEditedElement() - .addClass('quickedit-validation-error') - .after($errors); - }, - - /** - * Cleans up validation error messages. - * - * Should be called when the state is changed to 'candidate' or 'saving'. In - * the case of the latter: the user has modified the value in the in-place - * editor again to attempt to save again. In the case of the latter: the - * invalid value was discarded. - */ - removeValidationErrors() { - this.getEditedElement() - .removeClass('quickedit-validation-error') - .next('.quickedit-validation-errors') - .remove(); - }, - - }); -}(jQuery, Backbone, Drupal)); + ); +})(jQuery, Backbone, Drupal);