(function($){ // Copyright (c) 2009, SilverStripe Ltd. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY SilverStripe Ltd. ''AS IS'' AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL SilverStripe Ltd. BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** * @class Tracks onchange events on all form fields. * * @todo Implement form reset handling * * @name jQuery.changetracker * @author Ingo Schommer, SilverStripe Ltd. * @license BSD License */ $.fn.changetracker = function(_options) { var self = this; if(this.length > 1){ this.each(function(i, item) { this.changetracker(_options); }); return this; } this.defaults = { fieldSelector: ':input:not(:submit)', ignoreFieldSelector: "", changedCssClass: 'changed' }; var options = $.extend({}, this.defaults, _options); this.initialize = function() { // optional metadata plugin support if ($.meta) options = $.extend({}, options, this.data()); // Flag indicating this form was dirtied by an external component var dirty = false; var onchange = function(e) { var $field = $(e.target); var origVal = $field.data('changetracker.origVal'), newVal; // Determine value based on field type if($field.is(':checkbox')) { newVal = $field.is(':checked') ? 1 : 0; } else { newVal = $field.val(); } // Determine changed state based on value comparisons if(origVal === null || newVal != origVal) { $field.addClass(options.changedCssClass); self.addClass(options.changedCssClass); } else { $field.removeClass(options.changedCssClass); // Unset changed state on all radio buttons of the same name if($field.is(':radio')) { self.find(':radio[name=' + $field.attr('name') + ']').removeClass(options.changedCssClass); } // Only unset form state if no other fields are changed as well and the form isn't explicitly dirty if(!dirty && !self.getFields().filter('.' + options.changedCssClass).length) { self.removeClass(options.changedCssClass); } } }; // setup original values var fields = this.getFields(), origVal; fields.filter(':radio,:checkbox').bind('click.changetracker', onchange); fields.not(':radio,:checkbox').bind('change.changetracker', onchange); fields.each(function() { if($(this).is(':radio,:checkbox')) { origVal = self.find(':input[name=' + $(this).attr('name') + ']:checked').val(); } else { origVal = $(this).val(); } $(this).data('changetracker.origVal', origVal); }); self.bind('dirty.changetracker', function() { dirty = true; self.addClass(options.changedCssClass); }); this.data('changetracker', true); }; this.destroy = function() { this.getFields() .unbind('.changetracker') .removeClass(options.changedCssClass) .removeData('changetracker.origVal'); this.unbind('.changetracker') .removeData('changetracker'); }; /** * Reset change state of all form fields and the form itself. */ this.reset = function() { this.getFields().each(function() { self.resetField(this); }); this.removeClass(options.changedCssClass); }; /** * Reset the change single form field. * Does not reset to the original value. * * @param DOMElement field */ this.resetField = function(field) { return $(field).removeData('changetracker.origVal').removeClass('changed'); }; /** * @return jQuery Collection of fields */ this.getFields = function() { return this.find(options.fieldSelector).not(options.ignoreFieldSelector); }; // Support invoking "public" methods as string arguments if (typeof arguments[0] === 'string') { var property = arguments[1]; var args = Array.prototype.slice.call(arguments); args.splice(0, 1); return this[arguments[0]].apply(this, args); } else { return this.initialize(); } }; }(jQuery));