/** * Copyright (c) 2008, Nathan Bubna * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * * Simplifies the process of getting/setting values from/for the descendants * of any HTML element(s). This is a bit like form serialization except * that it can both get and set values for form fields, div/span elements, * or any such mix of nodes. Another way to look at it is as an HTML template * engine that is bi-directional and does not actually require a separate * template. Rather than replacing placeholders, it uses element attributes * to identify where values should be set or retrieved. * * To retrieve values, simply do: * * var values = $('.parent').values(); * * This will look amongst the descendants of the element(s) with class 'parent' * to find all elements with a 'name' attribute and then smartly * grabs the "value" for each of those elements. By "smartly", i mean that * it use $el.val() for input elements, $el.find('option:selected').text() * for select elements, and $el.text() for the rest, in other words, it grabs the * displayed values by default. It also trims whitespace when it uses text(). * Once each value is retrieved it is added to a hash object (think JSON) using * the value of the 'name' attribute as its key, and in the end, that hash * object is returned, giving you easy access to the values. * * If you pass in an object to this method, like: * * $('.parent').values({ foo: 'bar', answer: 42 }); * * it will reverse the process and set those values to the elements * with matching names. * * If you pass in a different HTML element(s), like: * * $('.parent').values($('form#foo')); * * then it will grab the values from
's children * and copy them to the descendants of the element(s) with class 'parent'. * * If you wish to set or get a single named value from amongs an HTML element(s) * descendants, this plugin does accept string keys to identify the single value * you wish to get/set. Just do something like: * * var foo = $('.parent').values('foo') * * to get the value with the name 'foo' from the descendants of the element(s) * with the class 'parent'. To set a value, just do: * * $('.parent').values('bar', 42); * * and any descendants whose name attribute has the value 'bar' will have their * value set to 42. * * All of the example method calls above will accept an options object as a * last argument. Or, you can just override the defaults at: $.values.defaults * The available options are: * keyAttr: to change the attribute containing the value key (default is 'name') * nodeFilter: to specify a selector used to filter out certain descendant elements (default is undefined) * copyToData: to also set values in the data() for the container element (default is false) * copyToAttr: to also set values as attributes of the container element (default is false) * useSelectValue: to set whether the returned value for select elements is * the value of the selected option instead of its text (default is false) * * The copyToData and copyToAttr settings serve as a way to ensure that any * set data is not entirely "lost" should there be no element with a matching 'name' * attribute for one or more of the keys. I generally use them as more of a * debugging utility than anything else. * * NOTE: if multiple elements with the same name/key are found during a "get" * call and those values are not equal, then they are pushed into an array * which is associated with that key. This also works in reverse; * if multiple values for the same name/key are found during a "set" call, * and there are multiple matching elements, then the values are applied * to those elements in order. If there are fewer values than elements, * the values are looped. If fewer elements than values, the extra values * are ignored. * * Also, this plugin is quite modular, so it is easy to tweak the various * behaviors by overriding the methods in the $.values object. * * @version 1.0 * @name values * @cat Plugins/Values * @author Nathan Bubna */ (function ($) { $.values = { defaults : { useSelectValue: false, keyAttr: 'name', copyToData: false, copyToAttr: false, nodeFilter: undefined }, isOptions: function(obj) { return (obj && typeof obj == "object" && (obj.useSelectValue || obj.keyAttr || obj.copyToData || obj.nodeFilter || obj.copyToAttr)); }, setAll: function(values, options) { var $container = this; $.each(values, function(key, val) { $.values.setOne.apply($container, [key, val, options]); }); if (options.copyToData) { this.data('values', values); } }, setOne: function(key, value, options) { // may be multiple fields w/same name var $fields = this.find('['+options.keyAttr+'='+key+']'); if (options.nodeFilter) { $fields = $fields.filter(options.nodeFilter); } if ($.isArray(value) && value.length > 0) { $.values.setArray.apply(this, [$fields, key, value, options]); } else { $fields.each(function() { $.values.setValue.apply(this, [key, value, options]); }); } if (options.copyToData) { this.data(key, value); } if (options.copyToAttr) { this.attr(key, value); } }, setArray: function($fields, key, values, options) { var i = 0; $fields.each(function() { // just wrap around if we have too few values if (i == values.length) { i = 0; } $.values.setValue.apply(this, [key, values[i], options]); i = i + 1; }); }, setValue: function(key, value, options) { var $field = $(this); if ($field.is('input')) { $field.val(value); } else if ($field.is('select')) { if (options.useSelectValue) { $field.val(value); } else { $field.children('option').each(function() { if ($.trim($(this).text()) == value) { this.selected = true; return false; } }); } } else { $field.text(value); } }, getAll: function(options) { var $fields = this.find('*['+options.keyAttr+']'); if (options.nodeFilter) { $fields = $fields.filter(options.nodeFilter); } var vals = {}; var counts = {}; // gather the keys and drop dupes $fields.each(function() { var key = $(this).attr(options.keyAttr); if (!vals[key]) { vals[key] = key; counts[key] = 0; } }); // get values for each key for (var key in vals) { var got = $.values.getOne.apply(this, [key, options, $fields]); vals[key] = got.val; counts[key] = got.count; } vals.valuesCounts = counts; return vals; }, getOne: function(key, options, $fields) { var selector = '['+options.keyAttr+'='+key+']'; if ($fields === undefined) { $fields = this.find(selector); if (options.nodeFilter) { $fields = $fields.filter(options.nodeFilter); } } else { // assume nodeFilter was applied in getAll $fields = $fields.filter(selector); } var results = [];// for all var result;// for one var same = true;// decider $fields.each(function() { var val = $.values.getValue.apply(this, [options]); if (result !== undefined && result != val) { same = false; } result = val; results.push(result); }); if (same) { return { count: results.length, val: result }; } return { count: results.length, val: results }; }, getValue: function(options) { var $field = $(this); if ($field.is('input')) { return $field.val(); } if ($field.is('select')) { if (options.useSelectValue) { return $field.find('option:selected').val(); } return $.trim($field.find('option:selected').text()); } return $.trim($field.text()); } }; $.fn.values = function(first, second, third) { var options = $.extend({}, $.values.defaults); // grab any options in the args and then wipe that arg if (third) { options = $.extend(options, third); } else if ($.values.isOptions(second)) { options = $.extend(options, second); second = undefined; } else if ($.values.isOptions(first)) { options = $.extend(options, first); first = undefined; } // a get/set for a particular val? if (typeof first == "string") { if (second === undefined) { var got = $.values.getOne.apply(this, [first, options]); return got.val; } $.values.setOne.apply(this, [first, second, options]); return this; // setting values? } else if (first) { // if an element or selection, replace with its values() if (first.jquery || first.nodeType) { first = $.values.getAll.apply($(first), [options]); } $.values.setAll.apply(this, [first, options]); return this; } // just get the values return $.values.getAll.apply(this, [options]); }; })(jQuery);