diff --git a/code/model/UserDefinedForm.php b/code/model/UserDefinedForm.php index 8deebd3..e1abcdf 100755 --- a/code/model/UserDefinedForm.php +++ b/code/model/UserDefinedForm.php @@ -87,6 +87,15 @@ class UserDefinedForm extends Page { */ private static $error_container_id = 'error-container'; + /** + * The configuration used to determine whether a confirmation message is to + * appear when navigating away from a partially completed form. + * + * @var boolean + * @config + */ + private static $enable_are_you_sure = true; + /** * Temporary storage of field ids when the form is duplicated. * Example layout: array('EditableCheckbox3' => 'EditableCheckbox14') @@ -335,6 +344,12 @@ class UserDefinedForm_Controller extends Page_Controller { if($this->HideFieldLabels) { Requirements::javascript(USERFORMS_DIR . '/thirdparty/Placeholders.js/Placeholders.min.js'); } + + // Bind a confirmation message when navigating away from a partially completed form. + $page = $this->data(); + if($page::config()->enable_are_you_sure) { + Requirements::javascript(USERFORMS_DIR . '/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js'); + } } /** diff --git a/javascript/UserForm.js b/javascript/UserForm.js index e913837..5e7eac6 100644 --- a/javascript/UserForm.js +++ b/javascript/UserForm.js @@ -612,7 +612,7 @@ jQuery(function ($) { */ FormActions.prototype.update = function () { var numberOfSteps = userform.steps.length, - stepID = userform.currentStep.id, + stepID = userform.currentStep ? userform.currentStep.id : 0, i, lastStep; // Update the "Prev" button. @@ -721,6 +721,14 @@ jQuery(function ($) { setInterval(function () { $.ajax({ url: 'UserDefinedForm_Controller/ping' }); }, 180 * 1000); + + // Bind a confirmation message when navigating away from a partially completed form. + var form = $('form.userform'); + if(typeof form.areYouSure != 'undefined') { + form.areYouSure({ + message: ss.i18n._t('UserForms.LEAVE_CONFIRMATION', 'You have unsaved changes!') + }); + } } main(); diff --git a/javascript/lang/en.js b/javascript/lang/en.js index 41915ee..d11a2b2 100644 --- a/javascript/lang/en.js +++ b/javascript/lang/en.js @@ -15,6 +15,7 @@ if(typeof(ss) == 'undefined' || typeof(ss.i18n) == 'undefined') { "UserForms.ERROR_CREATING_OPTION": "Error creating option", "UserForms.REMOVED_OPTION": "Removed option", "UserForms.ADDING_RULE": "Adding rule", + "UserForms.LEAVE_CONFIRMATION": "You have unsaved changes!", "GRIDFIELD.ERRORINTRANSACTION": "An error occured while fetching data from the server\n Please try again later." }); -} \ No newline at end of file +} diff --git a/javascript/lang/src/en.js b/javascript/lang/src/en.js index 3990fbd..c8a4306 100644 --- a/javascript/lang/src/en.js +++ b/javascript/lang/src/en.js @@ -10,5 +10,6 @@ "UserForms.ERROR_CREATING_OPTION": "Error creating option", "UserForms.REMOVED_OPTION": "Removed option", "UserForms.ADDING_RULE": "Adding rule", + "UserForms.LEAVE_CONFIRMATION": "You have unsaved changes!", "GRIDFIELD.ERRORINTRANSACTION": "An error occured while fetching data from the server\n Please try again later." -} \ No newline at end of file +} diff --git a/readme.md b/readme.md index b4c8ef3..1749c8d 100644 --- a/readme.md +++ b/readme.md @@ -22,6 +22,7 @@ See the "require" section of [composer.json](https://github.com/silverstripe/sil * Define custom error messages and validation settings * Optionally display and hide fields using javascript based on users input * Pre fill your form fields, by passing your values by url (http://yoursite.com/formpage?EditableField1=MyValue) +* Displays a confirmation message when navigating away from a partially completed form. ## Installation diff --git a/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js b/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js new file mode 100644 index 0000000..3c41e2f --- /dev/null +++ b/thirdparty/jquery.are-you-sure/jquery.are-you-sure.js @@ -0,0 +1,192 @@ +/*! + * jQuery Plugin: Are-You-Sure (Dirty Form Detection) + * https://github.com/codedance/jquery.AreYouSure/ + * + * Copyright (c) 2012-2014, Chris Dance and PaperCut Software http://www.papercut.com/ + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Author: chris.dance@papercut.com + * Version: 1.9.0 + * Date: 13th August 2014 + */ +(function($) { + + $.fn.areYouSure = function(options) { + + var settings = $.extend( + { + 'message' : 'You have unsaved changes!', + 'dirtyClass' : 'dirty', + 'change' : null, + 'silent' : false, + 'addRemoveFieldsMarksDirty' : false, + 'fieldEvents' : 'change keyup propertychange input', + 'fieldSelector': ":input:not(input[type=submit]):not(input[type=button])" + }, options); + + var getValue = function($field) { + if ($field.hasClass('ays-ignore') + || $field.hasClass('aysIgnore') + || $field.attr('data-ays-ignore') + || $field.attr('name') === undefined) { + return null; + } + + if ($field.is(':disabled')) { + return 'ays-disabled'; + } + + var val; + var type = $field.attr('type'); + if ($field.is('select')) { + type = 'select'; + } + + switch (type) { + case 'checkbox': + case 'radio': + val = $field.is(':checked'); + break; + case 'select': + val = ''; + $field.find('option').each(function(o) { + var $option = $(this); + if ($option.is(':selected')) { + val += $option.val(); + } + }); + break; + default: + val = $field.val(); + } + + return val; + }; + + var storeOrigValue = function($field) { + $field.data('ays-orig', getValue($field)); + }; + + var checkForm = function(evt) { + + var isFieldDirty = function($field) { + var origValue = $field.data('ays-orig'); + if (undefined === origValue) { + return false; + } + return (getValue($field) != origValue); + }; + + var $form = ($(this).is('form')) + ? $(this) + : $(this).parents('form'); + + // Test on the target first as it's the most likely to be dirty + if (isFieldDirty($(evt.target))) { + setDirtyStatus($form, true); + return; + } + + $fields = $form.find(settings.fieldSelector); + + if (settings.addRemoveFieldsMarksDirty) { + // Check if field count has changed + var origCount = $form.data("ays-orig-field-count"); + if (origCount != $fields.length) { + setDirtyStatus($form, true); + return; + } + } + + // Brute force - check each field + var isDirty = false; + $fields.each(function() { + $field = $(this); + if (isFieldDirty($field)) { + isDirty = true; + return false; // break + } + }); + + setDirtyStatus($form, isDirty); + }; + + var initForm = function($form) { + var fields = $form.find(settings.fieldSelector); + $(fields).each(function() { storeOrigValue($(this)); }); + $(fields).unbind(settings.fieldEvents, checkForm); + $(fields).bind(settings.fieldEvents, checkForm); + $form.data("ays-orig-field-count", $(fields).length); + setDirtyStatus($form, false); + }; + + var setDirtyStatus = function($form, isDirty) { + var changed = isDirty != $form.hasClass(settings.dirtyClass); + $form.toggleClass(settings.dirtyClass, isDirty); + + // Fire change event if required + if (changed) { + if (settings.change) settings.change.call($form, $form); + + if (isDirty) $form.trigger('dirty.areYouSure', [$form]); + if (!isDirty) $form.trigger('clean.areYouSure', [$form]); + $form.trigger('change.areYouSure', [$form]); + } + }; + + var rescan = function() { + var $form = $(this); + var fields = $form.find(settings.fieldSelector); + $(fields).each(function() { + var $field = $(this); + if (!$field.data('ays-orig')) { + storeOrigValue($field); + $field.bind(settings.fieldEvents, checkForm); + } + }); + // Check for changes while we're here + $form.trigger('checkform.areYouSure'); + }; + + var reinitialize = function() { + initForm($(this)); + } + + if (!settings.silent && !window.aysUnloadSet) { + window.aysUnloadSet = true; + $(window).bind('beforeunload', function() { + $dirtyForms = $("form").filter('.' + settings.dirtyClass); + if ($dirtyForms.length == 0) { + return; + } + // Prevent multiple prompts - seen on Chrome and IE + if (navigator.userAgent.toLowerCase().match(/msie|chrome/)) { + if (window.aysHasPrompted) { + return; + } + window.aysHasPrompted = true; + window.setTimeout(function() {window.aysHasPrompted = false;}, 900); + } + return settings.message; + }); + } + + return this.each(function(elem) { + if (!$(this).is('form')) { + return; + } + var $form = $(this); + + $form.submit(function() { + $form.removeClass(settings.dirtyClass); + }); + $form.bind('reset', function() { setDirtyStatus($form, false); }); + // Add a custom events + $form.bind('rescan.areYouSure', rescan); + $form.bind('reinitialize.areYouSure', reinitialize); + $form.bind('checkform.areYouSure', checkForm); + initForm($form); + }); + }; +})(jQuery);