 core/modules/edit/js/models/EntityModel.js |   23 ++++++++++++--
 core/modules/edit/js/util.js               |   46 +++++++++++++++++++++++++++-
 2 files changed, 65 insertions(+), 4 deletions(-)

diff --git a/core/modules/edit/js/models/EntityModel.js b/core/modules/edit/js/models/EntityModel.js
index 6fefaf3..d735734 100644
--- a/core/modules/edit/js/models/EntityModel.js
+++ b/core/modules/edit/js/models/EntityModel.js
@@ -288,6 +288,16 @@ Drupal.edit.EntityModel = Backbone.Model.extend({
                 'state': 'deactivating',
                 'isCommitting' : false
               }, {'saved': true});
+            },
+            error: function () {
+              // Reset the "isCommitting" mutex.
+              entityModel.set('isCommitting', false);
+              // Change the state back to "opened", to allow the user to hit the
+              // "Save" button again.
+              entityModel.set('state', 'opened', { reason: 'networkerror' });
+              // Show a modal to inform the user of the network error.
+              var message = Drupal.t('Your changes to <q>@entity-title</q> could not be saved, either due to a website problem or a network connection problem.<br>Please try again.', { '@entity-title' : entityModel.get('label') })
+              Drupal.edit.util.networkErrorModal(Drupal.t('Sorry!'), message);
             }
           });
         }
@@ -337,7 +347,13 @@ Drupal.edit.EntityModel = Backbone.Model.extend({
     var entitySaverAjax = new Drupal.ajax(id, $el, {
       url: Drupal.url('edit/entity/' + entityModel.id),
       event: 'edit-save.edit',
-      progress: { type: 'none' }
+      progress: { type: 'none' },
+      error: function () {
+        $el.off('edit-save.edit');
+        // Let the Drupal.edit.EntityModel Backbone model's error() method
+        // handle errors.
+        options.error.call(entityModel);
+      }
     });
     // Entity saved successfully.
     entitySaverAjax.commands.editEntitySaved = function(ajax, response, status) {
@@ -430,8 +446,9 @@ Drupal.edit.EntityModel = Backbone.Model.extend({
         accept = true;
       }
       // Allow: committing -> opened.
-      // Necessary to be able to correct an invalid field.
-      else if (from === 'committing' && to === 'opened' && context.reason && context.reason === 'invalid') {
+      // Necessary to be able to correct an invalid field, or to hit the "Save"
+      // button again after a server/network error.
+      else if (from === 'committing' && to === 'opened' && context.reason && (context.reason === 'invalid' || context.reason === 'networkerror')) {
         accept = true;
       }
       // Allow: deactivating -> opened.
diff --git a/core/modules/edit/js/util.js b/core/modules/edit/js/util.js
index 2663077..7c0f190 100644
--- a/core/modules/edit/js/util.js
+++ b/core/modules/edit/js/util.js
@@ -31,6 +31,37 @@ Drupal.edit.util.buildUrl = function (id, urlFormat) {
   });
 };
 
+/**
+ * Shows a network error modal dialog.
+ *
+ * @param String title
+ *   The title to use in the modal dialog.
+ * @param String message
+ *   The message to use in the modal dialog.
+ */
+Drupal.edit.util.networkErrorModal = function (title, message) {
+  var networkErrorModal = Drupal.dialog('<div>' + message + '</div>', {
+    title: title,
+    dialogClass: 'edit-network-error',
+    buttons: [
+      {
+        text: Drupal.t('OK'),
+        click: function() {
+          networkErrorModal.close();
+        }
+      }
+    ],
+    create: function () {
+      $(this).parent().find('.ui-dialog-titlebar-close').remove();
+    },
+    close: function (event) {
+      // Automatically destroy the DOM element that was used for the dialog.
+      $(event.target).remove();
+    }
+  });
+  networkErrorModal.showModal();
+};
+
 Drupal.edit.util.form = {
   /**
    * Loads a form, calls a callback to insert.
@@ -65,7 +96,20 @@ Drupal.edit.util.form = {
         nocssjs : options.nocssjs,
         reset : options.reset
       },
-      progress: { type : null } // No progress indicator.
+      progress: { type : null }, // No progress indicator.
+      error: function (xhr, url) {
+        $el.off('edit-internal.edit');
+
+        // Show a modal to inform the user of the network error.
+        var fieldLabel = Drupal.edit.metadata.get(fieldID, 'label');
+        var message = Drupal.t('Could not load the form for <q>@field-label</q>, either due to a website problem or a network connection problem.<br>Please try again.', { '@field-label' : fieldLabel });
+        Drupal.edit.util.networkErrorModal(Drupal.t('Sorry!'), message);
+
+        // Change the state back to "candidate", to allow the user to start
+        // in-place editing of the field again.
+        var fieldModel = Drupal.edit.app.model.get('activeField');
+        fieldModel.set('state', 'candidate');
+      }
     });
     // Implement a scoped editFieldForm AJAX command: calls the callback.
     formLoaderAjax.commands.editFieldForm = function (ajax, response, status) {
