Skip to content

Instantly share code, notes, and snippets.

@winhamwr
Created November 1, 2011 15:39
Show Gist options
  • Save winhamwr/1330851 to your computer and use it in GitHub Desktop.
Save winhamwr/1330851 to your computer and use it in GitHub Desktop.
Javascript to do autosave on a form using WYMeditor
/**
Automatically saves a policy every REFRESH milliseconds through AJAX,
but only when the .autosave form has changed. To use, add the class autosave
to your form.
Note: REFRESH must always be bigger than SAVE_TIMEOUT, otherwise a broken
save will cause deadlock for pending.
**/
function processJson(data) {
if (data.success === true) {
updateStatus(STATUS_SAVED);
needSaving = false;
} else {
errors = [];
$.each(data, function(key) {
var value = this;
errors.push(key + ': ' + value);
});
updateStatus(STATUS_SAVE_ERROR + '<br />' + errors.join('<br />'));
}
// Record the document save id.
if (typeof(data.document_save_id) !== 'undefined') {
$('#id_document_save_id').val(data.document_save_id);
}
// Saving is done, make sure the submit button is enabled.
clearTimeout(saveTimeout);
enableSubmit();
}
// The amount of time between save checks
REFRESH = 30000; // 30 seconds
CHECK_REFRESH = 2000; // 2 seconds
SAVE_TIMEOUT = 7000; // 7 seconds
SAVE_OPTIONS = {
target: '#saveresults', // Target element(s) to be updated with server response.
url: '/policy/save/', // Override for form's 'action' attribute.
type: 'POST',
dataType: 'json',
iframe: false,
success: processJson
};
HTML_ID = "id_doc-html";
SAVE_NOW = "Save Now";
SAVED = "Saved";
SAVED_STATUS_ID = "save_status";
STATUS_NEW = "No Modifications Made";
STATUS_SAVED = "Draft Saved";
STATUS_UNSAVED = "Draft Not Saved";
STATUS_UNSAVED_CHANGES = "Unsaved Modifications Exist";
STATUS_SAVE_ERROR = "Unable to Save";
AUTOSAVE_ON = '<a class="autosave_toggle" href="#">Disable Autosave</a>';
AUTOSAVE_OFF = '<a class="autosave_toggle" href="#">Enable Autosave</a>';
WORK_FROM_DRAFT = "Work From Draft";
DISCARD_DRAFT = "Discard Draft";
// For the discard_save dialog.
DIALOG_OPTIONS = {
autoOpen: false,
buttons: {'Work From Draft': discardChoice, 'Discard Draft': discardChoice},
modal: true,
overlay: {'background': 'gray', 'opacity': '0.4', 'filter': 'alpha(opacity=40)'}
};
// Whether a draft existed before we started editing.
var hasDraft = ($('#id_has_saved_copy').val() === 'True');
var continueDraft = ($('#id_continue_draft').val() === 'True');
// Value of the policy body.
body = '';
// Whether or not something has changed since the last save.
needSaving = false;
// For moving the status menu around.
statusYLoc = null;
// Used to disable/enable saving.
// (useful so that we don't save while we're in the process of deleting a draft)
shouldSave = true;
// True when we're in the middle of sending or waiting for a save AJAX request.
currentlySaving = false;
saveTimeout = null;
autosaveEnabled = true;
/**
* Set the HTML for displaying the autosave toggle link based on the given autosave
* status.
*/
function updateAutosaveToggle(enabled) {
var autosaveHtml = '';
var $target = $('#'+SAVED_STATUS_ID);
if (enabled) {
autosaveHtml = AUTOSAVE_ON;
} else {
autosaveHtml = AUTOSAVE_OFF;
}
// Remove any current autosave toggle status.
$target.find('.autosave_toggle').remove();
$target.append(autosaveHtml);
}
function updateStatus(newStatus) {
if (newStatus === STATUS_SAVED) {
$('#'+SAVED_STATUS_ID).removeClass('status_neutral');
$('#'+SAVED_STATUS_ID).removeClass('status_fail');
$('#'+SAVED_STATUS_ID).addClass('status_success');
$('#save_now').attr('value', SAVED);
$("#save_now").attr("disabled", true);
} else if (newStatus === STATUS_NEW) {
$('#'+SAVED_STATUS_ID).removeClass('status_fail');
$('#'+SAVED_STATUS_ID).removeClass('status_success');
$('#'+SAVED_STATUS_ID).addClass('status_neutral');
$('#save_now').attr('value', SAVED);
$("#save_now").attr("disabled", true);
} else {
$('#'+SAVED_STATUS_ID).removeClass('status_neutral');
$('#'+SAVED_STATUS_ID).removeClass('status_success');
$('#'+SAVED_STATUS_ID).addClass('status_fail');
$('#save_now').attr('value', SAVE_NOW);
$("#save_now").attr("disabled", false);
}
$('#'+SAVED_STATUS_ID).html(newStatus);
updateAutosaveToggle(autosaveEnabled);
}
function checkBodyForChange() {
var wym = $.wymeditors(0);
// Don't try and check the contents until the iframe is fully loaded.
// There was a race condition on startup otherwise.
if (wym._doc == null) {
return;
}
var oldBody = body;
var newBody = wym._doc.body.innerHTML;
if (oldBody != newBody) {
readyForSave();
}
}
function enableSubmit() {
$submitButton.removeAttr('disabled')
$submitButton.val(submitEnabledText)
}
function disableSubmit() {
$submitButton.attr('disabled', 'disabled')
$submitButton.val('Save in Progress')
}
function saveContentIfNeeded() {
checkBodyForChange();
if (needSaving && shouldSave) {
saveContentNow();
}
return false;
}
function saveContentNow(options) {
if (typeof(options) === 'undefined') {
options = SAVE_OPTIONS;
}
disableSubmit();
// Make sure and clear the old timeout function if we save quickly.
clearTimeout(saveTimeout);
saveTimeout = setTimeout(function() {
enableSubmit();
}, SAVE_TIMEOUT)
var wym = $.wymeditors(0);
body = wym._doc.body.innerHTML
wym.update();
$('.autosave').ajaxSubmit(options);
}
function discardChoice(event) {
var text = $(event.target).text()
if (text == WORK_FROM_DRAFT) {
$('#discard_dialog').dialog('close');
} else if (text == DISCARD_DRAFT) {
discardDraftNow(event);
} else {
alert("Choice failed. Please close this dialog."+text);
}
}
function discardDraftButton(event) {
// First save the draft, so that when we undo, any currently unsaved changes
// will be restored.
// Then, once the save request completes, delete the draft.
//
function processJsonThenDeleteDraft(data) {
if (data.success == true) {
processJson(data);
}
discardDraftNow();
}
saveContentNow({
target: '#saveresults',
url: '/policy/save/',
type: 'post',
dataType: 'json',
success: processJsonThenDeleteDraft
});
}
function discardDraftNow(event) {
options = {
url: DISCARD_SAVE_URL, // Override for form's 'action' attribute.
type: 'post',
dataType: 'json',
success: processDiscard
};
shouldSave = false;
$('.autosave').ajaxSubmit(options);
}
function processDiscard(data) {
window.location = '';
$('#discard_dialog').dialog('close');
}
function createSaveDiscardDialog() {
if (hasDraft && !continueDraft && typeof(DISCARD_SAVE_URL) == 'string') {
if ( $(".autosave #discard_dialog").length < 1 ) {
$('.autosave').append('<div id="discard_dialog" title="A Draft Exists"></div>');
if (DRAFT_AUTHOR == 'SELF') {
$('.autosave #discard_dialog').append(
'You already have a draft saved for this policy.'
);
} else {
$('.autosave #discard_dialog').append(
'<p>A draft version of this policy already exists, authored by:</p>' +
'<strong>' + DRAFT_AUTHOR + '</strong>'
);
}
}
$('#discard_dialog').addClass('flora').dialog(DIALOG_OPTIONS);
$('#discard_dialog').dialog('open');
}
}
function readyForSave() {
needSaving = true;
if (hasDraft) {
updateStatus(STATUS_UNSAVED_CHANGES);
} else {
updateStatus(STATUS_UNSAVED);
}
// If we have become ready to save, then the discard draft button can now be used.
$('#discard_draft_now').removeAttr('disabled');
}
function updateAutosaveInterval() {
if (typeof(saveContentIntervalId) != 'undefined') {
clearInterval(saveContentIntervalId);
}
if (autosaveEnabled) {
saveContentIntervalId = setInterval(saveContentIfNeeded, REFRESH);
}
}
// Prepare the form when the DOM is ready.
$(document).ready(function() {
// Handle moving the status floater around.
statusYLoc = parseInt($('#'+SAVED_STATUS_ID).css("top").substring(0,$('#'+SAVED_STATUS_ID).css("top").indexOf("px")))
$(window).scroll(function() {
var offset = statusYLoc + $(document).scrollTop() + "px";
$('#'+SAVED_STATUS_ID).animate({top:offset}, {duration:500, queue:false});
});
$(window).scroll(); // Set initial position of status floater.
// Wire up the save and discard buttons.
$('#save_now').click(saveContentIfNeeded);
$('#discard_draft_now').click(discardDraftButton);
// Set the initial value of disabled for the discard button.
if (hasDraft) {
$('#discard_draft_now').removeAttr('disabled');
} else {
$('#discard_draft_now').attr('disabled', 'disabled');
}
// Start autosave interval functions.
updateAutosaveInterval();
setInterval(checkBodyForChange, CHECK_REFRESH);
updateStatus(STATUS_NEW);
// Wire up the discard draft button in the popup.
$('#delete_draft').click(discardChoice);
// Register the form to detect changes.
$('.autosave :input').change(function() {
readyForSave();
});
createSaveDiscardDialog();
$submitButton = $(":submit.wymupdate")
submitEnabledText = $submitButton.val();
$submitButton.submit
$(".autosave").submit(function() {
shouldSave = false;
$("#save_now").remove()
return true;
});
// Load body html.
wym = $.wymeditors(0);
var postInitFunc = wym._options.postInit
wym._options.postInit = function() {
postInitFunc(wym);
body = wym._doc.body.innerHTML;
}
window.onbeforeunload = function() {
if (needSaving && shouldSave) {
return "You have unsaved policy changes. Leaving will abandon your changes."
}
}
// For links that need a saved draft (printing, viewing, etc), force a save
// when they're clicked.
$('.needs_save').click(function() {
saveContentIfNeeded();
return true;
});
$('.autosave_toggle').live('click', function() {
autosaveEnabled = !autosaveEnabled;
updateAutosaveToggle(autosaveEnabled);
updateAutosaveInterval();
return false; // Don't jump to the top of the screen.
});
});
@kirrabelloche
Copy link

addd

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment