123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- /**
- * @file
- * An abstract Backbone View that controls an in-place editor.
- */
- (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);
- },
- /**
- * {@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;
- },
- /**
- *
- * @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;
- 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 'highlighted':
- // Nothing to do for the typical in-place editor: it should not be
- // visible yet.
- break;
- case 'activating': {
- // The user is in the process of activating 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 '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 triggered a save 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);
- };
- // 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();
- },
- },
- );
- })(jQuery, Backbone, Drupal);
|