core/includes/form.inc | 1 + core/misc/form.autosave.js | 66 +++++++++++++++++++++++++++----- core/misc/garlic/garlic.js | 12 ++++-- core/modules/overlay/overlay-parent.js | 8 +--- core/modules/system/system.module | 19 +++++++-- 5 files changed, 82 insertions(+), 24 deletions(-) diff --git a/core/includes/form.inc b/core/includes/form.inc index 4540ab3..7ce62f7 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -974,6 +974,7 @@ function drupal_process_form($form_id, &$form, &$form_state) { } if (!empty($form['#autosave'])) { + $form['#attached']['library'][] = array('system', 'drupal.formAutoSave'); $form['#attached']['js'][] = array( 'data' => array( 'formAutoSave' => array( diff --git a/core/misc/form.autosave.js b/core/misc/form.autosave.js index 3e62618..2e0f4c3 100644 --- a/core/misc/form.autosave.js +++ b/core/misc/form.autosave.js @@ -1,4 +1,4 @@ -(function ($, Drupal) { +(function ($, Drupal, drupalSettings) { "use strict"; @@ -7,19 +7,65 @@ */ Drupal.behaviors.formAutoSave = { attach: function (context, settings) { + var that = this; for (var formID in settings.formAutoSave) { if (settings.formAutoSave.hasOwnProperty(formID)) { - $(context).find('#' + formID).garlic({ - expires: 86400 * 30, - conflictManager: { enabled: false }, - // Not yet supported by Garlic, but we need something like this to - // ensure Garlic doesn't overwrite values when the form contains - // different values by default. - lastModifiedIdentifier: settings.formAutoSave[formID], - }); + var changed = settings.formAutoSave[formID]; + $(context).find('#' + formID) + // Give the form a data- attribute to indicate its last modification. + .attr('data-localStorage-changed', changed) + // Attach garlic. + .garlic({ + expires: 86400 * 30, + conflictManager: { enabled: false }, + getPath: that.generateGarlicLocalStorageKey + }); } } + }, + /** + * Override for garlic.js' localStorage key generation. + * + * garlic.js' getPath() implementation contains a lot of complexity, because + * they can't make any assumptions about the HTML of the form; because all + * forms in Drupal are generated by Form API, we can simplify a lot. + * We don't need to store the entire DOM node path, because each form item + * (as well as the form itself) is uniquely identified by its id attribute. + * We also don't need to look at the current URL thanks to + * drupalSettings.currentPath; without that, we could run into edge cases of + * e.g. www vs. no-www URLs of the same site. + * Finally, it takes a "last modification" identifier into account, which + * allows us to ignore localStorage data that doesn't apply anymore to the + * latest version of the object that the form allows the user to edit. + * + * @param {jQuery} $element + * A jQuery element of a form item. + */ + generateGarlicLocalStorageKey: function ($element) { + var key = ''; + var $form = $element.closest('form'); + + // garlic.js prefix. Allows garlic.js to do clean-up. + key += 'garlic:'; + + // Drupal path at which this form lives. + key += drupalSettings.currentPath; + + // "last modification" identifier. + key += '~'; + key += $form.attr('data-localStorage-changed'); + + // Unique form identifier; CSS selector-like syntax. + key += '>'; + key += 'form#' + $form.attr('id'); + key += ' '; + + // Unique form item identifier; CSS selector-like syntax. + key += $element.prop('tagName').toLowerCase(); + key += '#' + $element.attr('id'); + + return key; } }; -})(jQuery, Drupal); +})(jQuery, Drupal, drupalSettings); diff --git a/core/misc/garlic/garlic.js b/core/misc/garlic/garlic.js index 9a3db32..129fb44 100644 --- a/core/misc/garlic/garlic.js +++ b/core/misc/garlic/garlic.js @@ -82,7 +82,7 @@ this.$element = $( element ); this.options = this.getOptions( options ); this.storage = storage; - this.path = this.getPath(); + this.path = this.options.getPath( this.$element ) || this.getPath(); this.parentForm = this.$element.closest( 'form' ); this.$element.addClass('garlic-auto-save'); this.expiresFlag = !this.options.expires ? false : ( this.$element.data( 'expires' ) ? this.path : this.getPath( this.parentForm ) ) + '_flag' ; @@ -271,11 +271,14 @@ we just need the element name / eq() inside a given form */ , getPath: function ( elem ) { - if ( 'undefined' === typeof elem ) { elem = this.$element; } + if ( this.options.getPath( elem ) ) { + return this.options.getPath( elem ); + } + // Requires one element. if ( elem.length != 1 ) { return false; @@ -404,9 +407,10 @@ , garlicPriority: true // If form have default data, garlic persisted data will be shown first , template: '' // Template used to swap between values if conflict detected , message: 'This is your saved data. Click here to see default one' // Default message for swapping data / state - , onConflictDetected: function ( item, storedVal ) { return true; } // This function will be triggered if a conflict is detected on an item. Return true if you want Garlic behavior, return false if you want to override it + , onConflictDetected: function ( $item, storedVal ) { return true; } // This function will be triggered if a conflict is detected on an item. Return true if you want Garlic behavior, return false if you want to override it } - , onRetrieve: function ( item, storedVal ) {} // This function will be triggered each time Garlic find an retrieve a local stored data for a field + , getPath: function ( $item ) {} // Set your own key-storing strategy per field + , onRetrieve: function ( $item, storedVal ) {} // This function will be triggered each time Garlic find an retrieve a local stored data for a field } /* GARLIC DATA-API diff --git a/core/modules/overlay/overlay-parent.js b/core/modules/overlay/overlay-parent.js index 47e85f1..2e7ef4c 100644 --- a/core/modules/overlay/overlay-parent.js +++ b/core/modules/overlay/overlay-parent.js @@ -142,8 +142,7 @@ Drupal.overlay.create = function () { .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage')) .bind('drupalOverlayBeforeClose' + eventClass + ' drupalOverlayBeforeLoad' + eventClass + - ' drupalOverlayResize' + eventClass + - ' drupalOverlayCloseByLink' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent')); + ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent')); if ($('.overlay-displace-top, .overlay-displace-bottom').length) { $(document) @@ -574,11 +573,6 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) { // Close the overlay when the link contains the overlay-close class. if ($target.hasClass('overlay-close')) { - // Trigger event informing scripts the overlay was - // deliberatley closed rather than just navigating away. - var linkCloseOverlayEvent = $.Event('drupalOverlayCloseByLink'); - $(document).trigger(linkCloseOverlayEvent); - // Clearing the overlay URL fragment will close the overlay. $.bbq.removeState('overlay'); return; diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 7ff4b29..4f7a0c7 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1284,13 +1284,26 @@ function system_library_info() { 'version' => VERSION, 'js' => array( 'core/misc/form.js' => array('group' => JS_LIBRARY, 'weight' => 1), - 'core/misc/form.autosave.js' => array('group' => JS_LIBRARY, 'weight' => 2), ), 'dependencies' => array( array('system', 'jquery'), array('system', 'drupal'), array('system', 'jquery.cookie'), array('system', 'jquery.once'), + ), + ); + + // Drupal's localStorage-powered auto form saving, powered by Garlic.js. + $libraries['drupal.formAutoSave'] = array( + 'title' => 'Drupal localStorage-powered auto form saving', + 'version' => VERSION, + 'js' => array( + 'core/misc/form.autosave.js' => array('group' => JS_LIBRARY), + ), + 'dependencies' => array( + array('system', 'jquery'), + array('system', 'drupal'), + array('system', 'drupalSettings'), array('system', 'garlic'), ), ); @@ -2035,9 +2048,9 @@ function system_library_info() { $libraries['garlic'] = array( 'title' => 'garlic.js', 'website' => 'http://garlicjs.org/', - 'version' => '1.1.2', + 'version' => '1.2.0', 'js' => array( - 'core/misc/garlic/garlic.js' => array('group' => JS_LIBRARY, 'weight' => 0), + 'core/misc/garlic/garlic.js' => array('group' => JS_LIBRARY), ), 'dependencies' => array( array('system', 'jquery'),