4 Copyright © 2009 Josh Pyles / Pixelmatrix Design LLC
5 http://pixelmatrixdesign.com
7 Requires jQuery 1.3 or newer
9 Much thanks to Thomas Reynolds and Buck Wilson for their help and advice on
12 Disabling text selection is made possible by Mathias Bynens
13 <http://mathiasbynens.be/> and his noSelect plugin.
14 <https://github.com/mathiasbynens/jquery-noselect>, which is embedded.
16 Also, thanks to David Kaneda and Eugene Bond for their contributions to the
19 Tyler Akins has also rewritten chunks of the plugin, helped close many issues,
20 and ensured version 2 got out the door.
23 MIT License - http://www.opensource.org/licenses/mit-license.php
29 /*global jQuery, document, navigator*/
31 (function (wind
, $, undef
) {
35 * Use .prop() if jQuery supports it, otherwise fall back to .attr()
37 * @param jQuery $el jQuery'd element on which we're calling attr/prop
38 * @param ... All other parameters are passed to jQuery's function
39 * @return The result from jQuery
41 function attrOrProp($el
) {
42 var args
= Array
.prototype.slice
.call(arguments
, 1);
46 return $el
.prop
.apply($el
, args
);
49 // jQuery 1.5 and below
50 return $el
.attr
.apply($el
, args
);
54 * For backwards compatibility with older jQuery libraries, only bind
55 * one thing at a time. Also, this function adds our namespace to
56 * events in one consistent location, shrinking the minified code.
58 * The properties on the events object are the names of the events
59 * that we are supposed to add to. It can be a space separated list.
60 * The namespace will be added automatically.
63 * @param Object options Uniform options for this element
64 * @param Object events Events to bind, properties are event names
66 function bindMany($el
, options
, events
) {
69 for (name
in events
) {
70 if (events
.hasOwnProperty(name
)) {
71 namespaced
= name
.replace(/ |$/g
, options
.eventNamespace
);
72 $el
.bind(namespaced
, events
[name
]);
78 * Bind the hover, active, focus, and blur UI updates
80 * @param jQuery $el Original element
81 * @param jQuery $target Target for the events (our div/span)
82 * @param Object options Uniform options for the element $target
84 function bindUi($el
, $target
, options
) {
85 bindMany($el
, options
, {
87 $target
.addClass(options
.focusClass
);
90 $target
.removeClass(options
.focusClass
);
91 $target
.removeClass(options
.activeClass
);
93 mouseenter: function () {
94 $target
.addClass(options
.hoverClass
);
96 mouseleave: function () {
97 $target
.removeClass(options
.hoverClass
);
98 $target
.removeClass(options
.activeClass
);
100 "mousedown touchbegin": function () {
101 if (!$el
.is(":disabled")) {
102 $target
.addClass(options
.activeClass
);
105 "mouseup touchend": function () {
106 $target
.removeClass(options
.activeClass
);
112 * Remove the hover, focus, active classes.
114 * @param jQuery $el Element with classes
115 * @param Object options Uniform options for the element
117 function classClearStandard($el
, options
) {
118 $el
.removeClass(options
.hoverClass
+ " " + options
.focusClass
+ " " + options
.activeClass
);
122 * Add or remove a class, depending on if it's "enabled"
124 * @param jQuery $el Element that has the class added/removed
125 * @param String className Class or classes to add/remove
126 * @param Boolean enabled True to add the class, false to remove
128 function classUpdate($el
, className
, enabled
) {
130 $el
.addClass(className
);
132 $el
.removeClass(className
);
137 * Updating the "checked" property can be a little tricky. This
138 * changed in jQuery 1.6 and now we can pass booleans to .prop().
139 * Prior to that, one either adds an attribute ("checked=checked") or
140 * removes the attribute.
142 * @param jQuery $tag Our Uniform span/div
143 * @param jQuery $el Original form element
144 * @param Object options Uniform options for this element
146 function classUpdateChecked($tag
, $el
, options
) {
147 setTimeout(function() { // sunhater@sunhater.com
150 isChecked
= $el
.is(":" + c
);
154 $el
.prop(c
, isChecked
);
156 // jQuery 1.5 and below
163 classUpdate($tag
, options
.checkedClass
, isChecked
);
168 * Set or remove the "disabled" class for disabled elements, based on
169 * if the element is detected to be disabled.
171 * @param jQuery $tag Our Uniform span/div
172 * @param jQuery $el Original form element
173 * @param Object options Uniform options for this element
175 function classUpdateDisabled($tag
, $el
, options
) {
176 classUpdate($tag
, options
.disabledClass
, $el
.is(":disabled"));
180 * Wrap an element inside of a container or put the container next
181 * to the element. See the code for examples of the different methods.
183 * Returns the container that was added to the HTML.
185 * @param jQuery $el Element to wrap
186 * @param jQuery $container Add this new container around/near $el
187 * @param String method One of "after", "before" or "wrap"
188 * @return $container after it has been cloned for adding to $el
190 function divSpanWrap($el
, $container
, method
) {
193 // Result: <element /> <container />
194 $el
.after($container
);
197 // Result: <container /> <element />
198 $el
.before($container
);
201 // Result: <container> <element /> </container>
202 $el
.wrap($container
);
211 * Create a div/span combo for uniforming an element
213 * @param jQuery $el Element to wrap
214 * @param Object options Options for the element, set by the user
215 * @param Object divSpanConfig Options for how we wrap the div/span
216 * @return Object Contains the div and span as properties
218 function divSpan($el
, options
, divSpanConfig
) {
221 if (!divSpanConfig
) {
225 divSpanConfig
= $.extend({
235 $span
= $('<span />');
237 // Automatically hide this div/span if the element is hidden.
238 // Do not hide if the element is hidden because a parent is hidden.
239 if (options
.autoHide
&& $el
.is(':hidden') && $el
.css('display') === 'none') {
243 if (divSpanConfig
.divClass
) {
244 $div
.addClass(divSpanConfig
.divClass
);
247 if (options
.wrapperClass
) {
248 $div
.addClass(options
.wrapperClass
);
251 if (divSpanConfig
.spanClass
) {
252 $span
.addClass(divSpanConfig
.spanClass
);
255 id
= attrOrProp($el
, 'id');
257 if (options
.useID
&& id
) {
258 attrOrProp($div
, 'id', options
.idPrefix
+ '-' + id
);
261 if (divSpanConfig
.spanHtml
) {
262 $span
.html(divSpanConfig
.spanHtml
);
265 $div
= divSpanWrap($el
, $div
, divSpanConfig
.divWrap
);
266 $span
= divSpanWrap($el
, $span
, divSpanConfig
.spanWrap
);
267 classUpdateDisabled($div
, $el
, options
);
276 * Wrap an element with a span to apply a global wrapper class
278 * @param jQuery $el Element to wrap
279 * @param object options
280 * @return jQuery Wrapper element
282 function wrapWithWrapperClass($el
, options
) {
285 if (!options
.wrapperClass
) {
289 $span
= $('<span />').addClass(options
.wrapperClass
);
290 $span
= divSpanWrap($el
, $span
, "wrap");
296 * Test if high contrast mode is enabled.
298 * In high contrast mode, background images can not be set and
299 * they are always returned as 'none'.
301 * @return boolean True if in high contrast mode
303 function highContrast() {
304 var c
, $div
, el
, rgb
;
306 // High contrast mode deals with white and black
307 rgb
= 'rgb(120,2,153)';
308 $div
= $('<div style="width:0;height:0;color:' + rgb
+ '">');
309 $('body').append($div
);
312 // $div.css() will get the style definition, not
313 // the actually displaying style
314 if (wind
.getComputedStyle
) {
315 c
= wind
.getComputedStyle(el
, '').color
;
317 c
= (el
.currentStyle
|| el
.style
|| {}).color
;
321 return c
.replace(/ /g
, '') !== rgb
;
326 * Change text into safe HTML
329 * @return String HTML version
331 function htmlify(text
) {
336 return $('<span />').text(text
).html();
340 * If not MSIE, return false.
341 * If it is, return the version number.
343 * @return false|number
346 return navigator
.cpuClass
&& !navigator
.product
;
350 * Return true if this version of IE allows styling
354 function isMsieSevenOrNewer() {
355 if (wind
.XMLHttpRequest
!== undefined) {
363 * Test if the element is a multiselect
365 * @param jQuery $el Element
366 * @return boolean true/false
368 function isMultiselect($el
) {
371 if ($el
[0].multiple
) {
375 elSize
= attrOrProp($el
, "size");
377 if (!elSize
|| elSize
<= 1) {
385 * Meaningless utility function. Used mostly for improving minification.
389 function returnFalse() {
394 * noSelect plugin, very slightly modified
395 * http://mths.be/noselect v1.0.3
397 * @param jQuery $elem Element that we don't want to select
398 * @param Object options Uniform options for the element
400 function noSelect($elem
, options
) {
402 bindMany($elem
, options
, {
403 'selectstart dragstart mousedown': returnFalse
409 webkitUserSelect
: none
,
415 * Updates the filename tag based on the value of the real input
418 * @param jQuery $el Actual form element
419 * @param jQuery $filenameTag Span/div to update
420 * @param Object options Uniform options for this element
422 function setFilename($el
, $filenameTag
, options
) {
423 var filename
= $el
.val();
425 if (filename
=== "") {
426 filename
= options
.fileDefaultHtml
;
428 filename
= filename
.split(/[\/\\]+/);
429 filename
= filename
[(filename
.length
- 1)];
432 $filenameTag
.text(filename
);
437 * Function from jQuery to swap some CSS values, run a callback,
438 * then restore the CSS. Modified to pass JSLint and handle undefined
439 * values with 'use strict'.
441 * @param jQuery $el Element
442 * @param object newCss CSS values to swap out
443 * @param Function callback Function to run
445 function swap($elements
, newCss
, callback
) {
450 $elements
.each(function () {
453 for (name
in newCss
) {
454 if (Object
.prototype.hasOwnProperty
.call(newCss
, name
)) {
458 old
: this.style
[name
]
461 this.style
[name
] = newCss
[name
];
468 while (restore
.length
) {
469 item
= restore
.pop();
470 item
.el
.style
[item
.name
] = item
.old
;
476 * The browser doesn't provide sizes of elements that are not visible.
477 * This will clone an element and add it to the DOM for calculations.
480 * @param String method
482 function sizingInvisible($el
, callback
) {
485 // We wish to target ourselves and any parents as long as
486 // they are not visible
487 targets
= $el
.parents();
488 targets
.push($el
[0]);
489 targets
= targets
.not(':visible');
491 visibility
: "hidden",
499 * Standard way to unwrap the div/span combination from an element
501 * @param jQuery $el Element that we wish to preserve
502 * @param Object options Uniform options for the element
503 * @return Function This generated function will perform the given work
505 function unwrapUnwrapUnbindFunction($el
, options
) {
507 $el
.unwrap().unwrap().unbind(options
.eventNamespace
);
511 var allowStyling
= true, // False if IE6 or other unsupported browsers
512 highContrastTest
= false, // Was the high contrast test ran?
513 uniformHandlers
= [ // Objects that take care of "unification"
516 match: function ($el
) {
517 return $el
.is("a, button, :submit, :reset, input[type='button']");
519 apply: function ($el
, options
) {
520 var $div
, defaultSpanHtml
, ds
, getHtml
, doingClickEvent
;
521 defaultSpanHtml
= options
.submitDefaultHtml
;
523 if ($el
.is(":reset")) {
524 defaultSpanHtml
= options
.resetDefaultHtml
;
527 if ($el
.is("a, button")) {
528 // Use the HTML inside the tag
529 getHtml = function () {
530 return $el
.html() || defaultSpanHtml
;
533 // Use the value property of the element
534 getHtml = function () {
535 return htmlify(attrOrProp($el
, "value")) || defaultSpanHtml
;
539 ds
= divSpan($el
, options
, {
540 divClass
: options
.buttonClass
,
544 bindUi($el
, $div
, options
);
545 doingClickEvent
= false;
546 bindMany($div
, options
, {
547 "click touchend": function () {
548 var ev
, res
, target
, href
;
550 if (doingClickEvent
) {
554 if ($el
.is(':disabled')) {
558 doingClickEvent
= true;
560 if ($el
[0].dispatchEvent
) {
561 ev
= document
.createEvent("MouseEvents");
562 ev
.initEvent("click", true, true);
563 res
= $el
[0].dispatchEvent(ev
);
565 if ($el
.is('a') && res
) {
566 target
= attrOrProp($el
, 'target');
567 href
= attrOrProp($el
, 'href');
569 if (!target
|| target
=== '_self') {
570 document
.location
.href
= href
;
572 wind
.open(href
, target
);
579 doingClickEvent
= false;
582 noSelect($div
, options
);
584 remove: function () {
588 // Remove div and span
592 $el
.unbind(options
.eventNamespace
);
595 update: function () {
596 classClearStandard($div
, options
);
597 classUpdateDisabled($div
, $el
, options
);
599 ds
.span
.html(getHtml()).append($el
);
606 match: function ($el
) {
607 return $el
.is(":checkbox");
609 apply: function ($el
, options
) {
611 ds
= divSpan($el
, options
, {
612 divClass
: options
.checkboxClass
617 // Add focus classes, toggling, active, etc.
618 bindUi($el
, $div
, options
);
619 bindMany($el
, options
, {
620 "click touchend": function () {
621 classUpdateChecked($span
, $el
, options
);
624 classUpdateChecked($span
, $el
, options
);
626 remove
: unwrapUnwrapUnbindFunction($el
, options
),
627 update: function () {
628 classClearStandard($div
, options
);
629 $span
.removeClass(options
.checkedClass
);
630 classUpdateChecked($span
, $el
, options
);
631 classUpdateDisabled($div
, $el
, options
);
637 // File selection / uploads
638 match: function ($el
) {
639 return $el
.is(":file");
641 apply: function ($el
, options
) {
642 var ds
, $div
, $filename
, $button
;
644 // The "span" is the button
645 ds
= divSpan($el
, options
, {
646 divClass
: options
.fileClass
,
647 spanClass
: options
.fileButtonClass
,
648 spanHtml
: options
.fileButtonHtml
,
653 $filename
= $("<span />").html(options
.fileDefaultHtml
);
654 $filename
.addClass(options
.filenameClass
);
655 $filename
= divSpanWrap($el
, $filename
, "after");
658 if (!attrOrProp($el
, "size")) {
659 attrOrProp($el
, "size", $div
.width() / 10);
663 function filenameUpdate() {
664 setFilename($el
, $filename
, options
);
667 bindUi($el
, $div
, options
);
669 // Account for input saved across refreshes
672 // IE7 doesn't fire onChange until blur or second fire.
674 // IE considers browser chrome blocking I/O, so it
675 // suspends tiemouts until after the file has
677 bindMany($el
, options
, {
679 $el
.trigger("change");
680 setTimeout(filenameUpdate
, 0);
684 // All other browsers behave properly
685 bindMany($el
, options
, {
686 change
: filenameUpdate
690 noSelect($filename
, options
);
691 noSelect($button
, options
);
693 remove: function () {
694 // Remove filename and button
698 // Unwrap parent div, remove events
699 return $el
.unwrap().unbind(options
.eventNamespace
);
701 update: function () {
702 classClearStandard($div
, options
);
703 setFilename($el
, $filename
, options
);
704 classUpdateDisabled($div
, $el
, options
);
710 // Input fields (text)
711 match: function ($el
) {
712 if ($el
.is("input")) {
713 var t
= (" " + attrOrProp($el
, "type") + " ").toLowerCase(),
714 allowed
= " color date datetime datetime-local email month number password search tel text time url week ";
715 return allowed
.indexOf(t
) >= 0;
720 apply: function ($el
, options
) {
721 var elType
, $wrapper
;
723 elType
= attrOrProp($el
, "type");
724 $el
.addClass(options
.inputClass
);
725 $wrapper
= wrapWithWrapperClass($el
, options
);
726 bindUi($el
, $el
, options
);
728 if (options
.inputAddTypeAsClass
) {
729 $el
.addClass(elType
);
733 remove: function () {
734 $el
.removeClass(options
.inputClass
);
736 if (options
.inputAddTypeAsClass
) {
737 $el
.removeClass(elType
);
750 match: function ($el
) {
751 return $el
.is(":radio");
753 apply: function ($el
, options
) {
755 ds
= divSpan($el
, options
, {
756 divClass
: options
.radioClass
761 // Add classes for focus, handle active, checked
762 bindUi($el
, $div
, options
);
763 bindMany($el
, options
, {
764 "click touchend": function () {
765 // Find all radios with the same name, then update
766 // them with $.uniform.update() so the right
767 // per-element options are used
768 $.uniform
.update($(':radio[name="' + attrOrProp($el
, "name") + '"]'));
771 classUpdateChecked($span
, $el
, options
);
773 remove
: unwrapUnwrapUnbindFunction($el
, options
),
774 update: function () {
775 classClearStandard($div
, options
);
776 classUpdateChecked($span
, $el
, options
);
777 classUpdateDisabled($div
, $el
, options
);
783 // Select lists, but do not style multiselects here
784 match: function ($el
) {
785 if ($el
.is("select") && !isMultiselect($el
)) {
791 apply: function ($el
, options
) {
792 var ds
, $div
, $span
, origElemWidth
;
794 if (options
.selectAutoWidth
) {
795 sizingInvisible($el
, function () {
796 origElemWidth
= $el
.width();
800 ds
= divSpan($el
, options
, {
801 divClass
: options
.selectClass
,
802 spanHtml
: ($el
.find(":selected:first") || $el
.find("option:first")).html(),
808 if (options
.selectAutoWidth
) {
809 // Use the width of the select and adjust the
810 // span and div accordingly
811 sizingInvisible($el
, function () {
812 // Force "display: block" - related to bug #287
813 swap($([ $span
[0], $div
[0] ]), {
817 spanPad
= $span
.outerWidth() - $span
.width();
818 $div
.width(origElemWidth
+ spanPad
);
819 $span
.width(origElemWidth
);
823 // Force the select to fill the size of the div
824 $div
.addClass('fixedWidth');
827 // Take care of events
828 bindUi($el
, $div
, options
);
829 bindMany($el
, options
, {
830 change: function () {
831 $span
.html($el
.find(":selected").html());
832 $div
.removeClass(options
.activeClass
);
834 "click touchend": function () {
835 // IE7 and IE8 may not update the value right
836 // until after click event - issue #238
837 var selHtml
= $el
.find(":selected").html();
839 if ($span
.html() !== selHtml
) {
840 // Change was detected
841 // Fire the change event on the select tag
842 $el
.trigger('change');
846 $span
.html($el
.find(":selected").html());
849 noSelect($span
, options
);
851 remove: function () {
852 // Remove sibling span
856 $el
.unwrap().unbind(options
.eventNamespace
);
859 update: function () {
860 if (options
.selectAutoWidth
) {
861 // Easier to remove and reapply formatting
862 $.uniform
.restore($el
);
863 $el
.uniform(options
);
865 classClearStandard($div
, options
);
867 // Reset current selected text
868 $span
.html($el
.find(":selected").html());
869 classUpdateDisabled($div
, $el
, options
);
876 // Select lists - multiselect lists only
877 match: function ($el
) {
878 if ($el
.is("select") && isMultiselect($el
)) {
884 apply: function ($el
, options
) {
887 $el
.addClass(options
.selectMultiClass
);
888 $wrapper
= wrapWithWrapperClass($el
, options
);
889 bindUi($el
, $el
, options
);
892 remove: function () {
893 $el
.removeClass(options
.selectMultiClass
);
905 match: function ($el
) {
906 return $el
.is("textarea");
908 apply: function ($el
, options
) {
911 $el
.addClass(options
.textareaClass
);
912 $wrapper
= wrapWithWrapperClass($el
, options
);
913 bindUi($el
, $el
, options
);
916 remove: function () {
917 $el
.removeClass(options
.textareaClass
);
929 // IE6 can't be styled - can't set opacity on select
930 if (isMsie() && !isMsieSevenOrNewer()) {
931 allowStyling
= false;
935 // Default options that can be overridden globally or when uniformed
936 // globally: $.uniform.defaults.fileButtonHtml = "Pick A File";
937 // on uniform: $('input').uniform({fileButtonHtml: "Pick a File"});
939 activeClass
: "active",
941 buttonClass
: "button",
942 checkboxClass
: "checker",
943 checkedClass
: "checked",
944 disabledClass
: "disabled",
945 eventNamespace
: ".uniform",
946 fileButtonClass
: "action",
947 fileButtonHtml
: "Choose File",
948 fileClass
: "uploader",
949 fileDefaultHtml
: "No file selected",
950 filenameClass
: "filename",
954 inputAddTypeAsClass
: true,
955 inputClass
: "uniform-input",
957 resetDefaultHtml
: "Reset",
958 resetSelector
: false, // We'll use our own function when you don't specify one
959 selectAutoWidth
: true,
960 selectClass
: "selector",
961 selectMultiClass
: "uniform-multiselect",
962 submitDefaultHtml
: "Submit", // Only text allowed
963 textareaClass
: "uniform",
968 // All uniformed elements - DOM objects
972 $.fn
.uniform = function (options
) {
974 options
= $.extend({}, $.uniform
.defaults
, options
);
976 // If we are in high contrast mode, do not allow styling
977 if (!highContrastTest
) {
978 highContrastTest
= true;
980 if (highContrast()) {
981 allowStyling
= false;
985 // Only uniform on browsers that work
990 // Code for specifying a reset button
991 if (options
.resetSelector
) {
992 $(options
.resetSelector
).mouseup(function () {
993 wind
.setTimeout(function () {
994 $.uniform
.update(el
);
999 return this.each(function () {
1000 var $el
= $(this), i
, handler
, callbacks
;
1002 // Avoid uniforming elements already uniformed - just update
1003 if ($el
.data("uniformed")) {
1004 $.uniform
.update($el
);
1008 // See if we have any handler for this type of element
1009 for (i
= 0; i
< uniformHandlers
.length
; i
= i
+ 1) {
1010 handler
= uniformHandlers
[i
];
1012 if (handler
.match($el
, options
)) {
1013 callbacks
= handler
.apply($el
, options
);
1014 $el
.data("uniformed", callbacks
);
1016 // Store element in our global array
1017 $.uniform
.elements
.push($el
.get(0));
1022 // Could not style this element
1026 $.uniform
.restore
= $.fn
.uniform
.restore = function (elem
) {
1027 if (elem
=== undef
) {
1028 elem
= $.uniform
.elements
;
1031 $(elem
).each(function () {
1032 var $el
= $(this), index
, elementData
;
1033 elementData
= $el
.data("uniformed");
1035 // Skip elements that are not uniformed
1040 // Unbind events, remove additional markup that was added
1041 elementData
.remove();
1043 // Remove item from list of uniformed elements
1044 index
= $.inArray(this, $.uniform
.elements
);
1047 $.uniform
.elements
.splice(index
, 1);
1050 $el
.removeData("uniformed");
1054 $.uniform
.update
= $.fn
.uniform
.update = function (elem
) {
1055 if (elem
=== undef
) {
1056 elem
= $.uniform
.elements
;
1059 $(elem
).each(function () {
1060 var $el
= $(this), elementData
;
1061 elementData
= $el
.data("uniformed");
1063 // Skip elements that are not uniformed
1068 elementData
.update($el
, elementData
.options
);