core/modules/edit/edit.module | 2 -
core/modules/edit/js/backbone.drupalform.js | 181 --------------------
core/modules/edit/js/edit.js | 3 +-
core/modules/edit/js/editors/directEditor.js | 81 +++++----
core/modules/edit/js/models/FieldModel.js | 1 +
core/modules/edit/js/views/AppView.js | 10 +-
core/modules/edit/js/views/EditorView.js | 134 ++++++++++-----
core/modules/edit/js/views/FieldToolbarView.js | 6 -
.../editor/js/editor.formattedTextEditor.js | 14 +-
9 files changed, 152 insertions(+), 280 deletions(-)
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index 35531b4..043e825 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -82,8 +82,6 @@ function edit_library_info() {
$path . '/js/views/ModalView.js' => $options,
$path . '/js/views/FieldToolbarView.js' => $options,
$path . '/js/views/EditorView.js' => $options,
- // Backbone.sync implementation on top of Drupal forms.
- $path . '/js/backbone.drupalform.js' => $options,
// Other.
$path . '/js/util.js' => $options,
$path . '/js/theme.js' => $options,
diff --git a/core/modules/edit/js/backbone.drupalform.js b/core/modules/edit/js/backbone.drupalform.js
deleted file mode 100644
index a5cb6f6..0000000
--- a/core/modules/edit/js/backbone.drupalform.js
+++ /dev/null
@@ -1,181 +0,0 @@
-/**
- * @file
- * Backbone.sync implementation for Edit. This is the beating heart.
- */
-(function (jQuery, Backbone, Drupal) {
-
-"use strict";
-
-Backbone.defaultSync = Backbone.sync;
-
-// @todo We should define a per-model sync method and assign it when creating
-// the fieldModel instance.
-Backbone.sync = function(method, model, options) {
- if (this.get('editorName') === 'form') {
- return Backbone.syncDrupalFormWidget(method, model, options);
- }
- else {
- return Backbone.syncDirect(method, model, options);
- }
-};
-
-/**
- * Performs syncing for "form" Editor widgets.
- *
- * Implemented on top of Form API and the AJAX commands framework. Sets up
- * scoped AJAX command closures specifically for a given Editor
- * (which contains a pre-existing form). By submitting the form through
- * Drupal.ajax and leveraging Drupal.ajax' ability to re-render the
- * form when there are validation errors and ensure no Drupal.ajax memory leaks.
- *
- * @see Drupal.edit.util.form
- */
-Backbone.syncDrupalFormWidget = function(method, model, options) {
- if (method === 'update') {
- var predicate = options.editor.options.property;
-
- var $formContainer = options.editor.$formContainer;
- var $submit = $formContainer.find('.edit-form-submit');
- var base = $submit.attr('id');
-
- // Successfully saved.
- Drupal.ajax[base].commands.editFieldFormSaved = function(ajax, response, status) {
- Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element));
-
- // Call Backbone.sync's success callback with the rerendered field.
- var changedAttributes = {};
- // @todo: POSTPONED_ON(Drupal core, http://drupal.org/node/1784216)
- changedAttributes[predicate] = undefined;
- changedAttributes[predicate + '/rendered'] = response.data;
- options.success(changedAttributes);
- };
-
- // Unsuccessfully saved; validation errors.
- Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) {
- // Call Backbone.sync's error callback with the validation error messages.
- options.error(response.data);
- };
-
- // The edit_field_form 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. Therefor, we have to replace
- // the existing form, unbind the existing Drupal.ajax instance and create a
- // new Drupal.ajax instance.
- Drupal.ajax[base].commands.editFieldForm = function(ajax, response, status) {
- Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element));
-
- Drupal.ajax.prototype.commands.insert(ajax, {
- data: response.data,
- selector: '#' + $formContainer.attr('id') + ' form'
- });
-
- // Create a Drupa.ajax instance for the re-rendered ("new") form.
- var $newSubmit = $formContainer.find('.edit-form-submit');
- Drupal.edit.util.form.ajaxifySaving({ nocssjs: false }, $newSubmit);
- };
-
- // Click the form's submit button; the scoped AJAX commands above will
- // handle the server's response.
- $submit.trigger('click.edit');
- }
-};
-
-/**
-* Performs syncing for "direct" PredicateEditor widgets.
- *
- * @see Backbone.syncDrupalFormWidget()
- * @see Drupal.edit.util.form
- */
-Backbone.syncDirect = function(method, model, options) {
- if (method === 'update') {
- var fillAndSubmitForm = function(value) {
- var $form = jQuery('#edit_backstage form');
- // Fill in the value in any 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('.edit-form-submit').trigger('click.edit');
- };
-
- var value = model.get('currentValue');
-
- // If form doesn't already exist, load it and then submit.
- if (jQuery('#edit_backstage form').length === 0) {
- var formOptions = {
- propertyID: model.id,
- $editorElement: jQuery(model.get('el')),
- nocssjs: true
- };
- Drupal.edit.util.form.load(formOptions, function(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).
- jQuery(Drupal.theme('editBackstage', { id: 'edit_backstage' })).appendTo('body');
- // Direct forms are stuffed into #edit_backstage, apparently.
- jQuery('#edit_backstage').append(form);
- // 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.)
- jQuery('#edit_backstage form').prop('novalidate', true);
- var $submit = jQuery('#edit_backstage form .edit-form-submit');
- var base = Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
-
- // Successfully saved.
- Drupal.ajax[base].commands.editFieldFormSaved = function (ajax, response, status) {
- Backbone.syncDirectCleanUp();
-
- // Call Backbone.sync's success callback with the rerendered field.
- var changedAttributes = {};
- // @todo: POSTPONED_ON(Drupal core, http://drupal.org/node/1784216)
- changedAttributes[model.id] = jQuery(response.data).find('.field-item').html();
- changedAttributes[model.id + '/rendered'] = response.data;
- options.success(changedAttributes);
- };
-
- // Unsuccessfully saved; validation errors.
- Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) {
- // Call Backbone.sync's error callback with the validation error messages.
- options.error(response.data);
- };
-
- // The editFieldForm 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
- // "form" editors, but pointless for "direct" editors: the form itself
- // won't be visible at all anyway! Therefor, we ignore the new form and
- // we continue to use the existing form.
- Drupal.ajax[base].commands.editFieldForm = function(ajax, response, status) {
- // no-op
- };
-
- fillAndSubmitForm(value);
- });
- }
- else {
- fillAndSubmitForm(value);
- }
- }
-};
-
-/**
- * Cleans up the hidden form that Backbone.syncDirect uses for syncing.
- *
- * This is called automatically by Backbone.syncDirect when saving is successful
- * (i.e. when there are no validation errors). Only when editing is canceled
- * while an Editor widget is in the invalid state, this must be called
- * "manually" (in practice, FieldToolbarView does this). This is necessary because
- * Backbone.syncDirect is not aware of the application state, it only does the
- * syncing.
- * An alternative could be to also remove the hidden form when validation errors
- * occur, but then the form must be retrieved again, thus resulting in another
- * roundtrip, which is bad for front-end performance.
- */
-Backbone.syncDirectCleanUp = function() {
- var $submit = jQuery('#edit_backstage form .edit-form-submit');
- Drupal.edit.util.form.unajaxifySaving($submit);
- jQuery('#edit_backstage form').remove();
-};
-
-})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
index 413692b..c68ebb3 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -198,8 +198,7 @@ function initializeField (fieldElement, editID) {
var field = new Drupal.edit.FieldModel({
entity: entity,
el: fieldElement,
- id: editID || null,
- editID: editID || null,
+ editID: editID,
label: Drupal.edit.Metadata.get(editID, 'label'),
editor: Drupal.edit.Metadata.get(editID, 'editor'),
html: fieldElement.outerHTML,
diff --git a/core/modules/edit/js/editors/directEditor.js b/core/modules/edit/js/editors/directEditor.js
index 12273ec..61fdf95 100644
--- a/core/modules/edit/js/editors/directEditor.js
+++ b/core/modules/edit/js/editors/directEditor.js
@@ -1,6 +1,6 @@
/**
* @file
- * Defined a editor for direct editable fields.
+ * contentEditable-based in-place editor for plain text content.
*/
(function ($, Drupal) {
@@ -9,43 +9,44 @@
Drupal.edit = Drupal.edit || {};
Drupal.edit.editors = Drupal.edit.editors || {};
-Drupal.edit.editors.direct = Backbone.View.extend({
+Drupal.edit.editors.direct = Drupal.edit.EditorView.extend({
- /**
- * {@inheritdoc}
- */
- getEditUISettings: function () {
- return { padding: true, unifiedToolbar: false, fullWidthToolbar: false };
- },
+ $textElement: null,
/**
* {@inheritdoc}
*/
- initialize: function () {
- var that = this;
+ initialize: function (options) {
+ Drupal.edit.EditorView.prototype.initialize.call(this, options);
- // Sets the state to 'changed' whenever the content has changed.
- var before = jQuery.trim(this.element.text());
- this.element.on('keyup paste', function (event) {
- var current = jQuery.trim(that.element.text());
- if (before !== current) {
- before = current;
- that.model.set('state', 'changed');
+ var model = this.model;
+
+ // Store the actual value of this field. We'll need this to restore the
+ // original value when the user discards his modifications.
+ var $textElement = this.$textElement = this.$el.find('.field-item:first');
+ model.set('originalValue', jQuery.trim(this.$textElement.text()));
- // @todo we have yet to set this value originally (before the editing
- // starts) AND we have to handle the reverting aspect when editing is
- // canceled, see editorStateChange().
- that.model.set('value', current);
+ // Sets the state to 'changed' whenever the content has changed.
+ var previousText = model.get('originalValue');
+ $textElement.on('keyup paste', function (event) {
+ var currentText = jQuery.trim($textElement.text());
+ if (previousText !== currentText) {
+ previousText = currentText;
+ model.set('currentValue', currentText);
+ model.set('state', 'changed');
}
});
},
/**
- * Determines the actions to take given a change of state.
- *
- * @param Drupal.edit.FieldModel model
- * @param String state
- * The state of the associated field. One of Drupal.edit.FieldModel.states.
+ * {@inheritdoc}
+ */
+ getEditedElement: function () {
+ return this.$textElement;
+ },
+
+ /**
+ * {@inheritdoc}
*/
stateChange: function (model, state) {
var from = model.previous('state');
@@ -55,8 +56,10 @@ Drupal.edit.editors.direct = Backbone.View.extend({
break;
case 'candidate':
if (from !== 'inactive') {
- // Removes the "contenteditable" attribute.
- this.disable();
+ this.$textElement.removeAttr('contentEditable');
+ }
+ if (from === 'invalid') {
+ this.removeValidationErrors();
}
break;
case 'highlighted':
@@ -70,20 +73,38 @@ Drupal.edit.editors.direct = Backbone.View.extend({
});
break;
case 'active':
- // Sets the "contenteditable" attribute to "true".
- this.enable();
+ this.$textElement.attr('contentEditable', 'true');
break;
case 'changed':
break;
case 'saving':
+ if (from === 'invalid') {
+ this.removeValidationErrors();
+ }
this.save();
break;
case 'saved':
break;
case 'invalid':
+ this.showValidationErrors();
break;
}
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ getEditUISettings: function () {
+ return { padding: true, unifiedToolbar: false, fullWidthToolbar: false };
+ },
+
+ /**
+ * {@inheritdoc}
+ */
+ revert: function () {
+ this.$textElement.html(this.model.get('originalValue'));
}
+
});
})(jQuery, Drupal);
diff --git a/core/modules/edit/js/models/FieldModel.js b/core/modules/edit/js/models/FieldModel.js
index 0ce6790..63f17fb 100644
--- a/core/modules/edit/js/models/FieldModel.js
+++ b/core/modules/edit/js/models/FieldModel.js
@@ -36,6 +36,7 @@ $.extend(Drupal.edit, {
//
// The edit ID, format: `::::`.
+ // @todo rename to "id"?
editID: null,
// The full HTML representation of this field (with the element that has
// the data-edit-id as the outer element). Used to propagate changes from
diff --git a/core/modules/edit/js/views/AppView.js b/core/modules/edit/js/views/AppView.js
index 10d73f6..4137ac0 100644
--- a/core/modules/edit/js/views/AppView.js
+++ b/core/modules/edit/js/views/AppView.js
@@ -178,15 +178,11 @@ $.extend(Drupal.edit, {
// @todo rename to decorateField
decorate: function (fieldModel) {
- var editID = fieldModel.get('editID');
- var el = fieldModel.get('el');
- var $el = $(el);
-
// Create a new Editor.
var editorName = fieldModel.get('editor');
fieldModel.set('editorName', editorName);
var editorView = new Drupal.edit.editors[editorName]({
- el: $el,
+ el: $(fieldModel.get('el')),
model: fieldModel
});
@@ -194,13 +190,13 @@ $.extend(Drupal.edit, {
// They are a sibling element before the editor's DOM element.
var toolbarView = new Drupal.edit.FieldToolbarView({
model: fieldModel,
- $field: $el,
+ $field: $(editorView.getEditedElement()),
editorView: editorView
});
// Decorate the editor's DOM element depending on its state.
var decorationView = new Drupal.edit.EditorDecorationView({
- el: $el,
+ el: $(editorView.getEditedElement()),
model: fieldModel,
editorView: editorView,
toolbarId: toolbarView.getId()
diff --git a/core/modules/edit/js/views/EditorView.js b/core/modules/edit/js/views/EditorView.js
index fc99aa2..5dd6f4b 100644
--- a/core/modules/edit/js/views/EditorView.js
+++ b/core/modules/edit/js/views/EditorView.js
@@ -31,6 +31,25 @@ $.extend(Drupal.edit, {
},
/**
+ * 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-edit-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.
+ *
+ * For example, @see Drupal.edit.editors.direct.
+ *
+ * @return jQuery
+ * A jQuery-wrapped DOM element.
+ */
+ getEditedElement: function () {
+ return this.$el;
+ },
+
+ /**
* Returns 3 Edit UI settings that depend on the in-place editor:
* - padding: @todo
* - unifiedToolbar: @todo
@@ -121,62 +140,82 @@ $.extend(Drupal.edit, {
* started).
*/
revert: function () {
- // @todo Should we implement a default here?
+ // 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: function () {
- // @todo implement here the "direct" case, i.e. the hidden form case
- this.model.save(null, {
- // Successfully saved without validation errors.
- success: function (model, response, options) {
- model.set('state', 'saved');
-
- var entity = model.get('entity');
- var id = model.get('id');
-
- // Now that the changes to this property have been saved, the saved
- // attributes are now the "original" attributes.
- entity._originalAttributes = entity._previousAttributes = _.clone(entity.attributes);
-
- // Get data necessary to rerender property before it is unavailable.
- var updatedProperty = response[id + '/rendered'];
- var $propertyWrapper = $(model.get('el'));
- var $context = $propertyWrapper.parent();
-
- model.set('state', 'candidate');
-
- // @todo Do we need this trigger now that everything is driven from
- // models?
- // Trigger event to allow for proper clean-up of editor-specific views.
- //editor.element.trigger('destroyedPropertyEditor.edit', editor);
+ var model = this.model;
+
+ function fillAndSubmitForm (value) {
+ var $form = jQuery('#edit_backstage form');
+ // Fill in the value in any 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('.edit-form-submit').trigger('click.edit');
+ }
- // Replace the old content with the new content.
- $propertyWrapper.replaceWith(updatedProperty);
- Drupal.attachBehaviors($context);
- },
+ var value = model.get('currentValue');
+
+ var formOptions = {
+ propertyID: model.get('editID'),
+ $editorElement: $(this.getEditedElement()),
+ nocssjs: true
+ };
+ Drupal.edit.util.form.load(formOptions, function(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).
+ jQuery(Drupal.theme('editBackstage', { id: 'edit_backstage' })).appendTo('body');
+ // Direct forms are stuffed into #edit_backstage, apparently.
+ jQuery('#edit_backstage').append(form);
+ // 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.)
+ jQuery('#edit_backstage form').prop('novalidate', true);
+ var $submit = jQuery('#edit_backstage form .edit-form-submit');
+ var base = Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
+
+ function removeHiddenForm () {
+ Drupal.edit.util.form.unajaxifySaving($submit);
+ jQuery('#edit_backstage').remove();
+ }
- // Save attempted but failed due to validation errors.
- error: function (validationErrorMessages) {
- editableEntity.setState('invalid', predicate);
+ // Successfully saved.
+ Drupal.ajax[base].commands.editFieldFormSaved = function (ajax, response, status) {
+ removeHiddenForm();
- if (that.editorName === 'form') {
- editor.$formContainer
- .find('.edit-form')
- .addClass('edit-validation-error')
- .find('form')
- .prepend(validationErrorMessages);
- }
- else {
- var $errors = $('')
- .append(validationErrorMessages);
- editor.element
- .addClass('edit-validation-error')
- .after($errors);
- }
- }
+ // First, transition the state to 'saved'.
+ model.set('state', 'saved');
+ // Then, set the 'html' attribute on the field model. This will cause the
+ // field to be rerendered.
+ model.set('html', response.data);
+ };
+
+ // Unsuccessfully saved; validation errors.
+ Drupal.ajax[base].commands.editFieldFormValidationErrors = function(ajax, response, status) {
+ removeHiddenForm();
+
+ model.set('validationErrors', response.data);
+ model.set('state', 'invalid');
+ };
+
+ // The editFieldForm 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.
+ Drupal.ajax[base].commands.editFieldForm = function() {};
+
+ fillAndSubmitForm(value);
});
},
@@ -207,6 +246,7 @@ $.extend(Drupal.edit, {
.next('.edit-validation-errors')
.remove();
}
+
})
});
diff --git a/core/modules/edit/js/views/FieldToolbarView.js b/core/modules/edit/js/views/FieldToolbarView.js
index a854312..117debb 100644
--- a/core/modules/edit/js/views/FieldToolbarView.js
+++ b/core/modules/edit/js/views/FieldToolbarView.js
@@ -79,9 +79,6 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
case 'inactive':
if (from) {
this.remove();
- if (this.model.get('editor') !== 'form') {
- Backbone.syncDirectCleanUp();
- }
}
break;
case 'candidate':
@@ -89,9 +86,6 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
this.render();
}
else {
- if (this.model.get('editor') !== 'form') {
- Backbone.syncDirectCleanUp();
- }
// Remove all toolgroups; they're no longer necessary.
this.$el
.removeClass('edit-highlighted edit-editing')
diff --git a/core/modules/editor/js/editor.formattedTextEditor.js b/core/modules/editor/js/editor.formattedTextEditor.js
index 4361548..5d3bd01 100644
--- a/core/modules/editor/js/editor.formattedTextEditor.js
+++ b/core/modules/editor/js/editor.formattedTextEditor.js
@@ -37,7 +37,6 @@ Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
// Store the actual value of this field. We'll need this to restore the
// original value when the user discards his modifications.
- // @todo: figure out a more nicely abstracted way to handle this.
this.$textElement = this.$el.find('.field-item:first');
this.model.set('originalValue', this.$textElement.html());
},
@@ -45,6 +44,13 @@ Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
/**
* {@inheritdoc}
*/
+ getEditedElement: function () {
+ return this.$textElement;
+ },
+
+ /**
+ * {@inheritdoc}
+ */
stateChange: function (model, state) {
var that = this;
var from = model.previous('state');
@@ -60,7 +66,7 @@ Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
this.textEditor.detach(this.$textElement.get(0), this.textFormat);
}
if (from === 'invalid') {
- this.removeValidationErrors();
+ this.removeValidationErrors();
}
break;
@@ -74,9 +80,7 @@ Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
if (this.textFormatHasTransformations) {
var editID = this.model.get('editID');
this._getUntransformedText(editID, this.$el, function (untransformedText) {
- // @todo update this
- debugger;
- that.$textElement.set(untransformedText);
+ that.$textElement.html(untransformedText);
that.model.set('state', 'active');
});
}