├── CHANGELOG.md ├── README.md ├── angular.html ├── bower.json ├── css └── screen.css ├── index.html ├── js ├── jquery.autotab.js └── jquery.autotab.min.js └── knockout.html /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.9.2 (2015-02-20) 2 | 3 | Bug fixes: 4 | 5 | * fixed an issue with input elements that are a button type having their text being overwritten when pasting and modified the demo to test against the bug (#79) 6 | * included a pull request that restores an element's original maxlength value 7 | 8 | 9 | ## 1.9.1 (2015-02-04) 10 | 11 | Bug fixes: 12 | 13 | * reverted a change that caused odd results with an element's maxlength property changed 14 | 15 | 16 | ## 1.9.0 (2015-01-31) 17 | 18 | Features: 19 | 20 | * improved support for other scripts that utilize the key events so as to prevent conflicts from happening 21 | * added support `drop` events, which are handle just like `paste` events (#73) 22 | * added support for tabbing through fields using the left and right arrow keys (#74) 23 | * auto tabbing support added to several input types: tel, number, email, url and search (#75) 24 | 25 | Bug fixes: 26 | 27 | * fixed a pasting issue that resulted in a target's value being cleared when no additional characters were being pasted (#72) 28 | * fixed a pasting issue in IE11 where backspacing into a previous element caused the cursor to be placed at the front of the element's value instead of at the end 29 | 30 | 31 | ## 1.8.1 (2014-11-01) 32 | 33 | Features: 34 | 35 | * added support for reusing predefined target/previous selectors for elements 36 | 37 | Bug fixes: 38 | 39 | * fixed the Angular and Knockout demos by properly applying the uppercase filter requirement on the product key example 40 | * removed debugging code 41 | 42 | 43 | ## 1.8.0 (2014-10-28) 44 | 45 | Features: 46 | 47 | * added refresh global method 48 | * updated demo to support removing examples in order to showcase the new refresh feature 49 | * added support for readonly fields 50 | * added support for specifying the filter format through class names based on the value of `$.autotab.selectFilterByClass` 51 | * added demos for Knockout and Angular 52 | 53 | Bug fixes: 54 | 55 | * fixes several issues with handling a pasted value that contained capital letters (#41, #65) 56 | * fixes an issue that caused disabled/readonly fields to be altered when pasting 57 | 58 | 59 | ## 1.7.1 (2014-08-21) 60 | 61 | Bug fixes: 62 | 63 | * fixes an issue when using deprecated methods `autotab_magic` and `autotab_filter` in a method chain 64 | 65 | 66 | ## 1.7 (2014-07-26) 67 | 68 | Features: 69 | 70 | * added support for additional fields and removed hidden fields from the selected elements, including maxlength support for textarea (#14) 71 | * added option to auto tab on single value select lists when selecting a value 72 | * added global methods to setup, remove and restore Autotab 73 | * elements passed to Autotab now filter out hidden form fields 74 | 75 | Bug fixes: 76 | 77 | * fixes an issue with tabbing to a previous field that is disabled, resulting in the value being modified (#47) 78 | 79 | 80 | ## 1.6 (2014-04-30) 81 | 82 | Features: 83 | 84 | * restores the ability to apply a filter only (#36) 85 | * added support to filter and tab on password fields (#36) 86 | 87 | 88 | ## 1.5.5 (2014-01-31) 89 | 90 | Bug fixes: 91 | 92 | * fixes a cursor position issue in IE6+ when replacing the value of a text box that is currently selected (#32) 93 | 94 | 95 | ## 1.5.4 (2014-01-15) 96 | 97 | Features: 98 | 99 | * added a check to apply filtering only to text fields, as support for other input types varies and may not support selection, while preserving auto tabbing functionality 100 | 101 | Bug fixes: 102 | 103 | * fixes an issue with multiple characters appearing after typing one when applying a filter rule more than once 104 | * fixes an issue with maxLength and Firefox, which defaults to -1 105 | 106 | 107 | ## 1.5.3 (2014-01-13) 108 | 109 | Bug fixes: 110 | 111 | * addresses an issue with forms not submitting from the on-screen keyboard (#22) 112 | 113 | 114 | ## 1.5.2 (2014-01-13) 115 | 116 | Bug fixes: 117 | 118 | * refactoring caused filtering rules, like uppercase or lowercase, to not work correctly (#23) 119 | 120 | 121 | ## 1.5.1 (2013-12-17) 122 | 123 | Bug fixes: 124 | 125 | * in Internet Explorer, reaching the max length caused the last character to appear in the target element after auto tabbing (#19) 126 | 127 | 128 | ## 1.5 (2013-12-12) 129 | 130 | Features: 131 | 132 | * added support for auto tabbing by specific keys 133 | * refactored various areas dealing with boolean evaulations and checking for iOS and Firefox 134 | 135 | Bug fixes: 136 | 137 | * fixed a bug that prevented the `change` event from triggering (#17) 138 | 139 | 140 | ## 1.4 (2013-11-12) 141 | 142 | Features: 143 | 144 | * pasting support has been greatly improved and behaves more intelligently 145 | * improved support in Firefox so that special keys no longer have to be tracked in order to be allowed 146 | 147 | Bug fixes: 148 | 149 | * fixed a bug where the `del` key would do nothing in Firefox (introduced in 1.3) 150 | * fixed several instances of bad evaluations of boolean values as a result of browsers storing data-* differently (introduced in 1.3) 151 | 152 | 153 | ## 1.3 (2013-11-09) 154 | 155 | Features: 156 | 157 | * added a 1ms timer to `$.autotab.next()` and `$.autotab.previous()` so that the calls perform as expected when the keypress event is complete 158 | * added the ability to turn auto tab and filtering on or off at will (#4) 159 | * added Firefox detection so that code specific to it can run separate from everything else 160 | * improved support for using a function as the format 161 | * refactored how Autotab's settings are stored as the previous method caused issues with turning it off and on again 162 | * added support for loading filter formats using `data-autotab-format` 163 | * added support for hexadecimal filtering 164 | 165 | Bug fixes: 166 | 167 | * if a selector has no matches, but `.autotab()` is still called, an error would occur 168 | * if allowing periods in a text box, Firefox would display two periods when only one was typed 169 | 170 | 171 | ## 1.2 (2013-11-02) 172 | 173 | Features: 174 | 175 | * updated `autotab()` to be the primary means of setting up auto tabbing and filtering (see documentation for more details) 176 | * greatly improved filtering by preventing illegal characters from appearing by changing `keyup` to `keypress` 177 | * improved functionality when making multiple calls for auto tab and/or filtering that prevents multiple triggers of the auto tabbing and filtering from occuring 178 | * added `$.autotab.next()` and `$.autotab.previous()`, which can be used to manually trigger the `autotab-next` and `autotab-previous` events respectively 179 | * added support to prevent double tabbing using a timer, ie. pressing tab immediately after auto tabbing has occurred 180 | * added basic support for pasting 181 | * improved auto tabbing by moving to the next element when the current one reaches its maxlength while holding down a key 182 | * auto tabbing forward will select the target element's value (#1) 183 | * while auto tabbing does not work on iOS devices, the behavior of the script has improved, including input validation 184 | * fully backwards compatible with Autotab 1.1b 185 | 186 | Bug fixes: 187 | 188 | * if rapidly typing illegal characters, the next element received focused despite the previous element not reaching it's maxlength 189 | * addressed sevearl behavioral issues in some of the latest versions of Firefox 190 | 191 | 192 | ## 1.1b (2008-09-10) 193 | 194 | Features: 195 | 196 | * refactored auto tabbing and filtering functionality so that one or the other could be applied 197 | * added `autotab_magic()`, simplifying the setup of `target` and `previous` elements 198 | * added `custom` format option, which requires a regular expression to be passed in the `pattern` field 199 | * updated `format` to support functions 200 | 201 | Bug fixes: 202 | 203 | * in Safari, pressing backspace in an empty text box would not focus on the `previous` element 204 | * in IE, pressing the left arrow key would force the cursor to the end of the field's content 205 | 206 | 207 | ## 1.0 (2008-05-22) 208 | 209 | Initial Release 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery Autotab 2 | ============== 3 | Autotab is a jQuery plugin that provides auto tabbing and filtering on text fields in a form. Once the maximum number of characters has been reached within a text field, the focus is automatically set to a defined element. Likewise, clearing out the text field's content by pressing backspace eventually places the focus on a previous element. 4 | 5 | 6 | ## Why jQuery Autotab? 7 | * Auto tabbing behaves logically, in both tabbing forward and tabbing backwards. 8 | * Allow your users to easily modify your text in a tab that would otherwise auto tab away. 9 | * Reduce the amount of bad data submitted in a form by filtering text fields. 10 | * Populate multiple text fields by pasting into one. 11 | * Enhance text fields by auto tabbing when a specific character is pressed. 12 | * It is small, fast, easy to load and built on the powerful jQuery library. 13 | 14 | 15 | ## Demo 16 | 17 | Always running the latest and greatest version of Autotab: http://autotab.mathachew.com/ 18 | 19 | [Angular](http://autotab.mathachew.com/angular.html) and [Knockout](http://autotab.mathachew.com/knockout.html) demos are also available. 20 | 21 | ## Table of Contents 22 | * [Requirements](#requirements) 23 | * [Installation](#installation) 24 | * [Setup and Usage](#setup-and-usage) 25 | * [Auto Tabbing](#auto-tabbing) 26 | * [Examples](#examples) 27 | * [Remove and Restore](#remove-and-restore) 28 | * [Examples](#examples-1) 29 | * [Filtering](#filtering) 30 | * [Examples](#examples-2) 31 | * [Global Methods](#global-methods) 32 | * [Options](#options) 33 | * [Filter Formats](#filter-formats) 34 | * [Known Issues](#known-issues) 35 | * [Minify](#minify) 36 | * [Feedback](#feedback) 37 | * [Copyright and License](#copyright-and-license) 38 | 39 | 40 | ## Requirements 41 | 42 | Autotab works in both jQuery 1.7+ and 2.x. If your site supports Internet Explorer 6, 7, and/or 8, use jQuery 1.7+ since 2.x drops support for these browsers. 43 | 44 | 45 | ## Installation 46 | 47 | Add a reference to jquery.autotab.js. 48 | 49 | ```html 50 | 51 | ``` 52 | 53 | 54 | ## Setup and Usage 55 | 56 | Autotab can be setup in several different ways within jQuery's `$(document).ready()` event. The two components that make up Autotab, auto tabbing and filtering, can be managed independently from one another, providing numerous ways of achieving the same result, depending on how indepth you want to setup your form. 57 | 58 | 59 | ### Auto Tabbing 60 | 61 | __Note:__ If the selector matches multiple elements, the target and previous fields will be overwritten if previously set. 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 75 | 76 | 77 | 78 | 79 | 80 |
.autotab()Accepts no arguments and applies auto tabbing rules only.
.autotab(string) 71 | string: Can be a filter format or a value that tells the script to remove or restore auto tab and filtering functionality. 72 |
73 | Note: Previous auto tabbing order will be overwritten. To change the filter only, call .autotab('filter', '') 74 |
.autotab(object)object: Applies the specified options to all matched elements.
81 | 82 | 83 | #### Examples 84 | 85 | Establishes auto tabbing rules only and forces each field to have a `maxlength` of 1. 86 | 87 | ```js 88 | $('.answer').autotab({ maxlength: 1 }); 89 | ``` 90 | ```html 91 |
92 | 93 | - 94 | 95 | - 96 | 97 | - 98 |
99 | ``` 100 | 101 | Automatically establishes auto tabbing order and number filtering. 102 | 103 | ```js 104 | $('.number').autotab('number'); 105 | ``` 106 | ```html 107 |
108 | 109 | - 110 | - 111 | 112 |
113 | ``` 114 | 115 | Manually defines auto tabbing order and alphanumeric filtering. 116 | 117 | ```js 118 | $('#alphanumeric1').autotab({ format: 'alphanumeric', target: '#alphanumeric2' }); 119 | $('#alphanumeric2').autotab({ format: 'alphanumeric', target: '#alphanumeric3', previous: '#alphanumeric1' }); 120 | $('#alphanumeric3').autotab({ format: 'alphanumeric', target: '#alphanumeric4', previous: '#alphanumeric2' }); 121 | $('#alphanumeric4').autotab({ format: 'alphanumeric', target: '#alphanumeric5', previous: '#alphanumeric3' }); 122 | $('#alphanumeric5').autotab({ format: 'alphanumeric', previous: '#alphanumeric4' }); 123 | ``` 124 | ```html 125 |
126 | 127 | - 128 | - 129 | - 130 | - 131 | 132 |
133 | ``` 134 | 135 | 136 | ### Remove and Restore 137 | 138 | 139 | 140 | 143 | 148 | 149 | 150 | 151 | 154 | 155 |
141 | .autotab('remove|destroy|disable') 142 | 144 | string: Disables auto tab and filtering functionality on all matched elements. 145 |
146 | Note: Both destroy and remove will disable auto tab and filtering. Standard and custom event bindings persist as they check the status of an element when called. Removing the keydown and keypress can have a negative impact in both user and developer experience if the same events have been bound in other areas. 147 |
.autotab('restore|enable') 152 | string: Restores auto tab and filtering functionality on all matched elements. 153 |
156 | 157 | 158 | #### Examples 159 | 160 | Manually turn off auto tab and filtering functionality on all text boxes. 161 | 162 | ```js 163 | $('#remove-autotab').on('click', function () { 164 | $('input[type=text]').autotab('remove'); 165 | }); 166 | ``` 167 | ```html 168 | 169 | ``` 170 | 171 | Manually turn on auto tab and filtering functionality on all text boxes. 172 | 173 | ```js 174 | $('#restore-autotab').on('click', function () { 175 | $('input[type=text]').autotab('restore'); 176 | }); 177 | ``` 178 | ```html 179 | 180 | ``` 181 | 182 | 183 | ### Filtering 184 | 185 | __Note:__ If passing an object, the target and previous fields are ignored, if included, to preserve previously established auto tab rules. Use `.autotab(object)` to modify the target and previous fields. 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 197 | 198 |
.autotab('filter', string)string: Applies the specified filter format to all matched elements.
.autotab('filter', object) 195 | object: Applies the specified filter options to all matched elements. The target and previous fields are ignored. 196 |
199 | 200 | Because of how Autotab's settings are stored, it is possible to define the filter format using `data-autotab-format`. If using `custom`, place your regular expression in `data-autotab-pattern`. 201 | 202 | It is possible to specify the filter through an element's class, using the same names that are available when calling the filter. In order to use this feature, `$.autotab.selectFilterByClass` must be set to true before initializing Autotab. 203 | 204 | 205 | #### Examples 206 | 207 | Manually defines text filtering. 208 | 209 | ```js 210 | $('#salt').autotab('filter', 'text'); 211 | ``` 212 | ```html 213 |
214 | 215 | 216 |
217 | ``` 218 | 219 | Manually defines alphanumeric filtering and converts all alpha characters to uppercase format. 220 | 221 | ```js 222 | $('.alphanumeric').autotab('filter', { format: 'alphanumeric', uppercase: true }); 223 | ``` 224 | ```html 225 |
226 | 227 | - 228 | - 229 | - 230 | - 231 | 232 |
233 | ``` 234 | 235 | Manually defines number filtering via `data-autotab-format`. In this example, `$(selector).autotab()` will take the attribute into account. 236 | 237 | ```html 238 |
239 | 240 | - 241 | - 242 | 243 |
244 | ``` 245 | 246 | Other random JavaScript examples 247 | 248 | ```js 249 | $(':input').autotab(); 250 | $('#username').autotab({ format: 'alphanumeric', target: '#password' }); 251 | $('#password').autotab({ previous: '#username', target: '#confirm' }); 252 | $('#product-key').autotab('filter', { format: 'alphanumeric', uppercase: true }); 253 | $('#ip-address').autotab('filter', { format: 'custom', pattern: '[^0-9\.]' }); 254 | $('#function').autotab('filter', function (text) { alert(text); }); 255 | $('#number1, #number2, #number3').autotab('filter', 'number'); 256 | $('.ipv6').autotab('filter', 'hexadecimal'); 257 | ``` 258 | 259 | 260 | ### Global Methods 261 | 262 | Autotab comes with several global methods, which are probably most useful in edge cases. 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 273 | 274 |
$.autotab()Initializes Autotab on all elements matching the :input selector.
$.autotab(object)object: Applies the specified options to all matched elements. 272 |
275 | 276 | 277 | 278 | 283 | 284 | 285 | 286 | 291 | 292 |
$.autotab.next() 279 | Triggers the `autotab-next` event, which sets the focus on the target element, if it exists. 280 |
281 | Note: This method is only useful if an element setup with Autotab has focus. 282 |
$.autotab.previous() 287 | Triggers the `autotab-previous` event, which sets the focus on the previous element, if it exists. 288 |
289 | Note: This method is only useful if an element setup with Autotab has focus. 290 |
293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 |
$.autotab.remove()Removes Autotab from all matched elements.
$.autotab.remove(string)string: A selector identifying the matched element(s).
$.autotab.remove(object)object: Applies the removal to all matched elements.
307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 |
$.autotab.restore()Restores Autotab to all matched elements.
$.autotab.restore(string)string: A selector identifying the matched element(s).
$.autotab.restore(object)object: Applies restoration to all matched elements.
321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 338 | 339 |
$.autotab.refresh()Refreshes the tabbing order on all elements matching the :input selector.
$.autotab.refresh(string)string: A selector identifying the matched element(s)
$.autotab.refresh(object)object: Refreshes the target/previous values for all matched elements.
336 | Under certain conditions, using refresh may cause an unexpected tabbing order, so the :input selector is recommended. 337 |
340 | 341 | 342 | ## Options 343 | 344 | ```js 345 | var options = { 346 | format: string|function, 347 | pattern: string, 348 | uppercase: boolean, 349 | lowercase: boolean, 350 | nospace: boolean, 351 | maxlength: integer, 352 | target: string|element, 353 | previous: string|element, 354 | trigger: string|array, 355 | tabOnSelect: boolean 356 | }; 357 | ``` 358 | 359 | 360 | 361 | 368 | 369 | 370 | 371 | 374 | 375 | 376 | 377 | 380 | 381 | 382 | 383 | 386 | 387 | 388 | 389 | 394 | 395 | 396 | 397 | 402 | 403 | 404 | 405 | 412 | 413 | 414 | 415 | 422 | 423 | 424 | 425 | 426 | 433 | 434 | 435 | 436 | 437 | 440 | 441 |
format 362 | string: Speficies which format rule to use on a text box's value. 363 |
364 | function(value, element): If a single regular expression is insufficient, a function can be used for custom formatting. The parameter value is the typed character and element is the focused JavaScript element. 365 |
366 | Note: Go to Filter Formats to see each available format option. 367 |
pattern 372 | string: Used only when the custom format is specified, and it must be a string. 373 |
uppercase 378 | boolean: Forces all alpha characters to uppercase format. 379 |
lowercase 384 | boolean: Forces all alpha characters to lowercase format. 385 |
nospace 390 | boolean: Determines if spaces are allowed or not. 391 |
392 | Note: Space and underscore are not alpha characters and are not included in the alpha and alphanumeric format options. Use the custom format to allow these characters. 393 |
maxlength 398 | integer: The maximum number of characters allowed in a text box. Maxlength can be specified with the HTML attribute maxlength. 399 |
400 | Note: The maxlength attribute value can be overwritten if the maxlength field is passed to autotab(). 401 |
target 406 | When auto tabbing occurs, target is the element that will gain focus. 407 |
408 | string: A selector identifying the next element. 409 |
410 | element: The JavaScript or jQuery element. 411 |
previous 416 | When backspacing or reverse tabbing, previous is the element that will gain focus. 417 |
418 | string: A selector identifying the next element. 419 |
420 | element: The JavaScript or jQuery element. 421 |
trigger 427 | Triggers autotab-next when the specified characters are pressed. 428 |
429 | string: A string of one or more characters 430 |
431 | element: An array of one or more strings 432 |
tabOnSelect 438 | boolean: Adds auto tabbing to all matched single value select lists. 439 |
442 | 443 | 444 | ## Filter Formats 445 | 446 | Autotab has several filter formats available, all passed into the `format` key. If none of the formats meet your needs, Autotab also supports a passing a function or specifying `custom` option where you can pass a regular expression. 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 477 | 482 | 483 | 484 | 485 | 490 | 491 |
allAllows any and all characters.
textAllows all characters, including special characters, except numbers.
alphaAllows only letters.
number|numericAllows only numbers.
alphanumericAllows only letters and numbers.
hex|hexadecimalAllows only letters A-F, a-f and numbers.
475 | custom 476 | 478 | Allows a developer to provide a custom regular expression: new RegExp(pattern, 'g'); 479 |
480 | Note: Requires pattern: string, ie: pattern: "[^0-9\.]" 481 |
function(value, element) 486 | Allows a developer to provide a their own function in case a regular expression is insufficient. 487 |
488 | Note: value is the typed character, element is the focused JavaScript element. 489 |
492 | 493 | 494 | ## Known Issues 495 | 496 | * Due to security measures placed in iOS, Autotab cannot achieve auto tabbing functionality when hitting a field's character limit. The problem stems from the `focus` event not being triggered manually. As a workaround, Autotab works with iOS by keeping the keyboard open, allowing you to navigate using the arrow shortcuts. 497 | * Any script that uses the `keydown` and `keypress` events may conflict with Autotab, or vice versa. As of 1.9.0, Autotab uses event extensions in an attempt to prevent this from happening. 498 | * With limitations of `selection` in most text field types, only `text`, `password` and `textarea` fields support auto tabbing and filtering, while `tel`, `number`, `email`, `url` and `search` support auto tabbing only. 499 | * `Drop` events will not work for IE11 since changing the `maxlength` property causes the event from proceeding. IE6-10 work fine, however. 500 | 501 | ## Minify 502 | 503 | Autotab uses the [Closure Compiler](http://closure-compiler.appspot.com/) (`simple` optimization) to create jquery.autotab.min.js. 504 | 505 | 506 | ## Feedback 507 | 508 | Please provide feature requests and bug reports here on [GitHub](../../issues). 509 | 510 | You can also reach out to me on twitter: [@mathachew](http://www.twitter.com/mathachew) 511 | 512 | ## Copyright and license 513 | 514 | © 2015 Matthew Miller 515 | 516 | Licensed under the MIT licensing: http://www.opensource.org/licenses/mit-license.php 517 | -------------------------------------------------------------------------------- /angular.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery Autotab Demo with Angular 6 | 7 | 8 | 9 | 10 | 11 | 72 | 73 | 74 | 75 |
76 |

jQuery Autotab Demo with Angular

77 |

Autotab's full documentation can be found in ReadMe.md on GitHub.

78 | 79 |

The purpose of this demo is to provide a proof of concept for dynamically adding or removing fields that are handled through Autotab. Click here to see this using Knockout.

80 | 81 |
82 | 83 | 84 |
85 | 86 |
87 | 88 | 89 |
90 |
91 | 92 | - 93 | 94 | - 95 | 96 | 97 | 98 | X 99 | 100 |
101 |
102 |
103 |
104 | 105 |
106 | 107 | 108 |
109 |
110 | 111 | - 112 | 113 | - 114 | 115 | - 116 | 117 | - 118 | 119 | 120 | 121 | X 122 | 123 |
124 |
125 |
126 |
127 |
128 | 129 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery.autotab", 3 | "version": "1.9.2", 4 | "description": "A jQuery plugin that provides auto-tabbing and filtering on text fields in a form.", 5 | "main": [ "./js/jquery.autotab.min.js" ], 6 | "homepage": "https://github.com/Mathachew/jquery-autotab", 7 | "dependencies" : { 8 | "jquery" : ">= 1.7" 9 | }, 10 | "keywords": [ 11 | "autotab", 12 | "filter" 13 | ], 14 | "author" : { 15 | "name" : "Matthew Miller", 16 | "web" : "https://github.com/Mathachew" 17 | }, 18 | "license": [ 19 | "http://www.opensource.org/licenses/mit-license.php" 20 | ] 21 | } -------------------------------------------------------------------------------- /css/screen.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #f7f7f9; 3 | color: #333; 4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | font-size: 14px; 6 | line-height: 20px; 7 | } 8 | hr { 9 | border: 0; 10 | border-top: 1px solid #eee; 11 | border-bottom: 1px solid #fff; 12 | } 13 | #container { 14 | background-color: #fff; 15 | border: 1px solid #e1e1e8; 16 | border-radius: 10px; 17 | box-sizing: border-box; 18 | margin: auto; 19 | padding: 20px; 20 | width: 800px; 21 | } 22 | pre, 23 | code { 24 | background-color: #f7f7f9; 25 | border: 1px solid #e1e1e8; 26 | border-radius: 5px; 27 | padding: 2px 4px; 28 | word-wrap: break-word; 29 | } 30 | code { 31 | display: inline-block; 32 | } 33 | .example { 34 | border-top: 1px solid #eee; 35 | padding-top: 5px; 36 | } 37 | .example label { 38 | display: block; 39 | font-weight: bold; 40 | margin-bottom: 5px; 41 | } 42 | button { 43 | background-color: #f3f3f3; 44 | border: 1px solid #ccc; 45 | border-radius: 4px; 46 | color: #333; 47 | cursor: pointer; 48 | display: inline-block; 49 | font-size: 14px; 50 | margin: 10px 0; 51 | /*outline: none;*/ 52 | padding: 6px 12px; 53 | position: relative; 54 | text-align: center; 55 | vertical-align: middle; 56 | white-space: nowrap; 57 | } 58 | button:hover { 59 | background-color: #eee; 60 | } 61 | button:active { 62 | top: 1px; 63 | } 64 | .on { 65 | color: green; 66 | } 67 | .off { 68 | color: red; 69 | } 70 | .remove-example { 71 | float: right; 72 | margin-top: 5px; 73 | } 74 | .remove a, 75 | .remove-example a { 76 | background-color: #f7f7f9; 77 | border: 1px solid #e1e1e8; 78 | border-radius: 5px; 79 | color: #333; 80 | padding: 2px 6px; 81 | text-decoration: none; 82 | } 83 | .remove a:hover, 84 | .remove-example a:hover { 85 | background-color: #e7e7e9; 86 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery Autotab Demo 6 | 7 | 8 | 9 | 10 | 62 | 63 | 64 | 65 |
66 |

jQuery Autotab Demo

67 |

Autotab's full documentation can be found in ReadMe.md on GitHub.

68 | 69 |

Angular and Knockout demos are also available.

70 | 71 |
72 | 73 |

Note: For the purposes of this demo, I am calling $.autotab({ tabOnSelect: true }); to initialize the auto tabbing order.

74 | 75 |
76 | 77 |
78 |

Use the buttons below to turn Autotab on or off. Turning Autotab off will remove both auto tabbing and filtering.

79 | 80 | 81 | 82 | Autotab status: On 83 |
84 | 85 |
86 |
87 | X 88 |
89 | 90 |
91 | 92 | 93 | 94 | - 95 | 96 | - 97 | 98 |
99 |
100 | $('.number').autotab('filter', 'number');
101 | 
102 |
103 | 104 |
105 |
106 | X 107 |
108 | 109 |
110 | 111 | 112 | 113 | - 114 | 115 | - 116 | 117 |
118 |
119 | $('.text').autotab('filter', 'text');
120 | 
121 |
122 | 123 |
124 |
125 | X 126 |
127 | 128 |
129 | 130 | 131 | 132 | - 133 | 134 | - 135 | 136 | - 137 | 138 | - 139 | 140 |
141 |
142 | $('.alpha').autotab('filter', 'alpha');
143 | 
144 |
145 | 146 |
147 |
148 | X 149 |
150 | 151 |
152 | 153 | 154 | 155 | - 156 | 157 | - 158 | 159 | - 160 | 161 | - 162 | 163 |
164 |
165 | $('.alphanumeric').autotab('filter', { format: 'alphanumeric', uppercase: true });
166 | 
167 |
168 | 169 |
170 |
171 | X 172 |
173 | 174 |
175 | 176 | 177 |
178 |
179 | $('#regex').autotab({ format: 'custom', pattern: '[^0-9\.]' });
180 | 
181 |
182 | 183 |
184 |
185 | X 186 |
187 | 188 |
189 | 190 | 191 |
192 |
193 | $('#function').autotab(function (value, element) {
194 |     var parsedValue = parseInt(value, 10);
195 | 
196 |     if (!value || parsedValue != value) {
197 |         return '';
198 |     }
199 | 
200 |     var newValue = element.value + value;
201 | 
202 |     if (newValue > 12) {
203 |         $.autotab.next();
204 |         return 2;
205 |     }
206 |     else if (element.value.length == 0 && value > 1) {
207 |         $.autotab.next();
208 |         return value;
209 |     }
210 |     else if (element.value.length == 1 && parsedValue === 0 && newValue != 10) {
211 |         $.autotab.next();
212 |         return 1;
213 |     }
214 | 
215 |     return value;
216 | });
217 | 
218 |
219 | 220 |
221 |
222 | X 223 |
224 | 225 |
226 | 227 | 228 | 229 | : 230 | 231 | : 232 | 233 | : 234 | 235 | : 236 | 237 | : 238 | 239 | : 240 | 241 | : 242 | 243 |
244 |
245 | $('.hexadecimal').autotab('filter', 'hexadecimal');
246 | 
247 |
248 | 249 |
250 |
251 | X 252 |
253 | 254 |
255 | 256 | 257 | 258 | . 259 | 260 | . 261 | 262 | . 263 | 264 |
265 |
266 | $('.ip').autotab('filter', { format: 'number', trigger: '.' });
267 | 
268 |
269 | 270 |
271 |
272 | X 273 |
274 | 275 |
276 | 277 | 278 | 279 | . 280 | 281 |
282 |
283 | $('.pin').autotab('filter', 'number');
284 | 
285 |
286 | 287 |
288 |
289 | X 290 |
291 | 292 |
293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 |
304 |
305 | 310 |
311 | 312 |
313 | 314 |
315 | 316 |
317 | 318 |
319 | Email: 320 |
321 | Number: 322 |
323 | Tel: 324 |
325 | Range: 326 |
327 | URL: 328 |
329 |
330 | // Note: This call is not necessary as 'all' is the default format
331 | $('.all').autotab('filter', 'all');
332 | 
333 |
334 |
335 | 336 | 350 | 351 | 352 | -------------------------------------------------------------------------------- /js/jquery.autotab.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Autotab - jQuery plugin 1.9.2 3 | * https://github.com/Mathachew/jquery-autotab 4 | * 5 | * Copyright (c) 2008, 2015 Matthew Miller 6 | * 7 | * Licensed under the MIT licensing: 8 | * http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | 11 | (function ($) { 12 | var platform = navigator.platform, 13 | settings = { 14 | tabPause: 800, 15 | focusChange: null, 16 | iOS: (platform === 'iPad' || platform === 'iPhone' || platform === 'iPod'), 17 | firefox: (typeof InstallTrigger !== 'undefined'), 18 | ie11: !(window.ActiveXObject) && "ActiveXObject" in window 19 | }; 20 | 21 | var setSettings = function (e, settings) { 22 | if (settings === null || typeof settings === 'undefined') { 23 | return; 24 | } 25 | 26 | for (var key in settings) { 27 | $(e).data('autotab-' + key, settings[key]); 28 | } 29 | }; 30 | 31 | var getSettings = function (e) { 32 | var settings = { 33 | arrowKey: false, 34 | format: 'all', 35 | loaded: false, 36 | disabled: false, 37 | pattern: null, 38 | uppercase: false, 39 | lowercase: false, 40 | nospace: false, 41 | maxlength: 2147483647, 42 | target: null, 43 | previous: null, 44 | trigger: null, 45 | originalValue: '', 46 | changed: false, 47 | editable: (e.type === 'text' || e.type === 'password' || e.type === 'textarea' || e.type === 'tel' || e.type === 'number' || e.type === 'email' || e.type === 'search' || e.type === 'url'), 48 | filterable: (e.type === 'text' || e.type === 'password' || e.type === 'textarea'), 49 | tabOnSelect: false 50 | }; 51 | 52 | // If $.autotab.selectFilterByClas is true and the format not specified, automatically select an element's format based on a matching class name. 53 | // The first matched element becomes the selected format for the filter. 54 | if ($.autotab.selectFilterByClass === true && typeof $(e).data('autotab-format') === 'undefined') { 55 | var classes = ['all', 'text', 'alpha', 'number', 'numeric', 'alphanumeric', 'hex', 'hexadecimal', 'custom']; 56 | 57 | for (var key in classes) { 58 | if ($(e).hasClass(classes[key])) { 59 | settings.format = classes[key]; 60 | break; 61 | } 62 | } 63 | } 64 | 65 | for (var key in settings) { 66 | if (typeof $(e).data('autotab-' + key) !== 'undefined') { 67 | settings[key] = $(e).data('autotab-' + key); 68 | } 69 | } 70 | 71 | // Save settings on first run 72 | if (!settings.loaded) { 73 | if (settings.trigger !== null && typeof settings.trigger === 'string') { 74 | settings.trigger = settings.trigger.toString(); 75 | } 76 | 77 | setSettings(e, settings); 78 | } 79 | 80 | return settings; 81 | }; 82 | 83 | var queryObject = function (e) { 84 | return (typeof e !== 'undefined' && (typeof e === 'string' || !(e instanceof jQuery))); 85 | }; 86 | 87 | var getSelection = function (e) { 88 | var start = 0, 89 | end = 0, 90 | selectionType = 0; 91 | 92 | if (e.type === 'text' || e.type === 'password' || e.type === 'textarea') { 93 | if (typeof e.selectionStart === 'number' && typeof e.selectionEnd === 'number') { 94 | // Non-IE browsers and IE 9+ 95 | start = e.selectionStart; 96 | end = e.selectionEnd; 97 | selectionType = 1; 98 | } 99 | else if (document.selection && document.selection.createRange) { 100 | // For IE up to version 8 101 | var selectionRange = document.selection.createRange(), 102 | textInputRange = e.createTextRange(), 103 | precedingRange = e.createTextRange(), 104 | bookmark = selectionRange.getBookmark(); 105 | textInputRange.moveToBookmark(bookmark); 106 | precedingRange.setEndPoint("EndToStart", textInputRange); 107 | start = precedingRange.text.length; 108 | end = start + selectionRange.text.length; 109 | selectionType = 2; 110 | } 111 | } 112 | 113 | return { 114 | start: start, 115 | end: end, 116 | selectionType: selectionType 117 | }; 118 | }; 119 | 120 | $.autotab = function (options) { 121 | if (typeof options !== 'object') { 122 | options = {}; 123 | } 124 | 125 | $(':input').autotab(options); 126 | }; 127 | 128 | $.autotab.selectFilterByClass = false; 129 | 130 | $.autotab.next = function () { 131 | var e = $(document.activeElement); 132 | 133 | if (e.length) { 134 | e.trigger('autotab-next'); 135 | } 136 | }; 137 | 138 | $.autotab.previous = function () { 139 | var e = $(document.activeElement); 140 | 141 | if (e.length) { 142 | e.trigger('autotab-previous'); 143 | } 144 | }; 145 | 146 | $.autotab.remove = function (e) { 147 | queryObject(e) ? $(e).autotab('remove') : $(':input').autotab('remove'); 148 | }; 149 | 150 | $.autotab.restore = function (e) { 151 | queryObject(e) ? $(e).autotab('restore') : $(':input').autotab('restore'); 152 | }; 153 | 154 | $.autotab.refresh = function (e) { 155 | queryObject(e) ? $(e).autotab('refresh') : $(':input').autotab('refresh'); 156 | }; 157 | 158 | $.fn.autotab = function (method, options) { 159 | if (!this.length) { 160 | return this; 161 | } 162 | 163 | // Remove hidden fields since tabbing backwards is supported on different form elements 164 | var filtered = $.grep(this, function (e, i) { 165 | return e.type != 'hidden'; 166 | }); 167 | 168 | // Apply filter options 169 | if (method == 'filter') { 170 | if (typeof options === 'string' || typeof options === 'function') { 171 | options = { format: options }; 172 | } 173 | 174 | for (var i = 0, length = filtered.length; i < length; i++) { 175 | var defaults = getSettings(filtered[i]), 176 | newOptions = options; 177 | 178 | // Retain the established target/previous values as this area is for filtering only 179 | newOptions.target = defaults.target; 180 | newOptions.previous = defaults.previous; 181 | 182 | $.extend(defaults, newOptions); 183 | 184 | if (!defaults.loaded) { 185 | defaults.disabled = true; 186 | autotabBind(filtered[i], newOptions); 187 | } 188 | else { 189 | setSettings(filtered[i], defaults); 190 | } 191 | } 192 | } 193 | // Disable auto tab and filtering 194 | else if (method == 'remove' || method == 'destroy' || method == 'disable') { 195 | for (var i = 0, length = filtered.length; i < length; i++) { 196 | var defaults = getSettings(filtered[i]); 197 | 198 | defaults.disabled = true; 199 | 200 | setSettings(filtered[i], defaults); 201 | } 202 | } 203 | // Re-enable auto tab and filtering 204 | else if (method == 'restore' || method == 'enable') { 205 | for (var i = 0, length = filtered.length; i < length; i++) { 206 | var defaults = getSettings(filtered[i]); 207 | 208 | defaults.disabled = false; 209 | 210 | setSettings(filtered[i], defaults); 211 | } 212 | } 213 | // Refresh target/previous elements 214 | else if (method == 'refresh') { 215 | for (var i = 0, length = filtered.length; i < length; i++) { 216 | var defaults = getSettings(filtered[i]), 217 | n = i + 1, 218 | p = i - 1, 219 | selectTarget = function () { 220 | if (i > 0 && n < length) { 221 | defaults.target = filtered[n]; 222 | } 223 | else if (i > 0) { 224 | defaults.target = null; 225 | } 226 | else { 227 | defaults.target = filtered[n]; 228 | } 229 | }, 230 | selectPrevious = function () { 231 | if (i > 0 && n < length) { 232 | defaults.previous = filtered[p]; 233 | } 234 | else if (i > 0) { 235 | defaults.previous = filtered[p]; 236 | } 237 | else { 238 | defaults.previous = null; 239 | } 240 | }; 241 | 242 | // Nothing was specified for the target element, so automatically set it 243 | if (defaults.target === null || defaults.target.selector === '') { 244 | selectTarget(); 245 | } 246 | else if (typeof defaults.target === 'string' || defaults.target.selector) { 247 | defaults.target = $(typeof defaults.target === 'string' ? defaults.target : defaults.target.selector); 248 | 249 | if (defaults.target.length === 0) { 250 | selectTarget(); 251 | } 252 | } 253 | 254 | // Nothing was specified for the previous element, so automatically set it 255 | if (defaults.previous === null || defaults.previous.selector === '') { 256 | selectPrevious(); 257 | } 258 | else if (typeof defaults.previous === 'string' || defaults.previous.selector) { 259 | defaults.previous = $(typeof defaults.previous === 'string' ? defaults.previous : defaults.previous.selector); 260 | 261 | if (defaults.previous.length === 0) { 262 | selectPrevious(); 263 | } 264 | } 265 | 266 | if (!defaults.loaded) { 267 | autotabBind(filtered[i], defaults); 268 | } 269 | else { 270 | if (queryObject(defaults.target)) { 271 | defaults.target = $(defaults.target); 272 | } 273 | 274 | if (queryObject(defaults.previous)) { 275 | defaults.previous = $(defaults.previous); 276 | } 277 | 278 | setSettings(filtered[i], defaults); 279 | } 280 | } 281 | } 282 | else { 283 | if (method === null || typeof method === 'undefined') { 284 | options = {}; 285 | } 286 | else if (typeof method === 'string' || typeof method === 'function') { 287 | options = { format: method }; 288 | } 289 | else if (typeof method === 'object') { 290 | options = method; 291 | } 292 | 293 | // Bind key events to element(s) passed 294 | if (filtered.length > 1) { 295 | for (var i = 0, length = filtered.length; i < length; i++) { 296 | var n = i + 1, 297 | p = i - 1, 298 | newOptions = options; 299 | 300 | if (i > 0 && n < length) { 301 | newOptions.target = filtered[n]; 302 | newOptions.previous = filtered[p]; 303 | } 304 | else if (i > 0) { 305 | newOptions.target = null; 306 | newOptions.previous = filtered[p]; 307 | } 308 | else { 309 | newOptions.target = filtered[n]; 310 | newOptions.previous = null; 311 | } 312 | 313 | autotabBind(filtered[i], newOptions); 314 | } 315 | } 316 | else { 317 | autotabBind(filtered[0], options); 318 | } 319 | } 320 | 321 | return this; 322 | }; 323 | 324 | var filterValue = function (e, value, defaults) { 325 | if (typeof defaults.format === 'function') { 326 | return defaults.format(value, e); 327 | } 328 | 329 | var pattern = null; 330 | 331 | switch (defaults.format) { 332 | case 'text': 333 | pattern = new RegExp('[0-9]+', 'g'); 334 | break; 335 | 336 | case 'alpha': 337 | pattern = new RegExp('[^a-zA-Z]+', 'g'); 338 | break; 339 | 340 | case 'number': 341 | case 'numeric': 342 | pattern = new RegExp('[^0-9]+', 'g'); 343 | break; 344 | 345 | case 'alphanumeric': 346 | pattern = new RegExp('[^0-9a-zA-Z]+', 'g'); 347 | break; 348 | 349 | case 'hex': 350 | case 'hexadecimal': 351 | pattern = new RegExp('[^0-9A-Fa-f]+', 'g'); 352 | break; 353 | 354 | case 'custom': 355 | pattern = new RegExp(defaults.pattern, 'g'); 356 | break; 357 | 358 | case 'all': 359 | default: 360 | break; 361 | } 362 | 363 | if (pattern !== null) { 364 | value = value.replace(pattern, ''); 365 | } 366 | 367 | if (defaults.nospace) { 368 | pattern = new RegExp('[ ]+', 'g'); 369 | value = value.replace(pattern, ''); 370 | } 371 | 372 | if (defaults.uppercase) { 373 | value = value.toUpperCase(); 374 | } 375 | 376 | if (defaults.lowercase) { 377 | value = value.toLowerCase(); 378 | } 379 | 380 | return value; 381 | }; 382 | 383 | var autotabBind = function (element, options) { 384 | var defaults = getSettings(element); 385 | 386 | if (defaults.disabled) { 387 | defaults.disabled = false; 388 | defaults.target = null; 389 | defaults.previous = null; 390 | } 391 | 392 | $.extend(defaults, options); 393 | 394 | // Sets targets to element based on the name or ID passed if they are not currently objects 395 | if (queryObject(defaults.target)) { 396 | defaults.target = $(defaults.target); 397 | } 398 | 399 | if (queryObject(defaults.previous)) { 400 | defaults.previous = $(defaults.previous); 401 | } 402 | 403 | var oldMaxlength = element.maxLength; 404 | 405 | if (typeof element.maxLength === 'undefined' && element.type == 'textarea') { 406 | oldMaxlength = element.maxLength = element.getAttribute('maxlength'); 407 | } 408 | 409 | // defaults.maxlength has not changed and maxlength was specified 410 | if (defaults.maxlength == 2147483647 && oldMaxlength != 2147483647 && oldMaxlength != -1) { 411 | defaults.maxlength = oldMaxlength; 412 | } 413 | // defaults.maxlength overrides maxlength 414 | else if (defaults.maxlength > 0) { 415 | element.maxLength = defaults.maxlength; 416 | } 417 | // defaults.maxlength and maxlength have not been specified 418 | // A target cannot be used since there is no defined maxlength 419 | else { 420 | defaults.target = null; 421 | } 422 | 423 | if (!defaults.loaded) { 424 | defaults.loaded = true; 425 | setSettings(element, defaults); 426 | } 427 | else { 428 | setSettings(element, defaults); 429 | return; 430 | } 431 | 432 | // Add a change event to select lists only so that we can auto tab when a value is selected 433 | if (element.type == 'select-one') { 434 | $(element).on('change', function (e) { 435 | var defaults = getSettings(this); 436 | 437 | if (defaults.tabOnSelect) { 438 | $(this).trigger('autotab-next'); 439 | } 440 | }); 441 | } 442 | 443 | // The 1ms timeouts allow for keypress events to complete in case a 444 | // custom function or exterior method calls for a manual auto tab 445 | $(element).on('autotab-next', function (event, defaults) { 446 | var self = this; 447 | setTimeout(function () { 448 | if (!defaults) { 449 | defaults = getSettings(self); 450 | } 451 | 452 | var target = defaults.target; 453 | 454 | if (!defaults.disabled && target.length) { 455 | // Using focus on iOS devices is a pain, so use the browser's next/previous buttons to proceed 456 | if (!settings.iOS) { 457 | 458 | // Field is disabled/readonly, so tab to next element 459 | if (target.prop('disabled') || target.prop('readonly')) { 460 | target.trigger('autotab-next'); 461 | } 462 | else { 463 | // Allows the user to navigate between each charater with arrow keys 464 | if (defaults.arrowKey) { 465 | target.focus(); 466 | } 467 | else { 468 | target.focus().select(); 469 | } 470 | } 471 | 472 | settings.focusChange = new Date(); 473 | } 474 | } 475 | }, 1); 476 | }).on('autotab-previous', function (event, defaults) { 477 | var self = this; 478 | setTimeout(function () { 479 | if (!defaults) { 480 | defaults = getSettings(self); 481 | } 482 | 483 | var previous = defaults.previous; 484 | 485 | if (!defaults.disabled && previous.length) { 486 | var value = previous.val(); 487 | 488 | // Field is disabled/readonly, so tab to previous element 489 | if (previous.prop('disabled') || previous.prop('readonly')) { 490 | previous.trigger('autotab-previous'); 491 | } 492 | else if (value.length && previous.data('autotab-editable') && !defaults.arrowKey) { 493 | if (settings.ie11) { 494 | previous.val(value.substring(0, value.length - 1)).focus(); 495 | } 496 | else { 497 | previous.focus().val(value.substring(0, value.length - 1)); 498 | } 499 | 500 | setSettings(previous, { changed: true }); 501 | } 502 | else { 503 | if (defaults.arrowKey) { 504 | setSettings(this, { arrowKey: false }); 505 | } 506 | 507 | if (settings.ie11) { 508 | previous.val(value).focus(); 509 | } 510 | else { 511 | previous.focus().val(value); 512 | } 513 | } 514 | 515 | settings.focusChange = null; 516 | } 517 | }, 1); 518 | }).on('focus', function () { 519 | setSettings(this, { originalValue: this.value }); 520 | }).on('blur', function () { 521 | var defaults = getSettings(this); 522 | 523 | if (defaults.changed && this.value != defaults.originalValue) { 524 | setSettings(this, { changed: false }); 525 | $(this).change(); 526 | } 527 | }).on('keydown.autotab', function (e) { 528 | var defaults = getSettings(this); 529 | 530 | if (!defaults || defaults.disabled) { 531 | return true; 532 | } 533 | 534 | var selection = getSelection(this), 535 | keyCode = e.which || e.charCode; 536 | 537 | // Go to the previous element when backspace 538 | // is pressed in an empty input field 539 | if (keyCode == 8) { 540 | defaults.arrowKey = false; 541 | 542 | // Prevent the browser from of navigating to the previous page 543 | if (!defaults.editable) { 544 | $(this).trigger('autotab-previous', defaults); 545 | return false; 546 | } 547 | 548 | setSettings(this, { changed: (this.value !== defaults.originalValue) }); 549 | 550 | if (this.value.length === 0) { 551 | $(this).trigger('autotab-previous', defaults); 552 | return; 553 | } 554 | } 555 | else if (keyCode == 9 && settings.focusChange !== null) { 556 | // Tab backwards 557 | if (e.shiftKey) { 558 | settings.focusChange = null; 559 | return; 560 | } 561 | 562 | if ((new Date().getTime() - settings.focusChange.getTime()) < settings.tabPause) { 563 | settings.focusChange = null; 564 | return false; 565 | } 566 | } 567 | else if (this.type !== 'range' && this.type !== 'select-one' && this.type !== 'select-multiple') { 568 | if ((this.type !== 'tel' && this.type !== 'number') || ((this.type === 'tel' || this.type === 'number') && this.value.length == 0)) { 569 | if (keyCode == 37 && (!defaults.editable || selection.start == 0)) { 570 | defaults.arrowKey = true; 571 | $(this).trigger('autotab-previous', defaults); 572 | } 573 | else if (keyCode == 39 && (!defaults.editable || !defaults.filterable || selection.end == this.value.length || this.value.length == 0)) { 574 | defaults.arrowKey = true; 575 | $(this).trigger('autotab-next', defaults); 576 | } 577 | } 578 | } 579 | }).on('keypress.autotab', function (e) { 580 | var defaults = getSettings(this), 581 | keyCode = e.which || e.keyCode; 582 | 583 | // e.charCode == 0 indicates a special key has been pressed, which only Firefox triggers 584 | if (!defaults || defaults.disabled || (settings.firefox && e.charCode === 0) || e.ctrlKey || e.altKey || keyCode == 13 || this.disabled) { 585 | return true; 586 | } 587 | 588 | var keyChar = String.fromCharCode(keyCode); 589 | 590 | if (this.type != 'text' && this.type != 'password' && this.type != 'textarea') { 591 | // this.value.length is the length before the keypress event was trigged 592 | if ((this.value.length + 1) >= defaults.maxlength) { 593 | defaults.arrowKey = false; 594 | $(this).trigger('autotab-next', defaults); 595 | } 596 | 597 | return !(this.value.length == defaults.maxlength); 598 | } 599 | 600 | // Prevents auto tabbing when defaults.trigger is pressed 601 | if (defaults.trigger !== null && defaults.trigger.indexOf(keyChar) >= 0) { 602 | if (settings.focusChange !== null && (new Date().getTime() - settings.focusChange.getTime()) < settings.tabPause) { 603 | settings.focusChange = null; 604 | } 605 | else { 606 | defaults.arrowKey = false; 607 | $(this).trigger('autotab-next', defaults); 608 | } 609 | 610 | return false; 611 | } 612 | 613 | settings.focusChange = null; 614 | 615 | var hasValue = document.selection && document.selection.createRange ? true : (keyCode > 0); 616 | 617 | keyChar = filterValue(this, keyChar, defaults); 618 | 619 | if (hasValue && (keyChar === null || keyChar === '')) { 620 | return false; 621 | } 622 | 623 | // Many, many thanks to Tim Down for this solution: http://stackoverflow.com/a/3923320/94656 624 | if (hasValue && (this.value.length <= this.maxLength)) { 625 | var selection = getSelection(this); 626 | 627 | // Text is fully selected, so it needs to be replaced 628 | if (selection.start === 0 && selection.end == this.value.length) { 629 | this.value = keyChar; 630 | setSettings(this, { changed: (this.value != defaults.originalValue) }); 631 | } 632 | else { 633 | if (this.value.length == this.maxLength && selection.start === selection.end) { 634 | defaults.arrowKey = false; 635 | $(this).trigger('autotab-next', defaults); 636 | return false; 637 | } 638 | 639 | this.value = this.value.slice(0, selection.start) + keyChar + this.value.slice(selection.end); 640 | setSettings(this, { changed: (this.value != defaults.originalValue) }); 641 | } 642 | 643 | // Prevents the cursor position from being set to the end of the text box 644 | // This is called even if the text is fully selected and replaced due to an unexpected behavior in IE6 and up (#32) 645 | if (this.value.length != defaults.maxlength) { 646 | selection.start++; 647 | 648 | if (selection.selectionType == 1) { 649 | this.selectionStart = this.selectionEnd = selection.start; 650 | } 651 | else if (selection.selectionType == 2) { 652 | var range = this.createTextRange(); 653 | range.collapse(true); 654 | range.moveEnd('character', selection.start); 655 | range.moveStart('character', selection.start); 656 | range.select(); 657 | } 658 | } 659 | } 660 | 661 | 662 | if (this.value.length == defaults.maxlength) { 663 | defaults.arrowKey = false; 664 | $(this).trigger('autotab-next', defaults); 665 | } 666 | 667 | return false; 668 | }).on('drop paste', function (e) { 669 | var defaults = getSettings(this); 670 | 671 | if (!defaults) { 672 | return true; 673 | } 674 | 675 | this.maxLength = 2147483647; 676 | 677 | (function (e, originDefaults) { 678 | setTimeout(function () { 679 | var lastIndex = -1, 680 | hiddenInput = document.createElement('input'); 681 | hiddenInput.type = 'hidden'; 682 | hiddenInput.value = e.value.toLowerCase(); 683 | hiddenInput.originalValue = e.value; 684 | 685 | e.value = filterValue(e, e.value, originDefaults).substr(0, originDefaults.maxlength); 686 | 687 | var handlePaste = function (e, previousValue) { 688 | if (!e) { 689 | return; 690 | } 691 | 692 | var defaults = getSettings(e); 693 | 694 | if ($(e).prop('disabled') || $(e).prop('readonly') || !defaults.editable) { 695 | $(e).trigger('autotab-next'); 696 | 697 | if (!settings.iOS) { 698 | handlePaste(defaults.target[0], previousValue); 699 | } 700 | return; 701 | } 702 | 703 | for (var i = 0, count = previousValue.length; i < count; i++) { 704 | lastIndex = hiddenInput.value.indexOf(previousValue.charAt(i).toLowerCase(), lastIndex) + 1; 705 | } 706 | 707 | var trimmedValue = hiddenInput.originalValue.substr(lastIndex), 708 | filteredValue = filterValue(e, trimmedValue, defaults).substr(0, defaults.maxlength); 709 | 710 | if (!filteredValue) { 711 | return; 712 | } 713 | 714 | e.value = filteredValue; 715 | 716 | if (filteredValue.length == defaults.maxlength) { 717 | defaults.arrowKey = false; 718 | $(e).trigger('autotab-next', defaults); 719 | 720 | // Firefox causes all but the first and last elements to retain a select all state, so in order to 721 | // effectively support arrow keys, the starting point of the selection is to the last possible cursor 722 | if (settings.firefox) { 723 | setTimeout(function () { 724 | e.selectionStart = e.value.length; 725 | }, 1); 726 | } 727 | 728 | if (!settings.iOS) { 729 | handlePaste(defaults.target[0], filteredValue); 730 | } 731 | } 732 | 733 | }; 734 | 735 | if (e.value.length == originDefaults.maxlength) { 736 | defaults.arrowKey = false; 737 | $(e).trigger('autotab-next', defaults); 738 | 739 | if (!settings.iOS) { 740 | handlePaste(originDefaults.target[0], e.value.toLowerCase()); 741 | } 742 | } 743 | 744 | e.maxLength = originDefaults.maxlength; 745 | }, 1); 746 | })(this, defaults); 747 | }); 748 | }; 749 | 750 | // Deprecated, here for backwards compatibility 751 | $.fn.autotab_magic = function (focus) { 752 | return $(this).autotab(); 753 | }; 754 | $.fn.autotab_filter = function (options) { 755 | var defaults = {}; 756 | 757 | if (typeof options === 'string' || typeof options === 'function') { 758 | defaults.format = options; 759 | } 760 | else { 761 | $.extend(defaults, options); 762 | } 763 | 764 | return $(this).autotab('filter', defaults); 765 | }; 766 | 767 | })(jQuery); -------------------------------------------------------------------------------- /js/jquery.autotab.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Autotab - jQuery plugin 1.9.2 3 | * https://github.com/Mathachew/jquery-autotab 4 | * 5 | * Copyright (c) 2008, 2015 Matthew Miller 6 | * 7 | * Licensed under the MIT licensing: 8 | * http://www.opensource.org/licenses/mit-license.php 9 | */ 10 | 11 | (function(d){var u=navigator.platform,g=null,r="iPad"===u||"iPhone"===u||"iPod"===u,w="undefined"!==typeof InstallTrigger,x=!window.ActiveXObject&&"ActiveXObject"in window,h=function(a,f){if(null!==f&&"undefined"!==typeof f)for(var b in f)d(a).data("autotab-"+b,f[b])},l=function(a){var f={arrowKey:!1,format:"all",loaded:!1,disabled:!1,pattern:null,uppercase:!1,lowercase:!1,nospace:!1,maxlength:2147483647,target:null,previous:null,trigger:null,originalValue:"",changed:!1,editable:"text"===a.type|| 12 | "password"===a.type||"textarea"===a.type||"tel"===a.type||"number"===a.type||"email"===a.type||"search"===a.type||"url"===a.type,filterable:"text"===a.type||"password"===a.type||"textarea"===a.type,tabOnSelect:!1};if(!0===d.autotab.selectFilterByClass&&"undefined"===typeof d(a).data("autotab-format")){var b="all text alpha number numeric alphanumeric hex hexadecimal custom".split(" "),e;for(e in b)if(d(a).hasClass(b[e])){f.format=b[e];break}}for(e in f)"undefined"!==typeof d(a).data("autotab-"+e)&& 13 | (f[e]=d(a).data("autotab-"+e));f.loaded||(null!==f.trigger&&"string"===typeof f.trigger&&(f.trigger=f.trigger.toString()),h(a,f));return f},p=function(a){return"undefined"!==typeof a&&("string"===typeof a||!(a instanceof jQuery))},y=function(a){var d=0,b=0,e=0;if("text"===a.type||"password"===a.type||"textarea"===a.type)"number"===typeof a.selectionStart&&"number"===typeof a.selectionEnd?(d=a.selectionStart,b=a.selectionEnd,e=1):document.selection&&document.selection.createRange&&(b=document.selection.createRange(), 14 | d=a.createTextRange(),a=a.createTextRange(),e=b.getBookmark(),d.moveToBookmark(e),a.setEndPoint("EndToStart",d),d=a.text.length,b=d+b.text.length,e=2);return{start:d,end:b,selectionType:e}};d.autotab=function(a){"object"!==typeof a&&(a={});d(":input").autotab(a)};d.autotab.selectFilterByClass=!1;d.autotab.next=function(){var a=d(document.activeElement);a.length&&a.trigger("autotab-next")};d.autotab.previous=function(){var a=d(document.activeElement);a.length&&a.trigger("autotab-previous")};d.autotab.remove= 15 | function(a){p(a)?d(a).autotab("remove"):d(":input").autotab("remove")};d.autotab.restore=function(a){p(a)?d(a).autotab("restore"):d(":input").autotab("restore")};d.autotab.refresh=function(a){p(a)?d(a).autotab("refresh"):d(":input").autotab("refresh")};d.fn.autotab=function(a,f){if(!this.length)return this;var b=d.grep(this,function(a,c){return"hidden"!=a.type});if("filter"==a){if("string"===typeof f||"function"===typeof f)f={format:f};for(var e=0,m=b.length;e(new Date).getTime()-g.getTime())return g=null,!1}else"range"!==this.type&&"select-one"!==this.type&&"select-multiple"!==this.type&&("tel"!==this.type&&"number"!==this.type||("tel"===this.type||"number"===this.type)&&0==this.value.length)&&(37!=e||c.editable&&0!=b.start?39!=e||c.editable&&c.filterable&&b.end!=this.value.length&&0!=this.value.length||(c.arrowKey=!0,d(this).trigger("autotab-next",c)):(c.arrowKey= 24 | !0,d(this).trigger("autotab-previous",c)))}).on("keypress.autotab",function(a){var c=l(this),b=a.which||a.keyCode;if(!c||c.disabled||w&&0===a.charCode||a.ctrlKey||a.altKey||13==b||this.disabled)return!0;a=String.fromCharCode(b);if("text"!=this.type&&"password"!=this.type&&"textarea"!=this.type)return this.value.length+1>=c.maxlength&&(c.arrowKey=!1,d(this).trigger("autotab-next",c)),this.value.length!=c.maxlength;if(null!==c.trigger&&0<=c.trigger.indexOf(a))return null!==g&&800>(new Date).getTime()- 25 | g.getTime()?g=null:(c.arrowKey=!1,d(this).trigger("autotab-next",c)),!1;g=null;b=document.selection&&document.selection.createRange?!0:0 2 | 3 | 4 | 5 | jQuery Autotab Demo with Knockout 6 | 7 | 8 | 9 | 10 | 11 | 95 | 96 | 97 | 98 |
99 |

jQuery Autotab Demo with Knockout

100 |

Autotab's full documentation can be found in ReadMe.md on GitHub.

101 | 102 |

The purpose of this demo is to provide a proof of concept for dynamically adding or removing fields that are handled through Autotab. Click here to see this using Angular.

103 | 104 |

Note: There is the possibility of having an unexpected tabbing order if you specify the target/previous elements with Knockout, so it is recommended that you use the default :input selector instead.

105 | 106 |
107 | 108 | 109 |
110 | 111 |
112 | 113 | 114 |
115 |
116 | 117 | - 118 | 119 | - 120 | 121 | 122 | 123 | X 124 | 125 |
126 |
127 |
128 |
129 | 130 |
131 | 132 | 133 |
134 |
135 | 136 | - 137 | 138 | - 139 | 140 | - 141 | 142 | - 143 | 144 | 145 | 146 | X 147 | 148 |
149 |
150 |
151 |
152 |
153 | 154 | 168 | 169 | 170 | --------------------------------------------------------------------------------