From 1f44a1001f360427ade9d8733df61e0aba9cba82 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= <splendidnoise@gmail.com>
Date: Wed, 24 Apr 2013 23:43:22 -0400
Subject: [PATCH] Issue #1678002-79
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

commit fc7e7b6a03bb32d4ef892ab1e8a35ac0ef9115de
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 23:35:52 2013 -0400

    Positioning is now much better

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit a09f4b09ff33f4943d4f4f45aa8861578455458e
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 23:24:57 2013 -0400

    Deleted EntityView

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit c46abca01bdc5d974f0c9327293f834079301955
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 23:08:34 2013 -0400

    Updated the EntityModel

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit 31e0065fb96e252f041f63bc5a58b1e46fbd0609
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 23:05:40 2013 -0400

    More cleanups of EntityToolbarView.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit 7dc0be2615a1106ecd279838795ad794683af6cb
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 22:55:53 2013 -0400

    Cleaning up the entity toolbar

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit 16dd9fe61dfc95c2ba07e9ececbcdd96b8a5feb1
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 22:51:35 2013 -0400

    Deleting a lot of files that came back.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit aee06c8264ec843950ea0fb3ea2e016be02292ee
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 22:47:23 2013 -0400

    Cleaned up some more EntityToolbarView stuff

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit 75e707ff590fece508ce259900cd5940ae11bda4
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 17:23:51 2013 -0400

    Turned the Toolbar's el into the toolbar element itself.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit 5e96d6a674cc15ccd072ae590aba0995fabc4d8d
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 17:04:38 2013 -0400

    Save and cancel buttons show up.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit 148af7ddec3f20d8fc39a3c99b6088ee15cc72e5
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 15:26:52 2013 -0400

    EntityToolbar is now listening to changes on the active entity.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

    Conflicts:
    	core/modules/edit/js/edit.js
    	core/modules/edit/js/models/EntityModel.js
    	core/modules/edit/js/views/ToolbarView.js

commit b612f8355d0627d9d2c7b13bc2e66b0d4ead210e
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 14:45:25 2013 -0400

    Entity toolbar now knows about its fields.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

    Conflicts:
    	core/modules/edit/js/edit.js
    	core/modules/edit/js/models/EntityModel.js
    	core/modules/edit/js/models/FieldModel.js

commit 814eaf8a8568d1aaed1bf9069822f57d7306f2ce
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 12:17:56 2013 -0400

    We've got a zippy entity toolbar.

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

commit fd5a0487282905f40caf16db30e4e60e000d38da
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 12:03:58 2013 -0400

    First steps to an entity Toolbar

    Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>

    Conflicts:
    	core/modules/edit/js/edit.js

commit 334a5d9b7a2fcafce9354a4ca79e60f4d7774748
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Wed Apr 24 16:24:04 2013 -0400

    1678002-77

Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>
---
 core/misc/create/create-editonly.js                | 1651 --------------------
 core/modules/edit/css/edit.css                     |   13 +-
 core/modules/edit/edit.module                      |   31 +-
 core/modules/edit/js/app.js                        |  391 -----
 core/modules/edit/js/backbone.drupalform.js        |    2 +-
 core/modules/edit/js/createjs/editable.js          |   30 -
 .../editingWidgets/drupalcontenteditablewidget.js  |   83 -
 .../edit/js/createjs/editingWidgets/formwidget.js  |  152 --
 core/modules/edit/js/createjs/storage.js           |   11 -
 core/modules/edit/js/edit.js                       |  270 +++-
 core/modules/edit/js/editors/directEditor.js       |   89 ++
 core/modules/edit/js/editors/formEditor.js         |  220 +++
 core/modules/edit/js/models/AppModel.js            |   23 +
 core/modules/edit/js/models/EntityModel.js         |   74 +
 core/modules/edit/js/models/FieldModel.js          |  119 ++
 core/modules/edit/js/models/edit-app-model.js      |   21 -
 core/modules/edit/js/storage.js                    |  518 ++++++
 core/modules/edit/js/theme.js                      |   17 +
 core/modules/edit/js/viejs/EditService.js          |  289 ----
 core/modules/edit/js/views/AppView.js              |  292 ++++
 core/modules/edit/js/views/ContextualLinkView.js   |   75 +
 core/modules/edit/js/views/EntityToolbarView.js    |  333 ++++
 core/modules/edit/js/views/ModalView.js            |   80 +
 .../edit/js/views/PropertyEditorDecorationView.js  |  393 +++++
 core/modules/edit/js/views/ToolbarView.js          |  194 +++
 core/modules/edit/js/views/contextuallink-view.js  |  109 --
 core/modules/edit/js/views/modal-view.js           |   83 -
 .../edit/js/views/propertyeditordecoration-view.js |  363 -----
 core/modules/edit/js/views/toolbar-view.js         |  490 ------
 core/modules/editor/js/editor.createjs.js          |   64 +-
 core/modules/editor/js/editor.js                   |    8 +-
 core/modules/system/system.module                  |   14 -
 32 files changed, 2718 insertions(+), 3784 deletions(-)
 delete mode 100644 core/misc/create/create-editonly.js
 delete mode 100644 core/modules/edit/js/app.js
 delete mode 100644 core/modules/edit/js/createjs/editable.js
 delete mode 100644 core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
 delete mode 100644 core/modules/edit/js/createjs/editingWidgets/formwidget.js
 delete mode 100644 core/modules/edit/js/createjs/storage.js
 create mode 100644 core/modules/edit/js/editors/directEditor.js
 create mode 100644 core/modules/edit/js/editors/formEditor.js
 create mode 100644 core/modules/edit/js/models/AppModel.js
 create mode 100644 core/modules/edit/js/models/EntityModel.js
 create mode 100644 core/modules/edit/js/models/FieldModel.js
 delete mode 100644 core/modules/edit/js/models/edit-app-model.js
 create mode 100644 core/modules/edit/js/storage.js
 delete mode 100644 core/modules/edit/js/viejs/EditService.js
 create mode 100644 core/modules/edit/js/views/AppView.js
 create mode 100644 core/modules/edit/js/views/ContextualLinkView.js
 create mode 100644 core/modules/edit/js/views/EntityToolbarView.js
 create mode 100644 core/modules/edit/js/views/ModalView.js
 create mode 100644 core/modules/edit/js/views/PropertyEditorDecorationView.js
 create mode 100644 core/modules/edit/js/views/ToolbarView.js
 delete mode 100644 core/modules/edit/js/views/contextuallink-view.js
 delete mode 100644 core/modules/edit/js/views/modal-view.js
 delete mode 100644 core/modules/edit/js/views/propertyeditordecoration-view.js
 delete mode 100644 core/modules/edit/js/views/toolbar-view.js

diff --git a/core/misc/create/create-editonly.js b/core/misc/create/create-editonly.js
deleted file mode 100644
index aed84a4..0000000
--- a/core/misc/create/create-editonly.js
+++ /dev/null
@@ -1,1651 +0,0 @@
-//     Create.js - On-site web editing interface
-//     (c) 2011-2012 Henri Bergius, IKS Consortium
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://createjs.org/
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false window:false console:false */
-  'use strict';
-
-  // # Widget for adding items to a collection
-  jQuery.widget('Midgard.midgardCollectionAdd', {
-    options: {
-      editingWidgets: null,
-      collection: null,
-      model: null,
-      definition: null,
-      view: null,
-      disabled: false,
-      vie: null,
-      editableOptions: null,
-      templates: {
-        button: '<button class="btn"><i class="icon-<%= icon %>"></i> <%= label %></button>'
-      }
-    },
-
-    _create: function () {
-      this.addButtons = [];
-      var widget = this;
-      if (!widget.options.collection.localStorage) {
-        try {
-          widget.options.collection.url = widget.options.model.url();
-        } catch (e) {
-          if (window.console) {
-            console.log(e);
-          }
-        }
-      }
-
-      widget.options.collection.on('add', function (model) {
-        model.primaryCollection = widget.options.collection;
-        widget.options.vie.entities.add(model);
-        model.collection = widget.options.collection;
-      });
-
-      // Re-check collection constraints
-      widget.options.collection.on('add remove reset', widget.checkCollectionConstraints, widget);
-
-      widget._bindCollectionView(widget.options.view);
-    },
-
-    _bindCollectionView: function (view) {
-      var widget = this;
-      view.on('add', function (itemView) {
-        itemView.$el.effect('slide', function () {
-          widget._makeEditable(itemView);
-        });
-      });
-    },
-
-    _makeEditable: function (itemView) {
-      this.options.editableOptions.disabled = this.options.disabled;
-      this.options.editableOptions.model = itemView.model;
-      itemView.$el.midgardEditable(this.options.editableOptions);
-    },
-
-    _init: function () {
-      if (this.options.disabled) {
-        this.disable();
-        return;
-      }
-      this.enable();
-    },
-
-    hideButtons: function () {
-      _.each(this.addButtons, function (button) {
-        button.hide();
-      });
-    },
-
-    showButtons: function () {
-      _.each(this.addButtons, function (button) {
-        button.show();
-      });
-    },
-
-    checkCollectionConstraints: function () {
-      if (this.options.disabled) {
-        return;
-      }
-
-      if (!this.options.view.canAdd()) {
-        this.hideButtons();
-        return;
-      }
-
-      if (!this.options.definition) {
-        // We have now information on the constraints applying to this collection
-        this.showButtons();
-        return;
-      }
-
-      if (!this.options.definition.max || this.options.definition.max === -1) {
-        // No maximum constraint
-        this.showButtons();
-        return;
-      }
-
-      if (this.options.collection.length < this.options.definition.max) {
-        this.showButtons();
-        return;
-      }
-      // Collection is already full by its definition
-      this.hideButtons();
-    },
-
-    enable: function () {
-      var widget = this;
-
-      var addButton = jQuery(_.template(this.options.templates.button, {
-        icon: 'plus',
-        label: this.options.editableOptions.localize('Add', this.options.editableOptions.language)
-      })).button();
-      addButton.addClass('midgard-create-add');
-      addButton.click(function () {
-        widget.addItem(addButton);
-      });
-      jQuery(widget.options.view.el).after(addButton);
-
-      widget.addButtons.push(addButton);
-      widget.checkCollectionConstraints();
-    },
-
-    disable: function () {
-      _.each(this.addButtons, function (button) {
-        button.remove();
-      });
-      this.addButtons = [];
-    },
-
-    _getTypeActions: function (options) {
-      var widget = this;
-      var actions = [];
-      _.each(this.options.definition.range, function (type) {
-        var nsType = widget.options.collection.vie.namespaces.uri(type);
-        if (!widget.options.view.canAdd(nsType)) {
-          return;
-        }
-        actions.push({
-          name: type,
-          label: type,
-          cb: function () {
-            widget.options.collection.add({
-              '@type': type
-            }, options);
-          },
-          className: 'create-ui-btn'
-        });
-      });
-      return actions;
-    },
-
-    addItem: function (button, options) {
-      if (options === undefined) {
-          options = {};
-      }
-      var addOptions = _.extend({}, options, { validate: false });
-
-      var itemData = {};
-      if (this.options.definition && this.options.definition.range) {
-        if (this.options.definition.range.length === 1) {
-          // Items can be of single type, add that
-          itemData['@type'] = this.options.definition.range[0];
-        } else {
-          // Ask user which type to add
-          jQuery('body').midgardNotifications('create', {
-            bindTo: button,
-            gravity: 'L',
-            body: this.options.editableOptions.localize('Choose type to add', this.options.editableOptions.language),
-            timeout: 0,
-            actions: this._getTypeActions(addOptions)
-          });
-          return;
-        }
-      } else {
-        // Check the view templates for possible non-Thing type to use
-        var keys = _.keys(this.options.view.templates);
-        if (keys.length == 2) {
-          itemData['@type'] = keys[0];
-        }
-      }
-      this.options.collection.add(itemData, addOptions);
-    }
-  });
-})(jQuery);
-//     Create.js - On-site web editing interface
-//     (c) 2011-2012 Henri Bergius, IKS Consortium
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://createjs.org/
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false window:false console:false */
-  'use strict';
-
-  // # Widget for adding items anywhere inside a collection
-  jQuery.widget('Midgard.midgardCollectionAddBetween', jQuery.Midgard.midgardCollectionAdd, {
-    _bindCollectionView: function (view) {
-      var widget = this;
-      view.on('add', function (itemView) {
-        //itemView.el.effect('slide');
-        widget._makeEditable(itemView);
-        widget._refreshButtons();
-      });
-      view.on('remove', function () {
-        widget._refreshButtons();
-      });
-    },
-
-    _refreshButtons: function () {
-      var widget = this;
-      window.setTimeout(function () {
-        widget.disable();
-        widget.enable();
-      }, 1);
-    },
-
-    prepareButton: function (index) {
-      var widget = this;
-      var addButton = jQuery(_.template(this.options.templates.button, {
-        icon: 'plus',
-        label: ''
-      })).button();
-      addButton.addClass('midgard-create-add');
-      addButton.click(function () {
-        widget.addItem(addButton, {
-          at: index
-        });
-      });
-      return addButton;
-    },
-
-    enable: function () {
-      var widget = this;
-
-      var firstAddButton = widget.prepareButton(0);
-      jQuery(widget.options.view.el).prepend(firstAddButton);
-      widget.addButtons.push(firstAddButton);
-      jQuery.each(widget.options.view.entityViews, function (cid, view) {
-        var index = widget.options.collection.indexOf(view.model);
-        var addButton = widget.prepareButton(index + 1);
-        jQuery(view.el).append(addButton);
-        widget.addButtons.push(addButton);
-      });
-
-      this.checkCollectionConstraints();
-    },
-
-    disable: function () {
-      var widget = this;
-      jQuery.each(widget.addButtons, function (idx, button) {
-        button.remove();
-      });
-      widget.addButtons = [];
-    }
-  });
-})(jQuery);
-//     Create.js - On-site web editing interface
-//     (c) 2011-2012 Henri Bergius, IKS Consortium
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://createjs.org/
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false window:false VIE:false */
-  'use strict';
-
-  // Define Create's EditableEntity widget.
-  jQuery.widget('Midgard.midgardEditable', {
-    options: {
-      propertyEditors: {},
-      collections: [],
-      model: null,
-      // the configuration (mapping and options) of property editor widgets
-      propertyEditorWidgetsConfiguration: {
-        hallo: {
-          widget: 'halloWidget',
-          options: {}
-        }
-      },
-      // the available property editor widgets by data type
-      propertyEditorWidgets: {
-        'default': 'hallo'
-      },
-      collectionWidgets: {
-        'default': 'midgardCollectionAdd'
-      },
-      toolbarState: 'full',
-      vie: null,
-      domService: 'rdfa',
-      predicateSelector: '[property]',
-      disabled: false,
-      localize: function (id, language) {
-        return window.midgardCreate.localize(id, language);
-      },
-      language: null,
-      // Current state of the Editable
-      state: null,
-      // Callback function for validating changes between states. Receives the previous state, new state, possibly property, and a callback
-      acceptStateChange: true,
-      // Callback function for listening (and reacting) to state changes.
-      stateChange: null,
-      // Callback function for decorating the full editable. Will be called on instantiation
-      decorateEditableEntity: null,
-      // Callback function for decorating a single property editor widget. Will
-      // be called on editing widget instantiation.
-      decoratePropertyEditor: null,
-
-      // Deprecated.
-      editables: [], // Now `propertyEditors`.
-      editors: {}, // Now `propertyEditorWidgetsConfiguration`.
-      widgets: {} // Now `propertyEditorW
-    },
-
-    // Aids in consistently passing parameters to events and callbacks.
-    _params: function(predicate, extended) {
-      var entityParams = {
-        entity: this.options.model,
-        editableEntity: this,
-        entityElement: this.element,
-
-        // Deprecated.
-        editable: this,
-        element: this.element,
-        instance: this.options.model
-      };
-      var propertyParams = (predicate) ? {
-        predicate: predicate,
-        propertyEditor: this.options.propertyEditors[predicate],
-        propertyElement: this.options.propertyEditors[predicate].element,
-
-        // Deprecated.
-        property: predicate,
-        element: this.options.propertyEditors[predicate].element
-      } : {};
-
-      return _.extend(entityParams, propertyParams, extended);
-    },
-
-    _create: function () {
-      // Backwards compatibility:
-      // - this.options.propertyEditorWidgets used to be this.options.widgets
-      // - this.options.propertyEditorWidgetsConfiguration used to be
-      //   this.options.editors
-      if (this.options.widgets) {
-        this.options.propertyEditorWidgets = _.extend(this.options.propertyEditorWidgets, this.options.widgets);
-      }
-      if (this.options.editors) {
-        this.options.propertyEditorWidgetsConfiguration = _.extend(this.options.propertyEditorWidgetsConfiguration, this.options.editors);
-      }
-
-      this.vie = this.options.vie;
-      this.domService = this.vie.service(this.options.domService);
-      if (!this.options.model) {
-        var widget = this;
-        this.vie.load({
-          element: this.element
-        }).from(this.options.domService).execute().done(function (entities) {
-          widget.options.model = entities[0];
-        });
-      }
-      if (_.isFunction(this.options.decorateEditableEntity)) {
-        this.options.decorateEditableEntity(this._params());
-      }
-    },
-
-    _init: function () {
-      // Backwards compatibility:
-      // - this.options.propertyEditorWidgets used to be this.options.widgets
-      // - this.options.propertyEditorWidgetsConfiguration used to be
-      //   this.options.editors
-      if (this.options.widgets) {
-        this.options.propertyEditorWidgets = _.extend(this.options.propertyEditorWidgets, this.options.widgets);
-      }
-      if (this.options.editors) {
-        this.options.propertyEditorWidgetsConfiguration = _.extend(this.options.propertyEditorWidgetsConfiguration, this.options.editors);
-      }
-
-      // Old way of setting the widget inactive
-      if (this.options.disabled === true) {
-        this.setState('inactive');
-        return;
-      }
-
-      if (this.options.disabled === false && this.options.state === 'inactive') {
-        this.setState('candidate');
-        return;
-      }
-      this.options.disabled = false;
-
-      if (this.options.state) {
-        this.setState(this.options.state);
-        return;
-      }
-      this.setState('candidate');
-    },
-
-    // Method used for cycling between the different states of the Editable widget:
-    //
-    // * Inactive: editable is loaded but disabled
-    // * Candidate: editable is enabled but not activated
-    // * Highlight: user is hovering over the editable (not set by Editable widget directly)
-    // * Activating: an editor widget is being activated for user to edit with it (skipped for editors that activate instantly)
-    // * Active: user is actually editing something inside the editable
-    // * Changed: user has made changes to the editable
-    // * Invalid: the contents of the editable have validation errors
-    //
-    // In situations where state changes are triggered for a particular property editor, the `predicate`
-    // argument will provide the name of that property.
-    //
-    // State changes may carry optional context information in a JavaScript object. The payload of these context objects is not
-    // standardized, and is meant to be set and used by the application controller
-    //
-    // The callback parameter is optional and will be invoked after a state change has been accepted (after the 'statechange'
-    // event) or rejected.
-    setState: function (state, predicate, context, callback) {
-      var previous = this.options.state;
-      var current = state;
-      if (current === previous) {
-        return;
-      }
-
-      if (this.options.acceptStateChange === undefined || !_.isFunction(this.options.acceptStateChange)) {
-        // Skip state transition validation
-        this._doSetState(previous, current, predicate, context);
-        if (_.isFunction(callback)) {
-          callback(true);
-        }
-        return;
-      }
-
-      var widget = this;
-      this.options.acceptStateChange(previous, current, predicate, context, function (accepted) {
-        if (accepted) {
-          widget._doSetState(previous, current, predicate, context);
-        }
-        if (_.isFunction(callback)) {
-          callback(accepted);
-        }
-        return;
-      });
-    },
-
-    getState: function () {
-      return this.options.state;
-    },
-
-    _doSetState: function (previous, current, predicate, context) {
-      this.options.state = current;
-      if (current === 'inactive') {
-        this.disable();
-      } else if ((previous === null || previous === 'inactive') && current !== 'inactive') {
-        this.enable();
-      }
-
-      this._trigger('statechange', null, this._params(predicate, {
-        previous: previous,
-        current: current,
-        context: context
-      }));
-    },
-
-    findEditablePredicateElements: function (callback) {
-      this.domService.findPredicateElements(this.options.model.id, jQuery(this.options.predicateSelector, this.element), false).each(callback);
-    },
-
-    getElementPredicate: function (element) {
-      return this.domService.getElementPredicate(element);
-    },
-
-    enable: function () {
-      var editableEntity = this;
-      if (!this.options.model) {
-        return;
-      }
-
-      this.findEditablePredicateElements(function () {
-        editableEntity._enablePropertyEditor(jQuery(this));
-      });
-
-      this._trigger('enable', null, this._params());
-
-      if (!this.vie.view || !this.vie.view.Collection) {
-        return;
-      }
-
-      _.each(this.domService.views, function (view) {
-        if (view instanceof this.vie.view.Collection && this.options.model === view.owner) {
-          var predicate = view.collection.predicate;
-          var editableOptions = _.clone(this.options);
-          editableOptions.state = null;
-          var collection = this.enableCollection({
-            model: this.options.model,
-            collection: view.collection,
-            property: predicate,
-            definition: this.getAttributeDefinition(predicate),
-            view: view,
-            element: view.el,
-            vie: editableEntity.vie,
-            editableOptions: editableOptions
-          });
-          editableEntity.options.collections.push(collection);
-        }
-      }, this);
-    },
-
-    disable: function () {
-      _.each(this.options.propertyEditors, function (editable) {
-        this.disablePropertyEditor({
-          widget: this,
-          editable: editable,
-          entity: this.options.model,
-          element: editable.element
-        });
-      }, this);
-      this.options.propertyEditors = {};
-
-      // Deprecated.
-      this.options.editables = [];
-
-      _.each(this.options.collections, function (collectionWidget) {
-        var editableOptions = _.clone(this.options);
-        editableOptions.state = 'inactive';
-        this.disableCollection({
-          widget: this,
-          model: this.options.model,
-          element: collectionWidget,
-          vie: this.vie,
-          editableOptions: editableOptions
-        });
-      }, this);
-      this.options.collections = [];
-
-      this._trigger('disable', null, this._params());
-    },
-
-    _enablePropertyEditor: function (element) {
-      var widget = this;
-      var predicate = this.getElementPredicate(element);
-      if (!predicate) {
-        return true;
-      }
-      if (this.options.model.get(predicate) instanceof Array) {
-        // For now we don't deal with multivalued properties in the editable
-        return true;
-      }
-
-      var propertyElement = this.enablePropertyEditor({
-        widget: this,
-        element: element,
-        entity: this.options.model,
-        property: predicate,
-        vie: this.vie,
-        decorate: this.options.decoratePropertyEditor,
-        decorateParams: _.bind(this._params, this),
-        changed: function (content) {
-          widget.setState('changed', predicate);
-
-          var changedProperties = {};
-          changedProperties[predicate] = content;
-          widget.options.model.set(changedProperties, {
-            silent: true
-          });
-
-          widget._trigger('changed', null, widget._params(predicate));
-        },
-        activating: function () {
-          widget.setState('activating', predicate);
-        },
-        activated: function () {
-          widget.setState('active', predicate);
-          widget._trigger('activated', null, widget._params(predicate));
-        },
-        deactivated: function () {
-          widget.setState('candidate', predicate);
-          widget._trigger('deactivated', null, widget._params(predicate));
-        }
-      });
-
-      if (!propertyElement) {
-        return;
-      }
-      var widgetType = propertyElement.data('createWidgetName');
-      this.options.propertyEditors[predicate] = propertyElement.data('Midgard-' + widgetType);
-
-      // Deprecated.
-      this.options.editables.push(propertyElement);
-
-      this._trigger('enableproperty', null, this._params(predicate));
-    },
-
-    // returns the name of the property editor widget to use for the given property
-    _propertyEditorName: function (data) {
-      if (this.options.propertyEditorWidgets[data.property] !== undefined) {
-        // Property editor widget configuration set for specific RDF predicate
-        return this.options.propertyEditorWidgets[data.property];
-      }
-
-      // Load the property editor widget configuration for the data type
-      var propertyType = 'default';
-      var attributeDefinition = this.getAttributeDefinition(data.property);
-      if (attributeDefinition) {
-        propertyType = attributeDefinition.range[0];
-      }
-      if (this.options.propertyEditorWidgets[propertyType] !== undefined) {
-        return this.options.propertyEditorWidgets[propertyType];
-      }
-      return this.options.propertyEditorWidgets['default'];
-    },
-
-    _propertyEditorWidget: function (editor) {
-      return this.options.propertyEditorWidgetsConfiguration[editor].widget;
-    },
-
-    _propertyEditorOptions: function (editor) {
-      return this.options.propertyEditorWidgetsConfiguration[editor].options;
-    },
-
-    getAttributeDefinition: function (property) {
-      var type = this.options.model.get('@type');
-      if (!type) {
-        return;
-      }
-      if (!type.attributes) {
-        return;
-      }
-      return type.attributes.get(property);
-    },
-
-    // Deprecated.
-    enableEditor: function (data) {
-      return this.enablePropertyEditor(data);
-    },
-
-    enablePropertyEditor: function (data) {
-      var editorName = this._propertyEditorName(data);
-      if (editorName === null) {
-        return;
-      }
-
-      var editorWidget = this._propertyEditorWidget(editorName);
-
-      data.editorOptions = this._propertyEditorOptions(editorName);
-      data.toolbarState = this.options.toolbarState;
-      data.disabled = false;
-      // Pass metadata that could be useful for some implementations.
-      data.editorName = editorName;
-      data.editorWidget = editorWidget;
-
-      if (typeof jQuery(data.element)[editorWidget] !== 'function') {
-        throw new Error(editorWidget + ' widget is not available');
-      }
-
-      jQuery(data.element)[editorWidget](data);
-      jQuery(data.element).data('createWidgetName', editorWidget);
-      return jQuery(data.element);
-    },
-
-    // Deprecated.
-    disableEditor: function (data) {
-      return this.disablePropertyEditor(data);
-    },
-
-    disablePropertyEditor: function (data) {
-      data.element[data.editable.widgetName]({
-        disabled: true
-      });
-      jQuery(data.element).removeClass('ui-state-disabled');
-
-      if (data.element.is(':focus')) {
-        data.element.blur();
-      }
-    },
-
-    collectionWidgetName: function (data) {
-      if (this.options.collectionWidgets[data.property] !== undefined) {
-        // Widget configuration set for specific RDF predicate
-        return this.options.collectionWidgets[data.property];
-      }
-
-      var propertyType = 'default';
-      var attributeDefinition = this.getAttributeDefinition(data.property);
-      if (attributeDefinition) {
-        propertyType = attributeDefinition.range[0];
-      }
-      if (this.options.collectionWidgets[propertyType] !== undefined) {
-        return this.options.collectionWidgets[propertyType];
-      }
-      return this.options.collectionWidgets['default'];
-    },
-
-    enableCollection: function (data) {
-      var widgetName = this.collectionWidgetName(data);
-      if (widgetName === null) {
-        return;
-      }
-      data.disabled = false;
-      if (typeof jQuery(data.element)[widgetName] !== 'function') {
-        throw new Error(widgetName + ' widget is not available');
-      }
-      jQuery(data.element)[widgetName](data);
-      jQuery(data.element).data('createCollectionWidgetName', widgetName);
-      return jQuery(data.element);
-    },
-
-    disableCollection: function (data) {
-      var widgetName = jQuery(data.element).data('createCollectionWidgetName');
-      if (widgetName === null) {
-        return;
-      }
-      data.disabled = true;
-      if (widgetName) {
-        // only if there has been an editing widget registered
-        jQuery(data.element)[widgetName](data);
-        jQuery(data.element).removeClass('ui-state-disabled');
-      }
-    }
-  });
-})(jQuery);
-//     Create.js - On-site web editing interface
-//     (c) 2012 Tobias Herrmann, IKS Consortium
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://createjs.org/
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false document:false */
-  'use strict';
-
-  // # Base property editor widget
-  //
-  // This property editor widget provides a very simplistic `contentEditable`
-  // property editor that can be used as standalone, but should more usually be
-  // used as the base class for other property editor widgets.
-  // This property editor widget is only useful for textual properties!
-  //
-  // Subclassing this base property editor widget is easy:
-  //
-  //     jQuery.widget('Namespace.MyWidget', jQuery.Create.editWidget, {
-  //       // override any properties
-  //     });
-  jQuery.widget('Midgard.editWidget', {
-    options: {
-      disabled: false,
-      vie: null
-    },
-    // override to enable the widget
-    enable: function () {
-      this.element.attr('contenteditable', 'true');
-    },
-    // override to disable the widget
-    disable: function (disable) {
-      this.element.attr('contenteditable', 'false');
-    },
-    // called by the jQuery UI plugin factory when creating the property editor
-    // widget instance
-    _create: function () {
-      this._registerWidget();
-      this._initialize();
-
-      if (_.isFunction(this.options.decorate) && _.isFunction(this.options.decorateParams)) {
-        // TRICKY: we can't use this.options.decorateParams()'s 'propertyName'
-        // parameter just yet, because it will only be available after this
-        // object has been created, but we're currently in the constructor!
-        // Hence we have to duplicate part of its logic here.
-        this.options.decorate(this.options.decorateParams(null, {
-          propertyName: this.options.property,
-          propertyEditor: this,
-          propertyElement: this.element,
-          // Deprecated.
-          editor: this,
-          predicate: this.options.property,
-          element: this.element
-        }));
-      }
-    },
-    // called every time the property editor widget is called
-    _init: function () {
-      if (this.options.disabled) {
-        this.disable();
-        return;
-      }
-      this.enable();
-    },
-    // override this function to initialize the property editor widget functions
-    _initialize: function () {
-      var self = this;
-      this.element.on('focus', function () {
-        if (self.options.disabled) {
-          return;
-        }
-        self.options.activated();
-      });
-      this.element.on('blur', function () {
-        if (self.options.disabled) {
-          return;
-        }
-        self.options.deactivated();
-      });
-      var before = this.element.html();
-      this.element.on('keyup paste', function (event) {
-        if (self.options.disabled) {
-          return;
-        }
-        var current = jQuery(this).html();
-        if (before !== current) {
-          before = current;
-          self.options.changed(current);
-        }
-      });
-    },
-    // used to register the property editor widget name with the DOM element
-    _registerWidget: function () {
-      this.element.data("createWidgetName", this.widgetName);
-    }
-  });
-})(jQuery);
-//     Create.js - On-site web editing interface
-//     (c) 2012 Tobias Herrmann, IKS Consortium
-//     (c) 2011 Rene Kapusta, Evo42
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://createjs.org/
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false document:false Aloha:false */
-  'use strict';
-
-  // # Aloha editing widget
-  //
-  // This widget allows editing textual contents using the
-  // [Aloha](http://aloha-editor.org) rich text editor.
-  //
-  // Due to licensing incompatibilities, Aloha Editor needs to be installed
-  // and configured separately.
-  jQuery.widget('Midgard.alohaWidget', jQuery.Midgard.editWidget, {
-    _initialize: function () {},
-    enable: function () {
-      var options = this.options;
-      var editable;
-      var currentElement = Aloha.jQuery(options.element.get(0)).aloha();
-      _.each(Aloha.editables, function (aloha) {
-        // Find the actual editable instance so we can hook to the events
-        // correctly
-        if (aloha.obj.get(0) === currentElement.get(0)) {
-          editable = aloha;
-        }
-      });
-      if (!editable) {
-        return;
-      }
-      editable.vieEntity = options.entity;
-
-      // Subscribe to activation and deactivation events
-      Aloha.bind('aloha-editable-activated', function (event, data) {
-        if (data.editable !== editable) {
-          return;
-        }
-        options.activated();
-      });
-      Aloha.bind('aloha-editable-deactivated', function (event, data) {
-        if (data.editable !== editable) {
-          return;
-        }
-        options.deactivated();
-      });
-
-      Aloha.bind('aloha-smart-content-changed', function (event, data) {
-        if (data.editable !== editable) {
-          return;
-        }
-        if (!data.editable.isModified()) {
-          return true;
-        }
-        options.changed(data.editable.getContents());
-        data.editable.setUnmodified();
-      });
-      this.options.disabled = false;
-    },
-    disable: function () {
-      Aloha.jQuery(this.options.element.get(0)).mahalo();
-      this.options.disabled = true;
-    }
-  });
-})(jQuery);
-//     Create.js - On-site web editing interface
-//     (c) 2012 Tobias Herrmann, IKS Consortium
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false document:false CKEDITOR:false */
-  'use strict';
-
-  // # CKEditor editing widget
-  //
-  // This widget allows editing textual content areas with the
-  // [CKEditor](http://ckeditor.com/) rich text editor.
-  jQuery.widget('Midgard.ckeditorWidget', jQuery.Midgard.editWidget, {
-    enable: function () {
-      this.element.attr('contentEditable', 'true');
-      this.editor = CKEDITOR.inline(this.element.get(0));
-      this.options.disabled = false;
-
-      var widget = this;
-      this.editor.on('focus', function () {
-        widget.options.activated();
-      });
-      this.editor.on('blur', function () {
-        widget.options.activated();
-        widget.options.changed(widget.editor.getData());
-      });
-      this.editor.on('key', function () {
-        widget.options.changed(widget.editor.getData());
-      });
-      this.editor.on('paste', function () {
-        widget.options.changed(widget.editor.getData());
-      });
-      this.editor.on('afterCommandExec', function () {
-        widget.options.changed(widget.editor.getData());
-      });
-      this.editor.on('configLoaded', function() {
-        jQuery.each(widget.options.editorOptions, function(optionName, option) {
-          widget.editor.config[optionName] = option;
-        });
-      });
-    },
-
-    disable: function () {
-      if (!this.editor) {
-        return;
-      }
-      this.element.attr('contentEditable', 'false');
-      this.editor.destroy();
-      this.editor = null;
-    },
-
-    _initialize: function () {
-      CKEDITOR.disableAutoInline = true;
-    }
-  });
-})(jQuery);
-//     Create.js - On-site web editing interface
-//     (c) 2012 Tobias Herrmann, IKS Consortium
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://createjs.org/
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false document:false */
-  'use strict';
-
-  // # Hallo editing widget
-  //
-  // This widget allows editing textual content areas with the
-  // [Hallo](http://hallojs.org) rich text editor.
-  jQuery.widget('Midgard.halloWidget', jQuery.Midgard.editWidget, {
-    options: {
-      editorOptions: {},
-      disabled: true,
-      toolbarState: 'full',
-      vie: null,
-      entity: null
-    },
-    enable: function () {
-      jQuery(this.element).hallo({
-        editable: true
-      });
-      this.options.disabled = false;
-    },
-
-    disable: function () {
-      jQuery(this.element).hallo({
-        editable: false
-      });
-      this.options.disabled = true;
-    },
-
-    _initialize: function () {
-      jQuery(this.element).hallo(this.getHalloOptions());
-      var self = this;
-      jQuery(this.element).on('halloactivated', function (event, data) {
-        self.options.activated();
-      });
-      jQuery(this.element).on('hallodeactivated', function (event, data) {
-        self.options.deactivated();
-      });
-      jQuery(this.element).on('hallomodified', function (event, data) {
-        self.options.changed(data.content);
-        data.editable.setUnmodified();
-      });
-
-      jQuery(document).on('midgardtoolbarstatechange', function(event, data) {
-        // Switch between Hallo configurations when toolbar state changes
-        if (data.display === self.options.toolbarState) {
-          return;
-        }
-        self.options.toolbarState = data.display;
-        if (!self.element.data('IKS-hallo')) {
-          // Hallo not yet instantiated
-          return;
-        }
-        var newOptions = self.getHalloOptions();
-        self.element.hallo('changeToolbar', newOptions.parentElement, newOptions.toolbar, true);
-      });
-    },
-
-    getHalloOptions: function() {
-      var defaults = {
-        plugins: {
-          halloformat: {},
-          halloblock: {},
-          hallolists: {},
-          hallolink: {},
-          halloimage: {
-            entity: this.options.entity
-          }
-        },
-        buttonCssClass: 'create-ui-btn-small',
-        placeholder: '[' + this.options.property + ']'
-      };
-
-      if (typeof this.element.annotate === 'function' && this.options.vie.services.stanbol) {
-        // Enable Hallo Annotate plugin by default if user has annotate.js
-        // loaded and VIE has Stanbol enabled
-        defaults.plugins.halloannotate = {
-            vie: this.options.vie
-        };
-      }
-
-      if (this.options.toolbarState === 'full') {
-        // Use fixed toolbar in the Create tools area
-        defaults.parentElement = jQuery('.create-ui-toolbar-dynamictoolarea .create-ui-tool-freearea');
-        defaults.toolbar = 'halloToolbarFixed';
-      } else {
-        // Tools area minimized, use floating toolbar
-        defaults.parentElement = 'body';
-        defaults.toolbar = 'halloToolbarContextual';
-      }
-      return _.extend(defaults, this.options.editorOptions);
-    }
-  });
-})(jQuery);
-//     Create.js - On-site web editing interface
-//     (c) 2012 Henri Bergius, IKS Consortium
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://createjs.org/
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false document:false */
-  'use strict';
-
-  // # Redactor editing widget
-  //
-  // This widget allows editing textual content areas with the
-  // [Redactor](http://redactorjs.com/) rich text editor.
-  jQuery.widget('Midgard.redactorWidget', jQuery.Midgard.editWidget, {
-    editor: null,
-
-    options: {
-      editorOptions: {},
-      disabled: true
-    },
-
-    enable: function () {
-      jQuery(this.element).redactor(this.getRedactorOptions());
-      this.options.disabled = false;
-    },
-
-    disable: function () {
-      jQuery(this.element).destroyEditor();
-      this.options.disabled = true;
-    },
-
-    _initialize: function () {
-      var self = this;
-      jQuery(this.element).on('focus', function (event) {
-        self.options.activated(); 
-      });
-      /*
-      jQuery(this.element).on('blur', function (event) {
-        self.options.deactivated(); 
-      });
-      */
-    },
-
-    getRedactorOptions: function () {
-      var self = this;
-      var overrides = {
-        keyupCallback: function (obj, event) {
-          self.options.changed(jQuery(self.element).getCode());
-        },
-        execCommandCallback: function (obj, command) {
-          self.options.changed(jQuery(self.element).getCode());
-        }
-      };
-
-      return _.extend(self.options.editorOptions, overrides);
-    }
-  });
-})(jQuery);
-//     Create.js - On-site web editing interface
-//     (c) 2011-2012 Henri Bergius, IKS Consortium
-//     Create may be freely distributed under the MIT license.
-//     For all details and documentation:
-//     http://createjs.org/
-(function (jQuery, undefined) {
-  // Run JavaScript in strict mode
-  /*global jQuery:false _:false window:false */
-  'use strict';
-
-  jQuery.widget('Midgard.midgardStorage', {
-    saveEnabled: true,
-    options: {
-      // Whether to use localstorage
-      localStorage: false,
-      removeLocalstorageOnIgnore: true,
-      // VIE instance to use for storage handling
-      vie: null,
-      // URL callback for Backbone.sync
-      url: '',
-      // Whether to enable automatic saving
-      autoSave: false,
-      // How often to autosave in milliseconds
-      autoSaveInterval: 5000,
-      // Whether to save entities that are referenced by entities
-      // we're saving to the server.
-      saveReferencedNew: false,
-      saveReferencedChanged: false,
-      // Namespace used for events from midgardEditable-derived widget
-      editableNs: 'midgardeditable',
-      // CSS selector for the Edit button, leave to null to not bind
-      // notifications to any element
-      editSelector: '#midgardcreate-edit a',
-      localize: function (id, language) {
-        return window.midgardCreate.localize(id, language);
-      },
-      language: null
-    },
-
-    _create: function () {
-      var widget = this;
-      this.changedModels = [];
-
-      if (window.localStorage) {
-        this.options.localStorage = true;
-      }
-
-      this.vie = this.options.vie;
-
-      this.vie.entities.on('add', function (model) {
-        // Add the back-end URL used by Backbone.sync
-        model.url = widget.options.url;
-        model.toJSON = model.toJSONLD;
-      });
-
-      widget._bindEditables();
-      if (widget.options.autoSave) {
-        widget._autoSave();
-      }
-    },
-
-    _autoSave: function () {
-      var widget = this;
-      widget.saveEnabled = true;
-
-      var doAutoSave = function () {
-        if (!widget.saveEnabled) {
-          return;
-        }
-
-        if (widget.changedModels.length === 0) {
-          return;
-        }
-
-        widget.saveRemoteAll({
-          // We make autosaves silent so that potential changes from server
-          // don't disrupt user while writing.
-          silent: true
-        });
-      };
-
-      var timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
-
-      this.element.on('startPreventSave', function () {
-        if (timeout) {
-          window.clearInterval(timeout);
-          timeout = null;
-        }
-        widget.disableAutoSave();
-      });
-      this.element.on('stopPreventSave', function () {
-        if (!timeout) {
-          timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
-        }
-        widget.enableAutoSave();
-      });
-
-    },
-
-    enableAutoSave: function () {
-      this.saveEnabled = true;
-    },
-
-    disableAutoSave: function () {
-      this.saveEnabled = false;
-    },
-
-    _bindEditables: function () {
-      var widget = this;
-      this.restorables = [];
-      var restorer;
-
-      widget.element.on(widget.options.editableNs + 'changed', function (event, options) {
-        if (_.indexOf(widget.changedModels, options.instance) === -1) {
-          widget.changedModels.push(options.instance);
-        }
-        widget._saveLocal(options.instance);
-      });
-
-      widget.element.on(widget.options.editableNs + 'disable', function (event, options) {
-        widget.revertChanges(options.instance);
-      });
-
-      widget.element.on(widget.options.editableNs + 'enable', function (event, options) {
-        if (!options.instance._originalAttributes) {
-          options.instance._originalAttributes = _.clone(options.instance.attributes);
-        }
-
-        if (!options.instance.isNew() && widget._checkLocal(options.instance)) {
-          // We have locally-stored modifications, user needs to be asked
-          widget.restorables.push(options.instance);
-        }
-
-        /*_.each(options.instance.attributes, function (attributeValue, property) {
-          if (attributeValue instanceof widget.vie.Collection) {
-            widget._readLocalReferences(options.instance, property, attributeValue);
-          }
-        });*/
-      });
-
-      widget.element.on('midgardcreatestatechange', function (event, options) {
-        if (options.state === 'browse' || widget.restorables.length === 0) {
-          widget.restorables = [];
-          if (restorer) {
-            restorer.close();
-          }
-          return;
-        }
-        restorer = widget.checkRestore();
-      });
-
-      widget.element.on('midgardstorageloaded', function (event, options) {
-        if (_.indexOf(widget.changedModels, options.instance) === -1) {
-          widget.changedModels.push(options.instance);
-        }
-      });
-    },
-
-    checkRestore: function () {
-      var widget = this;
-      if (widget.restorables.length === 0) {
-        return;
-      }
-
-      var message;
-      var restorer;
-      if (widget.restorables.length === 1) {
-        message = _.template(widget.options.localize('localModification', widget.options.language), {
-          label: widget.restorables[0].getSubjectUri()
-        });
-      } else {
-        message = _.template(widget.options.localize('localModifications', widget.options.language), {
-          number: widget.restorables.length
-        });
-      }
-
-      var doRestore = function (event, notification) {
-        widget.restoreLocalAll();
-        restorer.close();
-      };
-
-      var doIgnore = function (event, notification) {
-        widget.ignoreLocal();
-        restorer.close();
-      };
-
-      restorer = jQuery('body').midgardNotifications('create', {
-        bindTo: widget.options.editSelector,
-        gravity: 'TR',
-        body: message,
-        timeout: 0,
-        actions: [
-          {
-            name: 'restore',
-            label: widget.options.localize('Restore', widget.options.language),
-            cb: doRestore,
-            className: 'create-ui-btn'
-          },
-          {
-            name: 'ignore',
-            label: widget.options.localize('Ignore', widget.options.language),
-            cb: doIgnore,
-            className: 'create-ui-btn'
-          }
-        ],
-        callbacks: {
-          beforeShow: function () {
-            if (!window.Mousetrap) {
-              return;
-            }
-            window.Mousetrap.bind(['command+shift+r', 'ctrl+shift+r'], function (event) {
-              event.preventDefault();
-              doRestore();
-            });
-            window.Mousetrap.bind(['command+shift+i', 'ctrl+shift+i'], function (event) {
-              event.preventDefault();
-              doIgnore();
-            });
-          },
-          afterClose: function () {
-            if (!window.Mousetrap) {
-              return;
-            }
-            window.Mousetrap.unbind(['command+shift+r', 'ctrl+shift+r']);
-            window.Mousetrap.unbind(['command+shift+i', 'ctrl+shift+i']);
-          }
-        }
-      });
-      return restorer;
-    },
-
-    restoreLocalAll: function () {
-      _.each(this.restorables, function (instance) {
-        this.readLocal(instance);
-      }, this);
-      this.restorables = [];
-    },
-
-    ignoreLocal: function () {
-      if (this.options.removeLocalstorageOnIgnore) {
-        _.each(this.restorables, function (instance) {
-          this._removeLocal(instance);
-        }, this);
-      }
-      this.restorables = [];
-    },
-
-    saveReferences: function (model) {
-      _.each(model.attributes, function (value, property) {
-        if (!value || !value.isCollection) {
-          return;
-        }
-
-        value.each(function (referencedModel) {
-          if (this.changedModels.indexOf(referencedModel) !== -1) {
-            // The referenced model is already in the save queue
-            return;
-          }
-
-          if (referencedModel.isNew() && this.options.saveReferencedNew) {
-            return referencedModel.save();
-          }
-
-          if (referencedModel.hasChanged() && this.options.saveReferencedChanged) {
-            return referencedModel.save();
-          }
-        }, this);
-      }, this);
-    },
-
-    saveRemote: function (model, options) {
-      // Optionally handle entities referenced in this model first
-      this.saveReferences(model);
-
-      this._trigger('saveentity', null, {
-        entity: model,
-        options: options
-      });
-
-      var widget = this;
-      model.save(null, _.extend({}, options, {
-        success: function (m, response) {
-          // From now on we're going with the values we have on server
-          model._originalAttributes = _.clone(model.attributes);
-          widget._removeLocal(model);
-          window.setTimeout(function () {
-            // Remove the model from the list of changed models after saving
-            widget.changedModels.splice(widget.changedModels.indexOf(model), 1);
-          }, 0);
-          if (_.isFunction(options.success)) {
-            options.success(m, response);
-          }
-          widget._trigger('savedentity', null, {
-            entity: model,
-            options: options
-          });
-        },
-        error: function (m, response) {
-          if (_.isFunction(options.error)) {
-            options.error(m, response);
-          }
-        }
-      }));
-    },
-
-    saveRemoteAll: function (options) {
-      var widget = this;
-      if (widget.changedModels.length === 0) {
-        return;
-      }
-
-      widget._trigger('save', null, {
-        entities: widget.changedModels,
-        options: options,
-        // Deprecated
-        models: widget.changedModels
-      });
-
-      var notification_msg;
-      var needed = widget.changedModels.length;
-      if (needed > 1) {
-        notification_msg = _.template(widget.options.localize('saveSuccessMultiple', widget.options.language), {
-          number: needed
-        });
-      } else {
-        notification_msg = _.template(widget.options.localize('saveSuccess', widget.options.language), {
-          label: widget.changedModels[0].getSubjectUri()
-        });
-      }
-
-      widget.disableAutoSave();
-      _.each(widget.changedModels, function (model) {
-        this.saveRemote(model, {
-          success: function (m, response) {
-            needed--;
-            if (needed <= 0) {
-              // All models were happily saved
-              widget._trigger('saved', null, {
-                options: options
-              });
-              if (options && _.isFunction(options.success)) {
-                options.success(m, response);
-              }
-              jQuery('body').midgardNotifications('create', {
-                body: notification_msg
-              });
-              widget.enableAutoSave();
-            }
-          },
-          error: function (m, err) {
-            if (options && _.isFunction(options.error)) {
-              options.error(m, err);
-            }
-            jQuery('body').midgardNotifications('create', {
-              body: _.template(widget.options.localize('saveError', widget.options.language), {
-                error: err.responseText || ''
-              }),
-              timeout: 0
-            });
-
-            widget._trigger('error', null, {
-              instance: model
-            });
-          }
-        });
-      }, this);
-    },
-
-    _saveLocal: function (model) {
-      if (!this.options.localStorage) {
-        return;
-      }
-
-      if (model.isNew()) {
-        // Anonymous object, save as refs instead
-        if (!model.primaryCollection) {
-          return;
-        }
-        return this._saveLocalReferences(model.primaryCollection.subject, model.primaryCollection.predicate, model);
-      }
-      window.localStorage.setItem(model.getSubjectUri(), JSON.stringify(model.toJSONLD()));
-    },
-
-    _getReferenceId: function (model, property) {
-      return model.id + ':' + property;
-    },
-
-    _saveLocalReferences: function (subject, predicate, model) {
-      if (!this.options.localStorage) {
-        return;
-      }
-
-      if (!subject || !predicate) {
-        return;
-      }
-
-      var widget = this;
-      var identifier = subject + ':' + predicate;
-      var json = model.toJSONLD();
-      if (window.localStorage.getItem(identifier)) {
-        var referenceList = JSON.parse(window.localStorage.getItem(identifier));
-        var index = _.pluck(referenceList, '@').indexOf(json['@']);
-        if (index !== -1) {
-          referenceList[index] = json;
-        } else {
-          referenceList.push(json);
-        }
-        window.localStorage.setItem(identifier, JSON.stringify(referenceList));
-        return;
-      }
-      window.localStorage.setItem(identifier, JSON.stringify([json]));
-    },
-
-    _checkLocal: function (model) {
-      if (!this.options.localStorage) {
-        return false;
-      }
-
-      var local = window.localStorage.getItem(model.getSubjectUri());
-      if (!local) {
-        return false;
-      }
-
-      return true;
-    },
-
-    hasLocal: function (model) {
-      if (!this.options.localStorage) {
-        return false;
-      }
-
-      if (!window.localStorage.getItem(model.getSubjectUri())) {
-        return false;
-      }
-      return true;
-    },
-
-    readLocal: function (model) {
-      if (!this.options.localStorage) {
-        return;
-      }
-
-      var local = window.localStorage.getItem(model.getSubjectUri());
-      if (!local) {
-        return;
-      }
-      if (!model._originalAttributes) {
-        model._originalAttributes = _.clone(model.attributes);
-      }
-      var parsed = JSON.parse(local);
-      var entity = this.vie.entities.addOrUpdate(parsed, {
-        overrideAttributes: true
-      });
-
-      this._trigger('loaded', null, {
-        instance: entity
-      });
-    },
-
-    _readLocalReferences: function (model, property, collection) {
-      if (!this.options.localStorage) {
-        return;
-      }
-
-      var identifier = this._getReferenceId(model, property);
-      var local = window.localStorage.getItem(identifier);
-      if (!local) {
-        return;
-      }
-      collection.add(JSON.parse(local));
-    },
-
-    revertChanges: function (model) {
-      var widget = this;
-
-      // Remove unsaved collection members
-      if (!model) { return; }
-      _.each(model.attributes, function (attributeValue, property) {
-        if (attributeValue instanceof widget.vie.Collection) {
-          var removables = [];
-          attributeValue.forEach(function (model) {
-            if (model.isNew()) {
-              removables.push(model);
-            }
-          });
-          attributeValue.remove(removables);
-        }
-      });
-
-      // Restore original object properties
-      if (!model.changedAttributes()) {
-        if (model._originalAttributes) {
-          model.set(model._originalAttributes);
-        }
-        return;
-      }
-
-      model.set(model.previousAttributes());
-    },
-
-    _removeLocal: function (model) {
-      if (!this.options.localStorage) {
-        return;
-      }
-
-      window.localStorage.removeItem(model.getSubjectUri());
-    }
-  });
-})(jQuery);
-if (window.midgardCreate === undefined) {
-  window.midgardCreate = {};
-}
-if (window.midgardCreate.locale === undefined) {
-  window.midgardCreate.locale = {};
-}
-
-window.midgardCreate.locale.en = {
-  // Session-state buttons for the main toolbar
-  'Save': 'Save',
-  'Saving': 'Saving',
-  'Cancel': 'Cancel',
-  'Edit': 'Edit',
-  // Storage status messages
-  'localModification': 'Item "<%= label %>" has local modifications',
-  'localModifications': '<%= number %> items on this page have local modifications',
-  'Restore': 'Restore',
-  'Ignore': 'Ignore',
-  'saveSuccess': 'Item "<%= label %>" saved successfully',
-  'saveSuccessMultiple': '<%= number %> items saved successfully',
-  'saveError': 'Error occurred while saving<br /><%= error %>',
-  // Tagging
-  'Item tags': 'Item tags',
-  'Suggested tags': 'Suggested tags',
-  'Tags': 'Tags',
-  'add a tag': 'add a tag',
-  // Collection widgets
-  'Add': 'Add',
-  'Choose type to add': 'Choose type to add'
-};
diff --git a/core/modules/edit/css/edit.css b/core/modules/edit/css/edit.css
index 6a5ac83..af5cb0e 100644
--- a/core/modules/edit/css/edit.css
+++ b/core/modules/edit/css/edit.css
@@ -69,8 +69,17 @@
           transition: background, padding .2s ease;
 }
 
-
-
+/**
+ * Entity toolbar.
+ */
+.edit-entity-toolbar-container {
+  background-color: red;
+  position:absolute;
+  -webkit-transition: all 0.2s;
+  transition: all 0.2s;
+  width: 20em;
+  z-index: 350;
+}
 
 /**
  * Candidate editables + editables being edited.
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index 0125d45..c16990f 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -65,7 +65,6 @@ function edit_library_info() {
   $path = drupal_get_path('module', 'edit');
   $options = array(
     'scope' => 'footer',
-    'attributes' => array('defer' => TRUE),
   );
   $libraries['edit'] = array(
     'title' => 'Edit: in-place editing',
@@ -74,21 +73,21 @@ function edit_library_info() {
     'js' => array(
       // Core.
       $path . '/js/edit.js' => $options,
-      $path . '/js/app.js' => $options,
       // Models.
-      $path . '/js/models/edit-app-model.js' => $options,
+      $path . '/js/models/AppModel.js' => $options,
+      $path . '/js/models/EntityModel.js' => $options,
+      $path . '/js/models/FieldModel.js' => $options,
       // Views.
-      $path . '/js/views/propertyeditordecoration-view.js' => $options,
-      $path . '/js/views/contextuallink-view.js' => $options,
-      $path . '/js/views/modal-view.js' => $options,
-      $path . '/js/views/toolbar-view.js' => $options,
+      $path . '/js/views/AppView.js' => $options,
+      $path . '/js/views/PropertyEditorDecorationView.js' => $options,
+      $path . '/js/views/ContextualLinkView.js' => $options,
+      $path . '/js/views/ModalView.js' => $options,
+      $path . '/js/views/ToolbarView.js' => $options,
+      $path . '/js/views/EntityToolbarView.js' => $options,
       // Backbone.sync implementation on top of Drupal forms.
       $path . '/js/backbone.drupalform.js' => $options,
-      // VIE service.
-      $path . '/js/viejs/EditService.js' => $options,
-      // Create.js subclasses.
-      $path . '/js/createjs/editable.js' => $options,
-      $path . '/js/createjs/storage.js' => $options,
+      // Storage manager.
+      $path . '/js/storage.js' => $options,
       // Other.
       $path . '/js/util.js' => $options,
       $path . '/js/theme.js' => $options,
@@ -109,11 +108,11 @@ function edit_library_info() {
       array('system', 'jquery'),
       array('system', 'underscore'),
       array('system', 'backbone'),
-      array('system', 'vie.core'),
-      array('system', 'create.editonly'),
       array('system', 'jquery.form'),
+      array('system', 'jquery.ui.position'),
       array('system', 'drupal.form'),
       array('system', 'drupal.ajax'),
+      array('system', 'drupal.debounce'),
       array('system', 'drupalSettings'),
     ),
   );
@@ -121,7 +120,7 @@ function edit_library_info() {
     'title' => '"Form" Create.js PropertyEditor widget',
     'version' => VERSION,
     'js' => array(
-      $path . '/js/createjs/editingWidgets/formwidget.js' => $options,
+      $path . '/js/editors/formEditor.js' => $options,
     ),
     'dependencies' => array(
       array('edit', 'edit'),
@@ -131,7 +130,7 @@ function edit_library_info() {
     'title' => '"Direct" Create.js PropertyEditor widget',
     'version' => VERSION,
     'js' => array(
-      $path . '/js/createjs/editingWidgets/drupalcontenteditablewidget.js' => $options,
+      $path . '/js/editors/directEditor.js' => $options,
     ),
     'dependencies' => array(
       array('edit', 'edit'),
diff --git a/core/modules/edit/js/app.js b/core/modules/edit/js/app.js
deleted file mode 100644
index 14d76a0..0000000
--- a/core/modules/edit/js/app.js
+++ /dev/null
@@ -1,391 +0,0 @@
-/**
- * @file
- * A Backbone View that is the central app controller.
- */
-(function ($, _, Backbone, Drupal, VIE) {
-
-"use strict";
-
-  Drupal.edit = Drupal.edit || {};
-  Drupal.edit.EditAppView = Backbone.View.extend({
-    vie: null,
-    domService: null,
-
-    // Configuration for state handling.
-    states: [],
-    activeEditorStates: [],
-    singleEditorStates: [],
-
-    // State.
-    $entityElements: null,
-
-    /**
-     * Implements Backbone Views' initialize() function.
-     */
-    initialize: function() {
-      _.bindAll(this, 'appStateChange', 'acceptEditorStateChange', 'editorStateChange');
-
-      // VIE instance for Edit.
-      this.vie = new VIE();
-      // Use our custom DOM parsing service until RDFa is available.
-      this.vie.use(new this.vie.EditService());
-      this.domService = this.vie.service('edit');
-
-      // Instantiate configuration for state handling.
-      this.states = [
-        null, 'inactive', 'candidate', 'highlighted',
-        'activating', 'active', 'changed', 'saving', 'saved', 'invalid'
-      ];
-      this.activeEditorStates = ['activating', 'active'];
-      this.singleEditorStates = _.union(['highlighted'], this.activeEditorStates);
-
-      this.$entityElements = $([]);
-
-      // Use Create's Storage widget.
-      this.$el.createStorage({
-        vie: this.vie,
-        editableNs: 'createeditable'
-      });
-
-      // When view/edit mode is toggled in the menu, update the editor widgets.
-      this.model.on('change:activeEntity', this.appStateChange);
-    },
-
-    /**
-     * Finds editable properties within a given context.
-     *
-     * Finds editable properties, registers them with the app, updates their
-     * state to match the current app state.
-     *
-     * @param $context
-     *   A jQuery-wrapped context DOM element within which will be searched.
-     */
-    findEditableProperties: function($context) {
-      var that = this;
-      var activeEntity = this.model.get('activeEntity');
-
-      this.domService.findSubjectElements($context).each(function() {
-        var $element = $(this);
-
-        // Ignore editable properties for which we've already set up Create.js.
-        if (that.$entityElements.index($element) !== -1) {
-          return;
-        }
-
-        $element
-          // Instantiate an EditableEntity widget.
-          .createEditable({
-            vie: that.vie,
-            disabled: true,
-            state: 'inactive',
-            acceptStateChange: that.acceptEditorStateChange,
-            statechange: function(event, data) {
-              that.editorStateChange(data.previous, data.current, data.propertyEditor);
-            },
-            decoratePropertyEditor: function(data) {
-              that.decorateEditor(data.propertyEditor);
-            }
-          })
-          // This event is triggered just before Edit removes an EditableEntity
-          // widget, so that we can do proper clean-up.
-          .on('destroyedPropertyEditor.edit', function(event, editor) {
-            that.undecorateEditor(editor);
-            that.$entityElements = that.$entityElements.not($(this));
-          })
-          // Transition the new PropertyEditor into the default state.
-          .createEditable('setState', 'inactive');
-
-        // If the new PropertyEditor is for the entity that's currently being
-        // edited, then transition it to the 'candidate' state.
-        // (This happens when a field was modified and is re-rendered.)
-        var entityOfProperty = $element.createEditable('option', 'model');
-        if (entityOfProperty.getSubjectUri() === activeEntity) {
-          $element.createEditable('setState', 'candidate');
-        }
-
-        // Add this new EditableEntity widget element to the list.
-        that.$entityElements = that.$entityElements.add($element);
-      });
-    },
-
-    /**
-     * Sets the state of PropertyEditor widgets when edit mode begins or ends.
-     *
-     * Should be called whenever EditAppModel's "activeEntity" changes.
-     */
-    appStateChange: function() {
-      // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133, https://github.com/bergie/create/issues/140)
-      // We're currently setting the state on EditableEntity widgets instead of
-      // PropertyEditor widgets, because of
-      // https://github.com/bergie/create/issues/133.
-
-      var activeEntity = this.model.get('activeEntity');
-      var $editableFieldsForEntity = $('[data-edit-id^="' + activeEntity + '/"]');
-
-      // First, change the status of all PropertyEditor widgets to 'inactive'.
-      this.$entityElements.each(function() {
-        $(this).createEditable('setState', 'inactive', null, {reason: 'stop'});
-      });
-
-      // Then, change the status of PropertyEditor widgets of the currently
-      // active entity to 'candidate'.
-      $editableFieldsForEntity.each(function() {
-        $(this).createEditable('setState', 'candidate');
-      });
-
-      // Manage the page's tab indexes.
-    },
-
-    /**
-     * Accepts or reject editor (PropertyEditor) state changes.
-     *
-     * This is what ensures that the app is in control of what happens.
-     *
-     * @param from
-     *   The previous state.
-     * @param to
-     *   The new state.
-     * @param predicate
-     *   The predicate of the property for which the state change is happening.
-     * @param context
-     *   The context that is trying to trigger the state change.
-     * @param callback
-     *   The callback function that should receive the state acceptance result.
-     */
-    acceptEditorStateChange: function(from, to, predicate, context, callback) {
-      var accept = true;
-
-      // If the app is in view mode, then reject all state changes except for
-      // those to 'inactive'.
-      if (context && context.reason === 'stop') {
-        if (from === 'candidate' && to === 'inactive') {
-          accept = true;
-        }
-      }
-      // Handling of edit mode state changes is more granular.
-      else {
-        // In general, enforce the states sequence. Disallow going back from a
-        // "later" state to an "earlier" state, except in explicitly allowed
-        // cases.
-        if (_.indexOf(this.states, from) > _.indexOf(this.states, to)) {
-          accept = false;
-          // Allow: activating/active -> candidate.
-          // Necessary to stop editing a property.
-          if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') {
-            accept = true;
-          }
-          // Allow: changed/invalid -> candidate.
-          // Necessary to stop editing a property when it is changed or invalid.
-          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
-            accept = true;
-          }
-          // Allow: highlighted -> candidate.
-          // Necessary to stop highlighting a property.
-          else if (from === 'highlighted' && to === 'candidate') {
-            accept = true;
-          }
-          // Allow: saved -> candidate.
-          // Necessary when successfully saved a property.
-          else if (from === 'saved' && to === 'candidate') {
-            accept = true;
-          }
-          // Allow: invalid -> saving.
-          // Necessary to be able to save a corrected, invalid property.
-          else if (from === 'invalid' && to === 'saving') {
-            accept = true;
-          }
-        }
-
-        // If it's not against the general principle, then here are more
-        // disallowed cases to check.
-        if (accept) {
-          // Ensure only one editor (field) at a time may be higlighted or active.
-          if (from === 'candidate' && _.indexOf(this.singleEditorStates, to) !== -1) {
-            if (this.model.get('highlightedEditor') || this.model.get('activeEditor')) {
-              accept = false;
-            }
-          }
-          // Reject going from activating/active to candidate because of a
-          // mouseleave.
-          else if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') {
-            if (context && context.reason === 'mouseleave') {
-              accept = false;
-            }
-          }
-          // When attempting to stop editing a changed/invalid property, ask for
-          // confirmation.
-          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
-            if (context && context.reason === 'mouseleave') {
-              accept = false;
-            }
-            else {
-              // Check whether the transition has been confirmed?
-              if (context && context.confirmed) {
-                accept = true;
-              }
-              // Confirm this transition.
-              else {
-                // The callback will be called from the helper function.
-                this._confirmStopEditing(callback);
-                return;
-              }
-            }
-          }
-        }
-      }
-
-      callback(accept);
-    },
-
-    /**
-     * Asks the user to confirm whether he wants to stop editing via a modal.
-     *
-     * @param acceptCallback
-     *   The callback function as passed to acceptEditorStateChange(). This
-     *   callback function will be called with the user's choice.
-     *
-     * @see acceptEditorStateChange()
-     */
-    _confirmStopEditing: function(acceptCallback) {
-      // Only instantiate if there isn't a modal instance visible yet.
-      if (!this.model.get('activeModal')) {
-        var that = this;
-        var modal = new Drupal.edit.views.ModalView({
-          model: this.model,
-          message: Drupal.t('You have unsaved changes'),
-          buttons: [
-            { action: 'discard', classes: 'gray-button', label: Drupal.t('Discard changes') },
-            { action: 'save', type: 'submit', classes: 'blue-button', label: Drupal.t('Save') }
-          ],
-          callback: function(action) {
-            // The active modal has been removed.
-            that.model.set('activeModal', null);
-            if (action === 'discard') {
-              acceptCallback(true);
-            }
-            else {
-              acceptCallback(false);
-              var editor = that.model.get('activeEditor');
-              editor.options.widget.setState('saving', editor.options.property);
-            }
-          }
-        });
-        this.model.set('activeModal', modal);
-        // The modal will set the activeModal property on the model when rendering
-        // to prevent multiple modals from being instantiated.
-        modal.render();
-      }
-      else {
-        // Reject as there is still an open transition waiting for confirmation.
-        acceptCallback(false);
-      }
-    },
-
-    /**
-     * Reacts to editor (PropertyEditor) state changes; tracks global state.
-     *
-     * @param from
-     *   The previous state.
-     * @param to
-     *   The new state.
-     * @param editor
-     *   The PropertyEditor widget object.
-     */
-    editorStateChange: function(from, to, editor) {
-      // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
-      // Get rid of this once that issue is solved.
-      if (!editor) {
-        return;
-      }
-      else {
-        editor.stateChange(from, to);
-      }
-
-      // Keep track of the highlighted editor in the global state.
-      if (_.indexOf(this.singleEditorStates, to) !== -1 && this.model.get('highlightedEditor') !== editor) {
-        this.model.set('highlightedEditor', editor);
-      }
-      else if (this.model.get('highlightedEditor') === editor && to === 'candidate') {
-        this.model.set('highlightedEditor', null);
-      }
-
-      // Keep track of the active editor in the global state.
-      if (_.indexOf(this.activeEditorStates, to) !== -1 && this.model.get('activeEditor') !== editor) {
-        this.model.set('activeEditor', editor);
-      }
-      else if (this.model.get('activeEditor') === editor && to === 'candidate') {
-        // Discarded if it transitions from a changed state to 'candidate'.
-        if (from === 'changed' || from === 'invalid') {
-          // Retrieve the storage widget from DOM.
-          var createStorageWidget = this.$el.data('DrupalCreateStorage');
-          // Revert changes in the model, this will trigger the direct editable
-          // content to be reset and redrawn.
-          createStorageWidget.revertChanges(editor.options.entity);
-        }
-        this.model.set('activeEditor', null);
-      }
-
-      // Propagate the state change to the decoration and toolbar views.
-      // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
-      // Uncomment this once that issue is solved.
-      // editor.decorationView.stateChange(from, to);
-      // editor.toolbarView.stateChange(from, to);
-    },
-
-    /**
-     * Decorates an editor (PropertyEditor).
-     *
-     * Upon the page load, all appropriate editors are initialized and decorated
-     * (i.e. even before anything of the editing UI becomes visible; even before
-     * edit mode is enabled).
-     *
-     * @param editor
-     *   The PropertyEditor widget object.
-     */
-    decorateEditor: function(editor) {
-      // Toolbars are rendered "on-demand" (highlighting or activating).
-      // They are a sibling element before the editor's DOM element.
-      editor.toolbarView = new Drupal.edit.views.ToolbarView({
-        editor: editor,
-        $storageWidgetEl: this.$el
-      });
-
-      // Decorate the editor's DOM element depending on its state.
-      editor.decorationView = new Drupal.edit.views.PropertyEditorDecorationView({
-        el: editor.element,
-        editor: editor,
-        toolbarId: editor.toolbarView.getId()
-      });
-
-      // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
-      // Get rid of this once that issue is solved.
-      editor.options.widget.element.on('createeditablestatechange', function(event, data) {
-        editor.decorationView.stateChange(data.previous, data.current);
-        editor.toolbarView.stateChange(data.previous, data.current);
-      });
-    },
-
-    /**
-     * Undecorates an editor (PropertyEditor).
-     *
-     * Whenever a property has been updated, the old HTML will be replaced by
-     * the new (re-rendered) HTML. The EditableEntity widget will be destroyed,
-     * as will be the PropertyEditor widget. This method ensures Edit's editor
-     * views also are removed properly.
-     *
-     * @param editor
-     *   The PropertyEditor widget object.
-     */
-    undecorateEditor: function(editor) {
-      editor.toolbarView.undelegateEvents();
-      editor.toolbarView.remove();
-      delete editor.toolbarView;
-      editor.decorationView.undelegateEvents();
-      // Don't call .remove() on the decoration view, because that would remove
-      // a potentially rerendered field.
-      delete editor.decorationView;
-    }
-
-  });
-
-})(jQuery, _, Backbone, Drupal, VIE);
diff --git a/core/modules/edit/js/backbone.drupalform.js b/core/modules/edit/js/backbone.drupalform.js
index 750d16f..170f4a9 100644
--- a/core/modules/edit/js/backbone.drupalform.js
+++ b/core/modules/edit/js/backbone.drupalform.js
@@ -105,7 +105,7 @@ Backbone.syncDirect = function(method, model, options) {
     if (jQuery('#edit_backstage form').length === 0) {
       var formOptions = {
         propertyID: Drupal.edit.util.calcPropertyID(entity, predicate),
-        $editorElement: options.editor.element,
+        $editorElement: options.editor.$el,
         nocssjs: true
       };
       Drupal.edit.util.form.load(formOptions, function(form, ajax) {
diff --git a/core/modules/edit/js/createjs/editable.js b/core/modules/edit/js/createjs/editable.js
deleted file mode 100644
index 1316023..0000000
--- a/core/modules/edit/js/createjs/editable.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @file
- * Determines which editor (Create.js PropertyEditor widget) to use.
- */
-(function (jQuery, Drupal, drupalSettings) {
-
-"use strict";
-
-  jQuery.widget('Drupal.createEditable', jQuery.Midgard.midgardEditable, {
-    _create: function() {
-      this.vie = this.options.vie;
-
-      this.options.domService = 'edit';
-      this.options.predicateSelector = '*'; //'.edit-field.edit-allowed';
-
-      // The Create.js PropertyEditor widget configuration is not hardcoded; it
-      // is generated by the server.
-      this.options.propertyEditorWidgetsConfiguration = drupalSettings.edit.editors;
-
-      jQuery.Midgard.midgardEditable.prototype._create.call(this);
-    },
-
-    _propertyEditorName: function(data) {
-      // Pick a PropertyEditor widget for a property depending on its metadata.
-      var propertyID = Drupal.edit.util.calcPropertyID(data.entity, data.property);
-      return Drupal.edit.metadataCache[propertyID].editor;
-    }
-  });
-
-})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
deleted file mode 100644
index cde6163..0000000
--- a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @file
- * Override of Create.js' default "base" (plain contentEditable) widget.
- */
-(function (jQuery, Drupal) {
-
-"use strict";
-
-  // @todo D8: use jQuery UI Widget bridging.
-  // @see http://drupal.org/node/1874934#comment-7124904
-  jQuery.widget('Midgard.direct', jQuery.Midgard.editWidget, {
-
-    /**
-     * Implements getEditUISettings() method.
-     */
-    getEditUISettings: function() {
-      return { padding: true, unifiedToolbar: false, fullWidthToolbar: false };
-    },
-
-    /**
-     * Implements jQuery UI widget factory's _init() method.
-     *
-     * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142)
-     * Get rid of this once that issue is solved.
-     */
-    _init: function() {},
-
-    /**
-     * Implements Create's _initialize() method.
-     */
-    _initialize: function() {
-      var that = this;
-
-      // Sets the state to 'changed' whenever the content has changed.
-      var before = jQuery.trim(this.element.text());
-      this.element.on('keyup paste', function (event) {
-        if (that.options.disabled) {
-          return;
-        }
-        var current = jQuery.trim(that.element.text());
-        if (before !== current) {
-          before = current;
-          that.options.changed(current);
-        }
-      });
-    },
-
-    /**
-     * Makes this PropertyEditor widget react to state changes.
-     */
-    stateChange: function(from, to) {
-      switch (to) {
-        case 'inactive':
-          break;
-        case 'candidate':
-          if (from !== 'inactive') {
-            // Removes the "contenteditable" attribute.
-            this.disable();
-          }
-          break;
-        case 'highlighted':
-          break;
-        case 'activating':
-          this.options.activated();
-          break;
-        case 'active':
-          // Sets the "contenteditable" attribute to "true".
-          this.enable();
-          break;
-        case 'changed':
-          break;
-        case 'saving':
-          break;
-        case 'saved':
-          break;
-        case 'invalid':
-          break;
-      }
-    }
-
-  });
-
-})(jQuery, Drupal);
diff --git a/core/modules/edit/js/createjs/editingWidgets/formwidget.js b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
deleted file mode 100644
index aa2dd0a..0000000
--- a/core/modules/edit/js/createjs/editingWidgets/formwidget.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * @file
- * Form-based Create.js widget for structured content in Drupal.
- */
-(function ($, Drupal) {
-
-"use strict";
-
-  // @todo D8: change the name to "form" + use jQuery UI Widget bridging.
-  // @see http://drupal.org/node/1874934#comment-7124904
-  $.widget('Midgard.formEditEditor', $.Midgard.editWidget, {
-
-    id: null,
-    $formContainer: null,
-
-    /**
-     * Implements getEditUISettings() method.
-     */
-    getEditUISettings: function() {
-      return { padding: false, unifiedToolbar: false, fullWidthToolbar: false };
-    },
-
-    /**
-     * Implements jQuery UI widget factory's _init() method.
-     *
-     * @todo: POSTPONED_ON(Create.js, https://github.com/bergie/create/issues/142)
-     * Get rid of this once that issue is solved.
-     */
-    _init: function() {},
-
-    /**
-     * Implements Create's _initialize() method.
-     */
-    _initialize: function() {},
-
-    /**
-     * Makes this PropertyEditor widget react to state changes.
-     */
-    stateChange: function(from, to) {
-      switch (to) {
-        case 'inactive':
-          break;
-        case 'candidate':
-          if (from !== 'inactive') {
-            this.disable();
-          }
-          break;
-        case 'highlighted':
-          break;
-        case 'activating':
-          this.enable();
-          break;
-        case 'active':
-          break;
-        case 'changed':
-          break;
-        case 'saving':
-          break;
-        case 'saved':
-          break;
-        case 'invalid':
-          break;
-      }
-    },
-
-    /**
-     * Enables the widget.
-     */
-    enable: function () {
-      var $editorElement = $(this.options.widget.element);
-      var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
-
-      // Generate a DOM-compatible ID for the form container DOM element.
-      this.id = 'edit-form-for-' + propertyID.replace(/\//g, '_');
-
-      // Render form container.
-      this.$formContainer = $(Drupal.theme('editFormContainer', {
-        id: this.id,
-        loadingMsg: Drupal.t('Loading…')}
-      ));
-      this.$formContainer
-        .find('.edit-form')
-        .addClass('edit-editable edit-highlighted edit-editing')
-        .attr('role', 'dialog');
-
-      // Insert form container in DOM.
-      if ($editorElement.css('display') === 'inline') {
-        // @todo: POSTPONED_ON(Drupal core, title/author/date as Entity Properties)
-        // This is untested in Drupal 8, because in Drupal 8 we don't yet
-        // have the ability to edit the node title/author/date, because they
-        // haven't been converted into Entity Properties yet, and they're the
-        // only examples in core of "display: inline" properties.
-        this.$formContainer.prependTo($editorElement.offsetParent());
-
-        var pos = $editorElement.position();
-        this.$formContainer.css('left', pos.left).css('top', pos.top);
-      }
-      else {
-        this.$formContainer.insertBefore($editorElement);
-      }
-
-      // Load form, insert it into the form container and attach event handlers.
-      var widget = this;
-      var formOptions = {
-        propertyID: propertyID,
-        $editorElement: $editorElement,
-        nocssjs: false
-      };
-      Drupal.edit.util.form.load(formOptions, function(form, ajax) {
-        Drupal.ajax.prototype.commands.insert(ajax, {
-          data: form,
-          selector: '#' + widget.id + ' .placeholder'
-        });
-
-        var $submit = widget.$formContainer.find('.edit-form-submit');
-        Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
-        widget.$formContainer
-          .on('formUpdated.edit', ':input', function () {
-            // Sets the state to 'changed'.
-            widget.options.changed();
-          })
-          .on('keypress.edit', 'input', function (event) {
-            if (event.keyCode === 13) {
-              return false;
-            }
-          });
-
-        // Sets the state to 'activated'.
-        widget.options.activated();
-      });
-    },
-
-    /**
-     * Disables the widget.
-     */
-    disable: function () {
-      if (this.$formContainer === null) {
-        return;
-      }
-
-      Drupal.edit.util.form.unajaxifySaving(this.$formContainer.find('.edit-form-submit'));
-      // Allow form widgets to detach properly.
-      Drupal.detachBehaviors(this.$formContainer, null, 'unload');
-      this.$formContainer
-        .off('change.edit', ':input')
-        .off('keypress.edit', 'input')
-        .remove();
-      this.$formContainer = null;
-    }
-  });
-
-})(jQuery, Drupal);
diff --git a/core/modules/edit/js/createjs/storage.js b/core/modules/edit/js/createjs/storage.js
deleted file mode 100644
index 580ff82..0000000
--- a/core/modules/edit/js/createjs/storage.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
- * @file
- * Subclasses jQuery.Midgard.midgardStorage to have consistent namespaces.
- */
-(function(jQuery) {
-
-"use strict";
-
-  jQuery.widget('Drupal.createStorage', jQuery.Midgard.midgardStorage, {});
-
-})(jQuery);
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
index f924e7b..52088ec 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -13,29 +13,58 @@ Drupal.edit.metadataCache = Drupal.edit.metadataCache || {};
  * Attach toggling behavior and in-place editing.
  */
 Drupal.behaviors.edit = {
+
+  views: {
+    contextualLinks: {},
+    decorators: {},
+    editables: {},
+    entities: {},
+    propertyEditors: {},
+    toolbars: {
+      entities: {},
+      fields: {}
+    }
+  },
+
+  collections: {
+    entities: null,
+    fields: null
+  },
+
   attach: function(context) {
+    var that = this;
     var $context = $(context);
     var $fields = $context.find('[data-edit-id]');
+    var options = $.extend({}, this.defaults, (drupalSettings.edit || {}));
+    var Collection;
 
-    // Initialize the Edit app.
-    $('body').once('edit-init', Drupal.edit.init);
+    // A collection of all in-place editable entities on the page.
+    if (!this.collections.entities) {
+      Collection = Backbone.Collection.extend({
+        model: Drupal.edit.EntityModel
+      });
+      this.collections.entities = new Collection();
+    }
 
-    var annotateField = function(field) {
-      if (_.has(Drupal.edit.metadataCache, field.editID)) {
-        var meta = Drupal.edit.metadataCache[field.editID];
+    // A collection of all in-place editable fields on the page.
+    if (!this.collections.fields) {
+      this.collections.fields = new Drupal.edit.FieldCollection();
+    }
 
-        field.$el.addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed');
-        if (meta.access) {
-          field.$el
-            .attr('data-edit-field-label', meta.label)
-            .attr('aria-label', meta.aria)
-            .addClass('edit-field edit-type-' + ((meta.editor === 'form') ? 'form' : 'direct'));
-        }
+    // Respond to entity model change events.
+    this.collections.entities
+      .on('change:isActive', this.enforceSingleActiveEntity, this);
 
-        return true;
-      }
-      return false;
-    };
+    this.collections.fields
+      // Respond to field model HTML representation change events.
+      .on('change:html', this.updateAllKnownInstances, this);
+
+    // Initialize the Edit app.
+    $('body').once('edit-init', $.proxy(this.init, this, options));
+
+    // @todo currently must be after the call to init, because the app needs to be initialized
+    this.collections.fields
+      .on('change:state', Drupal.edit.app.editorStateChange, Drupal.edit.app);
 
     // Find all fields in the context without metadata.
     var fieldsToAnnotate = _.map($fields.not('.edit-allowed, .edit-disallowed'), function(el) {
@@ -46,14 +75,15 @@ Drupal.behaviors.edit = {
     // Fields whose metadata is known (typically when they were just modified)
     // can be annotated immediately, those remaining must be requested.
     var remainingFieldsToAnnotate = _.reduce(fieldsToAnnotate, function(result, field) {
-      if (!annotateField(field)) {
+      var isAnnotated = $.proxy(that.annotateField, that, field)();
+      if (!isAnnotated) {
         result.push(field);
       }
       return result;
     }, []);
 
     // Make fields that could be annotated immediately available for editing.
-    Drupal.edit.app.findEditableProperties($context);
+    this.findEditableProperties($context, options);
 
     if (remainingFieldsToAnnotate.length) {
       $(window).ready(function() {
@@ -69,45 +99,189 @@ Drupal.behaviors.edit = {
             });
 
             // Annotate the remaining fields based on the updated access cache.
-            _.each(remainingFieldsToAnnotate, annotateField);
+            _.each(remainingFieldsToAnnotate, $.proxy(that.annotateField, that));
 
-            // Find editable fields, make them editable.
-            Drupal.edit.app.findEditableProperties($context);
+            // Make fields that could be annotated immediately available for editing.
+            that.findEditableProperties($context, options);
           }
         });
       });
     }
-  }
-};
+  },
 
-Drupal.edit.init = function() {
-  // Instantiate EditAppView, which is the controller of it all. EditAppModel
-  // instance tracks global state (viewing/editing in-place).
-  var appModel = new Drupal.edit.models.EditAppModel();
-  var app = new Drupal.edit.EditAppView({
-    el: $('body'),
-    model: appModel
-  });
-
-  // Add "Quick edit" links to all contextual menus where editing the full
-  // node is possible.
-  // @todo Generalize this to work for all entities.
-  $('ul.contextual-links li.node-edit')
-  .before('<li class="quick-edit"></li>')
-  .each(function() {
-    // Instantiate ContextualLinkView.
-    var $editContextualLink = $(this).prev();
-    var editContextualLinkView = new Drupal.edit.views.ContextualLinkView({
-      el: $editContextualLink.get(0),
+  init: function (options) {
+    var that = this;
+    // Instantiate EditAppView, which is the controller of it all. EditAppModel
+    // instance tracks global state (viewing/editing in-place).
+    var appModel = new Drupal.edit.AppModel();
+    var app = new Drupal.edit.AppView({
+      el: $('body').get(0),
       model: appModel,
-      entity: $editContextualLink.parents('[data-edit-entity]').attr('data-edit-entity')
+      entitiesCollection: this.collections.entities
     });
-  });
+    var entityModel;
+
+    // Create a view for the Entity just once.
+    $('[data-edit-entity]').once('editEntity', function (index) {
+      var $this = $(this);
+      var id = $this.data('edit-entity');
+
+      entityModel = new Drupal.edit.EntityModel({
+        id: id
+      });
+      that.collections.entities.add(entityModel);
+
+      // Create a Toolbar for the entity.
+      // @todo, the toolbar should really be create when quick edit is launched
+      // for an entity, not up front for all of them.
+      that.views.toolbars.entities[id] = new Drupal.edit.EntityToolbarView({
+        el: this,
+        model: entityModel
+      });
+
+      // Create a view for the contextual links.
+      $this.find('.contextual-links')
+        .each(function () {
+          // Instantiate ContextualLinkView.
+          that.views.contextualLinks[id] = new Drupal.edit.ContextualLinkView($.extend({
+            el: this,
+            model: entityModel,
+            appModel: appModel
+          }, options));
+        });
+    });
+
+    // Add "Quick edit" links to all contextual menus where editing the full
+    // node is possible.
+    // @todo Generalize this to work for all entities.
+
+
+    // For now, we work with a singleton app, because for Drupal.behaviors to be
+    // able to discover new editable properties that get AJAXed in, it must know
+    // with which app instance they should be associated.
+    Drupal.edit.app = app;
+  },
+
+  /**
+   * Finds editable properties within a given context.
+   *
+   * Finds editable properties, registers them with the app, updates their
+   * state to match the current app state.
+   *
+   * @param $context
+   *   A jQuery-wrapped context DOM element within which will be searched.
+   */
+  findEditableProperties: function($context) {
+    var that = this;
+    // Retrieve the active entity, of which there can only ever be one.
+    var activeEntity = this.collections.entities.where({ isActive: true })[0];
 
-  // For now, we work with a singleton app, because for Drupal.behaviors to be
-  // able to discover new editable properties that get AJAXed in, it must know
-  // with which app instance they should be associated.
-  Drupal.edit.app = app;
+    $context.find('[data-edit-id]').each(function() {
+      var $element = $(this);
+      var editID = $element.attr('data-edit-id');
+
+      if (!_.has(Drupal.edit.metadataCache, editID)) {
+        return;
+      }
+
+      var entityId = $element.closest('[data-edit-entity]').data('edit-entity');
+
+      // Early-return when no surrounding [data-edit-entity] is found.
+      // @todo This breaks down in weird cases like full node views, where the
+      // node title is not rendered within the node DOM, but as the *page* title
+      if (entityId === null) {
+        return;
+      }
+
+      // @todo whether the "Quick Edit"
+      // link should appear depends on whether the user has access to edit any
+      // of the entity's fields. So, it is blocked on the metadata callback to
+      // the server. This is not yet implemented in the D8 HEAD Edit, but it is
+      // in http://drupal.org/node/1971108. We should take this into account if
+      // possible. If not, then we'll just have to refactor later.
+      // For now, this assumption is acceptable.
+      var entity = that.collections.entities.where({ id: entityId })[0];
+
+      // The FieldModel stores the state of an in-place editable entity field.
+      var field = new Drupal.edit.FieldModel({
+        entity: entity,
+        $el: $element,
+        // Store the field in a collection in its entity's model.
+        collection: entity.get('fields'),
+        editID: editID,
+        label: Drupal.edit.metadataCache[editID].label,
+        editor: Drupal.edit.metadataCache[editID].editor,
+        html: $element[0].outerHTML,
+        acceptStateChange: _.bind(Drupal.edit.app.acceptEditorStateChange, Drupal.edit.app)
+      });
+
+      // Track all fields on the page.
+      that.collections.fields.add(field);
+
+      // If the new in-place editable field is for the entity that's currently
+      // being edited, then transition it to the 'candidate' state.
+      // (This happens when a field was modified and is re-rendered.)
+      if (entity === activeEntity) {
+        field.set('state', 'candidate');
+      }
+    });
+  },
+
+  annotateField: function (field) {
+    if (_.has(Drupal.edit.metadataCache, field.editID)) {
+      var meta = Drupal.edit.metadataCache[field.editID];
+
+      field.$el.addClass((meta.access) ? 'edit-allowed' : 'edit-disallowed');
+      if (meta.access) {
+        field.$el
+          .attr('data-edit-field-label', meta.label)
+          .attr('aria-label', meta.aria)
+          .addClass('edit-field edit-type-' + ((meta.editor === 'form') ? 'form' : 'direct'));
+      }
+
+      return true;
+    }
+    return false;
+  },
+
+  /**
+   * EntityModel Collection change handler, called on change:isActive, enforces
+   * a single active entity.
+   */
+  enforceSingleActiveEntity: function (changedEntityModel) {
+    // When an entity is deactivated, we don't need to enforce anything.
+    if (changedEntityModel.get('isActive') === false) {
+      return;
+    }
+
+    // This entity was activated; deactivate all other entities.
+    changedEntityModel.collection.chain()
+      .filter(function (entityModel) {
+        return entityModel.get('isActive') === true && entityModel !== changedEntityModel;
+      })
+      .each(function (entityModel) {
+        entityModel.set('isActive', false);
+      });
+  },
+
+  // Updates all known instances of a specific entity field whenever the HTML
+  // representation of one of them has changed.
+  // @todo: this is currently prototype-level code, test this. The principle is
+  // sound, but the tricky thing is that an editID includes the view mode, but
+  // we actually want to update the same field in other view modes too, which
+  // means this method will have to check if there are any such instances, and
+  // if so, go to the server and re-render those too.
+  updateAllKnownInstances: function (changedModel) {
+    changedModel.collection.where({ editID: changedModel.get('editID') })
+      .set('html', changedModel.get('html'));
+  },
+
+  defaults: {
+    strings: {
+      quickEdit: Drupal.t('Quick edit'),
+      stopQuickEdit: Drupal.t('Stop quick edit')
+    }
+  }
 };
 
 })(jQuery, _, Backbone, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/editors/directEditor.js b/core/modules/edit/js/editors/directEditor.js
new file mode 100644
index 0000000..4864a4b
--- /dev/null
+++ b/core/modules/edit/js/editors/directEditor.js
@@ -0,0 +1,89 @@
+/**
+ * @file
+ * Override of Create.js' default "base" (plain contentEditable) widget.
+ */
+(function ($, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.editors = Drupal.edit.editors || {};
+
+Drupal.edit.editors.direct = Backbone.View.extend({
+
+  /**
+   * Implements getEditUISettings() method.
+   */
+  getEditUISettings: function () {
+    return { padding: true, unifiedToolbar: false, fullWidthToolbar: false };
+  },
+
+  /**
+   * Implements Backbone.View.prototype.initialize().
+   */
+  initialize: function () {
+    var that = this;
+
+    // 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');
+
+        // @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);
+      }
+    });
+  },
+
+  /**
+   * 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.
+   */
+  stateChange: function (model, state) {
+    var from = model.previous('state');
+    var to = state;
+    switch (to) {
+      case 'inactive':
+        break;
+      case 'candidate':
+        if (from !== 'inactive') {
+          // Removes the "contenteditable" attribute.
+          this.disable();
+        }
+        break;
+      case 'highlighted':
+        break;
+      case 'activating':
+        // As soon as the current state change has propagated, apply this one.
+        // @see http://jsfiddle.net/5MVzp/2/ vs. http://jsfiddle.net/5MVzp/3/
+        var that = this;
+        _.defer(function() {
+          that.model.set('state', 'active');
+        });
+        break;
+      case 'active':
+        // Sets the "contenteditable" attribute to "true".
+        this.enable();
+        break;
+      case 'changed':
+        break;
+      case 'saving':
+        this.save();
+        break;
+      case 'saved':
+        break;
+      case 'invalid':
+        break;
+    }
+  }
+});
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/editors/formEditor.js b/core/modules/edit/js/editors/formEditor.js
new file mode 100644
index 0000000..9625230
--- /dev/null
+++ b/core/modules/edit/js/editors/formEditor.js
@@ -0,0 +1,220 @@
+/**
+ * @file
+ * Form-based Create.js widget for structured content in Drupal.
+ */
+(function ($, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+Drupal.edit.editors = Drupal.edit.editors || {};
+
+Drupal.edit.editors.form = Backbone.View.extend({
+  id: null,
+  $formContainer: null,
+
+  /**
+   * Implements getEditUISettings() method.
+   */
+  getEditUISettings: function () {
+    return { padding: false, unifiedToolbar: false, fullWidthToolbar: false };
+  },
+
+  /**
+   * Implements Backbone.View.prototype.initialize().
+   */
+  initialize: function (options) {
+    this.model.on('change:state', this.stateChange, this);
+
+    // Generate a DOM-compatible ID for the form container DOM element.
+    this.elementId = 'edit-form-for-' + this.model.get('editID').replace(/\//g, '_');
+  },
+
+  /**
+   * Enables the widget.
+   */
+  enable: function () {
+    // Render form container.
+    this.$formContainer = $(Drupal.theme('editFormContainer', {
+      id: this.elementId,
+      loadingMsg: Drupal.t('Loading…')}
+    ));
+    this.$formContainer
+      .find('.edit-form')
+      .addClass('edit-editable edit-highlighted edit-editing')
+      .attr('role', 'dialog');
+
+    // Insert form container in DOM.
+    if (this.$el.css('display') === 'inline') {
+      this.$formContainer.prependTo(this.$el.offsetParent());
+      // Position the form container to render on top of the field's element.
+      var pos = this.$el.position();
+      this.$formContainer.css('left', pos.left).css('top', pos.top);
+    }
+    else {
+      this.$formContainer.insertBefore(this.$el);
+    }
+
+    // Load form, insert it into the form container and attach event handlers.
+    var that = this;
+    var formOptions = {
+      propertyID: this.model.get('editID'),
+      $editorElement: this.$el,
+      nocssjs: false
+    };
+    Drupal.edit.util.form.load(formOptions, function(form, ajax) {
+      Drupal.ajax.prototype.commands.insert(ajax, {
+        data: form,
+        selector: '#' + that.elementId + ' .placeholder'
+      });
+
+      var $submit = that.$formContainer.find('.edit-form-submit');
+      Drupal.edit.util.form.ajaxifySaving(formOptions, $submit);
+      that.$formContainer
+        .on('formUpdated.edit', ':input', function () {
+          // Sets the state to 'changed'.
+          that.model.set('state', 'changed');
+        })
+        .on('keypress.edit', 'input', function (event) {
+          if (event.keyCode === 13) {
+            return false;
+          }
+        });
+
+      // The in-place editor has loaded; change state to 'active'.
+      that.model.set('state', 'active');
+    });
+  },
+
+  /**
+   * Disables the widget.
+   */
+  disable: function () {
+    if (this.$formContainer === null) {
+      return;
+    }
+
+    Drupal.edit.util.form.unajaxifySaving(this.$formContainer.find('.edit-form-submit'));
+    // Allow form widgets to detach properly.
+    Drupal.detachBehaviors(this.$formContainer, null, 'unload');
+    this.$formContainer
+      .off('change.edit', ':input')
+      .off('keypress.edit', 'input')
+      .remove();
+    this.$formContainer = null;
+  },
+
+  /**
+   * Saves a property.
+   *
+   * This method deals with the complexity of the editor-dependent ways of
+   * inserting updated content and showing validation error messages.
+   *
+   * One might argue that this does not belong in a view. However, there is no
+   * actual "save" logic here, that lives in Backbone.sync. This is just some
+   * glue code, along with the logic for inserting updated content as well as
+   * showing validation error messages, the latter of which is certainly okay.
+   */
+  save: function () {
+    var that = this;
+    var editor = this;
+    var editableEntity = this.el;
+    var entity = this.entity;
+    var predicate = this.predicate;
+
+    // Use Create.js' Storage widget to handle saving. (Uses Backbone.sync.)
+    var storage = new Drupal.edit.Storage({
+      vie: that.vie,
+      editableNs: 'createeditable',
+      element: this.$el
+    });
+    storage.invoke('saveRemote', entity, {
+      editor: that,
+
+      // Successfully saved without validation errors.
+      success: function (model) {
+        //editableEntity.setState('saved', predicate);
+
+        // 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 = entity.get(predicate + '/rendered');
+        var $propertyWrapper = editor.$el.closest('.edit-field');
+        var $context = $propertyWrapper.parent();
+
+        //editableEntity.setState('candidate', predicate);
+        // Unset the property, because it will be parsed again from the DOM, iff
+        // its new value causes it to still be rendered.
+        entity.unset(predicate, { silent: true });
+        entity.unset(predicate + '/rendered', { silent: true });
+        // Trigger event to allow for proper clean-up of editor-specific views.
+        editor.$el.trigger('destroyedPropertyEditor.edit', editor);
+
+        // Replace the old content with the new content.
+        $propertyWrapper.replaceWith(updatedProperty);
+        Drupal.attachBehaviors($context);
+      },
+
+      // Save attempted but failed due to validation errors.
+      error: function (validationErrorMessages) {
+        editableEntity.setState('invalid', predicate);
+
+        if (that.editorName === 'form') {
+          editor.$formContainer
+            .find('.edit-form')
+            .addClass('edit-validation-error')
+            .find('form')
+            .prepend(validationErrorMessages);
+        }
+        else {
+          var $errors = $('<div class="edit-validation-errors"></div>')
+            .append(validationErrorMessages);
+          editor.element
+            .addClass('edit-validation-error')
+            .after($errors);
+        }
+      }
+    });
+  },
+
+  /**
+   * 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.
+   */
+  stateChange: function (model, state) {
+    var from = model.previous('state');
+    var to = state;
+    switch (to) {
+      case 'inactive':
+        break;
+      case 'candidate':
+        if (from !== 'inactive') {
+          this.disable();
+        }
+        break;
+      case 'highlighted':
+        break;
+      case 'activating':
+        this.enable();
+        break;
+      case 'active':
+        break;
+      case 'changed':
+        break;
+      case 'saving':
+        this.save();
+        break;
+      case 'saved':
+        break;
+      case 'invalid':
+        break;
+    }
+  }
+});
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/models/AppModel.js b/core/modules/edit/js/models/AppModel.js
new file mode 100644
index 0000000..402d0f7
--- /dev/null
+++ b/core/modules/edit/js/models/AppModel.js
@@ -0,0 +1,23 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+  /**
+   *
+   */
+  AppModel: Backbone.Model.extend({
+    defaults: {
+      activeEntity: null,
+      highlightedEditor: null,
+      activeEditor: null,
+      // Reference to a ModalView-instance if a state change requires confirmation.
+      activeModal: null
+    }
+  })
+});
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/models/EntityModel.js b/core/modules/edit/js/models/EntityModel.js
new file mode 100644
index 0000000..a527a0c
--- /dev/null
+++ b/core/modules/edit/js/models/EntityModel.js
@@ -0,0 +1,74 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+  /**
+   *
+   */
+  EntityModel: Backbone.Model.extend({
+    defaults: {
+      // An entity ID, of the form "<entity type>/<entity ID>", e.g. "node/1".
+      id: null,
+      // Indicates whether this instance of this entity is currently being
+      // edited.
+      isActive: false,
+      // A Drupal.edit.FieldCollection for all fields of this entity.
+      fields: null,
+      // The model for the currently active field. The default is a stub object
+      // with an 'on' method so that it can be result to the default value
+      // when no fields are active on this entity without breaking listener
+      // attachment calls to this property's object.
+      fieldModel: (function () {
+        return {
+          on: function () {},
+          off: function () {}
+        };
+      }())
+    },
+
+    /**
+     *
+     */
+    initialize: function () {
+      this.set('fields', new Drupal.edit.FieldCollection());
+
+      this.get('fields').on('change:state', this.stateChange, this);
+    },
+
+    /**
+     * Listens to FieldModel editor state changes.
+     *
+     * @param Drupal.edit.FieldModel model
+     * @param String state
+     *   The state of an editable element. Used to determine display and behavior.
+     */
+    stateChange: function (model, state) {
+      var from = model.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          this.set('fieldModel', this.defaults.fieldModel);
+          break;
+        case 'candidate':
+        case 'highlighted':
+          break;
+        case 'activating':
+          this.set('fieldModel', model);
+          break;
+        case 'active':
+        case 'changed':
+        case 'saving':
+        case 'saved':
+        case 'invalid':
+        default:
+          break;
+      }
+    }
+  })
+});
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/models/FieldModel.js b/core/modules/edit/js/models/FieldModel.js
new file mode 100644
index 0000000..7703d2f
--- /dev/null
+++ b/core/modules/edit/js/models/FieldModel.js
@@ -0,0 +1,119 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+  /**
+   *
+   */
+  FieldModel: Backbone.Model.extend({
+    defaults: {
+      // @todo Try to get rid of this, but it's hard; see appStateChange()
+      $el: null,
+
+      // Possible states:
+      //  - inactive
+      //  - candidate
+      //  - highlighted
+      //  - activating
+      //  - active
+      //  - changed
+      //  - saving
+      //  - saved
+      //  - saved
+      //  - invalid
+      //  @see http://createjs.org/guide/#states
+      state: 'inactive',
+      // A Drupal.edit.EntityModel. Its "fields" attribute, which is a
+      // FieldCollection, is automatically updated to include this FieldModel.
+      entity: null,
+      // A place to store any decoration views.
+      decorationViews: {},
+
+
+      //
+      // Data set by the metadata callback.
+      //
+      // The ID of the in-place editor to use.
+      editor: null,
+      // The label to use.
+      label: null,
+
+      //
+      // Data derived from the information in the DOM.
+      //
+
+      // The edit ID, format: `<entity type>:<id>:<field name>:<language code>:<view mode>`.
+      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
+      // this field instance to other instances of the same field.
+      html: null,
+
+      //
+      // Callbacks.
+      //
+
+      // Callback function for validating changes between states. Receives the
+      // previous state, new state, context, and a callback
+      acceptStateChange: null
+    },
+    initialize: function () {
+      this.get('entity').get('fields').add(this);
+      this.on('change:state', function(model, state) {
+        console.log('%c [MOD] ' + this.previous('state') + ' → ' + state + '    ' + model.get('editID'), 'background-color: blue; color: white');
+      });
+      this.on('error', function(model, error) {
+          console.log('%c' + model.get("editID") + " " + error, 'color: orange');
+      });
+    },
+    validate: function (attrs, options) {
+      // We only care about validating the 'state' attribute.
+      if (!_.has(attrs, 'state')) {
+        return;
+      }
+
+      var current = this.get('state');
+      var next = attrs.state;
+      if (current !== next) {
+        // Ensure it's a valid state.
+        if (_.indexOf(this.constructor.states, next) === -1) {
+          return '"' + next + '" is an invalid state';
+        }
+        // Check if the acceptStateChange callback accepts it.
+        if (!this.get('acceptStateChange')(current, next, options)) {
+          return 'state change not accepted';
+        }
+      }
+    },
+    // @see VIE.prototype.EditService.prototype.getElementSubject()
+    // Parses the `<entity type>/<id>` part from the edit ID.
+    getEntityID: function() {
+      return this.get('editID').split('/').slice(0, 2).join('/');
+    },
+    // @see VIE.prototype.EditService.prototype.getElementPredicate()
+    // Parses the `<field name>/<language code>/<view mode>` part from the edit ID.
+    getFieldID: function() {
+      return this.get('editID').split('/').slice(2, 5).join('/');
+    }
+  }, {
+    states: [
+        'inactive', 'candidate', 'highlighted',
+        'activating', 'active', 'changed', 'saving', 'saved', 'invalid'
+    ],
+    followsStateSequence: function (from, to) {
+      return _.indexOf(this.states, from) < _.indexOf(this.states, to);
+    }
+  }),
+
+  // A collection of FieldModels.
+  // @todo link back to the entity at the collection level (not the collection element level)?
+  FieldCollection: Backbone.Collection.extend({
+    model: Drupal.edit.FieldModel
+  })
+});
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/models/edit-app-model.js b/core/modules/edit/js/models/edit-app-model.js
deleted file mode 100644
index 0c90fd0..0000000
--- a/core/modules/edit/js/models/edit-app-model.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/**
- * @file
- * A Backbone Model that models the current Edit application state.
- */
-(function(Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.models = Drupal.edit.models || {};
-Drupal.edit.models.EditAppModel = Backbone.Model.extend({
-  defaults: {
-    activeEntity: null,
-    highlightedEditor: null,
-    activeEditor: null,
-    // Reference to a ModalView-instance if a transition requires confirmation.
-    activeModal: null
-  }
-});
-
-})(Backbone, Drupal);
diff --git a/core/modules/edit/js/storage.js b/core/modules/edit/js/storage.js
new file mode 100644
index 0000000..196337e
--- /dev/null
+++ b/core/modules/edit/js/storage.js
@@ -0,0 +1,518 @@
+/**
+ * @file
+ * Subclasses jQuery.Midgard.midgardStorage to have consistent namespaces.
+ */
+(function($, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+Drupal.edit.Storage = function (options) {
+  $.extend(this, {
+    saveEnabled: true,
+    // VIE instance to use for storage handling
+    vie: null,
+    // Whether to use localstorage
+    localStorage: false,
+    removeLocalstorageOnIgnore: true,
+    // URL callback for Backbone.sync
+    url: '',
+    // Whether to enable automatic saving
+    autoSave: false,
+    // How often to autosave in milliseconds
+    autoSaveInterval: 5000,
+    // Whether to save entities that are referenced by entities
+    // we're saving to the server.
+    saveReferencedNew: false,
+    saveReferencedChanged: false,
+    // Namespace used for events from midgardEditable-derived widget
+    editableNs: 'midgardeditable',
+    // CSS selector for the Edit button, leave to null to not bind
+    // notifications to any element
+    editSelector: '#midgardcreate-edit a',
+    localize: function (id, language) {
+      return window.midgardCreate.localize(id, language);
+    },
+    language: null
+  }, options);
+  this._create();
+};
+
+$.extend(Drupal.edit.Storage.prototype, {
+
+  invoke: function(method) {
+    var args = Array.prototype.slice.call(arguments, 1);
+    if (method in this && typeof this[method] === 'function') {
+      return this[method].apply(this, args);
+    }
+  },
+  _create: function () {
+    var widget = this;
+    this.changedModels = [];
+
+    if (window.localStorage) {
+      this.localStorage = true;
+    }
+
+    this.vie.entities.on('add', function (model) {
+      // Add the back-end URL used by Backbone.sync
+      model.url = widget.url;
+      model.toJSON = model.toJSONLD;
+    });
+
+    widget._bindEditables();
+    if (widget.autoSave) {
+      widget._autoSave();
+    }
+  },
+
+  _autoSave: function () {
+    var widget = this;
+    widget.saveEnabled = true;
+
+    var doAutoSave = function () {
+      if (!widget.saveEnabled) {
+        return;
+      }
+
+      if (widget.changedModels.length === 0) {
+        return;
+      }
+
+      widget.saveRemoteAll({
+        // We make autosaves silent so that potential changes from server
+        // don't disrupt user while writing.
+        silent: true
+      });
+    };
+
+    var timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
+
+    this.element.on('startPreventSave', function () {
+      if (timeout) {
+        window.clearInterval(timeout);
+        timeout = null;
+      }
+      widget.disableAutoSave();
+    });
+    this.element.on('stopPreventSave', function () {
+      if (!timeout) {
+        timeout = window.setInterval(doAutoSave, widget.options.autoSaveInterval);
+      }
+      widget.enableAutoSave();
+    });
+
+  },
+
+  enableAutoSave: function () {
+    this.saveEnabled = true;
+  },
+
+  disableAutoSave: function () {
+    this.saveEnabled = false;
+  },
+
+  _bindEditables: function () {
+    var widget = this;
+    this.restorables = [];
+    var restorer;
+
+    widget.element.on(widget.editableNs + 'changed', function (event, options) {
+      if (_.indexOf(widget.changedModels, options.instance) === -1) {
+        widget.changedModels.push(options.instance);
+      }
+      widget._saveLocal(options.instance);
+    });
+
+    widget.element.on(widget.editableNs + 'disable', function (event, options) {
+      widget.revertChanges(options.instance);
+    });
+
+    widget.element.on(widget.editableNs + 'enable', function (event, options) {
+      if (!options.instance._originalAttributes) {
+        options.instance._originalAttributes = _.clone(options.instance.attributes);
+      }
+
+      if (!options.instance.isNew() && widget._checkLocal(options.instance)) {
+        // We have locally-stored modifications, user needs to be asked
+        widget.restorables.push(options.instance);
+      }
+
+      /*_.each(options.instance.attributes, function (attributeValue, property) {
+        if (attributeValue instanceof widget.vie.Collection) {
+          widget._readLocalReferences(options.instance, property, attributeValue);
+        }
+      });*/
+    });
+
+    widget.element.on('midgardcreatestatechange', function (event, options) {
+      if (options.state === 'browse' || widget.restorables.length === 0) {
+        widget.restorables = [];
+        if (restorer) {
+          restorer.close();
+        }
+        return;
+      }
+      restorer = widget.checkRestore();
+    });
+
+    widget.element.on('midgardstorageloaded', function (event, options) {
+      if (_.indexOf(widget.changedModels, options.instance) === -1) {
+        widget.changedModels.push(options.instance);
+      }
+    });
+  },
+
+  checkRestore: function () {
+    var widget = this;
+    if (widget.restorables.length === 0) {
+      return;
+    }
+
+    var message;
+    var restorer;
+    if (widget.restorables.length === 1) {
+      message = _.template(widget.options.localize('localModification', widget.options.language), {
+        label: widget.restorables[0].getSubjectUri()
+      });
+    } else {
+      message = _.template(widget.options.localize('localModifications', widget.options.language), {
+        number: widget.restorables.length
+      });
+    }
+
+    var doRestore = function (event, notification) {
+      widget.restoreLocalAll();
+      restorer.close();
+    };
+
+    var doIgnore = function (event, notification) {
+      widget.ignoreLocal();
+      restorer.close();
+    };
+
+    restorer = jQuery('body').midgardNotifications('create', {
+      bindTo: widget.options.editSelector,
+      gravity: 'TR',
+      body: message,
+      timeout: 0,
+      actions: [
+        {
+          name: 'restore',
+          label: widget.options.localize('Restore', widget.options.language),
+          cb: doRestore,
+          className: 'create-ui-btn'
+        },
+        {
+          name: 'ignore',
+          label: widget.options.localize('Ignore', widget.options.language),
+          cb: doIgnore,
+          className: 'create-ui-btn'
+        }
+      ],
+      callbacks: {
+        beforeShow: function () {
+          if (!window.Mousetrap) {
+            return;
+          }
+          window.Mousetrap.bind(['command+shift+r', 'ctrl+shift+r'], function (event) {
+            event.preventDefault();
+            doRestore();
+          });
+          window.Mousetrap.bind(['command+shift+i', 'ctrl+shift+i'], function (event) {
+            event.preventDefault();
+            doIgnore();
+          });
+        },
+        afterClose: function () {
+          if (!window.Mousetrap) {
+            return;
+          }
+          window.Mousetrap.unbind(['command+shift+r', 'ctrl+shift+r']);
+          window.Mousetrap.unbind(['command+shift+i', 'ctrl+shift+i']);
+        }
+      }
+    });
+    return restorer;
+  },
+
+  restoreLocalAll: function () {
+    _.each(this.restorables, function (instance) {
+      this.readLocal(instance);
+    }, this);
+    this.restorables = [];
+  },
+
+  ignoreLocal: function () {
+    if (this.options.removeLocalstorageOnIgnore) {
+      _.each(this.restorables, function (instance) {
+        this._removeLocal(instance);
+      }, this);
+    }
+    this.restorables = [];
+  },
+
+  saveReferences: function (model) {
+    _.each(model.attributes, function (value, property) {
+      if (!value || !value.isCollection) {
+        return;
+      }
+
+      value.each(function (referencedModel) {
+        if (this.changedModels.indexOf(referencedModel) !== -1) {
+          // The referenced model is already in the save queue
+          return;
+        }
+
+        if (referencedModel.isNew() && this.options.saveReferencedNew) {
+          return referencedModel.save();
+        }
+
+        if (referencedModel.hasChanged() && this.options.saveReferencedChanged) {
+          return referencedModel.save();
+        }
+      }, this);
+    }, this);
+  },
+
+  saveRemote: function (model, options) {
+    // Optionally handle entities referenced in this model first
+    this.saveReferences(model);
+
+    // this._trigger('saveentity', null, {
+    //   entity: model,
+    //   options: options
+    // });
+
+    var widget = this;
+    model.save(null, _.extend({}, options, {
+      success: function (m, response) {
+        // From now on we're going with the values we have on server
+        model._originalAttributes = _.clone(model.attributes);
+        widget._removeLocal(model);
+        window.setTimeout(function () {
+          // Remove the model from the list of changed models after saving
+          widget.changedModels.splice(widget.changedModels.indexOf(model), 1);
+        }, 0);
+        if (_.isFunction(options.success)) {
+          options.success(m, response);
+        }
+        // widget._trigger('savedentity', null, {
+        //   entity: model,
+        //   options: options
+        // });
+      },
+      error: function (m, response) {
+        if (_.isFunction(options.error)) {
+          options.error(m, response);
+        }
+      }
+    }));
+  },
+
+  saveRemoteAll: function (options) {
+    var widget = this;
+    if (widget.changedModels.length === 0) {
+      return;
+    }
+
+    widget._trigger('save', null, {
+      entities: widget.changedModels,
+      options: options,
+      // Deprecated
+      models: widget.changedModels
+    });
+
+    var notification_msg;
+    var needed = widget.changedModels.length;
+    if (needed > 1) {
+      notification_msg = _.template(widget.options.localize('saveSuccessMultiple', widget.options.language), {
+        number: needed
+      });
+    } else {
+      notification_msg = _.template(widget.options.localize('saveSuccess', widget.options.language), {
+        label: widget.changedModels[0].getSubjectUri()
+      });
+    }
+
+    widget.disableAutoSave();
+    _.each(widget.changedModels, function (model) {
+      this.saveRemote(model, {
+        success: function (m, response) {
+          needed--;
+          if (needed <= 0) {
+            // All models were happily saved
+            widget._trigger('saved', null, {
+              options: options
+            });
+            if (options && _.isFunction(options.success)) {
+              options.success(m, response);
+            }
+            jQuery('body').midgardNotifications('create', {
+              body: notification_msg
+            });
+            widget.enableAutoSave();
+          }
+        },
+        error: function (m, err) {
+          if (options && _.isFunction(options.error)) {
+            options.error(m, err);
+          }
+          jQuery('body').midgardNotifications('create', {
+            body: _.template(widget.options.localize('saveError', widget.options.language), {
+              error: err.responseText || ''
+            }),
+            timeout: 0
+          });
+
+          widget._trigger('error', null, {
+            instance: model
+          });
+        }
+      });
+    }, this);
+  },
+
+  _saveLocal: function (model) {
+    if (!this.options.localStorage) {
+      return;
+    }
+
+    if (model.isNew()) {
+      // Anonymous object, save as refs instead
+      if (!model.primaryCollection) {
+        return;
+      }
+      return this._saveLocalReferences(model.primaryCollection.subject, model.primaryCollection.predicate, model);
+    }
+    window.localStorage.setItem(model.getSubjectUri(), JSON.stringify(model.toJSONLD()));
+  },
+
+  _getReferenceId: function (model, property) {
+    return model.id + ':' + property;
+  },
+
+  _saveLocalReferences: function (subject, predicate, model) {
+    if (!this.options.localStorage) {
+      return;
+    }
+
+    if (!subject || !predicate) {
+      return;
+    }
+
+    var widget = this;
+    var identifier = subject + ':' + predicate;
+    var json = model.toJSONLD();
+    if (window.localStorage.getItem(identifier)) {
+      var referenceList = JSON.parse(window.localStorage.getItem(identifier));
+      var index = _.pluck(referenceList, '@').indexOf(json['@']);
+      if (index !== -1) {
+        referenceList[index] = json;
+      } else {
+        referenceList.push(json);
+      }
+      window.localStorage.setItem(identifier, JSON.stringify(referenceList));
+      return;
+    }
+    window.localStorage.setItem(identifier, JSON.stringify([json]));
+  },
+
+  _checkLocal: function (model) {
+    if (!this.options.localStorage) {
+      return false;
+    }
+
+    var local = window.localStorage.getItem(model.getSubjectUri());
+    if (!local) {
+      return false;
+    }
+
+    return true;
+  },
+
+  hasLocal: function (model) {
+    if (!this.options.localStorage) {
+      return false;
+    }
+
+    if (!window.localStorage.getItem(model.getSubjectUri())) {
+      return false;
+    }
+    return true;
+  },
+
+  readLocal: function (model) {
+    if (!this.options.localStorage) {
+      return;
+    }
+
+    var local = window.localStorage.getItem(model.getSubjectUri());
+    if (!local) {
+      return;
+    }
+    if (!model._originalAttributes) {
+      model._originalAttributes = _.clone(model.attributes);
+    }
+    var parsed = JSON.parse(local);
+    var entity = this.vie.entities.addOrUpdate(parsed, {
+      overrideAttributes: true
+    });
+
+    this._trigger('loaded', null, {
+      instance: entity
+    });
+  },
+
+  _readLocalReferences: function (model, property, collection) {
+    if (!this.options.localStorage) {
+      return;
+    }
+
+    var identifier = this._getReferenceId(model, property);
+    var local = window.localStorage.getItem(identifier);
+    if (!local) {
+      return;
+    }
+    collection.add(JSON.parse(local));
+  },
+
+  revertChanges: function (model) {
+    var widget = this;
+
+    // Remove unsaved collection members
+    if (!model) { return; }
+    _.each(model.attributes, function (attributeValue, property) {
+      if (attributeValue instanceof widget.vie.Collection) {
+        var removables = [];
+        attributeValue.forEach(function (model) {
+          if (model.isNew()) {
+            removables.push(model);
+          }
+        });
+        attributeValue.remove(removables);
+      }
+    });
+
+    // Restore original object properties
+    if (!model.changedAttributes()) {
+      if (model._originalAttributes) {
+        model.set(model._originalAttributes);
+      }
+      return;
+    }
+
+    model.set(model.previousAttributes());
+  },
+
+  _removeLocal: function (model) {
+    if (!this.localStorage) {
+      return;
+    }
+
+    window.localStorage.removeItem(model.getSubjectUri());
+  }
+});
+
+})(jQuery, Drupal);
diff --git a/core/modules/edit/js/theme.js b/core/modules/edit/js/theme.js
index 7bef553..1c86fe4 100644
--- a/core/modules/edit/js/theme.js
+++ b/core/modules/edit/js/theme.js
@@ -75,6 +75,23 @@ Drupal.theme.editToolbarContainer = function(settings) {
 };
 
 /**
+ * Theme function for a toolbar container of the Edit module.
+ *
+ * @param settings
+ *   An object with the following keys:
+ *   - id: the id to apply to the toolbar container.
+ * @return
+ *   The corresponding HTML.
+ */
+Drupal.theme.editEntityToolbarContainer = function(settings) {
+  var html = '';
+  html += '<div id="' + settings.id + '" class="edit-entity-toolbar-container">';
+  html += '<div class="edit-toolbar primary" />';
+  html += '</div>';
+  return html;
+};
+
+/**
  * Theme function for a toolbar toolgroup of the Edit module.
  *
  * @param settings
diff --git a/core/modules/edit/js/viejs/EditService.js b/core/modules/edit/js/viejs/EditService.js
deleted file mode 100644
index 00cb04b..0000000
--- a/core/modules/edit/js/viejs/EditService.js
+++ /dev/null
@@ -1,289 +0,0 @@
-/**
- * @file
- * VIE DOM parsing service for Edit.
- */
-(function(jQuery, _, VIE, Drupal, drupalSettings) {
-
-"use strict";
-
-  VIE.prototype.EditService = function (options) {
-    var defaults = {
-      name: 'edit',
-      subjectSelector: '.edit-field.edit-allowed'
-    };
-    this.options = _.extend({}, defaults, options);
-
-    this.views = [];
-    this.vie = null;
-    this.name = this.options.name;
-  };
-
-  VIE.prototype.EditService.prototype = {
-    load: function (loadable) {
-      var correct = loadable instanceof this.vie.Loadable;
-      if (!correct) {
-        throw new Error('Invalid Loadable passed');
-      }
-
-      var element;
-      if (!loadable.options.element) {
-        if (typeof document === 'undefined') {
-          return loadable.resolve([]);
-        } else {
-          element = drupalSettings.edit.context;
-        }
-      } else {
-        element = loadable.options.element;
-      }
-
-      var entities = this.readEntities(element);
-      loadable.resolve(entities);
-    },
-
-    _getViewForElement:function (element, collectionView) {
-      var viewInstance;
-
-      jQuery.each(this.views, function () {
-        if (jQuery(this.el).get(0) === element.get(0)) {
-          if (collectionView && !this.template) {
-            return true;
-          }
-          viewInstance = this;
-          return false;
-        }
-      });
-      return viewInstance;
-    },
-
-    _registerEntityView:function (entity, element, isNew) {
-      if (!element.length) {
-        return;
-      }
-
-      // Let's only have this overhead for direct types. Form-based editors are
-      // handled in backbone.drupalform.js and the PropertyEditor instance.
-      if (jQuery(element).hasClass('edit-type-form')) {
-        return;
-      }
-
-      var service = this;
-      var viewInstance = this._getViewForElement(element);
-      if (viewInstance) {
-        return viewInstance;
-      }
-
-      viewInstance = new this.vie.view.Entity({
-        model:entity,
-        el:element,
-        tagName:element.get(0).nodeName,
-        vie:this.vie,
-        service:this.name
-      });
-
-      this.views.push(viewInstance);
-
-      return viewInstance;
-    },
-
-    save: function(saveable) {
-      var correct = saveable instanceof this.vie.Savable;
-      if (!correct) {
-        throw "Invalid Savable passed";
-      }
-
-      if (!saveable.options.element) {
-        // FIXME: we could find element based on subject
-        throw "Unable to write entity to edit.module-markup, no element given";
-      }
-
-      if (!saveable.options.entity) {
-        throw "Unable to write to edit.module-markup, no entity given";
-      }
-
-      var $element = jQuery(saveable.options.element);
-      this._writeEntity(saveable.options.entity, saveable.options.element);
-      saveable.resolve();
-    },
-
-    _writeEntity:function (entity, element) {
-      var service = this;
-      this.findPredicateElements(this.getElementSubject(element), element, true).each(function () {
-        var predicateElement = jQuery(this);
-        var predicate = service.getElementPredicate(predicateElement);
-        if (!entity.has(predicate)) {
-          return true;
-        }
-
-        var value = entity.get(predicate);
-        if (value && value.isCollection) {
-          // Handled by CollectionViews separately
-          return true;
-        }
-        if (value === service.readElementValue(predicate, predicateElement)) {
-          return true;
-        }
-        // Unlike in the VIE's RdfaService no (re-)mapping needed here.
-        predicateElement.html(value);
-      });
-      return true;
-    },
-
-    // The edit-id data attribute contains the full identifier of
-    // each entity element in the format
-    // `<entity type>:<id>:<field name>:<language code>:<view mode>`.
-    _getID: function (element) {
-      var id = jQuery(element).attr('data-edit-id');
-      if (!id) {
-        id = jQuery(element).closest('[data-edit-id]').attr('data-edit-id');
-      }
-      return id;
-    },
-
-    // Returns the "URI" of an entity of an element in format
-    // `<entity type>/<id>`.
-    getElementSubject: function (element) {
-      return this._getID(element).split('/').slice(0, 2).join('/');
-    },
-
-    // Returns the field name for an element in format
-    // `<field name>/<language code>/<view mode>`.
-    // (Slashes instead of colons because the field name is no namespace.)
-    getElementPredicate: function (element) {
-      if (!this._getID(element)) {
-        throw new Error('Could not find predicate for element');
-      }
-      return this._getID(element).split('/').slice(2, 5).join('/');
-    },
-
-    getElementType: function (element) {
-      return this._getID(element).split('/').slice(0, 1)[0];
-    },
-
-    // Reads all editable entities (currently each Drupal field is considered an
-    // entity, in the future Drupal entities should be mapped to VIE entities)
-    // from DOM and returns the VIE enties it found.
-    readEntities: function (element) {
-      var service = this;
-      var entities = [];
-      var entityElements = jQuery(this.options.subjectSelector, element);
-      entityElements = entityElements.add(jQuery(element).filter(this.options.subjectSelector));
-      entityElements.each(function () {
-        var entity = service._readEntity(jQuery(this));
-        if (entity) {
-          entities.push(entity);
-        }
-      });
-      return entities;
-    },
-
-    // Returns a filled VIE Entity instance for a DOM element. The Entity
-    // is also registered in the VIE entities collection.
-    _readEntity: function (element) {
-      var subject = this.getElementSubject(element);
-      var type = this.getElementType(element);
-      var entity = this._readEntityPredicates(subject, element, false);
-      if (jQuery.isEmptyObject(entity)) {
-        return null;
-      }
-      entity['@subject'] = subject;
-      if (type) {
-        entity['@type'] = this._registerType(type, element);
-      }
-
-      var entityInstance = new this.vie.Entity(entity);
-      entityInstance = this.vie.entities.addOrUpdate(entityInstance, {
-        updateOptions: {
-          silent: true,
-          ignoreChanges: true
-        }
-      });
-
-      this._registerEntityView(entityInstance, element);
-      return entityInstance;
-    },
-
-    _registerType: function (typeId, element) {
-      typeId = '<http://viejs.org/ns/' + typeId + '>';
-      var type = this.vie.types.get(typeId);
-      if (!type) {
-        this.vie.types.add(typeId, []);
-        type = this.vie.types.get(typeId);
-      }
-
-      var predicate = this.getElementPredicate(element);
-      if (type.attributes.get(predicate)) {
-        return type;
-      }
-      var range = predicate.split('/')[0];
-      type.attributes.add(predicate, [range], 0, 1, {
-        label: element.data('edit-field-label')
-      });
-
-      return type;
-    },
-
-    _readEntityPredicates: function (subject, element, emptyValues) {
-      var entityPredicates = {};
-      var service = this;
-      this.findPredicateElements(subject, element, true).each(function () {
-        var predicateElement = jQuery(this);
-        var predicate = service.getElementPredicate(predicateElement);
-        if (!predicate) {
-          return;
-        }
-        var value = service.readElementValue(predicate, predicateElement);
-        if (value === null && !emptyValues) {
-          return;
-        }
-
-        entityPredicates[predicate] = value;
-        entityPredicates[predicate + '/rendered'] = predicateElement[0].outerHTML;
-      });
-      return entityPredicates;
-    },
-
-    readElementValue : function(predicate, element) {
-      // Unlike in RdfaService there is parsing needed here.
-      if (element.hasClass('edit-type-form')) {
-        return undefined;
-      }
-      else {
-        return jQuery.trim(element.html());
-      }
-    },
-
-    // Subject elements are the DOM elements containing a single or multiple
-    // editable fields.
-    findSubjectElements: function (element) {
-      if (!element) {
-        element = drupalSettings.edit.context;
-      }
-      return jQuery(this.options.subjectSelector, element);
-    },
-
-    // Predicate Elements are the actual DOM elements that users will be able
-    // to edit.
-    findPredicateElements: function (subject, element, allowNestedPredicates, stop) {
-      var predicates = jQuery();
-      // Make sure that element is wrapped by jQuery.
-      var $element = jQuery(element);
-
-      // Form-type predicates
-      predicates = predicates.add($element.filter('.edit-type-form'));
-
-      // Direct-type predicates
-      var direct = $element.filter('.edit-type-direct');
-      predicates = predicates.add(direct.find('.field-item'));
-
-      if (!predicates.length && !stop) {
-        var parentElement = $element.parent(this.options.subjectSelector);
-        if (parentElement.length) {
-          return this.findPredicateElements(subject, parentElement, allowNestedPredicates, true);
-        }
-      }
-
-      return predicates;
-    }
-  };
-
-})(jQuery, _, VIE, Drupal, drupalSettings);
diff --git a/core/modules/edit/js/views/AppView.js b/core/modules/edit/js/views/AppView.js
new file mode 100644
index 0000000..900e910
--- /dev/null
+++ b/core/modules/edit/js/views/AppView.js
@@ -0,0 +1,292 @@
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit = Drupal.edit || {};
+
+$.extend(Drupal.edit, {
+
+  /**
+   *
+   */
+  AppView: Backbone.View.extend({
+
+    // Configuration for state handling.
+    activeEditorStates: [],
+    singleEditorStates: [],
+
+    /**
+     * Implements Backbone Views' initialize() function.
+     */
+    initialize: function (options) {
+      this.entitiesCollection = options.entitiesCollection;
+
+      _.bindAll(this, 'appStateChange', 'acceptEditorStateChange', 'editorStateChange');
+
+      // Instantiate configuration for state handling.
+      // @see Drupal.edit.FieldModel.states
+      this.activeEditorStates = ['activating', 'active'];
+      this.singleEditorStates = _.union(['highlighted'], this.activeEditorStates);
+
+      this.entitiesCollection.on('change:isActive', this.appStateChange, this);
+    },
+
+    /**
+     * Handles setup/teardown and state changes when the active entity changes.
+     */
+    appStateChange: function (entityModel, isActive) {
+      var app = this;
+      if (isActive) {
+        // Move all fields of this entity from the 'inactive' state to the
+        // 'candidate' state.
+        entityModel.get('fields').each(function (fieldModel) {
+          // First, set up decoration views.
+          app.decorate(fieldModel);
+          // Second, change the field's state.
+          fieldModel.set('state', 'candidate');
+        });
+      }
+      else {
+        // Move all fields of this entity from whatever state they are in to
+        // the 'inactive' state.
+        entityModel.get('fields').each(function (fieldModel) {
+          // First, change the field's state.
+          fieldModel.set('state', 'inactive', { reason: 'stop' });
+          // Second, tear down decoration views.
+          app.undecorate(fieldModel);
+        });
+      }
+    },
+
+    /**
+     * Accepts or reject editor (PropertyEditor) state changes.
+     *
+     * This is what ensures that the app is in control of what happens.
+     *
+     * @param String from
+     *   The previous state.
+     * @param String to
+     *   The new state.
+     * @param null|Object context
+     *   The context that is trying to trigger the state change.
+     * @param Function callback
+     *   The callback function that should receive the state acceptance result.
+     */
+    acceptEditorStateChange: function(from, to, context, callback) {
+      var accept = true;
+
+      console.log("accept? %s → %s (reason: %s)", from, to, (context && context.reason) ? context.reason : 'NONE');
+
+      // If the app is in view mode, then reject all state changes except for
+      // those to 'inactive'.
+      if (context && context.reason === 'stop') {
+        if (from === 'candidate' && to === 'inactive') {
+          accept = true;
+        }
+      }
+      // Handling of edit mode state changes is more granular.
+      else {
+        // In general, enforce the states sequence. Disallow going back from a
+        // "later" state to an "earlier" state, except in explicitly allowed
+        // cases.
+        if (!Drupal.edit.FieldModel.followsStateSequence(from, to)) {
+          accept = false;
+          // Allow: activating/active -> candidate.
+          // Necessary to stop editing a property.
+          if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: changed/invalid -> candidate.
+          // Necessary to stop editing a property when it is changed or invalid.
+          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: highlighted -> candidate.
+          // Necessary to stop highlighting a property.
+          else if (from === 'highlighted' && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: saved -> candidate.
+          // Necessary when successfully saved a property.
+          else if (from === 'saved' && to === 'candidate') {
+            accept = true;
+          }
+          // Allow: invalid -> saving.
+          // Necessary to be able to save a corrected, invalid property.
+          else if (from === 'invalid' && to === 'saving') {
+            accept = true;
+          }
+        }
+
+        // If it's not against the general principle, then here are more
+        // disallowed cases to check.
+        if (accept) {
+          // Ensure only one editor (field) at a time may be higlighted or active.
+          if (from === 'candidate' && _.indexOf(this.singleEditorStates, to) !== -1) {
+            if (this.model.get('highlightedEditor') || this.model.get('activeEditor')) {
+              accept = false;
+            }
+          }
+          // Reject going from activating/active to candidate because of a
+          // mouseleave.
+          else if (_.indexOf(this.activeEditorStates, from) !== -1 && to === 'candidate') {
+            if (context && context.reason === 'mouseleave') {
+              accept = false;
+            }
+          }
+          // When attempting to stop editing a changed/invalid property, ask for
+          // confirmation.
+          else if ((from === 'changed' || from === 'invalid') && to === 'candidate') {
+            if (context && context.reason === 'mouseleave') {
+              accept = false;
+            }
+            else {
+              // Check whether the transition has been confirmed?
+              if (context && context.confirmed) {
+                accept = true;
+              }
+              // Confirm this transition.
+              else {
+                // Do not accept this change right now, instead open a modal
+                // that will ask the user to confirm his choice.
+                accept = false;
+                // The callback will be called from the helper function.
+                this._confirmStopEditing(callback);
+              }
+            }
+          }
+        }
+      }
+
+      return accept;
+    },
+
+    // @todo rename to decorateField
+    decorate: function (fieldModel) {
+      var editID = fieldModel.get('editID');
+      var $el = fieldModel.get('$el');
+
+      // Create a new Editor.
+      var editorName = fieldModel.get('editor');
+      var editorView = new Drupal.edit.editors[editorName]({
+        el: $el,
+        model: fieldModel
+      });
+
+      // Toolbars are rendered "on-demand" (highlighting or activating).
+      // They are a sibling element before the editor's DOM element.
+      var toolbarView = new Drupal.edit.ToolbarView({
+        model: fieldModel,
+        $field: $el,
+        editorView: editorView
+      });
+
+      // Decorate the editor's DOM element depending on its state.
+      var decorationView = new Drupal.edit.PropertyEditorDecorationView({
+        el: $el,
+        model: fieldModel,
+        editorView: editorView,
+        toolbarId: toolbarView.getId()
+      });
+
+      // Create references in the field model; necessary for undecorate() and
+      // necessary for some EditorView implementations.
+      fieldModel.set('editorView', editorView);
+      fieldModel.set('toolbarView', toolbarView);
+      fieldModel.set('decorationView', decorationView);
+    },
+
+    // @todo rename to undecorateField
+    undecorate: function (fieldModel) {
+      // Unbind event handlers; remove toolbar element; delete toolbar view.
+      var toolbarView = fieldModel.get('toolbarView');
+      toolbarView.undelegateEvents();
+      toolbarView.remove();
+      fieldModel.unset('toolbarView');
+
+      // Unbind event handlers; delete decoration view. Don't remove the element
+      // because that would remove the field itself.
+      var decorationView = fieldModel.get('decorationView');
+      decorationView.undelegateEvents();
+      fieldModel.unset('decorationView');
+
+      // Unbind event handlers; delete editor view. Don't remove the element
+      // because that would remove the field itself.
+      var editorView = fieldModel.get('editorView');
+      editorView.undelegateEvents();
+      fieldModel.unset('editorView');
+    },
+
+    /**
+     * Asks the user to confirm whether he wants to stop editing via a modal.
+     *
+     * @see acceptEditorStateChange()
+     */
+    _confirmStopEditing: function () {
+      // Only instantiate if there isn't a modal instance visible yet.
+      if (!this.model.get('activeModal')) {
+        var that = this;
+        var modal = new Drupal.edit.ModalView({
+          model: this.model,
+          message: Drupal.t('You have unsaved changes'),
+          buttons: [
+            { action: 'discard', classes: 'gray-button', label: Drupal.t('Discard changes') },
+            { action: 'save', type: 'submit', classes: 'blue-button', label: Drupal.t('Save') }
+          ],
+          callback: function(action) {
+            // The active modal has been removed.
+            that.model.set('activeModal', null);
+            // Set the state that matches the user's action.
+            var targetState = (action === 'discard') ? 'candidate' : 'saving';
+            that.model.get('activeEditor').set('state', 'candidate', { confirmed: true });
+          }
+        });
+        this.model.set('activeModal', modal);
+        // The modal will set the activeModal property on the model when rendering
+        // to prevent multiple modals from being instantiated.
+        modal.render();
+      }
+    },
+
+    /**
+     * Reacts to field state changes; tracks global state.
+     *
+     * @param Drupal.edit.FieldModel fieldModel
+     * @param String state
+     *   The state of the associated field. One of Drupal.edit.FieldModel.states.
+     */
+    editorStateChange: function(fieldModel, state) {
+      var from = fieldModel.previous('state');
+      var to = state;
+
+      console.log("%c [APP] %s → %s    %s", "background-color: black; color: white", from, to, fieldModel.get('editID'));
+
+      // Keep track of the highlighted editor in the global state.
+      if (_.indexOf(this.singleEditorStates, to) !== -1 && this.model.get('highlightedEditor') !== fieldModel) {
+        this.model.set('highlightedEditor', fieldModel);
+      }
+      else if (this.model.get('highlightedEditor') === fieldModel && to === 'candidate') {
+        this.model.set('highlightedEditor', null);
+      }
+
+      // Keep track of the active editor in the global state.
+      if (_.indexOf(this.activeEditorStates, to) !== -1 && this.model.get('activeEditor') !== fieldModel) {
+        this.model.set('activeEditor', fieldModel);
+      }
+      else if (this.model.get('activeEditor') === fieldModel && to === 'candidate') {
+        // Discarded if it transitions from a changed state to 'candidate'.
+        if (from === 'changed' || from === 'invalid') {
+          // @todo FIX THIS!
+          // Retrieve the storage widget from DOM.
+          // var createStorageWidget = this.$el.data('DrupalCreateStorage');
+          // Revert changes in the model, this will trigger the direct editable
+          // content to be reset and redrawn.
+          // createStorageWidget.revertChanges(fieldModel.options.entity);
+        }
+        this.model.set('activeEditor', null);
+      }
+    }
+  })
+});
+
+}(jQuery, _, Backbone, Drupal));
diff --git a/core/modules/edit/js/views/ContextualLinkView.js b/core/modules/edit/js/views/ContextualLinkView.js
new file mode 100644
index 0000000..401e60a
--- /dev/null
+++ b/core/modules/edit/js/views/ContextualLinkView.js
@@ -0,0 +1,75 @@
+/**
+ * @file
+ * A Backbone View that a dynamic contextual link.
+ */
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+// Main model: Drupal.edit.EntityModel
+Drupal.edit.ContextualLinkView = Backbone.View.extend({
+
+  events: {
+    'click .quick-edit a': function (event) {
+      event.preventDefault();
+      this.model.set('isActive', !this.model.get('isActive'));
+    }
+  },
+
+  /**
+   * Implements Backbone.View.prototype.initialize().
+   *
+   * @param Object options
+   *   An object with the following keys:
+   *   - appModel: the application state model
+   *   - strings: the strings for the "Quick edit" link
+   */
+  initialize: function (options) {
+    this.appModel = options.appModel;
+    this.strings = options.strings;
+
+    // @todo Is this a good idea at all? I'm very unsure…
+    if (!this.model instanceof Drupal.edit.EntityModel) {
+      throw new Error('Drupal.edit.ContextualLinkView only accepts Drupal.edit.EntityModel models.');
+    }
+
+    // Build the DOM elements.
+    // @todo make this work in a generic way instead of applying this hack,
+    // being handled at http://drupal.org/node/1971108.
+    this.$el
+      .find('li.node-edit')
+      .before('<li class="quick-edit"><a href="#"></a></li>');
+
+    // Initial render.
+    this.render();
+
+    // Re-render whenever this entity's isActive attribute changes.
+    this.model.on('change:isActive', this.render, this);
+
+    // Hide the contextual links whenever an in-place editor is active.
+    this.appModel.on('change:activeEditor', this.toggleContextualLinksVisibility, this);
+  },
+
+  /**
+   * Implements Backbone.View.prototype.render().
+   */
+  render: function () {
+    var isActive = this.model.get('isActive');
+    this.$el.find('.quick-edit a').text((!isActive) ? this.strings.quickEdit : this.strings.stopQuickEdit);
+    return this;
+  },
+
+  /**
+   * Hides the contextual links if an in-place editor is active.
+   *
+   * @param Drupal.edit.AppModel model
+   * @param null|Drupal.edit.FieldModel activeEditor
+   *   The model of the field that is currently being edited, or, if none, null.
+   */
+  toggleContextualLinksVisibility: function (model, activeEditor) {
+    this.$el.parents('.contextual').toggle(activeEditor === null);
+  }
+
+});
+
+})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/EntityToolbarView.js b/core/modules/edit/js/views/EntityToolbarView.js
new file mode 100644
index 0000000..cbc46f1
--- /dev/null
+++ b/core/modules/edit/js/views/EntityToolbarView.js
@@ -0,0 +1,333 @@
+/**
+ * @file
+ * A Backbone View that provides an entity level toolbar.
+ */
+(function ($, Backbone, Drupal, debounce) {
+
+"use strict";
+
+Drupal.edit.EntityToolbarView = Backbone.View.extend({
+
+  _loader: null,
+  _loaderVisibleStart: 0,
+
+  events: function () {
+    var map = {
+      'click.edit button.field-save': 'onClickSave',
+      'click.edit button.field-close': 'onClickClose'
+    }
+    return map;
+  },
+
+  /**
+   * Implements Backbone.View.prototype.initialize().
+   */
+  initialize: function (options) {
+    var that = this;
+
+    this.model.on('change:isActive', this.render, this);
+
+    // When the fieldModel attribute of the EntityModel changes, set a listener
+    // on the active fieldModel that proxies its state changes to this view.
+    this.model.on('change:fieldModel', function (model, fieldModel) {
+      // Remove the listener from the previous fieldModel.
+      that.model.previous('fieldModel').off('change:state', that.fieldStateChange, that);
+      // Attach a change listener to the current active fieldModel.
+      fieldModel.on('change:state', that.fieldStateChange, that);
+    });
+
+    $(window).on('resize.edit scroll.edit', debounce($.proxy(this.windowChangeHandler, this), 150));
+
+    // Set the el into its own property. Eventually the el property will be
+    // replaced with the rendered toolbar.
+    this.$entity = this.$el;
+
+    // Set the toolbar container to this view's el property.
+    this.buildToolbar();
+
+    this._loader = null;
+    this._loaderVisibleStart = 0;
+  },
+
+  /**
+   * Implements Backbone.View.prototype.render().
+   */
+  render: function (model, changeValue) {
+
+    if (this.model.get('isActive')) {
+      // If the toolbar container doesn't exist, create it.
+      if ($('body').children('#edit-entity-toolbar').length === 0) {
+        $('body').append(this.$el);
+      }
+      // If render is being called and the toolbar is already visible, just
+      // reposition it.
+      this.position();
+      this.show('ops');
+    }
+    else {
+      this.$el.detach();
+    }
+
+    return this;
+  },
+
+  /**
+   *
+   */
+  windowChangeHandler: function (event) {
+    this.position();
+  },
+
+  /**
+   *
+   */
+  position: function (element) {
+    var of = element || this.$entity;
+    this.$el
+      .position({
+        my: 'left bottom',
+        at: 'left top',
+        of: of
+      });
+  },
+
+  /**
+   * 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.
+   */
+  fieldStateChange: function (model, state) {
+    var from = model.previous('state');
+    var to = state;
+    switch (to) {
+      case 'inactive':
+        break;
+        if (from) {
+          this.remove();
+          if (this.model.get('editor') !== 'form') {
+            Backbone.syncDirectCleanUp();
+          }
+        }
+        break;
+      case 'candidate':
+        break;
+        if (from === 'inactive') {
+          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')
+            .find('.edit-toolbar .edit-toolgroup').remove();
+          if (from !== 'highlighted' && this.getEditUISetting('padding')) {
+            this._unpad();
+          }
+        }
+        break;
+      case 'highlighted':
+        // As soon as we highlight, make sure we have a toolbar in the DOM (with at least a title).
+        this.startHighlight();
+        break;
+      case 'activating':
+        this.setLoadingIndicator(true);
+        break;
+      case 'active':
+        this.startEdit();
+        this.setLoadingIndicator(false);
+        // Position the toolbar against the active field.
+        this.position(model.get('$el'));
+        /*if (this.getEditUISetting('fullWidthToolbar')) {
+          this.$el.addClass('edit-toolbar-fullwidth');
+        }*/
+
+        /*if (this.getEditUISetting('padding')) {
+          this._pad();
+        }*/
+        /*if (this.getEditUISetting('unifiedToolbar')) {
+          this.insertWYSIWYGToolGroups();
+        }*/
+        break;
+      case 'changed':
+        this.$el
+          .find('button.save')
+          .addClass('blue-button')
+          .removeClass('gray-button');
+        break;
+      case 'saving':
+        this.setLoadingIndicator(true);
+        break;
+      case 'saved':
+        this.setLoadingIndicator(false);
+        break;
+      case 'invalid':
+        this.setLoadingIndicator(false);
+        break;
+    }
+  },
+
+  /**
+   * Set the model state to 'saving' when the save button is clicked.
+   *
+   * @param jQuery event
+   */
+  onClickSave: function (event) {
+    event.stopPropagation();
+    event.preventDefault();
+    this.model.set('state', 'saving');
+  },
+
+  /**
+   * Sets the model state to candidate when the cancel button is clicked.
+   *
+   * @param jQuery event
+   */
+  onClickClose: function (event) {
+    event.stopPropagation();
+    event.preventDefault();
+    this.model.get('fields')
+      .each(function (fieldModel) {
+        fieldModel.set('state', 'candidate', { reason: 'cancel' });
+      });
+    this.model.set('isActive', false);
+  },
+
+  /**
+   *
+   */
+  buildToolbar: function () {
+    var $toolbar;
+    $toolbar = $(Drupal.theme('editEntityToolbarContainer', {
+      id: 'edit-entity-toolbar'
+    }));
+
+    $toolbar
+      .find('.edit-toolbar')
+      // Append the "ops" toolgroup into the toolbar.
+      .append(Drupal.theme('editToolgroup', {
+        classes: 'ops',
+        buttons: [
+          { label: Drupal.t('Save'), type: 'submit', classes: 'field-save save gray-button' },
+          { label: '<span class="close">' + Drupal.t('Close') + '</span>', classes: 'field-close close gray-button' }
+        ]
+      }));
+
+    // Give the toolbar a sensible starting position so that it doesn't
+    // animiate on to the screen from a far off corner.
+    $toolbar
+      .css({
+        left: this.$entity.offset().left,
+        top: this.$entity.offset().top
+      });
+
+    this.setElement($toolbar);
+  },
+
+  /**
+   *
+   */
+  startEdit: function () {
+    this.$el.addClass('edit-editing');
+  },
+
+  /**
+   *
+   */
+  startHighlight: function () {
+    // Retrieve the label to show for this field.
+    var label = this.model.get('label');
+
+    this.$el
+      .addClass('edit-highlighted')
+      .find('.edit-toolbar')
+      // Append the "info" toolgroup into the toolbar.
+      .append(Drupal.theme('editToolgroup', {
+        classes: 'info edit-animate-only-background-and-padding',
+        buttons: [
+          { label: label, classes: 'blank-button label' }
+        ]
+      }));
+
+    // Animations.
+    var that = this;
+    setTimeout(function () {
+      that.show('info');
+    }, 0);
+  },
+
+  /**
+   * Indicates in the 'info' toolgroup that we're waiting for a server reponse.
+   *
+   * Prevents flickering loading indicator by only showing it after 0.6 seconds
+   * and if it is shown, only hiding it after another 0.6 seconds.
+   *
+   * @param Boolean enabled
+   *   Whether the loading indicator should be displayed or not.
+   */
+  setLoadingIndicator: function (enabled) {
+    var that = this;
+    if (enabled) {
+      this._loader = setTimeout(function() {
+        that.addClass('info', 'loading');
+        that._loaderVisibleStart = new Date().getTime();
+      }, 600);
+    }
+    else {
+      var currentTime = new Date().getTime();
+      clearTimeout(this._loader);
+      if (this._loaderVisibleStart) {
+        setTimeout(function() {
+          that.removeClass('info', 'loading');
+        }, this._loaderVisibleStart + 600 - currentTime);
+      }
+      this._loader = null;
+      this._loaderVisibleStart = 0;
+    }
+  },
+
+    /**
+   * Adds classes to a toolgroup.
+   *
+   * @param String toolgroup
+   *   A toolgroup name.
+   */
+  addClass: function (toolgroup, classes) {
+    this._find(toolgroup).addClass(classes);
+  },
+
+  /**
+   * Removes classes from a toolgroup.
+   *
+   * @param String toolgroup
+   *   A toolgroup name.
+   */
+  removeClass: function (toolgroup, classes) {
+    this._find(toolgroup).removeClass(classes);
+  },
+
+  /**
+   * Finds a toolgroup.
+   *
+   * @param String toolgroup
+   *   A toolgroup name.
+   */
+  _find: function (toolgroup) {
+    return this.$el.find('.edit-toolbar .edit-toolgroup.' + toolgroup);
+  },
+
+  /**
+   * Shows a toolgroup.
+   *
+   * @param String toolgroup
+   *   A toolgroup name.
+   */
+  show: function (toolgroup) {
+    this._find(toolgroup).removeClass('edit-animate-invisible');
+  }
+});
+
+})(jQuery, Backbone, Drupal, Drupal.debounce);
diff --git a/core/modules/edit/js/views/ModalView.js b/core/modules/edit/js/views/ModalView.js
new file mode 100644
index 0000000..15497cc
--- /dev/null
+++ b/core/modules/edit/js/views/ModalView.js
@@ -0,0 +1,80 @@
+/**
+ * @file
+ * A Backbone View that provides an interactive modal.
+ */
+(function ($, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit.ModalView = Backbone.View.extend({
+
+  message: null,
+  buttons: null,
+  callback: null,
+  $elementsToHide: null,
+
+  events: {
+    'click button': 'onButtonClick'
+  },
+
+  /**
+   * Implements Backbone.View.prototype.initialize().
+   *
+   * @param Object options
+   *   An object with the following keys:
+   *   - message: a message to show in the modal.
+   *   - buttons: a set of buttons with 'action's defined, ready to be passed to
+   *     Drupal.theme.editButtons().
+   *   - callback: a callback that will receive the 'action' of the clicked
+   *     button.
+   *
+   * @see Drupal.theme.editModal()
+   * @see Drupal.theme.editButtons()
+   */
+  initialize: function (options) {
+    this.message = options.message;
+    this.buttons = options.buttons;
+    this.callback = options.callback;
+  },
+
+  /**
+   * Implements Backbone.View.prototype.render().
+   */
+  render: function () {
+    this.setElement(Drupal.theme('editModal', {}));
+    this.$el.appendTo('body');
+    // Template.
+    this.$('.main p').text(this.message);
+    var $actions = $(Drupal.theme('editButtons', { 'buttons' : this.buttons}));
+    this.$('.actions').append($actions);
+
+    // Show the modal with an animation.
+    var that = this;
+    setTimeout(function() {
+      that.$el.removeClass('edit-animate-invisible');
+    }, 0);
+  },
+
+  /**
+   * Passes the clicked button action to the callback; closes the modal.
+   *
+   * @param jQuery event
+   */
+  onButtonClick: function (event) {
+    event.stopPropagation();
+    event.preventDefault();
+
+    // Remove after animation.
+    var that = this;
+    this.$el
+      .addClass('edit-animate-invisible')
+      .on(Drupal.edit.util.constants.transitionEnd, function(e) {
+        that.remove();
+      });
+
+    var action = $(event.target).attr('data-edit-modal-action');
+    return this.callback(action);
+  }
+});
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/PropertyEditorDecorationView.js b/core/modules/edit/js/views/PropertyEditorDecorationView.js
new file mode 100644
index 0000000..6eb7205
--- /dev/null
+++ b/core/modules/edit/js/views/PropertyEditorDecorationView.js
@@ -0,0 +1,393 @@
+/**
+ * @file
+ * A Backbone View that decorates a Property Editor widget.
+ *
+ * It listens to state changes of the property editor.
+ */
+(function ($, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit.PropertyEditorDecorationView = Backbone.View.extend({
+  toolbarId: null,
+
+  _widthAttributeIsEmpty: null,
+
+  events: {
+    'mouseenter.edit' : 'onMouseEnter',
+    'mouseleave.edit' : 'onMouseLeave',
+    'click': 'onClick',
+    'tabIn.edit': 'onMouseEnter',
+    'tabOut.edit': 'onMouseLeave'
+  },
+
+  /**
+   * Implements Backbone.View.prototype.initialize().
+   *
+   * @param Object options
+   *   An object with the following keys:
+   *   - editor: the editor object with an 'options' object that has these keys:
+   *      * entity: the VIE entity for the property.
+   *      * property: the predicate of the property.
+   *      * widget: the parent EditableEntity widget.
+   *      * editorName: the name of the PropertyEditor widget
+   *   - toolbarId: the ID attribute of the toolbar as rendered in the DOM.
+   */
+  initialize: function (options) {
+    this.editorView = options.editorView;
+
+    this.toolbarId = options.toolbarId;
+
+    this.model.on('change:state', this.stateChange, this);
+  },
+
+  /**
+   * 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.
+   */
+  stateChange: function (model, state) {
+    var from = model.previous('state');
+    var to = state;
+    switch (to) {
+      case 'inactive':
+        if (from !== null) {
+          this.undecorate();
+          if (from === 'invalid') {
+            this._removeValidationErrors();
+          }
+        }
+        break;
+      case 'candidate':
+        this.decorate();
+        if (from !== 'inactive') {
+          this.stopHighlight();
+          if (from !== 'highlighted') {
+            this.stopEdit();
+            if (from === 'invalid') {
+              this._removeValidationErrors();
+            }
+          }
+        }
+        break;
+      case 'highlighted':
+        this.startHighlight();
+        break;
+      case 'activating':
+        // NOTE: this state is not used by every editor! It's only used by those
+        // that need to interact with the server.
+        this.prepareEdit();
+        break;
+      case 'active':
+        if (from !== 'activating') {
+          this.prepareEdit();
+        }
+        this.startEdit();
+        break;
+      case 'changed':
+        break;
+      case 'saving':
+        if (from === 'invalid') {
+          this._removeValidationErrors();
+        }
+        break;
+      case 'saved':
+        break;
+      case 'invalid':
+        break;
+    }
+  },
+
+  /**
+   * Starts hover; transitions to 'highlight' state.
+   *
+   * @param jQuery event
+   */
+  onMouseEnter: function (event) {
+    var that = this;
+    this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
+      that.model.set('state', 'highlighted');
+      event.stopPropagation();
+    });
+  },
+
+  /**
+   * Stops hover; transitions to 'candidate' state.
+   *
+   * @param jQuery event
+   */
+  onMouseLeave: function (event) {
+    var that = this;
+    this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
+      that.model.set('state', 'candidate', { reason: 'mouseleave' });
+      event.stopPropagation();
+    });
+  },
+
+  /**
+   * Transition to 'activating' stage.
+   *
+   * @param jQuery event
+   */
+  onClick: function (event) {
+    this.model.set('state', 'activating');
+    event.preventDefault();
+    event.stopPropagation();
+  },
+
+  /**
+   * Adds classes used to indicate an elements editable state.
+   */
+  decorate: function () {
+    this.$el.addClass('edit-animate-fast edit-candidate edit-editable');
+  },
+
+  /**
+   * Removes classes used to indicate an elements editable state.
+   */
+  undecorate: function () {
+    this.$el.removeClass('edit-candidate edit-editable edit-highlighted edit-editing');
+  },
+
+  /**
+   * Adds that class that indicates that an element is highlighted.
+   */
+  startHighlight: function () {
+    // Animations.
+    var that = this;
+    // Use a timeout to grab the next available animation frame.
+    setTimeout(function () {
+      that.$el.addClass('edit-highlighted');
+    }, 0);
+  },
+
+  /**
+   * Removes the class that indicates that an element is highlighted.
+   */
+  stopHighlight: function () {
+    this.$el.removeClass('edit-highlighted');
+  },
+
+  /**
+   * Removes the class that indicates that an element as editable.
+   */
+  prepareEdit: function () {
+    this.$el.addClass('edit-editing');
+
+    // While editing, do not show any other editors.
+    $('.edit-candidate').not('.edit-editing').removeClass('edit-editable');
+  },
+
+  /**
+   * Updates the display of the editable element once editing has begun.
+   */
+  startEdit: function () {
+    if (this.getEditUISetting('padding')) {
+      this._pad();
+    }
+  },
+
+  /**
+   * Removes the class that indicates that an element is being edited.
+   *
+   * Reapplies the class that indicates that a candidate editable element is
+   * again available to be edited.
+   */
+  stopEdit: function () {
+    this.$el.removeClass('edit-highlighted edit-editing');
+
+    // Make the other editors show up again.
+    // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
+    // Revisit this.
+    $('.edit-candidate').addClass('edit-editable');
+
+    if (this.getEditUISetting('padding')) {
+      this._unpad();
+    }
+  },
+
+  /**
+   * Retrieves a setting of the editor-specific Edit UI integration.
+   *
+   * @param String setting
+   *
+   * @see Drupal.edit.util.getEditUISetting().
+   */
+  getEditUISetting: function (setting) {
+    return Drupal.edit.util.getEditUISetting(this.editorView, setting);
+  },
+
+  /**
+   * Adds padding around the editable element in order to make it pop visually.
+   */
+  _pad: function () {
+    var self = this;
+
+    // Add 5px padding for readability. This means we'll freeze the current
+    // width and *then* add 5px padding, hence ensuring the padding is added "on
+    // the outside".
+    // 1) Freeze the width (if it's not already set); don't use animations.
+    if (this.$el[0].style.width === "") {
+      this._widthAttributeIsEmpty = true;
+      this.$el
+        .addClass('edit-animate-disable-width')
+        .css('width', this.$el.width())
+        .css('background-color', this._getBgColor(this.$el));
+    }
+
+    // 2) Add padding; use animations.
+    var posProp = this._getPositionProperties(this.$el);
+    setTimeout(function() {
+      // Re-enable width animations (padding changes affect width too!).
+      self.$el.removeClass('edit-animate-disable-width');
+
+      // Pad the editable.
+      self.$el
+      .css({
+        'position': 'relative',
+        'top':  posProp.top  - 5 + 'px',
+        'left': posProp.left - 5 + 'px',
+        'padding-top'   : posProp['padding-top']    + 5 + 'px',
+        'padding-left'  : posProp['padding-left']   + 5 + 'px',
+        'padding-right' : posProp['padding-right']  + 5 + 'px',
+        'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
+        'margin-bottom':  posProp['margin-bottom'] - 10 + 'px'
+      });
+    }, 0);
+  },
+
+  /**
+   * Removes the padding around the element being edited when editing ceases.
+   */
+  _unpad: function () {
+    var self = this;
+
+    // 1) Set the empty width again.
+    if (this._widthAttributeIsEmpty) {
+      this.$el
+        .addClass('edit-animate-disable-width')
+        .css('width', '')
+        .css('background-color', '');
+    }
+
+    // 2) Remove padding; use animations (these will run simultaneously with)
+    // the fading out of the toolbar as its gets removed).
+    var posProp = this._getPositionProperties(this.$el);
+    setTimeout(function() {
+      // Re-enable width animations (padding changes affect width too!).
+      self.$el.removeClass('edit-animate-disable-width');
+
+      // Unpad the editable.
+      self.$el
+      .css({
+        'position': 'relative',
+        'top':  posProp.top  + 5 + 'px',
+        'left': posProp.left + 5 + 'px',
+        'padding-top'   : posProp['padding-top']    - 5 + 'px',
+        'padding-left'  : posProp['padding-left']   - 5 + 'px',
+        'padding-right' : posProp['padding-right']  - 5 + 'px',
+        'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
+        'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
+      });
+    }, 0);
+  },
+
+  /**
+   * Gets the background color of an element (or the inherited one).
+   *
+   * @param DOM $e
+   */
+  _getBgColor: function ($e) {
+    var c;
+
+    if ($e === null || $e[0].nodeName === 'HTML') {
+      // Fallback to white.
+      return 'rgb(255, 255, 255)';
+    }
+    c = $e.css('background-color');
+    // TRICKY: edge case for Firefox' "transparent" here; this is a
+    // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724
+    if (c === 'rgba(0, 0, 0, 0)' || c === 'transparent') {
+      return this._getBgColor($e.parent());
+    }
+    return c;
+  },
+
+  /**
+   * Gets the top and left properties of an element.
+   *
+   * Convert extraneous values and information into numbers ready for
+   * subtraction.
+   *
+   * @param DOM $e
+   */
+  _getPositionProperties: function ($e) {
+    var p,
+        r = {},
+        props = [
+          'top', 'left', 'bottom', 'right',
+          'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
+          'margin-bottom'
+        ];
+
+    var propCount = props.length;
+    for (var i = 0; i < propCount; i++) {
+      p = props[i];
+      r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
+    }
+    return r;
+  },
+
+  /**
+   * Replaces blank or 'auto' CSS "position: <value>" values with "0px".
+   *
+   * @param String pos
+   *   (optional) The value for a CSS position declaration.
+   */
+  _replaceBlankPosition: function (pos) {
+    if (pos === 'auto' || !pos) {
+      pos = '0px';
+    }
+    return pos;
+  },
+
+  /**
+   * Ignores hovering to/from the given closest element.
+   *
+   * When a hover occurs to/from another element, invoke the callback.
+   *
+   * @param jQuery event
+   * @param jQuery closest
+   *   A jQuery-wrapped DOM element or compatibale jQuery input. The element
+   *   whose mouseenter and mouseleave events should be ignored.
+   * @param Function callback
+   */
+  _ignoreHoveringVia: function (event, closest, callback) {
+    if ($(event.relatedTarget).closest(closest).length > 0) {
+      event.stopPropagation();
+    }
+    else {
+      callback();
+    }
+  },
+
+  /**
+   * Removes validation error's markup changes, if any.
+   *
+   * Note: this only needs to happen for type=direct, because for type=direct,
+   * the property DOM element itself is modified; this is not the case for
+   * type=form.
+   */
+  _removeValidationErrors: function () {
+    if (this.model.get('editor') !== 'form') {
+      this.$el
+        .removeClass('edit-validation-error')
+        .next('.edit-validation-errors')
+        .remove();
+    }
+  }
+});
+
+})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/ToolbarView.js b/core/modules/edit/js/views/ToolbarView.js
new file mode 100644
index 0000000..08dcb78
--- /dev/null
+++ b/core/modules/edit/js/views/ToolbarView.js
@@ -0,0 +1,194 @@
+/**
+ * @file
+ * A Backbone View that provides an interactive toolbar (1 per property editor).
+ *
+ * It listens to state changes of the property editor. It also triggers state
+ * changes in response to user interactions with the toolbar, including saving.
+ */
+(function ($, _, Backbone, Drupal) {
+
+"use strict";
+
+Drupal.edit.ToolbarView = Backbone.View.extend({
+  $field: null,
+
+  _id: null,
+
+  events: {
+    'click.edit button.label': 'onClickInfoLabel',
+    'mouseleave.edit': 'onMouseLeave'
+  },
+
+  /**
+   * Implements Backbone.View.prototype.initialize().
+   */
+  initialize: function (options) {
+    this.$field = options.$field;
+    this.editorView = options.editorView;
+
+    // Generate a DOM-compatible ID for the form container DOM element.
+    this._id = 'edit-toolbar-for-' + this.model.get('editID').replace(/\//g, '_');
+  },
+
+  /**
+   * Implements Backbone.View.prototype.render().
+   *
+   * Depending on whether the display property of the $el for which a
+   * toolbar is being inserted into the DOM, it will be inserted differently.
+   */
+  render: function () {
+    // Render toolbar.
+    this.setElement($(Drupal.theme('editToolbarContainer', {
+      id: this._id
+    })));
+
+    // Insert in DOM.
+    if (this.$field.css('display') === 'inline') {
+      this.$el.prependTo(this.$field.offsetParent());
+      var pos = this.$field.position();
+      this.$el.css('left', pos.left).css('top', pos.top);
+    }
+    else {
+      this.$el.insertBefore(this.$field);
+    }
+
+    return this;
+  },
+
+  /**
+   * Redirects the click.edit-event to the editor DOM element.
+   *
+   * @param jQuery event
+   */
+  onClickInfoLabel: function (event) {
+    event.stopPropagation();
+    event.preventDefault();
+    // Redirects the event to the editor DOM element.
+    this.$field.trigger('click.edit');
+  },
+
+  /**
+   * Controls mouseleave events.
+   *
+   * A mouseleave to the editor doesn't matter; a mouseleave to something else
+   * counts as a mouseleave on the editor itself.
+   *
+   * @param jQuery event
+   */
+  onMouseLeave: function (event) {
+    if (event.relatedTarget !== this.$field[0] && !$.contains(this.$field, event.relatedTarget)) {
+      this.$field.trigger('mouseleave.edit');
+    }
+    event.stopPropagation();
+  },
+
+  /**
+   * Retrieves a setting of the editor-specific Edit UI integration.
+   *
+   * @param String setting
+   *
+   * @see Drupal.edit.util.getEditUISetting().
+   */
+  getEditUISetting: function (setting) {
+    return Drupal.edit.util.getEditUISetting(this.editorView, setting);
+  },
+
+  /**
+   * Adjusts the toolbar to accomodate padding on the PropertyEditor widget.
+   *
+   * @see PropertyEditorDecorationView._pad().
+   */
+  _pad: function () {
+    // The whole toolbar must move to the top when the property's DOM element
+    // is displayed inline.
+    if (this.$field.css('display') === 'inline') {
+      this.$el.css('top', parseInt(this.$el.css('top'), 10) - 5 + 'px');
+    }
+
+    // The toolbar must move to the top and the left.
+    var $hf = this.$el.find('.edit-toolbar-heightfaker');
+    $hf.css({ bottom: '6px', left: '-5px' });
+
+    if (this.getEditUISetting('fullWidthToolbar')) {
+      $hf.css({ width: this.$field.width() + 10 });
+    }
+  },
+
+  /**
+   * Undoes the changes made by _pad().
+   *
+   * @see PropertyEditorDecorationView._unpad().
+   */
+  _unpad: function () {
+    // Move the toolbar back to its original position.
+    var $hf = this.$el.find('.edit-toolbar-heightfaker');
+    $hf.css({ bottom: '1px', left: '' });
+
+    if (this.getEditUISetting('fullWidthToolbar')) {
+      $hf.css({ width: '' });
+    }
+  },
+
+  /**
+   *
+   */
+  insertWYSIWYGToolGroups: function () {
+    this.$el
+      .find('.edit-toolbar')
+      .append(Drupal.theme('editToolgroup', {
+        id: this.getFloatedWysiwygToolgroupId(),
+        classes: 'wysiwyg-floated',
+        buttons: []
+      }))
+      .append(Drupal.theme('editToolgroup', {
+        id: this.getMainWysiwygToolgroupId(),
+        classes: 'wysiwyg-main',
+        buttons: []
+      }));
+
+    // Animate the toolgroups into visibility.
+    var that = this;
+    setTimeout(function () {
+      that.show('wysiwyg-floated');
+      that.show('wysiwyg-main');
+    }, 0);
+  },
+
+  /**
+   * Retrieves the ID for this toolbar's container.
+   *
+   * Only used to make sane hovering behavior possible.
+   *
+   * @return String
+   *   A string that can be used as the ID for this toolbar's container.
+   */
+  getId: function () {
+    return 'edit-toolbar-for-' + this._id;
+  },
+
+  /**
+   * Retrieves the ID for this toolbar's floating WYSIWYG toolgroup.
+   *
+   * Used to provide an abstraction for any WYSIWYG editor to plug in.
+   *
+   * @return String
+   *   A string that can be used as the ID.
+   */
+  getFloatedWysiwygToolgroupId: function () {
+    return 'edit-wysiwyg-floated-toolgroup-for-' + this._id;
+  },
+
+  /**
+   * Retrieves the ID for this toolbar's main WYSIWYG toolgroup.
+   *
+   * Used to provide an abstraction for any WYSIWYG editor to plug in.
+   *
+   * @return String
+   *   A string that can be used as the ID.
+   */
+  getMainWysiwygToolgroupId: function () {
+    return 'edit-wysiwyg-main-toolgroup-for-' + this._id;
+  }
+});
+
+})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/contextuallink-view.js b/core/modules/edit/js/views/contextuallink-view.js
deleted file mode 100644
index efe8ddd..0000000
--- a/core/modules/edit/js/views/contextuallink-view.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/**
- * @file
- * A Backbone View that a dynamic contextual link.
- */
-(function ($, _, Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.ContextualLinkView = Backbone.View.extend({
-
-  entity: null,
-
-  events: {
-    'click': 'onClick'
-  },
-
-  /**
-   * Implements Backbone Views' initialize() function.
-   *
-   * @param options
-   *   An object with the following keys:
-   *   - entity: the entity ID (e.g. node/1) of the entity
-   */
-  initialize: function (options) {
-    this.entity = options.entity;
-
-    // Initial render.
-    this.render();
-
-    // Re-render whenever the app state's active entity changes.
-    this.model.on('change:activeEntity', this.render, this);
-
-    // Hide the contextual links whenever an in-place editor is active.
-    this.model.on('change:activeEditor', this.toggleContextualLinksVisibility, this);
-  },
-
-  /**
-   * Equates clicks anywhere on the overlay to clicking the active editor's (if
-   * any) "close" button.
-   *
-   * @param {Object} event
-   */
-  onClick: function (event) {
-    event.preventDefault();
-
-    var that = this;
-    var updateActiveEntity = function() {
-      // The active entity is the current entity, i.e. stop editing the current
-      // entity.
-      if (that.model.get('activeEntity') === that.entity) {
-        that.model.set('activeEntity', null);
-      }
-      // The active entity is different from the current entity, i.e. start
-      // editing this entity instead of the previous one.
-      else {
-        that.model.set('activeEntity', that.entity);
-      }
-    };
-
-    // If there's an active editor, attempt to set its state to 'candidate', and
-    // only then do what the user asked.
-    // (Only when all PropertyEditor widgets of an entity are in the 'candidate'
-    // state, it is possible to stop editing it.)
-    var activeEditor = this.model.get('activeEditor');
-    if (activeEditor) {
-      var editableEntity = activeEditor.options.widget;
-      var predicate = activeEditor.options.property;
-      editableEntity.setState('candidate', predicate, { reason: 'stop or switch' }, function(accepted) {
-        if (accepted) {
-          updateActiveEntity();
-        }
-        else {
-          // No change.
-        }
-      });
-    }
-    // Otherwise, we can immediately do what the user asked.
-    else {
-      updateActiveEntity();
-    }
-  },
-
-  /**
-   * Render the "Quick edit" contextual link.
-   */
-  render: function () {
-    var activeEntity = this.model.get('activeEntity');
-    var string = (activeEntity !== this.entity) ? Drupal.t('Quick edit') : Drupal.t('Stop quick edit');
-    this.$el.html('<a href="">' + string + '</a>');
-    return this;
-  },
-
-  /**
-   * Model change handler; hides the contextual links if an editor is active.
-   *
-   * @param Drupal.edit.models.EditAppModel model
-   *   An EditAppModel model.
-   * @param jQuery|null activeEditor
-   *   The active in-place editor (jQuery object) or, if none, null.
-   */
-  toggleContextualLinksVisibility: function (model, activeEditor) {
-    this.$el.parents('.contextual').toggle(activeEditor === null);
-  }
-
-});
-
-})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/modal-view.js b/core/modules/edit/js/views/modal-view.js
deleted file mode 100644
index b98c876..0000000
--- a/core/modules/edit/js/views/modal-view.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * @file
- * A Backbone View that provides an interactive modal.
- */
-(function($, Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.ModalView = Backbone.View.extend({
-
-  message: null,
-  buttons: null,
-  callback: null,
-  $elementsToHide: null,
-
-  events: {
-    'click button': 'onButtonClick'
-  },
-
-  /**
-   * Implements Backbone Views' initialize() function.
-   *
-   * @param options
-   *   An object with the following keys:
-   *   - message: a message to show in the modal.
-   *   - buttons: a set of buttons with 'action's defined, ready to be passed to
-   *     Drupal.theme.editButtons().
-   *   - callback: a callback that will receive the 'action' of the clicked
-   *     button.
-   *
-   * @see Drupal.theme.editModal()
-   * @see Drupal.theme.editButtons()
-   */
-  initialize: function(options) {
-    this.message = options.message;
-    this.buttons = options.buttons;
-    this.callback = options.callback;
-  },
-
-  /**
-   * Implements Backbone Views' render() function.
-   */
-  render: function() {
-    this.setElement(Drupal.theme('editModal', {}));
-    this.$el.appendTo('body');
-    // Template.
-    this.$('.main p').text(this.message);
-    var $actions = $(Drupal.theme('editButtons', { 'buttons' : this.buttons}));
-    this.$('.actions').append($actions);
-
-    // Show the modal with an animation.
-    var that = this;
-    setTimeout(function() {
-      that.$el.removeClass('edit-animate-invisible');
-    }, 0);
-  },
-
-  /**
-   * When the user clicks on any of the buttons, the modal should be removed
-   * and the result should be passed to the callback.
-   *
-   * @param event
-   */
-  onButtonClick: function(event) {
-    event.stopPropagation();
-    event.preventDefault();
-
-    // Remove after animation.
-    var that = this;
-    this.$el
-      .addClass('edit-animate-invisible')
-      .on(Drupal.edit.util.constants.transitionEnd, function(e) {
-        that.remove();
-      });
-
-    var action = $(event.target).attr('data-edit-modal-action');
-    return this.callback(action);
-  }
-});
-
-})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/propertyeditordecoration-view.js b/core/modules/edit/js/views/propertyeditordecoration-view.js
deleted file mode 100644
index bee33d1..0000000
--- a/core/modules/edit/js/views/propertyeditordecoration-view.js
+++ /dev/null
@@ -1,363 +0,0 @@
-/**
- * @file
- * A Backbone View that decorates a Property Editor widget.
- *
- * It listens to state changes of the property editor.
- */
-(function($, Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
-
-  toolbarId: null,
-
-  _widthAttributeIsEmpty: null,
-
-  events: {
-    'mouseenter.edit' : 'onMouseEnter',
-    'mouseleave.edit' : 'onMouseLeave',
-    'click': 'onClick',
-    'tabIn.edit': 'onMouseEnter',
-    'tabOut.edit': 'onMouseLeave'
-  },
-
-  /**
-   * Implements Backbone Views' initialize() function.
-   *
-   * @param options
-   *   An object with the following keys:
-   *   - editor: the editor object with an 'options' object that has these keys:
-   *      * entity: the VIE entity for the property.
-   *      * property: the predicate of the property.
-   *      * widget: the parent EditableEntity widget.
-   *      * editorName: the name of the PropertyEditor widget
-   *   - toolbarId: the ID attribute of the toolbar as rendered in the DOM.
-   */
-  initialize: function(options) {
-    this.editor = options.editor;
-    this.toolbarId = options.toolbarId;
-
-    this.predicate = this.editor.options.property;
-    this.editorName = this.editor.options.editorName;
-
-    // Only start listening to events as soon as we're no longer in the 'inactive' state.
-    this.undelegateEvents();
-  },
-
-  /**
-   * Listens to editor state changes.
-   */
-  stateChange: function(from, to) {
-    switch (to) {
-      case 'inactive':
-        if (from !== null) {
-          this.undecorate();
-          if (from === 'invalid') {
-            this._removeValidationErrors();
-          }
-        }
-        break;
-      case 'candidate':
-        this.decorate();
-        if (from !== 'inactive') {
-          this.stopHighlight();
-          if (from !== 'highlighted') {
-            this.stopEdit();
-            if (from === 'invalid') {
-              this._removeValidationErrors();
-            }
-          }
-        }
-        break;
-      case 'highlighted':
-        this.startHighlight();
-        break;
-      case 'activating':
-        // NOTE: this state is not used by every editor! It's only used by those
-        // that need to interact with the server.
-        this.prepareEdit();
-        break;
-      case 'active':
-        if (from !== 'activating') {
-          this.prepareEdit();
-        }
-        this.startEdit();
-        break;
-      case 'changed':
-        break;
-      case 'saving':
-        if (from === 'invalid') {
-          this._removeValidationErrors();
-        }
-        break;
-      case 'saved':
-        break;
-      case 'invalid':
-        break;
-    }
-  },
-
-  /**
-   * Starts hover: transition to 'highlight' state.
-   *
-   * @param event
-   */
-  onMouseEnter: function(event) {
-    var that = this;
-    this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
-      var editableEntity = that.editor.options.widget;
-      editableEntity.setState('highlighted', that.predicate);
-      event.stopPropagation();
-    });
-  },
-
-  /**
-   * Stops hover: back to 'candidate' state.
-   *
-   * @param event
-   */
-  onMouseLeave: function(event) {
-    var that = this;
-    this._ignoreHoveringVia(event, '#' + this.toolbarId, function () {
-      var editableEntity = that.editor.options.widget;
-      editableEntity.setState('candidate', that.predicate, { reason: 'mouseleave' });
-      event.stopPropagation();
-    });
-  },
-
-  /**
-   * Clicks: transition to 'activating' stage.
-   *
-   * @param event
-   */
-  onClick: function(event) {
-    var editableEntity = this.editor.options.widget;
-    editableEntity.setState('activating', this.predicate);
-    event.preventDefault();
-    event.stopPropagation();
-  },
-
-  decorate: function () {
-    this.$el.addClass('edit-animate-fast edit-candidate edit-editable');
-    this.delegateEvents();
-  },
-
-  undecorate: function () {
-    this.$el
-      .removeClass('edit-candidate edit-editable edit-highlighted edit-editing');
-    this.undelegateEvents();
-  },
-
-  startHighlight: function () {
-    // Animations.
-    var that = this;
-    setTimeout(function() {
-      that.$el.addClass('edit-highlighted');
-    }, 0);
-  },
-
-  stopHighlight: function() {
-    this.$el
-      .removeClass('edit-highlighted');
-  },
-
-  prepareEdit: function() {
-    this.$el.addClass('edit-editing');
-
-    // While editing, don't show *any* other editors.
-    // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
-    // Revisit this.
-    $('.edit-candidate').not('.edit-editing').removeClass('edit-editable');
-  },
-
-  startEdit: function() {
-    if (this.getEditUISetting('padding')) {
-      this._pad();
-    }
-  },
-
-  stopEdit: function() {
-    this.$el.removeClass('edit-highlighted edit-editing');
-
-    // Make the other editors show up again.
-    // @todo: BLOCKED_ON(Create.js, https://github.com/bergie/create/issues/133)
-    // Revisit this.
-    $('.edit-candidate').addClass('edit-editable');
-
-    if (this.getEditUISetting('padding')) {
-      this._unpad();
-    }
-  },
-
-  /**
-   * Retrieves a setting of the editor-specific Edit UI integration.
-   *
-   * @see Drupal.edit.util.getEditUISetting().
-   */
-  getEditUISetting: function(setting) {
-    return Drupal.edit.util.getEditUISetting(this.editor, setting);
-  },
-
-  _pad: function () {
-    var self = this;
-
-    // Add 5px padding for readability. This means we'll freeze the current
-    // width and *then* add 5px padding, hence ensuring the padding is added "on
-    // the outside".
-    // 1) Freeze the width (if it's not already set); don't use animations.
-    if (this.$el[0].style.width === "") {
-      this._widthAttributeIsEmpty = true;
-      this.$el
-        .addClass('edit-animate-disable-width')
-        .css('width', this.$el.width())
-        .css('background-color', this._getBgColor(this.$el));
-    }
-
-    // 2) Add padding; use animations.
-    var posProp = this._getPositionProperties(this.$el);
-    setTimeout(function() {
-      // Re-enable width animations (padding changes affect width too!).
-      self.$el.removeClass('edit-animate-disable-width');
-
-      // Pad the editable.
-      self.$el
-      .css({
-        'position': 'relative',
-        'top':  posProp.top  - 5 + 'px',
-        'left': posProp.left - 5 + 'px',
-        'padding-top'   : posProp['padding-top']    + 5 + 'px',
-        'padding-left'  : posProp['padding-left']   + 5 + 'px',
-        'padding-right' : posProp['padding-right']  + 5 + 'px',
-        'padding-bottom': posProp['padding-bottom'] + 5 + 'px',
-        'margin-bottom':  posProp['margin-bottom'] - 10 + 'px'
-      });
-    }, 0);
-  },
-
-  _unpad: function () {
-    var self = this;
-
-    // 1) Set the empty width again.
-    if (this._widthAttributeIsEmpty) {
-      this.$el
-        .addClass('edit-animate-disable-width')
-        .css('width', '')
-        .css('background-color', '');
-    }
-
-    // 2) Remove padding; use animations (these will run simultaneously with)
-    // the fading out of the toolbar as its gets removed).
-    var posProp = this._getPositionProperties(this.$el);
-    setTimeout(function() {
-      // Re-enable width animations (padding changes affect width too!).
-      self.$el.removeClass('edit-animate-disable-width');
-
-      // Unpad the editable.
-      self.$el
-      .css({
-        'position': 'relative',
-        'top':  posProp.top  + 5 + 'px',
-        'left': posProp.left + 5 + 'px',
-        'padding-top'   : posProp['padding-top']    - 5 + 'px',
-        'padding-left'  : posProp['padding-left']   - 5 + 'px',
-        'padding-right' : posProp['padding-right']  - 5 + 'px',
-        'padding-bottom': posProp['padding-bottom'] - 5 + 'px',
-        'margin-bottom': posProp['margin-bottom'] + 10 + 'px'
-      });
-    }, 0);
-  },
-
-  /**
-   * Gets the background color of an element (or the inherited one).
-   *
-   * @param $e
-   *   A DOM element.
-   */
-  _getBgColor: function($e) {
-    var c;
-
-    if ($e === null || $e[0].nodeName === 'HTML') {
-      // Fallback to white.
-      return 'rgb(255, 255, 255)';
-    }
-    c = $e.css('background-color');
-    // TRICKY: edge case for Firefox' "transparent" here; this is a
-    // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724
-    if (c === 'rgba(0, 0, 0, 0)' || c === 'transparent') {
-      return this._getBgColor($e.parent());
-    }
-    return c;
-  },
-
-  /**
-   * Gets the top and left properties of an element and convert extraneous
-   * values and information into numbers ready for subtraction.
-   *
-   * @param $e
-   *   A DOM element.
-   */
-  _getPositionProperties: function($e) {
-    var p,
-        r = {},
-        props = [
-          'top', 'left', 'bottom', 'right',
-          'padding-top', 'padding-left', 'padding-right', 'padding-bottom',
-          'margin-bottom'
-        ];
-
-    var propCount = props.length;
-    for (var i = 0; i < propCount; i++) {
-      p = props[i];
-      r[p] = parseInt(this._replaceBlankPosition($e.css(p)), 10);
-    }
-    return r;
-  },
-
-  /**
-   * Replaces blank or 'auto' CSS "position: <value>" values with "0px".
-   *
-   * @param pos
-   *   The value for a CSS position declaration.
-   */
-  _replaceBlankPosition: function(pos) {
-    if (pos === 'auto' || !pos) {
-      pos = '0px';
-    }
-    return pos;
-  },
-
-  /**
-   * Ignores hovering to/from the given closest element, but as soon as a hover
-   * occurs to/from *another* element, then call the given callback.
-   */
-  _ignoreHoveringVia: function(event, closest, callback) {
-    if ($(event.relatedTarget).closest(closest).length > 0) {
-      event.stopPropagation();
-    }
-    else {
-      callback();
-    }
-  },
-
-  /**
-   * Removes validation errors' markup changes, if any.
-   *
-   * Note: this only needs to happen for type=direct, because for type=direct,
-   * the property DOM element itself is modified; this is not the case for
-   * type=form.
-   */
-  _removeValidationErrors: function() {
-    if (this.editorName !== 'form') {
-      this.$el
-        .removeClass('edit-validation-error')
-        .next('.edit-validation-errors')
-        .remove();
-    }
-  }
-
-});
-
-})(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/toolbar-view.js b/core/modules/edit/js/views/toolbar-view.js
deleted file mode 100644
index f4b2123..0000000
--- a/core/modules/edit/js/views/toolbar-view.js
+++ /dev/null
@@ -1,490 +0,0 @@
-/**
- * @file
- * A Backbone View that provides an interactive toolbar (1 per property editor).
- *
- * It listens to state changes of the property editor. It also triggers state
- * changes in response to user interactions with the toolbar, including saving.
- */
-(function ($, _, Backbone, Drupal) {
-
-"use strict";
-
-Drupal.edit = Drupal.edit || {};
-Drupal.edit.views = Drupal.edit.views || {};
-Drupal.edit.views.ToolbarView = Backbone.View.extend({
-
-  editor: null,
-  $storageWidgetEl: null,
-
-  entity: null,
-  predicate : null,
-  editorName: null,
-
-  _loader: null,
-  _loaderVisibleStart: 0,
-
-  _id: null,
-
-  events: {
-    'click.edit button.label': 'onClickInfoLabel',
-    'mouseleave.edit': 'onMouseLeave',
-    'click.edit button.field-save': 'onClickSave',
-    'click.edit button.field-close': 'onClickClose'
-  },
-
-  /**
-   * Implements Backbone Views' initialize() function.
-   *
-   * @param options
-   *   An object with the following keys:
-   *   - editor: the editor object with an 'options' object that has these keys:
-   *      * entity: the VIE entity for the property.
-   *      * property: the predicate of the property.
-   *      * editorName: the editor name.
-   *      * element: the jQuery-wrapped editor DOM element
-   *   - $storageWidgetEl: the DOM element on which the Create Storage widget is
-   *     initialized.
-   */
-  initialize: function(options) {
-    this.editor = options.editor;
-    this.$storageWidgetEl = options.$storageWidgetEl;
-
-    this.entity = this.editor.options.entity;
-    this.predicate = this.editor.options.property;
-    this.editorName = this.editor.options.editorName;
-
-    this._loader = null;
-    this._loaderVisibleStart = 0;
-
-    // Generate a DOM-compatible ID for the toolbar DOM element.
-    this._id = Drupal.edit.util.calcPropertyID(this.entity, this.predicate).replace(/\//g, '_');
-  },
-
-  /**
-   * Listens to editor state changes.
-   */
-  stateChange: function(from, to) {
-    switch (to) {
-      case 'inactive':
-        if (from) {
-          this.remove();
-          if (this.editorName !== 'form') {
-            Backbone.syncDirectCleanUp();
-          }
-        }
-        break;
-      case 'candidate':
-        if (from === 'inactive') {
-          this.render();
-        }
-        else {
-          if (this.editorName !== 'form') {
-            Backbone.syncDirectCleanUp();
-          }
-          // Remove all toolgroups; they're no longer necessary.
-          this.$el
-            .removeClass('edit-highlighted edit-editing')
-            .find('.edit-toolbar .edit-toolgroup').remove();
-          if (from !== 'highlighted' && this.getEditUISetting('padding')) {
-            this._unpad();
-          }
-        }
-        break;
-      case 'highlighted':
-        // As soon as we highlight, make sure we have a toolbar in the DOM (with at least a title).
-        this.startHighlight();
-        break;
-      case 'activating':
-        this.setLoadingIndicator(true);
-        break;
-      case 'active':
-        this.startEdit();
-        this.setLoadingIndicator(false);
-        if (this.getEditUISetting('fullWidthToolbar')) {
-          this.$el.addClass('edit-toolbar-fullwidth');
-        }
-
-        if (this.getEditUISetting('padding')) {
-          this._pad();
-        }
-        if (this.getEditUISetting('unifiedToolbar')) {
-          this.insertWYSIWYGToolGroups();
-        }
-        break;
-      case 'changed':
-        this.$el
-          .find('button.save')
-          .addClass('blue-button')
-          .removeClass('gray-button');
-        break;
-      case 'saving':
-        this.setLoadingIndicator(true);
-        this.save();
-        break;
-      case 'saved':
-        this.setLoadingIndicator(false);
-        break;
-      case 'invalid':
-        this.setLoadingIndicator(false);
-        break;
-    }
-  },
-
-  /**
-   * Saves a property.
-   *
-   * This method deals with the complexity of the editor-dependent ways of
-   * inserting updated content and showing validation error messages.
-   *
-   * One might argue that this does not belong in a view. However, there is no
-   * actual "save" logic here, that lives in Backbone.sync. This is just some
-   * glue code, along with the logic for inserting updated content as well as
-   * showing validation error messages, the latter of which is certainly okay.
-   */
-  save: function() {
-    var that = this;
-    var editor = this.editor;
-    var editableEntity = editor.options.widget;
-    var entity = editor.options.entity;
-    var predicate = editor.options.property;
-
-    // Use Create.js' Storage widget to handle saving. (Uses Backbone.sync.)
-    this.$storageWidgetEl.createStorage('saveRemote', entity, {
-      editor: editor,
-
-      // Successfully saved without validation errors.
-      success: function (model) {
-        editableEntity.setState('saved', predicate);
-
-        // 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 = entity.get(predicate + '/rendered');
-        var $propertyWrapper = editor.element.closest('.edit-field');
-        var $context = $propertyWrapper.parent();
-
-        editableEntity.setState('candidate', predicate);
-        // Unset the property, because it will be parsed again from the DOM, iff
-        // its new value causes it to still be rendered.
-        entity.unset(predicate, { silent: true });
-        entity.unset(predicate + '/rendered', { silent: true });
-        // Trigger event to allow for proper clean-up of editor-specific views.
-        editor.element.trigger('destroyedPropertyEditor.edit', editor);
-
-        // Replace the old content with the new content.
-        $propertyWrapper.replaceWith(updatedProperty);
-        Drupal.attachBehaviors($context);
-      },
-
-      // Save attempted but failed due to validation errors.
-      error: function (validationErrorMessages) {
-        editableEntity.setState('invalid', predicate);
-
-        if (that.editorName === 'form') {
-          editor.$formContainer
-            .find('.edit-form')
-            .addClass('edit-validation-error')
-            .find('form')
-            .prepend(validationErrorMessages);
-        }
-        else {
-          var $errors = $('<div class="edit-validation-errors"></div>')
-            .append(validationErrorMessages);
-          editor.element
-            .addClass('edit-validation-error')
-            .after($errors);
-        }
-      }
-    });
-  },
-
-  /**
-   * When the user clicks the info label, nothing should happen.
-   * @note currently redirects the click.edit-event to the editor DOM element.
-   *
-   * @param event
-   */
-  onClickInfoLabel: function(event) {
-    event.stopPropagation();
-    event.preventDefault();
-    // Redirects the event to the editor DOM element.
-    this.editor.element.trigger('click.edit');
-  },
-
-  /**
-   * A mouseleave to the editor doesn't matter; a mouseleave to something else
-   * counts as a mouseleave on the editor itself.
-   *
-   * @param event
-   */
-  onMouseLeave: function(event) {
-    var el = this.editor.element[0];
-    if (event.relatedTarget != el && !$.contains(el, event.relatedTarget)) {
-      this.editor.element.trigger('mouseleave.edit');
-    }
-    event.stopPropagation();
-  },
-
-  /**
-   * Upon clicking "Save", trigger a custom event to save this property.
-   *
-   * @param event
-   */
-  onClickSave: function(event) {
-    event.stopPropagation();
-    event.preventDefault();
-    this.editor.options.widget.setState('saving', this.predicate);
-  },
-
-  /**
-   * Upon clicking "Close", trigger a custom event to stop editing.
-   *
-   * @param event
-   */
-  onClickClose: function(event) {
-    event.stopPropagation();
-    event.preventDefault();
-    this.editor.options.widget.setState('candidate', this.predicate, { reason: 'cancel' });
-  },
-
-  /**
-   * Indicates in the 'info' toolgroup that we're waiting for a server reponse.
-   *
-   * Prevents flickering loading indicator by only showing it after 0.6 seconds
-   * and if it is shown, only hiding it after another 0.6 seconds.
-   *
-   * @param bool enabled
-   *   Whether the loading indicator should be displayed or not.
-   */
-  setLoadingIndicator: function(enabled) {
-    var that = this;
-    if (enabled) {
-      this._loader = setTimeout(function() {
-        that.addClass('info', 'loading');
-        that._loaderVisibleStart = new Date().getTime();
-      }, 600);
-    }
-    else {
-      var currentTime = new Date().getTime();
-      clearTimeout(this._loader);
-      if (this._loaderVisibleStart) {
-        setTimeout(function() {
-          that.removeClass('info', 'loading');
-        }, this._loaderVisibleStart + 600 - currentTime);
-      }
-      this._loader = null;
-      this._loaderVisibleStart = 0;
-    }
-  },
-
-  startHighlight: function() {
-    // We get the label to show for this property from VIE's type system.
-    var label = this.predicate;
-    var attributeDef = this.entity.get('@type').attributes.get(this.predicate);
-    if (attributeDef && attributeDef.metadata) {
-      label = attributeDef.metadata.label;
-    }
-
-    this.$el
-      .addClass('edit-highlighted')
-      .find('.edit-toolbar')
-      // Append the "info" toolgroup into the toolbar.
-      .append(Drupal.theme('editToolgroup', {
-        classes: 'info edit-animate-only-background-and-padding',
-        buttons: [
-          { label: label, classes: 'blank-button label' }
-        ]
-      }));
-
-    // Animations.
-    var that = this;
-    setTimeout(function () {
-      that.show('info');
-    }, 0);
-  },
-
-  startEdit: function() {
-    this.$el
-      .addClass('edit-editing')
-      .find('.edit-toolbar')
-      // Append the "ops" toolgroup into the toolbar.
-      .append(Drupal.theme('editToolgroup', {
-        classes: 'ops',
-        buttons: [
-          { label: Drupal.t('Save'), type: 'submit', classes: 'field-save save gray-button' },
-          { label: '<span class="close">' + Drupal.t('Close') + '</span>', classes: 'field-close close gray-button' }
-        ]
-      }));
-    this.show('ops');
-  },
-
-  /**
-   * Retrieves a setting of the editor-specific Edit UI integration.
-   *
-   * @see Drupal.edit.util.getEditUISetting().
-   */
-  getEditUISetting: function(setting) {
-    return Drupal.edit.util.getEditUISetting(this.editor, setting);
-  },
-
-  /**
-   * Adjusts the toolbar to accomodate padding on the PropertyEditor widget.
-   *
-   * @see PropertyEditorDecorationView._pad().
-   */
-  _pad: function() {
-    // The whole toolbar must move to the top when the property's DOM element
-    // is displayed inline.
-    if (this.editor.element.css('display') === 'inline') {
-      this.$el.css('top', parseInt(this.$el.css('top'), 10) - 5 + 'px');
-    }
-
-    // The toolbar must move to the top and the left.
-    var $hf = this.$el.find('.edit-toolbar-heightfaker');
-    $hf.css({ bottom: '6px', left: '-5px' });
-
-    if (this.getEditUISetting('fullWidthToolbar')) {
-      $hf.css({ width: this.editor.element.width() + 10 });
-    }
-  },
-
-  /**
-   * Undoes the changes made by _pad().
-   *
-   * @see PropertyEditorDecorationView._unpad().
-   */
-  _unpad: function() {
-    // Move the toolbar back to its original position.
-    var $hf = this.$el.find('.edit-toolbar-heightfaker');
-    $hf.css({ bottom: '1px', left: '' });
-
-    if (this.getEditUISetting('fullWidthToolbar')) {
-      $hf.css({ width: '' });
-    }
-  },
-
-  insertWYSIWYGToolGroups: function() {
-    this.$el
-      .find('.edit-toolbar')
-      .append(Drupal.theme('editToolgroup', {
-        id: this.getFloatedWysiwygToolgroupId(),
-        classes: 'wysiwyg-floated',
-        buttons: []
-      }))
-      .append(Drupal.theme('editToolgroup', {
-        id: this.getMainWysiwygToolgroupId(),
-        classes: 'wysiwyg-main',
-        buttons: []
-      }));
-
-    // Animate the toolgroups into visibility.
-    var that = this;
-    setTimeout(function () {
-      that.show('wysiwyg-floated');
-      that.show('wysiwyg-main');
-    }, 0);
-  },
-
-  /**
-   * Renders the Toolbar's markup into the DOM.
-   *
-   * Note: depending on whether the 'display' property of the $el for which a
-   * toolbar is being inserted into the DOM, it will be inserted differently.
-   */
-  render: function () {
-    // Render toolbar.
-    this.setElement($(Drupal.theme('editToolbarContainer', {
-      id: this.getId()
-    })));
-
-    // Insert in DOM.
-    if (this.editor.element.css('display') === 'inline') {
-      this.$el.prependTo(this.editor.element.offsetParent());
-      var pos = this.editor.element.position();
-      this.$el.css('left', pos.left).css('top', pos.top);
-    }
-    else {
-      this.$el.insertBefore(this.editor.element);
-    }
-  },
-
-  /**
-   * Retrieves the ID for this toolbar's container.
-   *
-   * Only used to make sane hovering behavior possible.
-   *
-   * @return string
-   *   A string that can be used as the ID for this toolbar's container.
-   */
-  getId: function () {
-    return 'edit-toolbar-for-' + this._id;
-  },
-
-  /**
-   * Retrieves the ID for this toolbar's floating WYSIWYG toolgroup.
-   *
-   * Used to provide an abstraction for any WYSIWYG editor to plug in.
-   *
-   * @return string
-   *   A string that can be used as the ID.
-   */
-  getFloatedWysiwygToolgroupId: function () {
-    return 'edit-wysiwyg-floated-toolgroup-for-' + this._id;
-  },
-
-  /**
-   * Retrieves the ID for this toolbar's main WYSIWYG toolgroup.
-   *
-   * Used to provide an abstraction for any WYSIWYG editor to plug in.
-   *
-   * @return string
-   *   A string that can be used as the ID.
-   */
-  getMainWysiwygToolgroupId: function () {
-    return 'edit-wysiwyg-main-toolgroup-for-' + this._id;
-  },
-
-  /**
-   * Shows a toolgroup.
-   *
-   * @param string toolgroup
-   *   A toolgroup name.
-   */
-  show: function (toolgroup) {
-    this._find(toolgroup).removeClass('edit-animate-invisible');
-  },
-
-  /**
-   * Adds classes to a toolgroup.
-   *
-   * @param string toolgroup
-   *   A toolgroup name.
-   */
-  addClass: function (toolgroup, classes) {
-    this._find(toolgroup).addClass(classes);
-  },
-
-  /**
-   * Removes classes from a toolgroup.
-   *
-   * @param string toolgroup
-   *   A toolgroup name.
-   */
-  removeClass: function (toolgroup, classes) {
-    this._find(toolgroup).removeClass(classes);
-  },
-
-  /**
-   * Finds a toolgroup.
-   *
-   * @param string toolgroup
-   *   A toolgroup name.
-   */
-  _find: function (toolgroup) {
-    return this.$el.find('.edit-toolbar .edit-toolgroup.' + toolgroup);
-  }
-});
-
-})(jQuery, _, Backbone, Drupal);
diff --git a/core/modules/editor/js/editor.createjs.js b/core/modules/editor/js/editor.createjs.js
index de15c77..0b734fb 100644
--- a/core/modules/editor/js/editor.createjs.js
+++ b/core/modules/editor/js/editor.createjs.js
@@ -12,13 +12,11 @@
  *  - Drupal.editors.magical.onChange()
  *  - Drupal.editors.magical.detach()
  */
-(function (jQuery, Drupal, drupalSettings) {
+(function ($, Drupal, drupalSettings) {
 
 "use strict";
 
-// @todo D8: use jQuery UI Widget bridging.
-// @see http://drupal.org/node/1874934#comment-7124904
-jQuery.widget('Midgard.editor', jQuery.Midgard.direct, {
+Drupal.edit.editors.editor = Backbone.View.extend({
 
   textFormat: null,
   textFormatHasTransformations: null,
@@ -32,29 +30,28 @@ jQuery.widget('Midgard.editor', jQuery.Midgard.direct, {
   },
 
   /**
-   * Implements jQuery.widget._init.
-   *
-   * @todo D8: Remove this.
-   * @see http://drupal.org/node/1874934
-   */
-  _init: function () {},
-
-  /**
    * Implements Create.editWidget._initialize.
    */
-  _initialize: function () {
-    var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
-    var metadata = Drupal.edit.metadataCache[propertyID].custom;
-
+  initialize: function () {
+    var editID = this.model.get('editID');
+    var metadata = Drupal.edit.metadataCache[editID].custom;
     this.textFormat = drupalSettings.editor.formats[metadata.format];
     this.textFormatHasTransformations = metadata.formatHasTransformations;
     this.textEditor = Drupal.editors[this.textFormat.editor];
+
+    this.model.on('change:state', this.stateChange, this);
   },
 
   /**
-   * Implements Create.editWidget.stateChange.
+   * 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.
    */
-  stateChange: function (from, to) {
+  stateChange: function (model, state) {
+    var from = model.previous('state');
+    var to = state;
     var that = this;
     switch (to) {
       case 'inactive':
@@ -64,7 +61,7 @@ jQuery.widget('Midgard.editor', jQuery.Midgard.direct, {
         // Detach the text editor when entering the 'candidate' state from one
         // of the states where it could have been attached.
         if (from !== 'inactive' && from !== 'highlighted') {
-            this.textEditor.detach(this.element.get(0), this.textFormat);
+            this.textEditor.detach(this.$el.get(0), this.textFormat);
         }
         break;
 
@@ -76,29 +73,40 @@ jQuery.widget('Midgard.editor', jQuery.Midgard.direct, {
         // text of this field, then we'll need to load a re-processed version of
         // it without the transformation filters.
         if (this.textFormatHasTransformations) {
-          var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
-          this._getUntransformedText(propertyID, this.element, function (untransformedText) {
+          var editID = this.model.get('editID');
+          this._getUntransformedText(editID, this.$el, function (untransformedText) {
+            // @todo update this
+            debugger;
             that.element.html(untransformedText);
-            that.options.activated();
+            that.model.set('state', 'active');
           });
         }
         // When no transformation filters have been applied: start WYSIWYG
         // editing immediately!
         else {
-          this.options.activated();
+          // As soon as the current state change has propagated, apply this one.
+          // @see http://jsfiddle.net/5MVzp/2/ vs. http://jsfiddle.net/5MVzp/3/
+          _.defer(function() {
+            that.model.set('state', 'active');
+          });
         }
         break;
 
       case 'active':
         this.textEditor.attachInlineEditor(
-          this.element.get(0),
+          this.$el.get(0),
           this.textFormat,
-          this.toolbarView.getMainWysiwygToolgroupId(),
-          this.toolbarView.getFloatedWysiwygToolgroupId()
+          this.model.get('toolbarView').getMainWysiwygToolgroupId(),
+          this.model.get('toolbarView').getFloatedWysiwygToolgroupId()
         );
         // Set the state to 'changed' whenever the content has changed.
-        this.textEditor.onChange(this.element.get(0), function (html) {
-          that.options.changed(html);
+        this.textEditor.onChange(this.$el.get(0), function (value) {
+          that.model.set('state', 'changed');
+
+          // @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', value);
         });
         break;
 
diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js
index 6483170..f07aa73 100644
--- a/core/modules/editor/js/editor.js
+++ b/core/modules/editor/js/editor.js
@@ -83,8 +83,12 @@ Drupal.behaviors.editor = {
       var $this = $(this);
       var activeFormatID = $this.val();
       var field = behavior.findFieldForFormatSelector($this);
-
-      Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger);
+      if ('activeFormatID' in settings.editor.formats) {
+        Drupal.editorDetach(field, settings.editor.formats[activeFormatID], trigger);
+      }
+      else {
+        console.log('%c editor.js: The format ' + activeFormatID + ' does not have an editor.', 'background-color: red; color: white;');
+      }
     });
   },
 
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index eee53b1..4185d43 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2108,20 +2108,6 @@ function system_library_info() {
     ),
   );
 
-  // Create.
-  $libraries['create.editonly'] = array(
-    'title' => 'Create.js edit-only (editing features only)',
-    'website' => 'http://backbonejs.org/',
-    'version' => '1.0.0-dev',
-    'js' => array(
-      'core/misc/create/create-editonly.js' => array('group' => JS_LIBRARY),
-    ),
-    'dependencies' => array(
-      array('system', 'vie.core'),
-      array('system', 'jquery.ui.widget'),
-    ),
-  );
-
   // Cookie.
   $libraries['jquery.cookie'] = array(
     'title' => 'Cookie',
-- 
1.7.10.4

