Created
November 1, 2011 15:39
-
-
Save winhamwr/1330851 to your computer and use it in GitHub Desktop.
Javascript to do autosave on a form using WYMeditor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
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. | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
addd