Skip to content

Autosave

dpobel edited this page Jan 3, 2012 · 34 revisions

Autosave

Requirements

  • regularly save the draft as if the user clicks on “Store Draft” (timeout comes from a settings)
  • save the draft when leaving a form field after something has been changed and hide the "Store draft" button
  • try to save the draft (status eZContentObjectVersion::STATUS_DRAFT while the others save it as eZContentObjectVersion::STATUS_INTERNAL_DRAFT) if the user unexpectedly quits the page (browser window closed, back button,...)
  • work for admin interface and frontend editing
  • inform the user when the draft was last stored (like in GMail for instance) and how much time has passed since then
  • inform the user when something goes wrong
  • [OPTIONAL] let the user to disable the autosave feature globally or when editing a content
  • [OPTIONAL] let the user define the timeout of autosave
  • [OPTIONAL/future enhancement] make the system “offline” compatible with localStorage http://coding.smashingmagazine.com/2011/12/05/sisyphus-js-client-side-drafts-and-more/
  • make it sexy :-)

Existing solution

Dependencies

  • jQuery (ezjscore)
  • ezjscore (for jQuery, $.ez and the ezjscore server function used by qhjsiniloader)
  • qhjsiniloader (loading of settings through an AJAX request)
  • qhnotifications (display a notification)

Features

qhautosave is an eZ Publish extension written by Quoc-Huy NGUYEN DINH that provides the autosave feature while editing a content. Actually, the extension simulates a click on the "Store Draft" button at a fixed interval (settings) and if the user did any change in the edit form, the draft is saved.

In addition, if the user quits the page without using one of the available buttons, the autosave is also triggered.

Review

  • The extension is clearly written to avoid as much as possible any template override to ease maintenance. The qhjsiniloader is here to not have to build a JavaScript configuration object in a template and thus having to maintain an override, but this generates an AJAX request which is not needed in our final solution (even if loading some settings in AJAX might be useful, but it’s another problem).
  • The qhautosave.js file needs to be refactored, as part of the logic is directly triggered when the dom ready event is handled while another part is handled in the qhAutoSave “class”.
  • The qhnotifications provides a general notification system, which is a good idea, but as is, it’s difficult to customize anything (except colors) without overriding the full JavaScript files. It would be better to provide a solution based on regular templates and JavaScript. In addition, it's probably not needed to be that verbose on what happen behind the scene, the editor probably only wants to know when the draft was last stored and if something goes wrong.

Issues

# Issues Severity
1 Warning messages when the user tries to quit the page are not translatable Minor
2 “the warning on quit the page” is not reliable against browsers. For instance, Firefox displays a generic message instead of the intended string. (IE6-9 / Chrome seem to be OK ). In addition, some browsers may stop the running JavaScript code while displaying the confirm window. Possible solution: https://developer.mozilla.org/en/DOM/window.onbeforeunload#Example ?
3 IE6: the notification bar does not follow the scroll (position fixed bug in IE6) Minor
4 A first store draft is always done after the timeout even if the user did no change Minor
5 In frontend editing * does not work out of the box (ezflow package) * user notifications are difficult to see and make an annoying effect (quick move of the button every 15 sec) Medium
6 In the admin interface, small visual issue of the notification toolbar with the fullscreen mode (notification bar overlaps the buttons bar) and with the overlay of the AJAX Uploader in the relation which has a too low zIndex. Minor
7 The code to prevent the autosave when a button is clicked in the page is broken, this might result in some race conditions. Medium
8 IE: the fullscreen editing button triggers the “quit warning” as if the user wanted to quit the page. The same occurs in some parts of the Online Editor (for instance when clicking on an element in the Path) and probably on edit interfaces of some others datatypes (eZFlow?) Major
9 The code only simulates a click on the “Store Draft” button. This means that a full page is loaded in AJAX while the generated HTML is not used at all. This may be a big waste of resources. In addition, the JavaScript is currently not able to check if the draft has really successfully been stored, so we might give a wrong feedback to the user. For instance, if the HTTP code of response is an error code (40x or 50x), the notification bar keeps on displaying “in progress...” Major
10 Does not work with upload (AJAX uploading is not possible, need to use an iframe “hack” like in AJAX Uploader in relation) Major
11 Complex datatypes with a complex UI might need a JavaScript function to be executed before the save draft step. This is the case for instance for eZXMLText with Online Editor and the solution currently implemented is a hardcoded call to tinyMCE.triggerSave() when tinyMCE is in the page. This should probably be handled in a more generic fashion through an Event system. Medium
12 Validation issue : if one attribute does not validate (for instance an invalid email in a Email attribute), the whole draft (even valid attributes) are not stored and the user has no clue of this Major

There’s also a potential issue on datatypes that are modified server side. For instance, if the user copies/pastes an arbitrary document in Online Editor, it might contain some unsupported HTML tags that will be transformed/removed. With the autosave, those transformations are not visible by the user until a full page refresh (regular store draft, preview, ...).

Conclusion

qhautosave is a very good starting point to provide the autosave in eZ Publish but some work/refactoring/rewrite is needed to fix the issues above.

Specifications

General

  • the autosave is provided as an extension called ezautosave without any dependency excepting eZ Publish and ezjscore.
    • the notification will be simplified to be less verbose/useful to the editor, this avoids to depend on qhnotification (see below)
    • the configuration of the autosave will come from an JS literal object generated in the template to avoid the extra AJAX request to not depend on qhjsiniloader (even if such an extension can be useful/integrated in the future)
  • the default autosave interval is 5 minutes (defined in settings)

Server side

  • the autosave has to be done with a custom ezjscore server function. (This allows to "fix" the issue #9 and #12 and to provide a reliable feedback to the user in the frontend).
    • to simplify the client side code, it should accept the same POST parameters as content/edit to store a draft
    • its output is formated in JSON with localized strings so that it can be used directly

Client side

  • the "Store draft" button is hidden when ezautosave is enabled
  • When the user unexpectedly quits the page (back button, close the window), the draft is stored as if he has clicked on "Store draft and exit" (status eZContentObjectVersion::STATUS_DRAFT)
  • YUI3 (embed in ezjscore) provides a module called io-upload-iframe to correctly handle uploads "in AJAX", this is a good candidate to solve issue #10. Mixing several JavaScript frameworks in one component is a bad practice, so the solution has to be YUI based (or a jQuery plugin can do the same but we have to be careful on the maintenance of this plugin)
  • when the draft is automatically stored, the date and time of the event is added (or changed) next to the buttons in the control bar and updated in left menu. The control bar also indicates how much time has passed since the last autosave and this has to be updated every minutes.
    • in eZ Publish, the behaviour of the control bar has to be changed (not part of the autosave extension), the control bar will become "fixed" as soon as the user scrolls down (see screenshot below)
  • if something goes wrong while autosaving the draft, a message with a different style is generated. this message has link "Retry" to let the user manually store the draft.
  • The JavaScript component has to detect if the user clicks on a button that triggers a Store draft (Preview, Browse, ...) so that it's stops an autosave that is currently being done and avoids an autosave to be triggered.

Technical notes

JavaScript

The main part of the JavaScript will be in an AutoSubmitManager component, it will take care of managing the behaviour change in the page:

  • set up the autosave after an interval
  • set up the autosave after leaving a form field (if enabled)
  • stop the process when the user clicks on a button that triggers a Store draft (preview, browse, ...)
  • triggers some events so that external code can be plugged on it
    • init : initialization (for intance, hide the Store draft button
    • beforesave : right before posting the form
    • success : after successfylly posting the form, used to notify (different code between admin and frontend)
    • error : after positing the form if there's an error

Example of code:

var asManager = new Y.eZ.AutoSubmitManager({
    form: '#editform',
    action: '/ezjscore/call/ezautosave::savedraft::125::3::fre-FR',
    interval: 300, // seconds
    trackUserInput: true, // save if the user leave an input
});
asManager.start(); // will autosave using the settings in parameters

asManager.stop(); // autosave is in pause, no autosave event will be triggered and if an autosave is currently done, it is stopped

asManager.submit({'SpecialVar': 1}); // "manually" trigger autosave and include custom field in the form

asManager.on('init', function() {
    // hide the Store draft buttons
    // set the function to update the message "Draft last saved YY minutes ago"
    // bind the asManager.submit({'StoreAndExit': 1}) on beforeunload event
});

asManager.on('success', function(data) {
    // data is the full response in JSON
    // add Draft last saved at XXX
});

asManager.on('error', function(data) {
    // generate error message
});

Extensibility of content/edit.tpl

Currently, the only way to inject JavaScript or HTML code in content/edit.tpl is to override this template or a template that is included in it. This works but it's difficult to maintain. To avoid this, we have to add an extension point in content/edit.tpl similar to what already exists for the context menus in the admin interface.

Example

in an override of content.ini

[EditSettings]
AdditionalTemplates[]=content/edit/autosave.tpl

in edit.tpl (either just before <div class="context-attributes">)

{def $additional_tpl = ezini( 'EditSettings', 'AdditionalTemplates', 'content.ini' )}
{foreach $additional_tpl as $tpl}
    {include uri=concat( 'design:', $tpl )}
{/foreach}
{undef $additional_tpl}

Admin interface

Initial form state

initial edit form state

After scrolling down and an autosave

initial edit form state

Scroll to the top

initial edit form state

Frontend editing

Autosave notification

Autosave notification

Autosave notification scroll

Autosave notification after scroll down

Future enhancements

  • Autosave offline (client storage)
  • Let the user disabled the autosave
  • Let the user choose the autosave interval