From b628b13a0a587b04f0262da740daf1c0b8bd045d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= <splendidnoise@gmail.com>
Date: Fri, 3 May 2013 11:38:58 -0400
Subject: [PATCH] Squashed commit of the following:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

commit c1d27c949c5f4519a2cdfe46934d1e90e07f2e33
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Thu May 2 15:04:36 2013 -0400

    Position the toolbar after a field closes.

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

commit 39d7b9bccee5aff877120fe964fcdb226f516e7d
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Thu May 2 15:00:02 2013 -0400

    The field label now attaches correctly.

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

commit 4b1d1c33196f5ca784cd5e16146d16e1334a1c5c
Author: J. Renée Beach <splendidnoise@gmail.com>
Date:   Thu May 2 14:35:42 2013 -0400

    entity toolbar

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

Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>
---
 core/modules/edit/css/edit.css                     |   48 +--
 core/modules/edit/edit.module                      |    3 +
 core/modules/edit/js/edit.js                       |    5 +-
 core/modules/edit/js/models/EntityModel.js         |   65 +++-
 core/modules/edit/js/theme.js                      |   29 +-
 core/modules/edit/js/views/AppView.js              |   27 +-
 core/modules/edit/js/views/EditorDecorationView.js |   28 ++
 core/modules/edit/js/views/EntityToolbarView.js    |  384 ++++++++++++++++++++
 core/modules/edit/js/views/FieldToolbarView.js     |  247 ++-----------
 .../editor/js/editor.formattedTextEditor.js        |    3 +-
 10 files changed, 576 insertions(+), 263 deletions(-)
 create mode 100644 core/modules/edit/js/views/EntityToolbarView.js

diff --git a/core/modules/edit/css/edit.css b/core/modules/edit/css/edit.css
index 37e7578..b10d11f 100644
--- a/core/modules/edit/css/edit.css
+++ b/core/modules/edit/css/edit.css
@@ -61,17 +61,19 @@
           transition: opacity .2s ease;
 }
 
-.edit-animate-only-background-and-padding {
-  -webkit-transition: background, padding .2s ease;
-     -moz-transition: background, padding .2s ease;
-      -ms-transition: background, padding .2s ease;
-       -o-transition: background, padding .2s ease;
-          transition: background, padding .2s ease;
+/**
+ * Entity toolbar.
+ */
+.edit-toolbar-container {
+  background-color: white;
+  border: 1px solid #ababab;
+  position: absolute;
+  -webkit-transition: all 0.2s;
+  transition: all 0.2s;
+  width: 20em;
+  z-index: 350;
 }
 
-
-
-
 /**
  * Candidate editables + editables being edited.
  *
@@ -214,8 +216,6 @@
 .edit-toolbar-container,
 .edit-form-container {
   position: relative;
-  padding: 0;
-  border: 0;
   margin: 0;
   vertical-align: baseline;
   z-index: 310;
@@ -229,38 +229,12 @@
           user-select: none;
 }
 
-.edit-toolbar-heightfaker {
-  height: auto;
-  position: absolute;
-  bottom: 1px;
-  box-shadow: 0 0 1px 1px #0199ff, 0 0 3px 3px rgba(153, 153, 153, .5);
-  background: #fff;
-  display: none;
-}
-.edit-highlighted .edit-toolbar-heightfaker {
-  display: block;
-}
-
 /* The toolbar; these are not necessarily visible. */
 .edit-toolbar {
   position: relative;
   height: 100%;
   font-family: 'Droid sans', 'Lucida Grande', sans-serif;
 }
-.edit-toolbar-heightfaker {
-  clip: rect(-1000px, 1000px, auto, -1000px); /* Remove bottom box-shadow. */
-}
-/* Exception: when the toolbar is instructed to be "full width". */
-.edit-toolbar-fullwidth .edit-toolbar-heightfaker {
-  width: 100%;
-  clip: auto;
-}
-
-
-/* The toolbar contains toolgroups; these are visible. */
-.edit-toolgroup {
-  float: left; /* LTR */
-}
 
 /* Info toolgroup. */
 .edit-toolgroup.info {
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index bad1f68..caeb7ff 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -78,6 +78,7 @@ function edit_library_info() {
       // Views.
       $path . '/js/views/AppView.js' => $options,
       $path . '/js/views/EditorDecorationView.js' => $options,
+      $path . '/js/views/EntityToolbarView.js' => $options,
       $path . '/js/views/ContextualLinkView.js' => $options,
       $path . '/js/views/ModalView.js' => $options,
       $path . '/js/views/FieldToolbarView.js' => $options,
@@ -106,8 +107,10 @@ function edit_library_info() {
       array('system', 'underscore'),
       array('system', 'backbone'),
       array('system', 'jquery.form'),
+      array('system', 'jquery.ui.position'),
       array('system', 'drupal.form'),
       array('system', 'drupal.ajax'),
+      array('system', 'drupal.debounce'),
       array('system', 'drupalSettings'),
     ),
   );
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
index 9b032b3..f0a99a5 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -124,7 +124,8 @@ Drupal.behaviors.edit = {
       var id = $this.data('edit-entity');
 
       entityModel = new Drupal.edit.EntityModel({
-        id: id
+        id: id,
+        el: this
       });
       that.collections.entities.add(entityModel);
 
@@ -191,6 +192,8 @@ Drupal.behaviors.edit = {
         // trigger an "update" method rather than a "create" method.
         id: editID || null,
         editID: editID || null,
+        // Store the field in a collection in its entity's model.
+        collection: entity.get('fields'),
         label: Drupal.edit.metadataCache[editID].label,
         editor: Drupal.edit.metadataCache[editID].editor,
         html: $element[0].outerHTML,
diff --git a/core/modules/edit/js/models/EntityModel.js b/core/modules/edit/js/models/EntityModel.js
index 047ffe7..929ad26 100644
--- a/core/modules/edit/js/models/EntityModel.js
+++ b/core/modules/edit/js/models/EntityModel.js
@@ -17,10 +17,73 @@ $.extend(Drupal.edit, {
       // edited.
       isActive: false,
       // A Drupal.edit.FieldCollection for all fields of this entity.
-      fields: null
+      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 () {},
+          get: function () {}
+        };
+      }())
     },
+
+    /**
+     *
+     */
     initialize: function () {
+
+      this.on('viewChanged', this.viewChange, this);
+
       this.set('fields', new Drupal.edit.FieldCollection());
+
+      this.get('fields').on('change:state', this.fieldStateChange, 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.
+     */
+    fieldStateChange: function (model, state) {
+      var from = model.previous('state');
+      var to = state;
+      switch (to) {
+        case 'inactive':
+          this.set('fieldModel', this.defaults.fieldModel);
+          break;
+        case 'candidate':
+          break;
+        case 'highlighted':
+          this.set('fieldModel', model);
+          break;
+        case 'activating':
+          break;
+        case 'active':
+          break;
+        case 'changed':
+          break;
+        case 'saving':
+          break;
+        case 'saved':
+          break;
+        case 'invalid':
+          break;
+        default:
+          break;
+      }
+    },
+
+    /**
+     *
+     */
+    viewChange: function (view) {
+      this.trigger('fieldViewChange', view);
     }
   })
 });
diff --git a/core/modules/edit/js/theme.js b/core/modules/edit/js/theme.js
index 7bef553..46d5a49 100644
--- a/core/modules/edit/js/theme.js
+++ b/core/modules/edit/js/theme.js
@@ -64,17 +64,31 @@ Drupal.theme.editModal = function(settings) {
  * @return
  *   The corresponding HTML.
  */
-Drupal.theme.editToolbarContainer = function(settings) {
+Drupal.theme.editEntityToolbar = function(settings) {
   var html = '';
-  html += '<div id="' + settings.id + '" class="edit-toolbar-container">';
-  html += '  <div class="edit-toolbar-heightfaker edit-animate-fast">';
-  html += '    <div class="edit-toolbar primary" />';
-  html += '  </div>';
+  html += '<div id="' + settings.id + '" class="edit-toolbar-container clearfix">';
+  html += '<div class="edit-toolbar edit-toolbar-entity clearfix">';
+  html += '<div class="edit-toolbar-label" />';
+  html += '</div>';
+  html += '<div class="edit-toolbar edit-toolbar-field clearfix" />';
   html += '</div>';
   return html;
 };
 
 /**
+ * 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.editFieldToolbar = function(settings) {
+  return '<div id="' + settings.id + '" class="edit-toolbar" />';
+};
+
+/**
  * Theme function for a toolbar toolgroup of the Edit module.
  *
  * @param settings
@@ -86,9 +100,10 @@ Drupal.theme.editToolbarContainer = function(settings) {
  *   The corresponding HTML.
  */
 Drupal.theme.editToolgroup = function(settings) {
-  var classes = 'edit-toolgroup edit-animate-slow edit-animate-invisible edit-animate-delay-veryfast';
+  var classes = (settings.classes || []);
+  classes.unshift('edit-toolgroup');
   var html = '';
-  html += '<div class="' + classes + ' ' + settings.classes + '"';
+  html += '<div class="' + classes.join(' ') + '"';
   if (settings.id) {
     html += ' id="' + settings.id + '"';
   }
diff --git a/core/modules/edit/js/views/AppView.js b/core/modules/edit/js/views/AppView.js
index 742dcc5..ace7abc 100644
--- a/core/modules/edit/js/views/AppView.js
+++ b/core/modules/edit/js/views/AppView.js
@@ -50,16 +50,32 @@ $.extend(Drupal.edit, {
     appStateChange: function (entityModel, isActive) {
       var app = this;
       if (isActive) {
+        // Create an entity toolbar.
+        var entityToolbar = new Drupal.edit.EntityToolbarView({
+          el: entityModel.get('el'),
+          model: entityModel
+        });
+        entityModel.set('entityToolbar', entityToolbar);
+
+        // Get the field toolbar DOM root from the entity toolbar.
+        var fieldToolbarRoot = entityToolbar.getToolbarRoot();
+        var fieldLabelRoot = entityToolbar.getLabelRoot();
+
         // 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);
+          app.decorate(entityModel, fieldModel, {toolbar: fieldToolbarRoot, label: fieldLabelRoot});
           // Second, change the field's state.
           fieldModel.set('state', 'candidate');
         });
       }
       else {
+        // Remove the entity toolbar.
+        var entityToolbar = entityModel.get('entityToolbar');
+        entityToolbar.remove();
+        entityModel.unset('entityToolbar');
+
         // Move all fields of this entity from whatever state they are in to
         // the 'inactive' state.
         entityModel.get('fields').each(function (fieldModel) {
@@ -175,7 +191,7 @@ $.extend(Drupal.edit, {
     },
 
     // @todo rename to decorateField
-    decorate: function (fieldModel) {
+    decorate: function (entityModel, fieldModel, attachmentPoints) {
       var editID = fieldModel.get('editID');
       var $el = fieldModel.get('$el');
 
@@ -191,8 +207,10 @@ $.extend(Drupal.edit, {
       // They are a sibling element before the editor's DOM element.
       var toolbarView = new Drupal.edit.FieldToolbarView({
         model: fieldModel,
+        el: attachmentPoints.toolbar,
         $field: $el,
-        editorView: editorView
+        editorView: editorView,
+        entityModel: entityModel
       });
 
       // Decorate the editor's DOM element depending on its state.
@@ -200,7 +218,8 @@ $.extend(Drupal.edit, {
         el: $el,
         model: fieldModel,
         editorView: editorView,
-        toolbarId: toolbarView.getId()
+        toolbarId: toolbarView.getId(),
+        labelEl: attachmentPoints.label
       });
 
       // Create references in the field model; necessary for undecorate() and
diff --git a/core/modules/edit/js/views/EditorDecorationView.js b/core/modules/edit/js/views/EditorDecorationView.js
index 41647df..3dff6c8 100644
--- a/core/modules/edit/js/views/EditorDecorationView.js
+++ b/core/modules/edit/js/views/EditorDecorationView.js
@@ -36,6 +36,8 @@ Drupal.edit.EditorDecorationView = Backbone.View.extend({
 
     this.toolbarId = options.toolbarId;
 
+    this.$labelEl = $(options.labelEl);
+
     this.model.on('change:state', this.stateChange, this);
   },
 
@@ -61,6 +63,7 @@ Drupal.edit.EditorDecorationView = Backbone.View.extend({
             this.stopEdit();
           }
         }
+        this.detachLabel();
         break;
       case 'highlighted':
         this.startHighlight();
@@ -69,6 +72,7 @@ Drupal.edit.EditorDecorationView = Backbone.View.extend({
         // NOTE: this state is not used by every editor! It's only used by those
         // that need to interact with the server.
         this.prepareEdit();
+        this.attachLabel();
         break;
       case 'active':
         if (from !== 'activating') {
@@ -194,6 +198,30 @@ Drupal.edit.EditorDecorationView = Backbone.View.extend({
   },
 
   /**
+   *
+   */
+  attachLabel: function () {
+    // Retrieve the label to show for this field.
+    var label = this.model.get('label');
+
+    this.$labelEl
+      // Append the "info" toolgroup into the toolbar.
+      .append(Drupal.theme('editToolgroup', {
+        classes: ['info'],
+        buttons: [
+          { label: label, classes: 'blank-button label' }
+        ]
+      }));
+  },
+
+  /**
+   *
+   */
+  detachLabel: function () {
+    this.$labelEl.find('.edit-toolgroup.info').remove();
+  },
+
+  /**
    * Retrieves a setting of the editor-specific Edit UI integration.
    *
    * @param String setting
diff --git a/core/modules/edit/js/views/EntityToolbarView.js b/core/modules/edit/js/views/EntityToolbarView.js
new file mode 100644
index 0000000..8df0083
--- /dev/null
+++ b/core/modules/edit/js/views/EntityToolbarView.js
@@ -0,0 +1,384 @@
+/**
+ * @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,
+  _fieldToolbarRoot: null,
+  _fieldLabelRoot: null,
+
+  events: function () {
+    var map = {
+      'click.edit button.field-save': 'onClickSave',
+      'click.edit button.field-close': 'onClickClose',
+      'click.edit button.label': 'onClickInfoLabel'
+    }
+    return map;
+  },
+
+  /**
+   * Implements Backbone.View.prototype.initialize().
+   */
+  initialize: function (options) {
+    var that = this;
+
+    this.model.on('change:isActive', this.render, this);
+    this.model.on('fieldViewChange', this.fieldViewChangeHandler, 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.buildToolbarEl();
+    this._fieldToolbarRoot = this.$el.find('.edit-toolbar-field').get(0);
+    this._fieldLabelRoot = this.$el.find('.edit-toolbar-label').get(0);
+
+    this._loader = null;
+    this._loaderVisibleStart = 0;
+
+    this.render();
+  },
+
+  /**
+   * 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);
+      }
+
+      this.show('ops');
+      // If render is being called and the toolbar is already visible, just
+      // reposition it.
+      this.position();
+    }
+    else {
+      this.remove();
+    }
+
+    return this;
+  },
+
+  /**
+   *
+   */
+  windowChangeHandler: function (event) {
+    this.position();
+  },
+
+  /**
+   *
+   */
+  fieldViewChangeHandler: function (view) {
+    this.render(this, view);
+  },
+
+  /**
+   * Uses the jQuery.ui.position() method to position the entity toolbar.
+   */
+  position: function (element) {
+    // Vary the edge of the positioning according to the direction of language
+    // in the document.
+    var edge = (document.documentElement.dir === 'rtl') ? 'right' : 'left';
+    // If a field in this entity is active, position against it.
+    var fieldModel = this.model.get('fieldModel');
+    var field = fieldModel && fieldModel.collection && fieldModel.collection.where({'state': 'active'})[0];
+    var $fieldElement = field && field.get('$el');
+    // Prefer the specified element from the parameters, then the acive field
+    // and finally the entity itself to determine the position of the toolbar.
+    var of = element || $fieldElement || this.$entity;
+    // Uses the jQuery.ui.position() method.
+    this.$el
+      .position({
+        my: edge + ' bottom',
+        at: edge + ' 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, options) {
+    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':
+        // Position the toolbar against the entity.
+        this.position();
+        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':
+        break;
+      case 'activating':
+        this.setLoadingIndicator(true);
+        break;
+      case 'active':
+        this.startEdit();
+        this.setLoadingIndicator(false);
+        // Position the toolbar against the active field.
+        this.position();
+        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();
+    var fields = this.model.get('fields');
+    var activeFields = fields.where({'state': 'active'});
+    var changedFieldds = fields.where({'state': 'changed'});
+    // If a field is active, then set it back to highlighted state. If not,
+    // close the editing of this entity.
+    if (activeFields.length > 0) {
+      fields.each(function (fieldModel) {
+        fieldModel.set('state', 'candidate');
+      });
+    }
+    else {
+      this.model.set('isActive', false);
+      // Set all the fields back to candidate status.
+      fields.each(function (fieldModel) {
+        fieldModel.set('state', 'inactive');
+      });
+    }
+  },
+
+
+  /**
+   * 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) {
+    var fieldModel = this.model.get('fieldModel');
+    if (fieldModel) {
+      var $field = this.model.get('fieldModel').get('$el');
+      if (event.relatedTarget !== $field[0] && !$.contains($field, event.relatedTarget)) {
+        // @todo, this is triggered, but nothing is listening. This method was
+        // taken from ToolbarView.js
+        $field.trigger('mouseleave.edit');
+      }
+      event.stopPropagation();
+    }
+  },
+
+  /**
+   *
+   */
+  buildToolbarEl: function () {
+    var $toolbar;
+    $toolbar = $(Drupal.theme('editEntityToolbar', {
+      id: 'edit-entity-toolbar'
+    }));
+
+    $toolbar
+      .find('.edit-toolbar-entity')
+      // 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);
+  },
+
+  /**
+   *
+   */
+  getToolbarRoot: function () {
+    return this._fieldToolbarRoot;
+  },
+
+  /**
+   *
+   */
+  getLabelRoot: function () {
+    return this._fieldLabelRoot;
+  },
+
+  /**
+   *
+   */
+  startEdit: function () {
+    this.$el.addClass('edit-editing');
+  },
+
+  /**
+   * 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.$el.removeClass('edit-animate-invisible');
+  }
+});
+
+})(jQuery, Backbone, Drupal, Drupal.debounce);
diff --git a/core/modules/edit/js/views/FieldToolbarView.js b/core/modules/edit/js/views/FieldToolbarView.js
index a854312..20e7ae5 100644
--- a/core/modules/edit/js/views/FieldToolbarView.js
+++ b/core/modules/edit/js/views/FieldToolbarView.js
@@ -12,32 +12,20 @@
 Drupal.edit.FieldToolbarView = Backbone.View.extend({
   $field: 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.View.prototype.initialize().
    */
   initialize: function (options) {
     this.$field = options.$field;
     this.editorView = options.editorView;
+    this.entityModel = options.entityModel;
 
-    this._loader = null;
-    this._loaderVisibleStart = 0;
+    this.model.on('change:state', this.stateChange, this);
 
     // Generate a DOM-compatible ID for the form container DOM element.
     this._id = 'edit-toolbar-for-' + this.model.get('editID').replace(/\//g, '_');
-
-    this.model.on('change:state', this.stateChange, this);
   },
 
   /**
@@ -48,18 +36,16 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
    */
   render: function () {
     // Render toolbar.
-    this.setElement($(Drupal.theme('editToolbarContainer', {
+    this.$toolbar = $(Drupal.theme('editFieldToolbar', {
       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);
+      this.$toolbar.prependTo(this.el);
     }
     else {
-      this.$el.insertBefore(this.$field);
+      this.$toolbar.insertBefore(this.$field);
     }
 
     return this;
@@ -72,45 +58,17 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
    * @param String state
    *   The state of the associated field. One of Drupal.edit.FieldModel.states.
    */
-  stateChange: function (model, state) {
+  stateChange: function (model, state, options) {
     var from = model.previous('state');
     var to = state;
     switch (to) {
       case 'inactive':
-        if (from) {
-          this.remove();
-          if (this.model.get('editor') !== 'form') {
-            Backbone.syncDirectCleanUp();
-          }
-        }
         break;
       case 'candidate':
-        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);
         if (this.getEditUISetting('fullWidthToolbar')) {
           this.$el.addClass('edit-toolbar-fullwidth');
         }
@@ -122,147 +80,20 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
           this.insertWYSIWYGToolGroups();
         }
         break;
+      case 'active':
+        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;
     }
   },
 
   /**
-   * 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();
-  },
-
-  /**
-   * 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.set('state', 'candidate', { 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 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;
-    }
-  },
-
-  /**
-   *
-   */
-  startHighlight: function () {
-    // Retrieve the lavel 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);
-  },
-
-  /**
-   *
-   */
-  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.
    *
    * @param String setting
@@ -279,6 +110,7 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
    * @see EditorDecorationView._pad().
    */
   _pad: function () {
+    return;
     // The whole toolbar must move to the top when the property's DOM element
     // is displayed inline.
     if (this.$field.css('display') === 'inline') {
@@ -300,6 +132,7 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
    * @see EditorDecorationView._unpad().
    */
   _unpad: function () {
+    return;
     // Move the toolbar back to its original position.
     var $hf = this.$el.find('.edit-toolbar-heightfaker');
     $hf.css({ bottom: '1px', left: '' });
@@ -314,24 +147,20 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
    */
   insertWYSIWYGToolGroups: function () {
     this.$el
-      .find('.edit-toolbar')
       .append(Drupal.theme('editToolgroup', {
         id: this.getFloatedWysiwygToolgroupId(),
-        classes: 'wysiwyg-floated',
+        classes: ['wysiwyg-floated', 'edit-animate-slow', 'edit-animate-invisible', 'edit-animate-delay-veryfast'],
         buttons: []
       }))
       .append(Drupal.theme('editToolgroup', {
         id: this.getMainWysiwygToolgroupId(),
-        classes: 'wysiwyg-main',
+        classes: ['wysiwyg-main', 'edit-animate-slow', 'edit-animate-invisible', 'edit-animate-delay-veryfast'],
         buttons: []
       }));
 
     // Animate the toolgroups into visibility.
-    var that = this;
-    setTimeout(function () {
-      that.show('wysiwyg-floated');
-      that.show('wysiwyg-main');
-    }, 0);
+    this.show('wysiwyg-floated');
+    this.show('wysiwyg-main');
   },
 
   /**
@@ -371,43 +200,37 @@ Drupal.edit.FieldToolbarView = Backbone.View.extend({
   },
 
   /**
-   * 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.
+   * Finds a toolgroup.
    *
    * @param String toolgroup
    *   A toolgroup name.
    */
-  removeClass: function (toolgroup, classes) {
-    this._find(toolgroup).removeClass(classes);
+  _find: function (toolgroup) {
+    return this.$el.find('.edit-toolgroup.' + toolgroup);
   },
 
   /**
-   * Finds a toolgroup.
+   * Shows a toolgroup.
    *
    * @param String toolgroup
    *   A toolgroup name.
    */
-  _find: function (toolgroup) {
-    return this.$el.find('.edit-toolbar .edit-toolgroup.' + toolgroup);
+  show: function (toolgroup) {
+    var that = this;
+    var $group = this._find(toolgroup);
+    // Attach a transitionEnd event handler to the toolbar group so that update
+    // events can be triggered after the animations have ended.
+    $group
+      .on(Drupal.edit.util.constants.transitionEnd, function (event) {
+          that.entityModel.trigger('viewChanged', that);
+          $group.off(Drupal.edit.util.constants.transitionEnd);
+      });
+    // The call to remove the class and start the animation must be started in
+    // the next animation frame or the event handler attached above won't be
+    // triggered.
+    window.setTimeout(function () {
+      $group.removeClass('edit-animate-invisible');
+    }, 0);
   }
 });
 
diff --git a/core/modules/editor/js/editor.formattedTextEditor.js b/core/modules/editor/js/editor.formattedTextEditor.js
index 2dcdf92..016310f 100644
--- a/core/modules/editor/js/editor.formattedTextEditor.js
+++ b/core/modules/editor/js/editor.formattedTextEditor.js
@@ -51,8 +51,8 @@ Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
     var to = state;
     switch (to) {
       case 'inactive':
+        this.model.off();
         break;
-
       case 'candidate':
         // Detach the text editor when entering the 'candidate' state from one
         // of the states where it could have been attached.
@@ -68,6 +68,7 @@ Drupal.edit.editors.editor = Drupal.edit.EditorView.extend({
         break;
 
       case 'activating':
+        console.log(this.cid);
         // When transformation filters have been been applied to the processed
         // text of this field, then we'll need to load a re-processed version of
         // it without the transformation filters.
-- 
1.7.10.4

