2 * jQuery Field Plug-in
\r
4 * Copyright (c) 2011 Dan G. Switzer, II
\r
6 * Dual licensed under the MIT and GPL licenses:
\r
7 * http://www.opensource.org/licenses/mit-license.php
\r
8 * http://www.gnu.org/licenses/gpl.html
\r
12 * NOTES: The getValue() and setValue() methods are designed to be
\r
13 * executed on single field (i.e. any field that would share the same
\r
14 * "name" attribute--a single text box, a group of checkboxes or radio
\r
19 * - Added clear argument to fieldHash() and formHash() method for resetting
\r
20 * values not in the supplied hash map; default is still to ignore values
\r
21 * not in the hash map
\r
24 * - Fixed issue with IE9 not getting option tag values with certain doctypes
\r
25 * - Added fieldHash() for grabbing fields out by a CSS selector (the formHash()
\r
26 * requires a form object)
\r
29 * - Fixed issues with jQuery v1.6.x
\r
30 * - Fixed file fields not currently supported by getValue or getType
\r
31 * - Fixed formHash breaks on fields with apostrophes
\r
32 * - Fixed moveNext() did not work, it have invisible parent
\r
35 * - Enhanced createCheckboxRange() to trigger change handler for each checkbox
\r
37 * - Enhanced createCheckboxRange() to pass in event object when triggering click
\r
41 * - Fixed code for jQuery v1.3.x support
\r
42 * - Fixed bug #6333 - setValue when value is typeof "number" and it is 0 (zero)
\r
43 * - Fixed bug in autoAdvance where it wasn't correctly advancing to the next field
\r
44 * - Fixed bug in createCheckboxRange where it where the high value wouldn't always
\r
45 * be unchecked/checked
\r
48 * - Optimized the createCheckboxRange to reduced complexity and code size.
\r
49 * Functionality has not changed.
\r
52 * - Removed createCheckboxRange custom event, this fixes problems with
\r
53 * older jQuery versions that have problems with the trigger
\r
54 * - Changed the limitSelection() function to use an options argument,
\r
55 * instead of all the individual arguments
\r
58 * - createCheckboxRange() no longer breaks the chain
\r
59 * - Added callback to createCheckboxRange() (the trigger() method
\r
60 * does not execute correctly in jQuery v1.1.2, so this functionality
\r
61 * doesn't work in that version.)
\r
62 * - Added configurable setting for the [SHIFT] key bind to the
\r
63 * createCheckboxRange().
\r
64 * - Added the $.Field.setProperty() and getProperty() methods
\r
67 * - Fixed "bug in checkbox range" (http://plugins.jquery.com/node/1642)
\r
68 * - Fixed "Bug when setting value on a select element and the select is empty"
\r
69 * (http://plugins.jquery.com/node/1281)
\r
72 * - Changed a comma in code to a semi-colon in a variable definition line
\r
73 * - Fixed code to work in min mode
\r
74 * - Fixed bug in hashForm() that would not see struct keys with empty values
\r
77 * - Added tabIndex related function (getTabIndex, moveNext, movePrev, moveIndex)
\r
80 * - Fixed bug in the $.formHash() where the arrayed form elements would
\r
81 * not correctly report their values.
\r
82 * - Added the $.createCheckboxRange() which allow you to select multiple
\r
83 * checkbox elements by doing a [SHIFT] + click.
\r
86 * - Added $.limitSelection() method for limiting the number of
\r
87 * selection in a select-multiple of checkbox array.
\r
90 * - Moved $.type and $.isType into private functions
\r
91 * - Rewrote $type() function to use instanceof operator
\r
94 * - Added the formHash() method
\r
97 * - First public release
\r
102 // set the defaults
\r
104 // use a comma as the string delimiter
\r
106 // the default key binding to check for when using the createCheckboxRange()
\r
107 checkboxRangeKeyBinding: "shiftKey",
\r
108 // for methods that could return either a string or array, decide default behavior
\r
112 // set default options
\r
115 setDefaults: function(options){
\r
116 $.extend(defaults, options);
\r
118 setProperty: function(prop, value){
\r
119 defaults[prop] = value;
\r
121 getProperty: function(prop){
\r
122 return defaults[prop];
\r
128 * jQuery.fn.fieldArray()
\r
130 * returns either an array of values or a jQuery object
\r
132 * NOTE: This *MAY* break the jQuery chain
\r
135 * $("input[name='name']").fieldArray();
\r
136 * > Gets the current value of the name text element
\r
138 * $("input[name='name']").fieldArray(["Dan G. Switzer, II"]);
\r
139 * > Sets the value of the name text element to "Dan G. Switzer, II"
\r
141 * $("select[name='state']").fieldArray();
\r
142 * > Gets the current value of the state text element
\r
144 * $("select[name='state']").setValue(["OH","NY","CA"]);
\r
145 * > Sets the selected value of the "state" select element to OH, NY and CA
\r
148 // this will set/get the values for a field based upon and array
\r
149 $.fn.fieldArray = function(v){
\r
152 // if no value supplied, return an array of values
\r
153 if( t == "undefined" ) return getValue(this);
\r
155 // convert the number/string into an array
\r
156 if( t == "string" || t == "number" ){
\r
157 v = v.toString().split(defaults.delimiter);
\r
161 // set the value -- doesn't break the chaing
\r
162 if( t == "array" ) return setValue(this, v);
\r
164 // if we don't know what do to, don't break the chain
\r
169 * jQuery.fn.getValue()
\r
171 * returns String - a comma delimited list of values for the field
\r
173 * NOTE: Breaks the jQuery chain, since it returns a string.
\r
176 * $("input[name='name']").getValue();
\r
177 * > This would return the value of the name text element
\r
179 * $("select[name='state']").getValue();
\r
180 * > This would return the currently selected value of the "state" select element
\r
183 // the getValue() method -- break the chain
\r
184 $.fn.getValue = function(){
\r
185 // return the values as a comma-delimited string
\r
186 return getValue(this).join(defaults.delimiter);
\r
192 * returns Array - an array of values for the field
\r
195 // the getValue() method -- break the chain
\r
196 var getValue = function(jq){
\r
201 // get the current type
\r
202 var t = getType(this);
\r
205 case "checkbox": case "radio":
\r
206 // if the checkbox or radio element is checked
\r
207 if( this.checked ) v.push(this.value);
\r
211 if( this.type == "select-one" ){
\r
212 v.push( (this.selectedIndex == -1) ? "" : getOptionVal(this[this.selectedIndex]) );
\r
214 // loop through all element in the array for this field
\r
215 for( var i=0; i < this.length; i++ ){
\r
216 // if the element is selected, get the selected values
\r
217 if( this[i].selected ){
\r
218 // append the selected value, if the value property doesn't exist, use the text
\r
219 v.push(getOptionVal(this[i]));
\r
226 v.push(this.value);
\r
232 // return the values as an array
\r
239 * returns jQuery object
\r
241 * NOTE: This does *NOT* break the jQuery chain
\r
244 * $("input[name='name']").setValue("Dan G. Switzer, II");
\r
245 * > Sets the value of the name text element to "Dan G. Switzer, II"
\r
247 * $("select[name='state']").setValue("OH");
\r
248 * > Sets the selected value of the "state" select element to "OH"
\r
251 // the setValue() method -- does *not* break the chain
\r
252 $.fn.setValue = function(v){
\r
253 // f no value, set to empty string
\r
254 return setValue(this, ((!v && (v !== 0)) ? [""] : v.toString().split(defaults.delimiter)));
\r
260 * returns jQuery object
\r
263 // the setValue() method -- does *not* break the chain
\r
264 var setValue = function(jq, v){
\r
267 var t = getType(this), x;
\r
270 case "checkbox": case "radio":
\r
271 if( valueExists(v, this.value) ) this.checked = true;
\r
272 else this.checked = false;
\r
276 var bSelectOne = (this.type == "select-one");
\r
277 var bKeepLooking = true; // if select-one type, then only select the first value found
\r
278 // loop through all element in the array for this field
\r
279 for( var i=0; i < this.length; i++ ){
\r
280 x = getOptionVal(this[i]);
\r
281 bSelectItem = valueExists(v, x);
\r
283 this[i].selected = true;
\r
284 // if a select-one element
\r
286 // no need to look farther
\r
287 bKeepLooking = false;
\r
291 } else if( !bSelectOne ) this[i].selected = false;
\r
293 // if a select-one box and nothing selected, then try to select the default value
\r
294 if( bSelectOne && bKeepLooking && !!this[0] ){
\r
295 this[0].selected = true;
\r
300 this.value = v.join(defaults.delimiter);
\r
311 * jQuery.fn.formHash()
\r
313 * returns either an hash table of form fields or a jQuery object
\r
315 * NOTE: This *MAY* break the jQuery chain
\r
318 * $("#formName").formHash();
\r
319 * > Returns a hash map of all the form fields and their values
\r
321 * $("#formName").formHash({"name": "Dan G. Switzer, II", "state": "OH"});
\r
322 * > Returns the jQuery chain and sets the fields "name" and "state" with
\r
323 * > the values "Dan G. Switzer, II" and "OH" respectively.
\r
326 // the formHash() method -- break the chain (we could use fieldHash() now, but we stick w/DOM access for speed)
\r
327 $.fn.formHash = function(map, clear){
\r
328 var isGet = (arguments.length == 0);
\r
329 // create a hash to return
\r
332 // run the code for each form
\r
333 this.filter("form").each(
\r
335 // get all the form elements
\r
336 var els = this.elements, el, n, fields = {}, $el;
\r
338 // loop through the elements and process
\r
339 for( var i=0, elsMax = els.length; i < elsMax; i++ ){
\r
343 // if the element doesn't have a name, then skip it
\r
344 if( !n || fields[n] ) continue;
\r
346 // create a jquery object to the current named form elements (for fields containing apostrophe's, escape them)
\r
347 var $el = $(el.form[n]);
\r
349 // if we're getting the values, get them now
\r
351 hash[n] = $el[defaults.useArray ? "fieldArray" : "getValue"]();
\r
352 // if we're setting values, set them now
\r
353 } else if( n in map ){
\r
354 $el[defaults.useArray ? "fieldArray" : "setValue"](map[n]);
\r
355 } else if( clear === true ){
\r
356 $el[defaults.useArray ? "fieldArray" : "setValue"]("");
\r
364 // if getting a hash map return it, otherwise return the jQuery object
\r
365 return (isGet) ? hash : this;
\r
369 * jQuery.fn.fieldHash()
\r
371 * Returns either an hash table of form fields matching the selector or a jQuery object
\r
373 * NOTE: This *MAY* break the jQuery chain
\r
376 * $("#formName").fieldHash();
\r
377 * > Returns a hash map of all the form fields matched by the selector and their values.
\r
379 * $("#formName").fieldHash({"name": "Dan G. Switzer, II", "state": "OH"});
\r
380 * > Returns the jQuery chain and sets the fields "name" and "state" with
\r
381 * > the values "Dan G. Switzer, II" and "OH" respectively--provided the fields exist in the jQuery selector.
\r
384 // the fieldHash() method -- break the chain
\r
385 $.fn.fieldHash = function(map, clear){
\r
386 var isGet = !(map && typeof map == "object");
\r
387 // create a hash to return
\r
388 var hash = {}, fields = {};
\r
390 // run the code for each form field
\r
391 this.filter(":input").each(
\r
393 var el = this, n = el.name;
\r
395 // if the element doesn't have a name or it's already processed--stop
\r
396 if( !n || fields[n] ) return;
\r
398 // create a jquery object to the current named form elements (for fields containing apostrophe's, escape them)
\r
399 var $el = $(el.form[n]);
\r
401 // if we're getting the values, get them now
\r
403 hash[n] = $el[defaults.useArray ? "fieldArray" : "getValue"]();
\r
404 // if we're setting values, set them now
\r
405 } else if( n in map ){
\r
406 $el[defaults.useArray ? "fieldArray" : "setValue"](map[n]);
\r
407 } else if( clear === true ){
\r
408 $el[defaults.useArray ? "fieldArray" : "setValue"]("");
\r
415 // if getting a hash map return it, otherwise return the jQuery object
\r
416 return (isGet) ? hash : this;
\r
420 * jQuery.fn.autoAdvance()
\r
422 * Finds all text-based input fields and makes them autoadvance to the next
\r
423 * fields when they've met their maxlength property.
\r
427 * $("#form").autoAdvance();
\r
428 * > When a field reaches it's maxlength attribute value, it'll advance to the
\r
429 * > next field in the form's tabindex.
\r
431 * $("#form").autoAdvance(callback);
\r
432 * > Automatic advances to next field and triggers the callback function on the
\r
433 * > field the user left.
\r
436 // the autoAdvance() method
\r
437 $.fn.autoAdvance = function(callback){
\r
438 return this.find(":text,:password,textarea").bind(
\r
439 "keyup.autoAdvance",
\r
444 // get the maxlength for the field
\r
445 iMaxLength = parseInt($field.attr("maxlength"), 10);
\r
447 // if the user tabs to the field, exit event handler
\r
448 // this will prevent movement if the field is already
\r
449 // field in with the max number of characters
\r
450 if( isNaN(iMaxLength) || ("|9|16|37|38|39|40|".indexOf("|" + e.keyCode + "|") > -1) ) return true;
\r
452 // if the value of the field is greater than maxlength attribute,
\r
453 // then move the focus to the next field
\r
454 if( $field.getValue().length >= $field.attr("maxlength") ){
\r
455 // move to the next field and select the existing value
\r
456 var $next = $field.moveNext().select();
\r
457 if( $.isFunction(callback) ) callback.apply($field, [$next]);
\r
464 * jQuery.fn.moveNext()
\r
466 * places the focus in the next form field. if the field element is
\r
467 * the last in the form array, it'll return to the top.
\r
469 * returns a jQuery object pointing to the next field element
\r
471 * NOTE: if the selector returns multiple items, the first item is used.
\r
475 * $("#firstName").moveNext();
\r
476 * > Moves the focus to the next form field found after firstName
\r
479 // the moveNext() method
\r
480 $.fn.moveNext = function(){
\r
481 return this.moveIndex("next");
\r
485 * jQuery.fn.movePrev()
\r
487 * places the focus in the previous form field. if the field element is
\r
488 * the first in the form array, it'll return to the last element.
\r
490 * returns a jQuery object pointing to the previos field element
\r
492 * NOTE: if the selector returns multiple items, the first item is used
\r
495 * $("#firstName").movePrev();
\r
496 * > Moves the focus to the next form field found after firstName
\r
499 // the movePrev() method
\r
500 $.fn.movePrev = function(){
\r
501 return this.moveIndex("prev");
\r
505 * jQuery.fn.moveIndex()
\r
507 * Places the tab index into the specified index position
\r
509 * returns a jQuery object pointing to the previos field element
\r
511 * NOTE: if the selector returns multiple items, the first item is used
\r
514 * $("#firstName").movePrev();
\r
515 * > Moves the focus to the next form field found after firstName
\r
518 // the moveIndex() method
\r
519 $.fn.moveIndex = function(i){
\r
520 // get the current position and elements
\r
521 var pos = getFieldPosition(this);
\r
523 // if a string option has been specified, calculate the position
\r
524 if( i == "next" ) i = pos[0] + 1; // get the next item
\r
525 else if( i == "prev" ) i = pos[0] - 1; // get the previous item
\r
527 // make sure the index position is within the bounds of the elements array
\r
528 if( i < 0 ) i = pos[1].length - 1;
\r
529 else if( i >= pos[1].length ) i = 0;
\r
531 return $(pos[1][i]).trigger("focus");
\r
535 * jQuery.fn.getTabIndex()
\r
537 * gets the current tab index of the first element found in the selector
\r
539 * NOTE: if the selector returns multiple items, the first item is used
\r
542 * $("#firstName").getTabIndex();
\r
543 * > Gets the tabIndex for the firstName field
\r
546 // the getTabIndex() method
\r
547 $.fn.getTabIndex = function(){
\r
548 // return the position of the form field
\r
549 return getFieldPosition(this)[0];
\r
552 var getFieldPosition = function (jq){
\r
554 // get the first matching field
\r
555 $field = jq.filter("input, select, textarea").get(0),
\r
556 // store items with a tabindex
\r
558 // store items with no tabindex
\r
561 // if there is no match, return 0
\r
562 if( !$field ) return [-1, []];
\r
564 // make a single pass thru all form elements
\r
566 $field.form.elements,
\r
568 if( o.tagName != "FIELDSET" && !o.disabled && jQuery(o).is(":visible") ){
\r
569 if( o.tabIndex > 0 ){
\r
578 // sort the fields that had tab indexes
\r
581 return a.tabIndex - b.tabIndex;
\r
585 // merge the elements to create the correct tab position
\r
586 tabIndex = $.merge(tabIndex, posIndex);
\r
588 for( var i=0; i < tabIndex.length; i++ ){
\r
589 if( tabIndex[i] == $field ) return [i, tabIndex];
\r
592 return [-1, tabIndex];
\r
596 * jQuery.fn.limitSelection()
\r
598 * limits the number of items that can be selected
\r
601 * $("input:checkbox").limitSelection(3);
\r
602 * > No more than 3 items can be selected
\r
604 * $("input:checkbox").limitSelection(2, {onsuccess: function (), onfailure: function ()});
\r
605 * > Limits the selection to 2 items and runs the callback function when
\r
606 * > more than 2 items have been selected.
\r
608 * NOTE: Current when a "select-multiple" option undoes the selection,
\r
609 * it selects the first 3 options in the array--which isn't necessarily
\r
610 * the first 3 options the user selected. This is not the most desired
\r
614 $.fn.limitSelection = function(limit, options){
\r
615 // get the options to use
\r
616 var opt = jQuery.extend(
\r
617 (limit && limit.constructor == Object ? limit : {
\r
619 , onsuccess: function (limit){ return true; }
\r
620 , onfailure: function (limit){ alert("You can only select a maximum a of " + limit + " items."); return false; }
\r
626 var getCount = function (el){
\r
627 if( el.type == "select-multiple" ) return $("option:selected", self).length;
\r
628 else if( el.type == "checkbox" ) return self.filter(":checked").length;
\r
632 var undoSelect = function (){
\r
633 // reduce selection to n items
\r
634 setValue(self, getValue(self).slice(0, opt.limit));
\r
636 return opt.onfailure.apply(self, [opt.limit]);
\r
640 (!!self[0] && self[0].type == "select-multiple") ? "change.limitSelection" : "click.limitSelection",
\r
642 if( getCount(this) > opt.limit ){
\r
643 // run callback, it must return false to prevent action
\r
644 return (this.type == "select-multiple") ? undoSelect() : opt.onfailure.apply(self, [opt.limit]);
\r
646 opt.onsuccess.apply(self, [opt.limit]);
\r
653 * jQuery.fn.createCheckboxRange()
\r
655 * limits the number of items that can be selected
\r
658 * $("input:checkbox").createCheckboxRange();
\r
659 * > Allows a [SHIFT] + mouseclick to select all the items from the last
\r
660 * > checked checkmark to the current checkbox.
\r
662 * $("input:checkbox").createCheckboxRange(callback);
\r
663 * > Runs the callback method for each item who's checked status changes.
\r
664 * > This allows you to build hooks to highlight rows.
\r
667 $.fn.createCheckboxRange = function(callback){
\r
668 // get the options to use
\r
669 var opt = jQuery.extend(
\r
670 (callback && callback.constructor == Object ? callback : {
\r
671 bind: defaults.checkboxRangeKeyBinding
\r
676 var iLastSelection = 0, self = this, bCallback = $.isFunction(opt.click);
\r
678 // if there's a call back, bind it now and run it
\r
680 this.each(function (){opt.click.apply(this, [$.event.fix({type: null}), $(this).is(":checked")])});
\r
682 // loop through each checkbox and return the jQuery object
\r
685 // only perform this action on checkboxes
\r
686 if( this.type != "checkbox" ) return false;
\r
689 var updateLastCheckbox = function (e){
\r
690 iLastSelection = self.index(e.target);
\r
693 var checkboxClicked = function (e){
\r
694 var bSetChecked = this.checked, current = self.index(e.target), low = Math.min(iLastSelection, current), high = Math.max(iLastSelection+1, current);
\r
695 // run the callback for the clicked item
\r
696 if( bCallback ) opt.click.apply(this, [e, bSetChecked]);
\r
697 // if we don't detect the keypress, exit function
\r
698 if( !e[opt.bind] ) return;
\r
700 // loop through the items in the selected range
\r
701 for( var i=low; i < high; i++ ){
\r
702 // make sure to correctly set the checked status and run the change handler
\r
703 var item = self.eq(i).attr("checked", bSetChecked).trigger("change");
\r
704 // run the callback
\r
705 if( bCallback ) opt.click.apply(item[0], [e, bSetChecked]);
\r
712 // unbind the events so we can re-run the createCheckboxRange() plug-in for dynamically created elements
\r
713 .unbind("click.createCheckboxRange")
\r
715 // bind the functions, we bind on blur for keyboard selected items
\r
716 .bind("click.createCheckboxRange", checkboxClicked)
\r
717 .bind("click.createCheckboxRange", updateLastCheckbox)
\r
725 // determines how to process a field
\r
726 var getType = function (el){
\r
730 case "select": case "select-one": case "select-multiple":
\r
733 case "text": case "hidden": case "textarea": case "password": case "button": case "submit": case "submit": case "file":
\r
736 case "checkbox": case "radio":
\r
743 // gets the value of a select element
\r
744 var getOptionVal = function (el){
\r
745 // the attributes.specfied hack is for IE < 9 to deal with <option> tags with no value attribute
\r
746 return el.value || ((el.attributes && el.attributes['value'] && !(el.attributes['value'].specified)) ? el.text : null) || "";
\r
749 // checks to see if a value exists in an array
\r
750 var valueExists = function (a, v){
\r
751 return ($.inArray(v, a) > -1);
\r
754 // correctly gets the type of an object (including array/dates)
\r
755 var $type = function (o){
\r
756 var t = (typeof o).toLowerCase();
\r
758 if( t == "object" ){
\r
759 if( o instanceof Array ) t = "array";
\r
760 else if( o instanceof Date ) t = "date";
\r
765 // checks to see if an object is the specified type
\r
766 var $isType = function (o, v){
\r
767 return ($type(o) == String(v).toLowerCase());
\r