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 + + +
    +

    Examples

    + +
    + + 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 + + + + + + + + + + + + + +

    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 + + + + + + + + + + + + + + + + + + + + + + + + +

      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 + + + + + + + + + + + + + +

        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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

          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

          1. Scale
          2. +

            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 ));