diff --git a/build/tasks/testswarm.js b/build/tasks/testswarm.js
index 0685315e698..1e2edbf6df0 100644
--- a/build/tasks/testswarm.js
+++ b/build/tasks/testswarm.js
@@ -18,6 +18,7 @@ var versions = {
"Draggable": "draggable/draggable.html",
"Droppable": "droppable/droppable.html",
"Effects": "effects/effects.html",
+ "Mask": "mask/mask.html",
"Menu": "menu/menu.html",
"Position": "position/position.html",
"Progressbar": "progressbar/progressbar.html",
@@ -27,6 +28,7 @@ var versions = {
"Sortable": "sortable/sortable.html",
"Spinner": "spinner/spinner.html",
"Tabs": "tabs/tabs.html",
+ "Timepicker": "timepicker/timepicker.html",
"Tooltip": "tooltip/tooltip.html",
"Widget": "widget/widget.html"
};
diff --git a/demos/index.html b/demos/index.html
index 4739d76cd5a..d8df663d04b 100644
--- a/demos/index.html
+++ b/demos/index.html
@@ -18,6 +18,7 @@
droppable
effect
hide
+ mask
menu
position
progressbar
@@ -30,6 +31,7 @@
spinner
switchClass
tabs
+ timepicker
toggle
toggleClass
tooltip
diff --git a/demos/mask/default.html b/demos/mask/default.html
new file mode 100644
index 00000000000..0409b145ea6
--- /dev/null
+++ b/demos/mask/default.html
@@ -0,0 +1,38 @@
+
+
+
+
+ jQuery UI Mask - Simple Mask Demo
+
+
+
+
+
+
+
+
+
+
+
+
+ Phone Number: (999) 999-9999
+
+
+
+
+
A few simple masks
+
+
+
+
+
+
+
diff --git a/demos/mask/functiondefinition.html b/demos/mask/functiondefinition.html
new file mode 100644
index 00000000000..3266f903a1b
--- /dev/null
+++ b/demos/mask/functiondefinition.html
@@ -0,0 +1,93 @@
+
+
+
+
+ jQuery UI Mask - Function Definition Demo
+
+
+
+
+
+
+
+
+
+
+
+
+ Date: Format: yyyy/mm/dd
+
+
+
+
+
An example of using functions to define new mask definitions. These definitions are "multiple character" fields that allow us to have a date formatted input that validates.
+
+
+
+
+
+
+
diff --git a/demos/mask/index.html b/demos/mask/index.html
new file mode 100644
index 00000000000..c860d9ddfc2
--- /dev/null
+++ b/demos/mask/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+ jQuery UI Mask Demos
+
+
+
+
+
diff --git a/demos/mask/regulardefinition.html b/demos/mask/regulardefinition.html
new file mode 100644
index 00000000000..9dcd226acbf
--- /dev/null
+++ b/demos/mask/regulardefinition.html
@@ -0,0 +1,41 @@
+
+
+
+
+ jQuery UI Mask - Regular Expressions Demo
+
+
+
+
+
+
+
+
+
+
+
+
+ Lens Perscription: Format: ~9.99 ~9.99 999
+
+
+
+
+
An example of using a regular expression to define a new mask definition. Using the definitions
option we define the ~
in the mask to only accept a +
or a -
.
+
+
+
+
+
+
+
diff --git a/grunt.js b/grunt.js
index 524421be79c..96818bba719 100644
--- a/grunt.js
+++ b/grunt.js
@@ -281,7 +281,7 @@ grunt.initConfig({
files: grunt.file.expandFiles( "tests/unit/**/*.html" ).filter(function( file ) {
// disabling everything that doesn't (quite) work with PhantomJS for now
// TODO except for all|index|test, try to include more as we go
- return !( /(all|index|test|dialog|tabs|tooltip)\.html$/ ).test( file );
+ return !( /(all|index|test|dialog|tabs|timepicker|tooltip)\.html$/ ).test( file );
})
},
lint: {
diff --git a/tests/unit/all.html b/tests/unit/all.html
index 7581a737b5e..172d17ea7f5 100644
--- a/tests/unit/all.html
+++ b/tests/unit/all.html
@@ -25,6 +25,7 @@
"draggable/draggable.html",
"droppable/droppable.html",
"effects/effects.html",
+ "mask/mask.html",
"menu/menu.html",
"position/position.html",
"progressbar/progressbar.html",
@@ -34,6 +35,8 @@
"sortable/sortable.html",
"spinner/spinner.html",
"tabs/tabs.html",
+ "tabs/tabs_deprecated.html",
+ "timepicker/timepicker.html",
"tooltip/tooltip.html",
"widget/widget.html"
];
diff --git a/tests/unit/index.html b/tests/unit/index.html
index 8075ce2a449..6b8ffe795d2 100644
--- a/tests/unit/index.html
+++ b/tests/unit/index.html
@@ -42,11 +42,13 @@ Widgets
Button
Datepicker
Dialog
+ Mask
Menu
Progressbar
Slider
Spinner
Tabs
+ Timepicker
Tooltip
diff --git a/tests/unit/mask/all.html b/tests/unit/mask/all.html
new file mode 100644
index 00000000000..46690c4c787
--- /dev/null
+++ b/tests/unit/mask/all.html
@@ -0,0 +1,30 @@
+
+
+
+
+ jQuery UI Mask Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/mask/mask.html b/tests/unit/mask/mask.html
new file mode 100644
index 00000000000..d0d4117865f
--- /dev/null
+++ b/tests/unit/mask/mask.html
@@ -0,0 +1,39 @@
+
+
+
+
+ jQuery UI Mask Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/mask/mask_common.js b/tests/unit/mask/mask_common.js
new file mode 100644
index 00000000000..c5d4cd0a00a
--- /dev/null
+++ b/tests/unit/mask/mask_common.js
@@ -0,0 +1,16 @@
+TestHelpers.commonWidgetTests( "mask", {
+ defaults: {
+ clearEmpty: true,
+ definitions: {
+ '9': /[0-9]/,
+ 'a': /[A-Za-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]/,
+ '*': /[A-Za-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]/
+ },
+ disabled: false,
+ mask: null,
+ placeholder: "_",
+
+ // callbacks
+ create: null
+ }
+});
diff --git a/tests/unit/mask/mask_core.js b/tests/unit/mask/mask_core.js
new file mode 100644
index 00000000000..1b63f5145aa
--- /dev/null
+++ b/tests/unit/mask/mask_core.js
@@ -0,0 +1,108 @@
+(function( $ ) {
+
+module( "mask: core" );
+
+test( "masked inputs get the '.ui-mask' class", function() {
+ expect( 3 );
+ var input = $( "#mask1" );
+ ok( !input.is( ".ui-mask" ), "Input is not masked" );
+ input.mask({ mask: "999" });
+ ok( input.is( ".ui-mask" ), "Input is now masked" );
+ input.mask( "destroy" );
+ ok( !input.is( ".ui-mask" ), "destroy clears masked class" );
+});
+
+test( "_caret() can move and read the text cursor", 4, function() {
+
+ var input = $( "#mask1" ).val("1234").mask({
+ mask: "9999"
+ }),
+ instance = input.data( "ui-mask" );
+
+ input.focus();
+ instance._caret( 0 );
+ deepEqual( instance._caret(), {
+ begin: 0,
+ end: 0
+ }, "Caret position set to 0 results in 0, 0" );
+
+ instance._caret( 1 );
+ deepEqual( instance._caret(), {
+ begin: 1,
+ end: 1
+ }, "Caret position set 1 results in 1, 1" );
+
+ instance._caret( 5 );
+ deepEqual( instance._caret(), {
+ begin: 4,
+ end: 4
+ }, "Caret position set beyond bounds (5) results in 4, 4" );
+
+ instance._caret( 0, 2 );
+ deepEqual( instance._caret(), {
+ begin: 0,
+ end: 2
+ }, "Caret position set to 0, 2 results in 0, 2" );
+});
+
+test( "Parsing initial value skips literals", function() {
+ expect( 2 );
+ var input = $( "#mask1" );
+ input.val("123456").mask({
+ mask: "99/99/99"
+ });
+
+ equal( input.val(), "12/34/56", "Literals were inserted into val");
+ input.mask( "option", "mask", "99-99-99" );
+ equal( input.val(), "12-34-56", "Old literals were ignored, and new ones inserted into val");
+
+});
+
+test( "Parsing initial value with multi-character fields", function() {
+ expect( 2 );
+ var defs = {
+ hh: function( value ) {
+ value = parseInt( value, 10 );
+ if ( value >= 1 || value <= 12 ) {
+ return ( value < 10 ? "0" : "" ) + value;
+ }
+ },
+ ss: function( value ) {
+ value = parseInt( value, 10 );
+ if ( value >= 0 || value <= 59 ) {
+ return ( value < 10 ? "0" : "" ) + value;
+ }
+ }
+ },
+ input = $( "#mask1" );
+
+ input.val("123456").mask({
+ mask: "hh:ss:ss",
+ definitions: defs
+ });
+
+ equal( input.val(), "12:34:56", "Literals were inserted into val");
+ input.mask( "option", "mask", "99-99-99" );
+ equal( input.val(), "12-34-56", "Old literals were ignored, and new ones inserted into val");
+});
+
+test( "Default values provided by function", function() {
+ expect( 1 );
+ var defs = {
+ hh: function( value ) {
+ if ( value === "" ) {
+ return "11";
+ }
+ }
+ },
+ input = $( "#mask1" );
+
+ input.val("").mask({
+ mask: "hh",
+ definitions: defs
+ });
+ equal( input.val(), "11", "No value was accepted, so the 'default' from the mask was provided" );
+
+});
+
+}( jQuery ) );
diff --git a/tests/unit/mask/mask_events.js b/tests/unit/mask/mask_events.js
new file mode 100644
index 00000000000..f524d627f82
--- /dev/null
+++ b/tests/unit/mask/mask_events.js
@@ -0,0 +1,330 @@
+(function( $ ) {
+
+module( "mask: events" );
+
+test( "focus: Initial Caret Positioning", 4, function() {
+ var input = $( "#mask1" ).val("").mask({
+ mask: "9",
+ clearEmpty: false
+ }),
+ mask = input.data( "ui-mask" );
+
+ equal( input.val(), "_", "Initial Value Expected" );
+ TestHelpers.focus( input );
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+ TestHelpers.blur( input );
+
+ input.mask( "option", "mask", "(9)" );
+ equal( input.val(), "(_)", "Initial Value Expected" );
+ TestHelpers.focus( input );
+
+ deepEqual( mask._caret(), { begin: 1, end: 1 }, "Caret position correct");
+});
+
+test( "keydown: Backspace pulls values from right", function() {
+ expect( 12 );
+ var input = $( "#mask1" ).val("123").mask({ mask: "999" }),
+ mask = input.data( "ui-mask" );
+
+
+ TestHelpers.focus( input );
+ equal( input.val(), "123", "Initial Value Expected" );
+
+ mask._caret( 2 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "13_", "Backspaced the second character");
+ deepEqual( mask._caret(), { begin: 1, end: 1 }, "Caret position correct");
+
+ input.val( "1z" ).mask( "option", "mask", "9a" );
+ equal( input.val(), "1z", "Initial Value Expected" );
+
+ mask._caret( 1 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "_z", "Backspace did not pull value because it wasn't valid" );
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+
+ input.val( "12" ).mask( "option", "mask", "9-9" );
+ equal( input.val(), "1-2", "Initial Value Expected" );
+
+ mask._caret( 1 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "2-_", "Backspace pulled value because it was valid" );
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+
+
+ input.val( "1z" ).mask( "option", "mask", "9-a" );
+ equal( input.val(), "1-z", "Initial Value Expected" );
+
+ mask._caret( 1 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "_-z", "Backspace did not pull value because it wasn't valid" );
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+});
+
+test( "keydown: Backspace with the cursor to the right of a mask literal", function() {
+ expect( 6 );
+ var input = $( "#mask1" ).val("123").mask({ mask: "9-99" }),
+ mask = input.data( "ui-mask" );
+
+ TestHelpers.focus( input );
+ equal( input.val(), "1-23", "Initial Value Expected" );
+
+ mask._caret( 2 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "2-3_", "Backspaced across the literal -, brought values with");
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+
+ input.val("z12").mask( "option", "mask", "a-99" );
+ equal( input.val(), "z-12", "New Initial Value Expected");
+
+ mask._caret( 2 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "_-12", "Backspaced across the literal -, values held position");
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+});
+
+test( "keydown: Backspace with multiple values higlighted", function() {
+ expect( 3 );
+ var input = $( "#mask1" ).val("1234567890").mask({ mask: "(999)999-9999" }),
+ mask = input.data( "ui-mask" );
+
+ TestHelpers.focus( input );
+ equal( input.val(), "(123)456-7890", "Initial Value Expected" );
+
+ mask._caret( 5, 8 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "(123)789-0___", "Deleted three highlighted values, pulled values from right" );
+ deepEqual( mask._caret(), { begin: 5, end: 5 }, "Caret position correct");
+});
+
+test( "keypress: Typing with multiple values higlighted", function() {
+ expect( 3 );
+ var input = $( "#mask1" ).val("1234567890").mask({ mask: "(999)999-9999" }),
+ mask = input.data( "ui-mask" );
+
+ TestHelpers.focus( input );
+ equal( input.val(), "(123)456-7890", "Initial Value Expected" );
+
+ mask._caret( 5, 8 );
+ TestHelpers.press( input, "0" );
+ equal( input.val(), "(123)078-90__", "Deleted three highlighted values, pulled values from right" );
+ deepEqual( mask._caret(), { begin: 6, end: 6 }, "Caret position correct");
+});
+
+test( "keypress: Typing with multi-character fields", function() {
+ expect( 5 );
+ var input = $( "#mask1" ).val("").mask({
+ mask: "xx-xx-xx",
+ definitions: {
+ xx: function( value ) {
+ return value;
+ }
+ }
+ }),
+ mask = input.data( "ui-mask" );
+
+ TestHelpers.focus( input );
+ equal( input.val(), "__-__-__", "Initial Value Expected" );
+
+ mask._caret( 0 );
+ TestHelpers.press( input, "0" );
+ equal( input.val(), "0_-__-__", "typed a 0" );
+ deepEqual( mask._caret(), { begin: 1, end: 1 }, "Caret position correct");
+ TestHelpers.press( input, "z" );
+ equal( input.val(), "0z-__-__", "typed a z" );
+ deepEqual( mask._caret(), { begin: 3, end: 5 }, "Caret position correct");
+});
+
+test( "keypress: Typing with multi-character only accepts valid values", function() {
+ expect( 12 );
+ var input = $( "#mask1" ).val( "" ).mask({
+ mask: "xx-xx-xx",
+ definitions: {
+ xx: function( value ) {
+ if ( !value.length ) {
+ return;
+ }
+ if ( value.charAt( 0 ) === "_" ) {
+ return;
+ }
+ if ( value.charAt( 1 ) === "-" || value.length === 1 ) {
+ return value.charAt(0) + value.charAt( 0 );
+ }
+ if ( value.charAt( 0 ) === value.charAt( 1 ) ) {
+ return value;
+ }
+ }
+ }
+ }),
+ mask = input.data( "ui-mask" );
+
+ TestHelpers.focus( input );
+ equal( input.val(), "__-__-__", "Initial Value Expected" );
+
+ deepEqual( mask._caret(), { begin: 0, end: 2 }, "Caret position correct");
+
+ TestHelpers.press( input, "0" );
+ equal( input.val(), "0_-__-__", "typed a 0" );
+ deepEqual( mask._caret(), { begin: 1, end: 1 }, "Caret position correct");
+
+ TestHelpers.press( input, "z" );
+ equal( input.val(), "0_-__-__", "typed a z, wasn't allowed" );
+ deepEqual( mask._caret(), { begin: 1, end: 1 }, "Caret position correct");
+
+ TestHelpers.press( input, "0" );
+ equal( input.val(), "00-__-__", "typed a 0, was allowed" );
+ deepEqual( mask._caret(), { begin: 3, end: 5 }, "Caret position correct");
+
+ TestHelpers.press( input, "1" );
+ equal( input.val(), "00-1_-__", "typed a 1, was allowed" );
+ deepEqual( mask._caret(), { begin: 4, end: 4 }, "Caret position correct");
+
+ TestHelpers.press( input, "-" );
+ equal( input.val(), "00-11-__", "typed a 1, was replaced with correct value" );
+ deepEqual( mask._caret(), { begin: 6, end: 8 }, "Caret position correct");
+});
+
+test( "keypress: Backspace with multi-character ", 6, function() {
+ var input = $( "#mask1" ).val( "aa-bb-cc" ).mask({
+ mask: "xx-xx-xx",
+ definitions: {
+ xx: function( value ) {
+ if ( !value.length ) {
+ return;
+ }
+ if ( value.charAt( 0 ) === "_" ) {
+ return;
+ }
+ if ( value.length === 1 ) {
+ return value+value;
+ }
+ if ( value.charAt( 0 ) === value.charAt( 1 ) ) {
+ return value;
+ }
+ }
+ }
+ }),
+ mask = input.data( "ui-mask" );
+
+ TestHelpers.focus( input );
+ equal( input.val(), "aa-bb-cc", "Initial Value Expected" );
+ mask._caret( 6, 8 );
+
+ deepEqual( mask._caret(), { begin: 6, end: 8 }, "Caret position correct");
+
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "aa-bb-__", "Deleted Value Expected" );
+ deepEqual( mask._caret(), { begin: 6, end: 8 }, "Caret position correct");
+
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.BACKSPACE });
+ equal( input.val(), "aa-__-__", "Deleted Value Expected" );
+ deepEqual( mask._caret(), { begin: 3, end: 5 }, "Caret position correct");
+
+});
+
+test( "keydown: Delete pulling values", function() {
+ expect( 18 );
+ var input = $( "#mask1" ).val("123").mask({ mask: "9-99" }),
+ mask = input.data( "ui-mask" );
+
+ TestHelpers.focus( input );
+ equal( input.val(), "1-23", "Initial value expected" );
+
+ mask._caret( 1 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.DELETE });
+ equal( input.val(), "1-3_", "Delete across the literal -, brought values with");
+ deepEqual( mask._caret(), { begin: 2, end: 2 }, "Caret position correct");
+
+ input.val("12z").mask( "option", "mask", "9-9a" );
+ equal( input.val(), "1-2z", "Initial value expected" );
+
+ mask._caret( 1 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.DELETE });
+ equal( input.val(), "1-_z", "Deleted across the literal -, z was not pulled");
+ deepEqual( mask._caret(), { begin: 2, end: 2 }, "Caret position correct");
+
+ input.val("12").mask( "option", "mask", "99" );
+ equal( input.val(), "12", "Initial value expected" );
+
+ mask._caret( 0 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.DELETE });
+ equal( input.val(), "2_", "Deleted value, pulled values from the right");
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+
+ input.val("1z").mask( "option", "mask", "9a" );
+ equal( input.val(), "1z", "Initial value expected" );
+
+ mask._caret( 0 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.DELETE });
+ equal( input.val(), "_z", "Deleted value, couldn't pull values from the right");
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+
+ input.val("12").mask( "option", "mask", "9-9" );
+ equal( input.val(), "1-2", "Initial value expected" );
+
+ mask._caret( 0 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.DELETE });
+ equal( input.val(), "2-_", "Deleted value, pulled values from the right across the literal");
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+
+
+ input.val("1z").mask( "option", "mask", "9-a" );
+ equal( input.val(), "1-z", "Initial value expected" );
+
+ mask._caret( 0 );
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.DELETE });
+ equal( input.val(), "_-z", "Deleted value, couldn't pull values from the right");
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct");
+});
+
+test( "keydown: escape returns to original value", function() {
+ expect( 3 );
+ var input = $( "#mask1" ).val("6").mask({ mask: "9" }),
+ mask = input.data( "ui-mask" );
+
+ equal( input.val(), "6", "Initial value expected" );
+ TestHelpers.focus( input );
+
+ mask._caret( 0 );
+ TestHelpers.press( input, "1" );
+ equal( input.val(), "1", "Typed over" );
+
+ input.simulate( "keydown", { keyCode: $.ui.keyCode.ESCAPE });
+ equal( input.val(), "6", "Reverted value after pressing escape" );
+
+});
+
+test( "keypress: typing behaviors", function() {
+ expect( 8 );
+ var input = $( "#mask1" ).mask({
+ mask: "9-9",
+ clearEmpty: false
+ }),
+ mask = input.data( "ui-mask" );
+
+ TestHelpers.focus( input );
+ equal( input.val(), "_-_", "Initial value expected" );
+
+ mask._caret( 0 );
+ TestHelpers.press( input, "1" );
+ equal( input.val(), "1-_", "Typed a 1" );
+
+ mask._caret( 0 );
+ TestHelpers.press( input, "2" );
+ equal( input.val(), "2-1", "Typed a 2 before the 1" );
+ deepEqual( mask._caret(), { begin: 2, end: 2 }, "Caret position correct");
+
+ input.val("").mask( "option", "mask", "9-a" );
+ equal( input.val(), "_-_", "Initial value expected" );
+
+ mask._caret( 0 );
+ TestHelpers.press( input, "1" );
+ equal( input.val(), "1-_", "Typed a 1" );
+
+ mask._caret( 0 );
+ TestHelpers.press( input, "2" );
+ equal( input.val(), "2-_", "Typed a 2 before the 1 - 1 is lost because not valid" );
+ deepEqual( mask._caret(), { begin: 2, end: 2 }, "Caret position correct");
+});
+
+}( jQuery ) );
diff --git a/tests/unit/mask/mask_methods.js b/tests/unit/mask/mask_methods.js
new file mode 100644
index 00000000000..d10db426316
--- /dev/null
+++ b/tests/unit/mask/mask_methods.js
@@ -0,0 +1,58 @@
+(function( $ ) {
+
+module( "mask: methods" );
+
+test( "refresh", function() {
+ expect( 1 );
+
+ var input = $( "#mask1" ).mask({
+ mask: "99/99/99"
+ });
+
+ input.val( "123456" ).mask( "refresh" );
+ equal( input.val(), "12/34/56", "Refresh re-parsed the value of the input" );
+});
+
+test( "value: able to get (and set) raw values", function() {
+ expect( 3 );
+ var input = $( "#mask1" ).mask({
+ mask: "99/99/99"
+ });
+
+ equal( input.mask( "value" ), "", "Reading empty raw value" );
+ input.mask( "value", "123456" );
+ equal( input.val(), "12/34/56", "Raw value set properly" );
+ equal( input.mask( "value" ), "123456", "Raw value read correctly" );
+});
+
+test( "value: able to get (and set) raw values with optional section", function() {
+ expect( 5 );
+ var input = $( "#mask1" ).val("1234").mask({
+ mask: "(999) 999-9999?x9999"
+ });
+
+ equal( input.mask('value'), "1234", "Reading initial value" );
+
+ input.mask( "value", "123456" );
+
+ equal( input.val(), "(123) 456-____", "Raw value set properly" );
+ equal( input.mask( "value" ), "123456", "Raw value read correctly" );
+
+ input.mask( "value", "12345678901234" );
+
+ equal( input.val(), "(123) 456-7890x1234", "Raw value with optional set properly" );
+ equal( input.mask( "value" ), "12345678901234", "Raw value read correctly" );
+});
+
+test( "valid: returns true when all required placeholders are filled", function() {
+ expect( 2 );
+ var input = $( "#mask1" ).mask({
+ mask: "99/99/99"
+ });
+
+ equal( input.mask( "valid" ), false, "Empty value is invalid" );
+ input.mask( "value", "123456" );
+ equal( input.mask( "valid" ), true, "All placheholders are full" );
+});
+
+}( jQuery ) );
diff --git a/tests/unit/mask/mask_options.js b/tests/unit/mask/mask_options.js
new file mode 100644
index 00000000000..f651d90caeb
--- /dev/null
+++ b/tests/unit/mask/mask_options.js
@@ -0,0 +1,202 @@
+(function( $ ) {
+
+module( "mask: options" );
+
+test( "clearEmpty", function() {
+ expect( 4 );
+ var input = $( "#mask1" ).val( "" ).mask({
+ mask: "99/99/99",
+ placeholder: "_",
+ clearEmpty: true
+ });
+
+ equal( input.val(), "", "Empty value with clearEmpty displays no mask" );
+ TestHelpers.focus( input );
+ equal( input.val(), "__/__/__", "Empty value with clearEmpty & element focus displays mask" );
+ TestHelpers.blur( input );
+ equal( input.val(), "", "Empty value with clearEmpty displays no mask after blur" );
+ input.mask( "option", "clearEmpty", false );
+ equal( input.val(), "__/__/__", "Disabling clearEmpty displays mask" );
+});
+
+test( "placeholder", function() {
+ expect( 2 );
+ var input = $( "#mask1" ).mask({
+ mask: "99/99/99",
+ placeholder: "_",
+ clearEmpty: false
+ });
+
+ equal( input.val(), "__/__/__", "Initial value" );
+ input.mask( "option", "placeholder", " " );
+ equal( input.val(), " / / ", "Placeholder changed" );
+});
+
+test( "mask", function() {
+ expect( 2 );
+ var input = $( "#mask1" ).val( "1234" ).mask({
+ mask: "99/99/99",
+ placeholder: "_"
+ });
+
+ equal( input.val(), "12/34/__", "Initial value" );
+ input.mask( "option", "mask", "(999)999-9999" );
+ equal( input.val(), "(123)4__-____", "Mask changed" );
+});
+
+test( "mask with optional input", function() {
+ expect( 1 );
+ var input = $( "#mask1" ).val( "1234" ).mask({
+ mask: "(999) 999-9999?x9999"
+ });
+
+ equal( input.val(), "(123) 4__-____", "Initial value" );
+});
+
+test( "mask with multiple optional inputs", function() {
+ expect( 1 );
+ var input = $( "#mask1" ).val( "1234" ).mask({
+ mask: "(999) 999-9999?x9?9?9?9"
+ });
+
+ equal( input.val(), "(123) 4__-____", "Initial value" );
+});
+
+test( "mask with escaped characters", function() {
+ expect( 1 );
+ var input = $( "#mask1" ).val( "1234" ).mask({
+ mask: "(\\9\\9\\9)\\\\ 999-99\\a\\*\\?x9999"
+ });
+
+ equal( input.val(), "(999)\\ 123-4_a*?x____", "Initial value" );
+});
+
+test( "escaped use of custom mask with wrapper ", function() {
+ expect( 1 );
+ var input = $( "#mask1" ).val( "1" ).mask({
+ mask: "9\\"
+ });
+
+ equal( input.val(), "1", "Initial value" );
+});
+
+test( "custom mask with wrapper and escape", function() {
+ expect( 1 );
+ var input = $( "#mask1" ).val( "monA0" ).mask({
+ mask: ":a\\?:?x",
+ definitions: {
+ day: function ( value ) {
+ if ( $.inArray(value, [ "mon", "tue", "wed", "thu", "fri", "sat", "sun" ]) ) {
+ return value;
+ } else if ( $.inArray(value, [ "m", "mo" ])) {
+ return "mon";
+ } else if ( value === "t" ) {
+ return "t";
+ } else if ( value === "tu" ) {
+ return "tue";
+ } else if ( value === "thu" ) {
+ return "thu";
+ } else if ( $.inArray(value, [ "w", "we" ]) ) {
+ return "wed";
+ } else if ( $.inArray(value, [ "f", "fr" ]) ) {
+ return "fri";
+ } else if ( value === "s" ) {
+ return "s";
+ } else if ( value === "sa" ) {
+ return "sat";
+ } else if ( value === "su" ) {
+ return "sun";
+ }
+ },
+ d: /[0-9]/
+ }
+ });
+
+ equal( input.val(), "mon:A?:0", "Initial value" );
+});
+
+test( "mask option parser", 1, function() {
+ var defs = {
+ hh: function( value ) {
+ value = parseInt( value, 10 );
+ if ( value >= 1 || value <= 12 ) {
+ return ( value < 10 ? "0" : "" ) + value;
+ }
+ },
+ ss: function( value ) {
+ value = parseInt( value, 10 );
+ if ( value >= 0 || value <= 59 ) {
+ return ( value < 10 ? "0" : "" ) + value;
+ }
+ }
+ },
+ input = $( "#mask1" ).mask({
+ mask: "hh:ss:ss.999",
+ definitions: defs
+ }),
+ instance = input.data( "ui-mask" );
+ deepEqual( instance.buffer, [
+ {
+ valid: defs.hh,
+ start: 0,
+ length: 2
+ },
+ {
+ valid: defs.hh,
+ start: 0,
+ length: 2
+ },
+ {
+ literal: ":",
+ start: 2,
+ length: 1
+ },
+ {
+ valid: defs.ss,
+ start: 3,
+ length: 2
+ },
+ {
+ valid: defs.ss,
+ start: 3,
+ length: 2
+ },
+ {
+ literal: ":",
+ start: 5,
+ length: 1
+ },
+ {
+ valid: defs.ss,
+ start: 6,
+ length: 2
+ },
+ {
+ valid: defs.ss,
+ start: 6,
+ length: 2
+ },
+ {
+ literal: ".",
+ start: 8,
+ length: 1
+ },
+ {
+ valid: instance.options.definitions[ 9 ],
+ start: 9,
+ length: 1
+ },
+ {
+ valid: instance.options.definitions[ 9 ],
+ start: 10,
+ length: 1
+ },
+ {
+ valid: instance.options.definitions[ 9 ],
+ start: 11,
+ length: 1
+ }
+ ], "Buffer correctly parsed" );
+});
+
+}( jQuery ) );
diff --git a/tests/unit/mask/mask_test_helpers.js b/tests/unit/mask/mask_test_helpers.js
new file mode 100644
index 00000000000..97df3800f59
--- /dev/null
+++ b/tests/unit/mask/mask_test_helpers.js
@@ -0,0 +1,46 @@
+jQuery.extend( TestHelpers, {
+ press: function( input, key ) {
+ var code = key.charCodeAt( 0 );
+
+ input.simulate( "keypress", {
+ charCode: code,
+ which: code
+ });
+ },
+ focus: function( element ) {
+ var triggered = false;
+
+ function trigger() {
+ triggered = true;
+ }
+
+ element.bind( "focus", trigger );
+ element[ 0 ].focus();
+
+ if ( !triggered ) {
+ element.triggerHandler( "focus" );
+ }
+ element.unbind( "focus", trigger );
+ },
+
+ blur: function( element ) {
+ var triggered = false;
+
+ function trigger() {
+ triggered = true;
+ }
+
+ element.bind( "blur", trigger );
+ element[ 0 ].blur();
+
+ // Some versions of IE don't actually .blur() on an element - so we focus the body
+ if ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) {
+ element[ 0 ].ownerDocument.body.focus();
+ }
+
+ if ( !triggered ) {
+ element.triggerHandler( "blur" );
+ }
+ element.unbind( "blur", trigger );
+ }
+});
\ No newline at end of file
diff --git a/tests/unit/timepicker/all.html b/tests/unit/timepicker/all.html
new file mode 100644
index 00000000000..b4b86b3d5e8
--- /dev/null
+++ b/tests/unit/timepicker/all.html
@@ -0,0 +1,30 @@
+
+
+
+
+ jQuery UI Timepicker Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/timepicker/timepicker.html b/tests/unit/timepicker/timepicker.html
new file mode 100644
index 00000000000..e8739ef7821
--- /dev/null
+++ b/tests/unit/timepicker/timepicker.html
@@ -0,0 +1,43 @@
+
+
+
+
+ jQuery UI Timepicker Test Suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/unit/timepicker/timepicker_common.js b/tests/unit/timepicker/timepicker_common.js
new file mode 100644
index 00000000000..133f57e53e3
--- /dev/null
+++ b/tests/unit/timepicker/timepicker_common.js
@@ -0,0 +1,9 @@
+TestHelpers.commonWidgetTests( "timepicker", {
+ defaults: {
+ ampm: true,
+ disabled: false,
+ seconds: true,
+ // callbacks
+ create: null
+ }
+});
diff --git a/tests/unit/timepicker/timepicker_core.js b/tests/unit/timepicker/timepicker_core.js
new file mode 100644
index 00000000000..94654064d1e
--- /dev/null
+++ b/tests/unit/timepicker/timepicker_core.js
@@ -0,0 +1,5 @@
+(function() {
+
+module( "timepicker: core" );
+
+}());
diff --git a/tests/unit/timepicker/timepicker_events.js b/tests/unit/timepicker/timepicker_events.js
new file mode 100644
index 00000000000..dcedbc3f09c
--- /dev/null
+++ b/tests/unit/timepicker/timepicker_events.js
@@ -0,0 +1,56 @@
+(function( $ ) {
+
+module( "timepicker: events" );
+
+test( "keydown: Up/Down/Left/Right behaviors", function() {
+ expect( 12 );
+
+ var i,
+ input = $( "#timepicker1" ).val( "12:00:00 PM" ).timepicker(),
+ timepicker = input.data( "ui-timepicker" ),
+ mask = timepicker.mask;
+
+ mask._caret( 0, 0 );
+ mask._caret( 0, 0 );
+ timepicker._setField( 0 );
+ deepEqual( mask._caret(), { begin: 0, end: 0 }, "Caret position correct" );
+
+ TestHelpers.downup( input, $.ui.keyCode.UP );
+ equal( input.val(), " 1:00:00 PM", "After up keypress in hours field, value went to proper value" );
+
+ TestHelpers.downup( input, $.ui.keyCode.DOWN );
+ equal( input.val(), "12:00:00 PM", "After down keypress in hours field, value went to proper value" );
+
+ TestHelpers.downup( input, $.ui.keyCode.DOWN );
+ equal( input.val(), "11:00:00 PM", "After down keypress in hours field, value went to proper value" );
+ deepEqual( mask._caret(), { begin: 0, end: 2 }, "Caret position selects hours" );
+
+ TestHelpers.downup( input, $.ui.keyCode.RIGHT );
+ deepEqual( mask._caret(), { begin: 3, end: 5 }, "After Right - Caret position selects minutes" );
+
+ for ( i = 0; i < 10; i++ ) {
+ TestHelpers.downup( input, $.ui.keyCode.DOWN );
+ }
+ equal( input.val(), "11:50:00 PM", "After 10 down keypress in minutes field, value went to proper value" );
+
+ TestHelpers.downup( input, $.ui.keyCode.RIGHT );
+ deepEqual( mask._caret(), { begin: 6, end: 8 }, "After Right - Caret position selects seconds" );
+
+ TestHelpers.downup( input, $.ui.keyCode.RIGHT );
+ deepEqual( mask._caret(), { begin: 9, end: 11 }, "After Right - Caret position selects am/pm" );
+
+ TestHelpers.downup( input, $.ui.keyCode.DOWN );
+ equal( input.val(), "11:50:00 AM", "After down keypress in am/pm field, value went to proper value" );
+
+ input.timepicker( "destroy" ).val( "23:00:00" ).timepicker({ ampm: false });
+ timepicker = input.data( "ui-timepicker" );
+ timepicker._setField( 0 );
+ equal( input.val(), "23:00:00", "Sanity Check" );
+
+ TestHelpers.downup( input, $.ui.keyCode.UP );
+ equal( input.val(), " 0:00:00", "After up keypress in hours field, value went to proper value" );
+
+
+});
+
+}( jQuery ) );
diff --git a/tests/unit/timepicker/timepicker_methods.js b/tests/unit/timepicker/timepicker_methods.js
new file mode 100644
index 00000000000..8678461fea0
--- /dev/null
+++ b/tests/unit/timepicker/timepicker_methods.js
@@ -0,0 +1,22 @@
+(function( $ ) {
+
+module( "timepicker: methods" );
+
+test( "value - get and set value", function() {
+ expect( 3 );
+
+ var input = $( "#timepicker1" ).val( "12:00:00 AM" ).timepicker();
+
+ equal( input.timepicker( "value" ), "00:00:00", "Expected value for 12am" );
+
+
+ input.timepicker( "value", "02:34:56" );
+ equal( input.val(), " 2:34:56 AM", "Expected val() for 02:34:56" );
+
+
+ input.timepicker( "value", "12:34:56" );
+ equal( input.val(), "12:34:56 PM", "Expected val() for 12:34:56" );
+
+});
+
+}( jQuery ) );
diff --git a/tests/unit/timepicker/timepicker_options.js b/tests/unit/timepicker/timepicker_options.js
new file mode 100644
index 00000000000..a4c8e672c1e
--- /dev/null
+++ b/tests/unit/timepicker/timepicker_options.js
@@ -0,0 +1,43 @@
+(function( $ ) {
+
+module( "timepicker: options" );
+
+test( "seconds", function() {
+ expect( 3 );
+
+ var input = $( "#timepicker1" ).val("12:30 AM").timepicker({
+ seconds: false
+ });
+
+ equal( input.val(), "12:30 AM", "Seconds: false startup option" );
+
+ input.timepicker( "option", "seconds", true );
+
+ equal( input.val(), "12:30:00 AM", "Seconds: enabled seconds via option method" );
+
+ input.timepicker( "option", "seconds", false );
+
+ equal( input.val(), "12:30 AM", "Seconds: disabled seconds via option method" );
+
+});
+
+test( "ampm", function() {
+ expect( 3 );
+
+ var input = $( "#timepicker1" ).val(" 1:30:00 PM").timepicker({
+ ampm: true
+ });
+
+ equal( input.val(), " 1:30:00 PM", "Sanity Check" );
+
+ input.timepicker( "option", "ampm", false );
+
+ equal( input.val(), "13:30:00", "Disabled ampm option via method" );
+
+ input.timepicker( "option", "ampm", true );
+
+ equal( input.val(), " 1:30:00 PM", "Enabled ampm option via method" );
+
+});
+
+}( jQuery ) );
diff --git a/tests/unit/timepicker/timepicker_test_helpers.js b/tests/unit/timepicker/timepicker_test_helpers.js
new file mode 100644
index 00000000000..ae5eac7addd
--- /dev/null
+++ b/tests/unit/timepicker/timepicker_test_helpers.js
@@ -0,0 +1,54 @@
+jQuery.extend( TestHelpers, {
+ // helper funciton to quick "press" a key - keydown/keyup
+ downup: function( element, key ) {
+ element.simulate( "keydown", { keyCode: key } );
+ element.simulate( "keyup", { keyCode: key } );
+ },
+
+ press: function( element, key ) {
+ var code = key.charCodeAt( 0 );
+
+ element.simulate( "keypress", {
+ charCode: code,
+ which: code
+ });
+ },
+
+ focus: function( element ) {
+ var triggered = false;
+
+ function trigger() {
+ triggered = true;
+ }
+
+ element.bind( "focus", trigger );
+ element[ 0 ].focus();
+
+ if ( !triggered ) {
+ element.triggerHandler( "focus" );
+ }
+ element.unbind( "focus", trigger );
+ },
+
+ blur: function( element ) {
+ var triggered = false;
+
+ function trigger() {
+ triggered = true;
+ }
+
+ element.bind( "blur", trigger );
+ element[ 0 ].blur();
+
+ // Some versions of IE don't actually .blur() on an element - so we focus the body
+ if ( element[ 0 ].ownerDocument.activeElement === element[ 0 ] ) {
+ element[ 0 ].ownerDocument.body.focus();
+ }
+
+ if ( !triggered ) {
+ element.triggerHandler( "blur" );
+ }
+ element.unbind( "blur", trigger );
+ }
+
+});
diff --git a/tests/visual/index.html b/tests/visual/index.html
index d8c8751f6ee..2ee563a9775 100644
--- a/tests/visual/index.html
+++ b/tests/visual/index.html
@@ -42,6 +42,11 @@ Effects
Scale
+ Mask
+
+
Menu
+ Timepicker
+
+
Tooltip
General
diff --git a/tests/visual/mask/mask.html b/tests/visual/mask/mask.html
new file mode 100644
index 00000000000..a4031ad887b
--- /dev/null
+++ b/tests/visual/mask/mask.html
@@ -0,0 +1,137 @@
+
+
+
+
+ Mask Visual Test: Default
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/visual/timepicker/timepicker.html b/tests/visual/timepicker/timepicker.html
new file mode 100644
index 00000000000..9548d5495c5
--- /dev/null
+++ b/tests/visual/timepicker/timepicker.html
@@ -0,0 +1,61 @@
+
+
+
+
+ Timepicker Visual Test: Default
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/jquery.ui.mask.js b/ui/jquery.ui.mask.js
new file mode 100644
index 00000000000..0f3bcd7c8ca
--- /dev/null
+++ b/ui/jquery.ui.mask.js
@@ -0,0 +1,598 @@
+/*!
+ * jQuery UI Mask @VERSION
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ *
+ */
+(function( $, undefined ) {
+
+var keyCode = $.ui.keyCode;
+
+$.widget( "ui.mask", {
+ version: "@VERSION",
+ defaultElement: " ",
+ options: {
+ clearEmpty: true,
+ definitions: {
+ "9": /[0-9]/,
+ "a": /[A-Za-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]/,
+ "*": /[A-Za-z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]/
+ },
+ mask: null,
+ placeholder: "_"
+ },
+
+ _create: function() {
+ this._parseMask();
+ this.refresh();
+ this.element.addClass( "ui-mask" );
+ this._on( this._events );
+ },
+
+ _destroy: function() {
+ this.element.removeClass( "ui-mask" );
+ },
+
+ refresh: function() {
+ this._parseValue();
+ this._paint();
+ },
+
+ valid: function() {
+ return this.isValid;
+ },
+
+ // returns (or sets) the value without the mask
+ value: function( value ) {
+ if ( value !== undefined ) {
+ this.element.val( value );
+ this.refresh();
+ } else {
+ return this._getValue( true );
+ }
+ },
+
+ _setOption: function( key, value ) {
+ this._super( key, value );
+ if ( key === "mask" ) {
+ this._parseMask();
+ this._parseValue();
+ }
+ },
+ _setOptions: function( options ) {
+ this._super( options );
+ this._paint();
+ },
+
+ // helper function to get or set position of text cursor (caret)
+ _caret: function( begin, end ) {
+ var range,
+ elem = this.element,
+ dom = elem[0];
+
+ // if begin is defined, we are setting a range
+ if ( begin !== undefined ) {
+ end = ( end === undefined ) ? begin : end;
+ if ( dom.setSelectionRange ) {
+ dom.setSelectionRange( begin, end );
+ } else if ( dom.createTextRange ) {
+ range = dom.createTextRange();
+ range.collapse( true );
+ range.moveEnd( "character", end );
+ range.moveStart( "character", begin );
+ range.select();
+ }
+ } else {
+
+ // begin is undefined, we are reading the range
+ if ( dom.setSelectionRange ) {
+ begin = dom.selectionStart;
+ end = dom.selectionEnd;
+ } else if ( document.selection && document.selection.createRange ) {
+ range = document.selection.createRange();
+
+ // the moveStart returns the number of characters it moved as a negative number
+ begin = 0 - range.duplicate().moveStart( "character", -100000 );
+ end = begin + range.text.length;
+ }
+ return {
+ begin: begin,
+ end: end
+ };
+ }
+ },
+ _caretSelect: function( bufferPosition ) {
+ var bufferObject = this.buffer[ bufferPosition ];
+ if ( bufferObject && bufferObject.length > 1 ) {
+ this._caret( bufferObject.start, bufferObject.start + bufferObject.length );
+ } else {
+ this._caret( bufferPosition );
+ }
+ },
+ _getValue: function( raw, focused ) {
+ var bufferPosition, bufferObject, counter,
+ bufferLength = this.buffer.length,
+ value = "",
+ lastValue = 0;
+
+ this.isEmpty = this.isValid = true;
+ for ( bufferPosition = 0; bufferPosition < bufferLength; bufferPosition += bufferObject.length ) {
+ bufferObject = this.buffer[ bufferPosition ];
+ if ( bufferObject.literal ) {
+ if ( !raw && ( bufferPosition < this.optionalPosition || this.isValid ) ) {
+ value += bufferObject.literal;
+ }
+ } else if ( bufferObject.value ) {
+ lastValue = bufferObject.start + bufferObject.length;
+ this.isEmpty = false;
+ value += bufferObject.value;
+ for ( counter = bufferObject.value.length; counter < bufferObject.length; counter++ ) {
+ value += this.options.placeholder;
+ }
+ } else {
+ if ( !raw ) {
+ for ( counter = bufferObject.length ; counter; counter-- ) {
+ value += this.options.placeholder;
+ }
+ }
+ if ( bufferPosition < this.optionalPosition ) {
+ this.isValid = false;
+ }
+ }
+ }
+
+ // don't display the "optional" portion until the input is "valid" or there are
+ // values past the optional position
+ if ( this.options.clearEmpty && this.isEmpty && focused === false ) {
+ return "";
+ }
+
+ // strip the optional parts off if we haven't gotten there yet, or there are no values past it
+ // and we aren't focused
+ if ( lastValue <= this.optionalPosition && !( this.isValid && focused ) ) {
+ value = value.substr( 0, this.optionalPosition );
+ }
+ return value;
+ },
+ _events: {
+ focus: function() {
+ this.lastUnsavedValue = this.element.val();
+ this._paint( true );
+ this._caretSelect( this._seekRight( this._parseValue() ) );
+
+ this._justFocused = true;
+ this._delay(function(){
+ this._justFocused = false;
+ }, 100);
+ },
+ click: function() {
+ // Normally, the call to handle this in the focus event handler would be
+ // sufficient, but Chrome fires the focus events before positioning the
+ // cursor based on where the user clicked (and then fires the click event).
+
+ // We only want to move the caret on clicks that resulted in focus
+ if ( this._justFocused ) {
+ this._caretSelect( this._seekRight( this._parseValue() ) );
+ this._justFocused = false;
+ }
+ },
+ blur: function() {
+ this._justFocused = false;
+
+ // because we are constantly setting the value of the input, the change event
+ // never fires - we re-introduce the change event here
+ this._parseValue();
+ this._paint( false );
+ if ( this.element.val() !== this.lastUnsavedValue ) {
+ this.element.change();
+ }
+ },
+ keydown: function( event ) {
+ var bufferObject,
+ key = event.keyCode,
+ position = this._caret();
+
+ if ( event.shiftKey || event.metaKey || event.altKey || event.ctrlKey ) {
+ return;
+ }
+
+
+ switch ( key ) {
+ case keyCode.ESCAPE:
+ this.element.val( this.lastUnsavedValue );
+ this._caretSelect( 0, this._parseValue() );
+ event.preventDefault();
+ return;
+
+ case keyCode.BACKSPACE:
+ case keyCode.DELETE:
+ event.preventDefault();
+
+ // if the caret is not "selecting" values, we need to find the proper
+ // character in the buffer to delete/backspace over.
+ if ( position.begin === position.end || this._isEmpty( position.begin, position.end ) ) {
+ if ( key === keyCode.DELETE ) {
+ position.begin = position.end = this._seekRight( position.begin - 1 );
+ } else {
+ position.begin = position.end = this._seekLeft( position.begin );
+ }
+
+ // nothing to backspace
+ if ( position.begin < 0 ) {
+ this._caret( this._seekRight( -1 ) );
+ return;
+ }
+ }
+ this._removeValues( position.begin, position.end );
+ this._paint();
+ this._caretSelect( position.begin );
+ return;
+
+ case keyCode.LEFT:
+ case keyCode.RIGHT:
+ bufferObject = this.buffer[ position.begin ];
+ if ( bufferObject && bufferObject.length > 1 ) {
+ bufferObject.value = this._validValue( bufferObject, bufferObject.value );
+ this._paint();
+ event.preventDefault();
+ }
+ if ( key === keyCode.LEFT ) {
+ position = this._seekLeft( bufferObject ? bufferObject.start : position.begin );
+ } else {
+ position = this._seekRight( bufferObject ?
+ bufferObject.start + bufferObject.length - 1 :
+ position.end );
+ }
+ this._caretSelect( position );
+ event.preventDefault();
+ return;
+ }
+ },
+ keypress: function( event ) {
+ var tempValue, valid,
+ key = event.which,
+ position = this._caret(),
+ bufferPosition = this._seekRight( position.begin - 1 ),
+ bufferObject = this.buffer[ bufferPosition ];
+
+ // ignore keypresses with special keys, or control characters
+ if ( event.metaKey || event.altKey || event.ctrlKey || key < 32 ) {
+ return;
+ }
+ if ( position.begin !== position.end ) {
+ this._removeValues( position.begin, position.end );
+ }
+ if ( bufferObject ) {
+ tempValue = String.fromCharCode( key );
+ if ( bufferObject.length > 1 && bufferObject.value ) {
+ tempValue = bufferObject.value.substr( 0, bufferPosition - bufferObject.start ) +
+ tempValue +
+ bufferObject.value.substr( bufferPosition - bufferObject.start + 1 );
+ tempValue = tempValue.substr( 0, bufferObject.length );
+ }
+ valid = this._validValue( bufferObject, tempValue );
+ if ( valid ) {
+ this._shiftRight( position.begin );
+ bufferObject.value = tempValue;
+ position = this._seekRight( bufferPosition );
+ if ( position <= bufferObject.start + bufferObject.length ) {
+ this._paint();
+ this._caret( position );
+ } else {
+ bufferObject.value = valid;
+ this._paint();
+ this._caretSelect( position );
+ }
+ }
+ }
+ event.preventDefault();
+ },
+ paste: "_paste",
+ input: "_paste"
+ },
+ _isEmpty: function( begin, end ) {
+ var index;
+ if ( begin === undefined ) {
+ begin = 0;
+ end = this.buffer.length - 1;
+ } else if ( end === undefined ) {
+ end = begin;
+ }
+ for ( index = begin; index <= end; index++ ) {
+ if ( this.buffer[ index ] && this.buffer[ index ].value ) {
+ return false;
+ }
+ }
+ return true;
+ },
+ _paste: function() {
+ this._delay( function() {
+ var position = this._parseValue();
+ this._paint();
+ this._caret( this._seekRight( position ) );
+ }, 0 );
+ },
+ _paint: function( focused ) {
+ if ( focused === undefined ) {
+ focused = this.element[ 0 ] === document.activeElement;
+ }
+ this.element.val( this._getValue( false, focused ) );
+ },
+ _addBuffer: function( bufferObject ) {
+ var x,
+ begin = bufferObject.start,
+ end = bufferObject.start + bufferObject.length;
+
+ for ( x = begin; x < end; x++ ) {
+ if ( this.buffer[ x ] !== undefined ) {
+ return false;
+ }
+ }
+
+ for ( x = begin; x < end; x++ ) {
+ this.buffer[ x ] = bufferObject;
+ }
+
+ return true;
+ },
+ _removeCharacter: function( mask, index ) {
+ var x, bufferObject;
+
+ for ( x = index ; x < mask.length - 1 ; x++ ) {
+ bufferObject = this.buffer[ x + 1 ];
+ this.buffer[ x ] = bufferObject;
+ if ( bufferObject !== undefined ) {
+ bufferObject.start = bufferObject.start - 1;
+ x += bufferObject.length - 1;
+ }
+ }
+ this.buffer.splice( x, 1 );
+
+ if ( this.optionalPosition > index ) {
+ this.optionalPosition--;
+ }
+
+ return mask.substring( 0, index ) + mask.substring( index + 1 );
+ },
+ _parseMask: function() {
+ var key, x, optionalPosition,
+ index = -1,
+ options = this.options,
+ mask = options.mask,
+ reservedChars = [ "a", "9", "*", "?", "<", ">", "\\" ];
+
+ this.buffer = [];
+ if ( !mask ) {
+ return;
+ }
+
+ // search for escaped reserved characters
+ for ( index = 0 ; index < mask.length - 1 ; index++ ) {
+ if ( mask.charAt( index ) === "\\" &&
+ $.inArray( mask.charAt( index + 1 ), reservedChars ) !== -1 ) {
+ // remove escape character
+ mask = mask.substring( 0, index ) + mask.substring( index + 1 );
+
+ this._addBuffer({
+ start: index,
+ literal: mask.charAt( index ),
+ length: 1
+ });
+ }
+ }
+ // locate unescaped optional markers ; use attention to the first, remove all others
+ optionalPosition = -1;
+ this.optionalPosition = undefined;
+ while ( ( optionalPosition = mask.indexOf( "?", optionalPosition + 1 ) ) > -1 ) {
+ if ( this.buffer[ optionalPosition ] === undefined ) {
+ if ( this.optionalPosition === undefined ) {
+ this.optionalPosition = optionalPosition;
+ }
+
+ // remove the ? from the mask
+ mask = this._removeCharacter( mask, optionalPosition );
+ }
+ }
+ if ( this.optionalPosition === undefined ) {
+ this.optionalPosition = mask.length;
+ }
+
+ // search for strictly definied "masks"
+ for ( x = 0 ; x < mask.length ; x++ ) {
+ if ( mask.charAt(x) === "<" ) {
+ index = x;
+ for ( ; x < mask.length ; x++ ) {
+ if ( mask.charAt(x) === ">" ) {
+ key = mask.substring( index + 1 , x );
+ if ( options.definitions[key] !== undefined ) {
+ if (this._addBuffer({
+ start: index + 1,
+ length: key.length,
+ valid: options.definitions[ key ]
+ })) {
+ mask = this._removeCharacter( mask, x );
+ mask = this._removeCharacter( mask, index );
+ for ( x = index ; x < index + key.length ; x++ ) {
+ mask[x] = " ";
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ // search for definied "masks"
+ index = -1;
+ for ( key in options.definitions ) {
+ while ( ( index = mask.indexOf( key, index + 1 ) ) > -1 ) {
+ this._addBuffer({
+ start: index,
+ length: key.length,
+ valid: options.definitions[ key ]
+ });
+ }
+ }
+
+ // anything we didn't find is a literal
+ for ( index = 0 ; index < mask.length ; index++ ) {
+ this._addBuffer({
+ start: index,
+ literal: mask.charAt( index ),
+ length: 1
+ });
+ }
+ },
+
+ // parses the .val() and places it into the buffer
+ // returns the last filled in value position
+ _parseValue: function() {
+ var bufferPosition, bufferObject, character,
+ valuePosition = 0,
+ lastFilledPosition = -1,
+ value = this.element.val(),
+ bufferLength = this.buffer.length;
+
+ // remove all current values from the buffer
+ this._removeValues( 0, bufferLength );
+
+ // seek through the buffer pulling characters from the value
+ for ( bufferPosition = 0; bufferPosition < bufferLength; bufferPosition += bufferObject.length ) {
+ bufferObject = this.buffer[ bufferPosition ];
+
+ while ( valuePosition < value.length ) {
+ character = value.substr( valuePosition, bufferObject.length );
+ if ( bufferObject.literal ) {
+ if ( this._validValue( bufferObject, character ) ) {
+ valuePosition++;
+ }
+
+ // when parsing a literal from a raw .val() if it doesn't match,
+ // assume that the literal is missing from the val()
+ break;
+ }
+ valuePosition++;
+ character = this._validValue( bufferObject, character );
+ if ( character ) {
+ bufferObject.value = character;
+ lastFilledPosition = bufferPosition + bufferObject.length - 1;
+ valuePosition += bufferObject.length - 1;
+ break;
+ }
+ }
+
+ // allow "default values" to be passed back from the buffer functions
+ if ( !bufferObject.value && (character = this._validValue( bufferObject, "" )) ) {
+ bufferObject.value = character;
+ }
+ }
+ return lastFilledPosition;
+ },
+ _removeValues: function( begin, end ) {
+ var position, bufferObject;
+ for ( position = begin; position <= end; position++ ) {
+ bufferObject = this.buffer[ position ];
+ if ( bufferObject && bufferObject.value ) {
+ delete bufferObject.value;
+ }
+ }
+ this._shiftLeft( begin, end + 1 );
+ return this;
+ },
+
+ // _seekLeft and _seekRight will tell the next non-literal position in the buffer
+ _seekLeft: function( position ) {
+ while ( --position >= 0 ) {
+ if ( this.buffer[ position ] && !this.buffer[ position ].literal ) {
+ return position;
+ }
+ }
+ return -1;
+ },
+ _seekRight: function( position ) {
+ var length = this.buffer.length;
+ while ( ++position < length ) {
+ if ( this.buffer[ position ] && !this.buffer[ position ].literal ) {
+ return position;
+ }
+ }
+
+ return length;
+ },
+
+ // _shiftLeft and _shiftRight will move values in the buffer over to the left/right
+ _shiftLeft: function( begin, end ) {
+ var bufferPosition,
+ bufferObject,
+ destPosition,
+ destObject,
+ bufferLength = this.buffer.length;
+
+ for ( destPosition = begin, bufferPosition = this._seekRight( end - 1 );
+ destPosition < bufferLength;
+ destPosition += destObject.length ) {
+ destObject = this.buffer[ destPosition ];
+ bufferObject = this.buffer[ bufferPosition ];
+
+ // we don't want to shift values into multi character fields
+ if ( destObject.valid && destObject.length === 1 ) {
+ if ( bufferPosition < bufferLength ) {
+ if ( this._validValue( destObject, bufferObject.value ) ) {
+ destObject.value = bufferObject.value;
+ delete bufferObject.value;
+ bufferPosition = this._seekRight( bufferPosition );
+ } else {
+
+ // once we find a value that doesn't fit anymore, we stop this shift
+ break;
+ }
+ }
+ }
+ }
+ },
+ _shiftRight: function ( bufferPosition ) {
+ var bufferObject,
+ temp,
+ shiftingValue = false,
+ bufferLength = this.buffer.length;
+
+ bufferPosition--;
+ while ( ( bufferPosition = this._seekRight( bufferPosition ) ) < bufferLength )
+ {
+ bufferObject = this.buffer[ bufferPosition ];
+ if ( shiftingValue === false ) {
+ shiftingValue = bufferObject.value;
+ } else {
+
+ // we don't want to shift values into multi character fields
+ if ( bufferObject.length === 1 && this._validValue( bufferObject, shiftingValue ) ) {
+ temp = bufferObject.value;
+ bufferObject.value = shiftingValue;
+ shiftingValue = temp;
+ } else {
+ return;
+ }
+ }
+ }
+ },
+
+ // returns the value if valid, otherwise returns false
+ _validValue: function( bufferObject, value ) {
+ if ( bufferObject.valid ) {
+ if ( $.isFunction( bufferObject.valid ) ) {
+ return bufferObject.valid( value || "" ) || false;
+ }
+ return bufferObject.valid.test( value ) && value;
+ }
+ return ( bufferObject.literal === value ) && value;
+ }
+});
+
+}( jQuery ) );
diff --git a/ui/jquery.ui.timepicker.js b/ui/jquery.ui.timepicker.js
new file mode 100644
index 00000000000..eb055886e43
--- /dev/null
+++ b/ui/jquery.ui.timepicker.js
@@ -0,0 +1,270 @@
+/*!
+ * jQuery UI Time Picker @VERSION
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.widget.js
+ * jquery.ui.spinner.js
+ * jquery.ui.mask.js
+ *
+ */
+(function( $, undefined ) {
+
+function makeBetweenMaskFunction( min, max, def, pad ) {
+ return function( value ) {
+ if ( !value ) {
+ return def;
+ }
+ value = parseInt( value, 10 );
+ if ( value >= min && value <= max ) {
+ return ( value < 10 ? pad : "" ) + value;
+ }
+ };
+}
+
+var formatNonPaddedHours = /\b(h)(?=:)/i,
+ format12Hour = /h/g,
+ maskDefinitions = {
+ _h: makeBetweenMaskFunction( 1, 12, "12", " " ),
+ hh: makeBetweenMaskFunction( 1, 12, "12", "0" ),
+ _H: makeBetweenMaskFunction( 0, 23, "12", " " ),
+ HH: makeBetweenMaskFunction( 0, 23, "12", "0" ),
+ mm: makeBetweenMaskFunction( 0, 59, "00", "0" ),
+ ss: makeBetweenMaskFunction( 0, 59, "00", "0" )
+ };
+
+$.widget( "ui.timepicker", {
+ version: "@VERSION",
+ defaultElement: " ",
+ options: {
+ ampm: true,
+ seconds: true
+ },
+ _create: function() {
+
+ // handles globalization options
+ this.element.mask({
+ mask: this._generateMask(),
+ clearEmpty: false,
+ definitions: $.extend({
+ tt: $.proxy( this, "_validAmPm" )
+ }, maskDefinitions )
+ });
+ this.mask = this.element.data( "ui-mask" );
+ this.element.spinner();
+ this.spinner = this.element.data( "ui-spinner" );
+ $.extend( this.spinner, {
+ _parse: $.proxy( this, "_spinnerParse" ),
+ _value: $.proxy( this, "_spinnerValue" ),
+ _adjustValue: function( value ) {
+
+ // if under min, return max - if over max, return min
+ if ( value < this.options.min ) {
+ return this.options.max;
+ }
+ if ( value > this.options.max ) {
+ return this.options.min;
+ }
+ return value;
+ }
+ });
+ this._setField( 0 );
+ this._on( this._events );
+ },
+ _destroy: function() {
+ this.element.mask( "destroy" );
+ this.element.spinner( "destroy" );
+ },
+
+ refresh: function() {
+ this.mask.refresh();
+ },
+
+ // getter/setter for the current state of the input as a "valid time string"
+ // http://dev.w3.org/html5/spec/common-microsyntaxes.html#times
+ value: function( value ) {
+ var bufferIndex, bufferObject,
+ buffer = this.mask.buffer,
+ bufferLength = buffer.length,
+ maskDefinitions = this.mask.options.definitions,
+ ampm = this._getCulture();
+
+ if ( value == null ) {
+
+ // storing the hours as a number until the very end
+ value = [ 0, "00", "00" ];
+ for ( bufferIndex = 0; bufferIndex < bufferLength; bufferIndex += 3 ) {
+ bufferObject = buffer[ bufferIndex ];
+ if (
+ bufferObject.valid === maskDefinitions._h || bufferObject.valid === maskDefinitions.hh ||
+ bufferObject.valid === maskDefinitions._H || bufferObject.valid === maskDefinitions.HH
+ ) {
+ value[ 0 ] = parseInt( bufferObject.value, 10 );
+ } else if ( bufferObject.valid === maskDefinitions.mm ) {
+ value[ 1 ] = bufferObject.value;
+ } else if ( bufferObject.valid === maskDefinitions.ss ) {
+ value[ 2 ] = bufferObject.value;
+ } else if ( bufferObject.valid === maskDefinitions.tt ) {
+ value[ 0 ] %= 12;
+ if ( jQuery.inArray( bufferObject.value, ampm.PM ) > -1 ) {
+ value[ 0 ] += 12;
+ }
+ }
+ }
+
+ // pads with zeros
+ value[ 0 ] = maskDefinitions.HH( "" + value[ 0 ] );
+ return value.join( ":" );
+ } else {
+
+ // setter for values
+ value = value.split( ":" );
+ for ( bufferIndex = 0; bufferIndex < bufferLength; bufferIndex += 3 ) {
+ bufferObject = buffer[ bufferIndex ];
+ if ( bufferObject.valid === maskDefinitions._h || bufferObject.valid === maskDefinitions.hh ) {
+
+ // 12 hr mode
+ bufferObject.value = bufferObject.valid( parseInt( value[0], 10 ) % 12 || 12 );
+ } else if ( bufferObject.valid === maskDefinitions.tt ) {
+
+ // am/pm
+ bufferObject.value = ampm[ parseInt( value[0], 10 ) < 12 ? "AM" : "PM" ][ 0 ];
+ } else {
+
+ // minutes/seconds
+ bufferObject.value = bufferObject.valid( value[ bufferIndex / 3 ] );
+ }
+ }
+
+ // repaint the values
+ this.mask._paint();
+ }
+ },
+
+ _events: {
+ click: "_checkPosition",
+ keydown: "_checkPosition"
+ },
+ _checkPosition: function( event ) {
+ var position = this.mask._caret(),
+ field = Math.floor( position.begin / 3 );
+
+ this._setField( field );
+
+ // if the cursor is left of the first field, ensure that the selection
+ // covers the first field to make overtyping make more sense
+ if ( position.begin === position.end && position.begin === 0 ) {
+ this.mask._caret( 0, 2 );
+ }
+
+ // after detecting the new position on a click, we should highlight the new field
+ if ( event.type === "click" ) {
+ this._highlightField();
+ }
+ },
+ _getCulture: function() {
+ if ( window.Globalize ) {
+ return Globalize.culture( this.options.culture ).calendars.standard;
+ } else {
+
+ // minimal calendar object for timepicker
+ return {
+ patterns: {
+ t: ": ",
+ T: ":: "
+ },
+ AM: [ "AM", "am" ],
+ PM: [ "PM", "pm" ]
+ };
+ }
+ },
+ _generateMask: function() {
+ var mask = this._getCulture().patterns[ this.options.seconds ? "T" : "t" ];
+
+ mask = mask.replace( formatNonPaddedHours, "_$1" );
+
+ if ( !this.options.ampm ) {
+ mask = mask.replace( format12Hour, "H" ).replace( / ?/, "" );
+ }
+
+ return mask;
+ },
+ _highlightField: function() {
+ this.mask._caretSelect( this.currentField * 3 );
+ },
+ _setField: function( field ) {
+ this.currentField = field;
+ switch( field ) {
+ case 0:
+ if ( this.options.ampm && this.mask.options.mask.indexOf( "h" ) ) {
+ this.spinner.options.min = 1;
+ this.spinner.options.max = 12;
+ } else {
+ this.spinner.options.min = 0;
+ this.spinner.options.max = 23;
+ }
+ break;
+ case 1:
+ case this.options.seconds ? 2 : -1 :
+ this.spinner.options.min = 0;
+ this.spinner.options.max = 59;
+ break;
+ case this.options.seconds ? 3 : 2 :
+ this.spinner.options.min = 0;
+ this.spinner.options.max = 1;
+ break;
+ }
+ },
+ _setOptions: function( options ) {
+
+ var currentValue = this.value();
+
+ // change the option
+ this._super( options );
+
+ // update the mask, all of the option changes have a chance of changing it
+ this.element.mask( "option", "mask", this._generateMask() );
+
+ // restore the value from before the option changed
+ this.value( currentValue );
+ },
+ _spinnerParse: function( val ) {
+ val = this.mask.buffer[ this.currentField * 3 ].value;
+ if ( this.currentField === ( this.options.seconds ? 3 : 2 ) ) {
+ return jQuery.inArray( val, this._getCulture().AM ) > -1 ? 0 : 1;
+ }
+ return parseInt( val, 10 ) || 0;
+ },
+ _spinnerValue: function( val ) {
+ var bufferObject = this.mask.buffer[ this.currentField * 3 ];
+ if ( this.currentField === ( this.options.seconds ? 3 : 2 ) ) {
+ val = this._getCulture()[ parseInt( val, 10 ) ? "PM" : "AM" ][ 0 ];
+ }
+ bufferObject.value = bufferObject.valid( val + "" );
+ this.mask._paint();
+ this.spinner._refresh();
+ this.mask._caretSelect( this.currentField * 3 );
+ },
+ _validAmPm: function( val ) {
+ var ampm, j, l,
+ valid = this._getCulture();
+
+ if ( val === "" ) {
+ return valid.PM && valid.PM[0];
+ }
+
+ for ( ampm in { AM: 1, PM: 1 } ) {
+ for ( j = 0, l = valid[ ampm ].length; j < l; j++ ) {
+ if ( valid[ ampm ][ j ].substr( 0, val.length ) === val ) {
+ return valid[ ampm ][ 0 ];
+ }
+ }
+ }
+ }
+});
+
+}( jQuery ));