Compare commits

..

1 commit

Author SHA1 Message Date
0eada46ece Switch to a diffrent keyboard and timepicker
these should hopefully work better on mobile browsers
2015-10-18 14:03:49 -07:00
32 changed files with 7379 additions and 2418 deletions

View file

@ -183,19 +183,7 @@ You will have 50 love gems on 01/16/2016. Good things come to those who wait!
## The Web App
You asked for a web app, and here it is! The [web_app](web_app) directory contains a fully featured web app version of SIF Tools. In fact, the web app actually does some things that the original Python scripts don't! (yet.) The best part is, you don't need a web server to run it. You should just be able to open the [sif_tools.html](web_app/sif_tools.html) file in your local web browser and run it right on your own computer. You will need to have JavaScript enabled in your browser however. (Of course you can host these files on a web server just like any other website, if you happen to have access to a web server.) Or you can run the copy hosted on my own web server [here](https://beta.DonaldBurr.com/sif_tools/sif_tools.html).
## Credits
The web app uses the following third-party Javascript libraries:
* [jQuery](https://jquery.com) ([License](https://jquery.org/license/))
* [jQuery UI](https://jqueryui.com) ([License](https://github.com/jquery/jquery-ui/blob/master/LICENSE.txt))
* [jQuery Keypad](http://keith-wood.name/keypad.html) ([License](http://keith-wood.name/licence.html))
* [Moment.js](http://momentjs.com) ([License](https://github.com/moment/moment/blob/develop/LICENSE))
* [jquery.timepicker](http://jonthornton.github.io/jquery-timepicker/) ([License](https://opensource.org/licenses/MIT))
* [node-sprintf](https://github.com/maritz/node-sprintf) ([License](https://github.com/maritz/node-sprintf#copyrightlicense))
* [twitterFetcher](https://github.com/jasonmayes/Twitter-Post-Fetcher) ([License](https://github.com/jasonmayes/Twitter-Post-Fetcher/blob/master/License.txt))
You asked for a web app, and here it is! The [web_app](web_app) directory contains a (mostly) fully featured web app version of SIF Tools. The best part is, you don't need a web server to run it. You should just be able to open the [sif_tools.html](web_app/sif_tools.html) file in your local web browser and run it right on your own computer. You will need to have JavaScript enabled in your browser however. (Of course you can host these files on a web server just like any other website, if you happen to have access to a web server.)
## Bugs? Need help? Got any suggestions/ideas for new features? Or want to chat?

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="#111">
<path d="M7.4 4.4V2.5c0-1.4-1.1-2.5-2.5-2.5c-1.4 0-2.5 1.1-2.5 2.5v1.9 c-0.7 0-1.2 0.5-1.2 1.2v3.1C1.3 9.4 1.8 10 2.5 10h4.9c0.7 0 1.2-0.6 1.2-1.2V5.6 C8.7 4.9 8.1 4.4 7.4 4.4z M5.3 7.4v1.0c0 0.2-0.1 0.3-0.3 0.3c-0.2 0-0.3-0.1-0.3-0.3V7.4 c-0.2-0.1-0.3-0.3-0.3-0.5c0-0.3 0.3-0.6 0.6-0.6c0.3 0 0.6 0.3 0.6 0.6 C5.6 7.1 5.5 7.3 5.3 7.4z M6.2 4.4H3.7V2.5c0-0.7 0.5-1.2 1.2-1.2c0.7 0 1.2 0.6 1.2 1.2 V4.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 489 B

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="#111">
<path d="M8.7,4.4H7.5H5.0v-1.9c0-1.4-1.1-2.5-2.5-2.5c-1.4,0-2.5,1.1-2.5,2.5v1.9h1.2 v-1.9c0-0.7,0.6-1.2,1.2-1.2s1.2,0.6,1.2,1.2v1.9c-0.7,0-1.2,0.6-1.2,1.2V8.8 c0,0.7,0.6,1.2,1.2,1.2h5.0C9.4,10,10,9.4,10,8.8V5.6C10,5.0,9.4,4.4,8.8,4.4z M6.6,7.4v1.0 c0,0.2-0.1,0.3-0.3,0.3S6.0,8.6,6.0,8.4V7.4c-0.2-0.1-0.3-0.3-0.3-0.5c0-0.3,0.3-0.6,0.6-0.6 S6.9,6.6,6.9,6.9C6.9,7.1,6.8,7.3,6.6,7.4z"/>
</svg>

After

Width:  |  Height:  |  Size: 468 B

View file

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" style="fill:#888">
<g>
<path style="fill:none;stroke:#888" d="M 0.5,4.5 15.5,4.5 15.5,15.5 0.5,15.5 Z"></path>
<rect width="2" height="2" x="2" y="6"></rect>
<rect width="2" height="2" x="5" y="6"></rect>
<rect width="2" height="2" x="8" y="6"></rect>
<path d="m 11,6 3,0 0,5 -2,0 0,-3 -1,0 z"></path>
<rect width="2" height="2" x="12" y="12"></rect>
<rect width="6" height="2" x="5" y="12"></rect>
<rect width="2" height="2" x="9" y="9"></rect>
<rect width="2" height="2" x="6" y="9"></rect>
<rect width="2" height="2" x="2" y="12"></rect>
<rect width="3" height="2" x="2" y="9"></rect>
</g>
</svg>

After

Width:  |  Height:  |  Size: 686 B

View file

@ -0,0 +1,27 @@
.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; }
.ui-timepicker-div dl { text-align: left; }
.ui-timepicker-div dl dt { float: left; clear:left; padding: 0 0 0 5px; }
.ui-timepicker-div dl dd { margin: 0 10px 10px 40%; }
.ui-timepicker-div td { font-size: 90%; }
.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; }
.ui-timepicker-div .ui_tpicker_unit_hide{ display: none; }
.ui-timepicker-rtl{ direction: rtl; }
.ui-timepicker-rtl dl { text-align: right; padding: 0 5px 0 0; }
.ui-timepicker-rtl dl dt{ float: right; clear: right; }
.ui-timepicker-rtl dl dd { margin: 0 40% 10px 10px; }
/* Shortened version style */
.ui-timepicker-div.ui-timepicker-oneLine { padding-right: 2px; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time,
.ui-timepicker-div.ui-timepicker-oneLine dt { display: none; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_time_label { display: block; padding-top: 2px; }
.ui-timepicker-div.ui-timepicker-oneLine dl { text-align: right; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd,
.ui-timepicker-div.ui-timepicker-oneLine dl dd > div { display:inline-block; margin:0; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_minute:before,
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_second:before { content:':'; display:inline-block; }
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_millisec:before,
.ui-timepicker-div.ui-timepicker-oneLine dl dd.ui_tpicker_microsec:before { content:'.'; display:inline-block; }
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide,
.ui-timepicker-div.ui-timepicker-oneLine .ui_tpicker_unit_hide:before{ display: none; }

View file

@ -1,106 +0,0 @@
/* Main style sheet for jQuery Keypad v2.0.0 */
button.keypad-trigger {
width: 25px;
padding: 0px;
}
img.keypad-trigger {
margin: 2px;
vertical-align: middle;
}
.keypad-popup, .keypad-inline, .keypad-key, .keypad-special {
font-family: Arial,Helvetica,sans-serif;
font-size: 24px;
}
.keypad-popup {
display: none;
z-index: 10;
margin: 0;
padding: 0;
background-color: #fff;
color: #000;
border: 1px solid #888;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
border-radius: 0.25em;
}
.keypad-keyentry {
display: none;
}
.keypad-inline {
background-color: #fff;
border: 1px solid #888;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
border-radius: 0.25em;
}
.keypad-disabled {
position: absolute;
z-index: 100;
background-color: white;
opacity: 0.5;
filter: alpha(opacity=50);
}
.keypad-rtl {
direction: rtl;
}
.keypad-prompt {
clear: both;
text-align: center;
}
.keypad-prompt.ui-widget-header {
margin: 0.125em;
}
.keypad-row {
width: 100%;
}
.keypad-space {
display: inline-block;
margin: 0.125em;
width: 2em;
}
.keypad-half-space {
display: inline-block;
margin: 0.125em 0.0625em;
width: 1em;
}
.keypad-key, .keypad-special {
margin: 0.125em;
padding: 0em;
width: 2em;
background-color: #f4f4f4;
-moz-border-radius: 0.25em;
-webkit-border-radius: 0.25em;
border-radius: 0.25em;
text-align: center;
cursor: pointer;
}
.keypad-key[disabled] {
border: 0.125em outset;
}
.keypad-key-down {
}
.keypad-special {
width: 4.25em;
}
.keypad-spacebar {
width: 13.25em;
}
.keypad-tab {
width: 2em;
}
.keypad-clear, .keypad-back, .keypad-close, .keypad-shift {
color: #fff;
font-weight: bold;
}
.keypad-clear {
background-color: #a00;
}
.keypad-back {
background-color: #00a;
}
.keypad-close {
background-color: #0a0;
}
.keypad-shift {
background-color: #0aa;
}

0
web_app/css/external/jquery.timepicker.css vendored Normal file → Executable file
View file

170
web_app/css/external/keyboard-basic.css vendored Normal file
View file

@ -0,0 +1,170 @@
/* *** keyboard light theme ***
for when jQuery UI themes are not being used
See http://jsfiddle.net/Mottie/jsh0377k/
*/
.ui-keyboard {
/* adjust overall keyboard size using "font-size" */
font-size: 14px;
text-align: center;
/* include the following setting to place the
keyboard at the bottom of the browser window */
width: 100%;
height: auto;
left: 0px;
top: auto;
bottom: 0px;
position: fixed;
}
.ui-keyboard {
background: #fefefe;
border: 1px solid #aaa;
padding: 4px;
}
.ui-keyboard-button {
border: 1px solid #aaa;
padding: 0 0.5em;
margin: 1px;
min-width: 3em;
height: 3em;
line-height: 3em;
vertical-align: top;
font-family: Helvetica, Arial, sans-serif;
color: #333;
text-align: center;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.5);
-moz-box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.5);
box-shadow: 1px 1px 3px 0 rgba(0, 0, 0, 0.5);
background: white;
background-image: -moz-linear-gradient(-90deg, white 0%, #e3e3e3 100%);
background-image: -webkit-linear-gradient(-90deg, white 0%, #e3e3e3 100%);
background-image: -o-linear-gradient(-90deg, white 0%, #e3e3e3 100%);
background-image: -ms-linear-gradient(-90deg, white 0%, #e3e3e3 100%);
cursor: pointer;
overflow: hidden;
-moz-user-focus: ignore;
}
.ui-keyboard-button span {
/* padding: 0; margin: 0; white-space:nowrap; display: inline-block; */
display: block;
width: 100%;
font-size: 1.2em;
text-align: center;
}
/* make action keys extra-wide */
.ui-keyboard-actionkey:not(.ui-keyboard-dec) {
min-width: 6em;
}
.ui-keyboard-space {
width: 15em;
}
.ui-keyboard-actionkey:not(.ui-keyboard-dec) span {
font-size: 0.8em;
position: relative;
top: -1em;
left: -1.6em;
}
.ui-keyboard-placeholder {
color: #888;
}
/* disabled or readonly inputs, or use input[disabled='disabled'] { color: #f00; } */
.ui-keyboard-nokeyboard {
color: #888;
border-color: #888;
}
/* combo key styling - toggles diacritics on/off */
.ui-keyboard-button.ui-keyboard-combo.ui-state-default {
border-color: #375a7f;
}
/* (in)valid inputs */
button.ui-keyboard-accept.ui-keyboard-valid-input {
background: #008966;
border-color: #007f5e;
color: #fff;
}
button.ui-keyboard-accept.ui-keyboard-valid-input:hover {
background: #00bc8c;
border-color: #00bc8c;
}
button.ui-keyboard-accept.ui-keyboard-invalid-input {
background: #d62c1a;
border-color: #cd2a19;;
color: #fff;
}
button.ui-keyboard-accept.ui-keyboard-invalid-input:hover {
background: #e74c3c;
border-color: #e74c3c;
}
/* unlocked icon (keyboard enabled) */
button.ui-keyboard-toggle span {
width: .9em;
height: .9em;
display: inline-block;
margin-bottom: 3px;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
/* light theme unlocked icon - fill: #111 */
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgZmlsbD0iIzExMSI+PHBhdGggZD0iTTguNyw0LjRINy41SDUuMHYtMS45YzAtMS40LTEuMS0yLjUtMi41LTIuNWMtMS40LDAtMi41LDEuMS0yLjUsMi41djEuOWgxLjIgdi0xLjljMC0wLjcsMC42LTEuMiwxLjItMS4yczEuMiwwLjYsMS4yLDEuMnYxLjljLTAuNywwLTEuMiwwLjYtMS4yLDEuMlY4LjggYzAsMC43LDAuNiwxLjIsMS4yLDEuMmg1LjBDOS40LDEwLDEwLDkuNCwxMCw4LjhWNS42QzEwLDUuMCw5LjQsNC40LDguOCw0LjR6IE02LjYsNy40djEuMCBjMCwwLjItMC4xLDAuMy0wLjMsMC4zUzYuMCw4LjYsNi4wLDguNFY3LjRjLTAuMi0wLjEtMC4zLTAuMy0wLjMtMC41YzAtMC4zLDAuMy0wLjYsMC42LTAuNiBTNi45LDYuNiw2LjksNi45QzYuOSw3LjEsNi44LDcuMyw2LjYsNy40eiIvPjwvc3ZnPg==');
}
.ui-keyboard-dark-theme button.ui-keyboard-toggle span {
/* dark theme unlocked icon - fill: #eee */
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgZmlsbD0iI2VlZSI+PHBhdGggZD0iTTguNyw0LjRINy41SDUuMHYtMS45YzAtMS40LTEuMS0yLjUtMi41LTIuNWMtMS40LDAtMi41LDEuMS0yLjUsMi41djEuOWgxLjIgdi0xLjljMC0wLjcsMC42LTEuMiwxLjItMS4yczEuMiwwLjYsMS4yLDEuMnYxLjljLTAuNywwLTEuMiwwLjYtMS4yLDEuMlY4LjggYzAsMC43LDAuNiwxLjIsMS4yLDEuMmg1LjBDOS40LDEwLDEwLDkuNCwxMCw4LjhWNS42QzEwLDUuMCw5LjQsNC40LDguOCw0LjR6IE02LjYsNy40djEuMCBjMCwwLjItMC4xLDAuMy0wLjMsMC4zUzYuMCw4LjYsNi4wLDguNFY3LjRjLTAuMi0wLjEtMC4zLTAuMy0wLjMtMC41YzAtMC4zLDAuMy0wLjYsMC42LTAuNiBTNi45LDYuNiw2LjksNi45QzYuOSw3LjEsNi44LDcuMyw2LjYsNy40eiIvPjwvc3ZnPg==');
}
/* locked icon (keyboard disabled) */
button.ui-keyboard-toggle.ui-keyboard-disabled span {
/* light theme locked icon - fill: #111 */
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgZmlsbD0iIzExMSI+PHBhdGggZD0iTTcuNCA0LjRWMi41YzAtMS40LTEuMS0yLjUtMi41LTIuNWMtMS40IDAtMi41IDEuMS0yLjUgMi41djEuOSBjLTAuNyAwLTEuMiAwLjUtMS4yIDEuMnYzLjFDMS4zIDkuNCAxLjggMTAgMi41IDEwaDQuOWMwLjcgMCAxLjItMC42IDEuMi0xLjJWNS42IEM4LjcgNC45IDguMSA0LjQgNy40IDQuNHogTTUuMyA3LjR2MS4wYzAgMC4yLTAuMSAwLjMtMC4zIDAuM2MtMC4yIDAtMC4zLTAuMS0wLjMtMC4zVjcuNCBjLTAuMi0wLjEtMC4zLTAuMy0wLjMtMC41YzAtMC4zIDAuMy0wLjYgMC42LTAuNmMwLjMgMCAwLjYgMC4zIDAuNiAwLjYgQzUuNiA3LjEgNS41IDcuMyA1LjMgNy40eiBNNi4yIDQuNEgzLjdWMi41YzAtMC43IDAuNS0xLjIgMS4yLTEuMmMwLjcgMCAxLjIgMC42IDEuMiAxLjIgVjQuNHoiLz48L3N2Zz4=');
}
.ui-keyboard-dark-theme button.ui-keyboard-toggle.ui-keyboard-disabled span {
/* dark theme locked icon - fill: #eee */
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgZmlsbD0iI2VlZSI+PHBhdGggZD0iTTcuNCA0LjRWMi41YzAtMS40LTEuMS0yLjUtMi41LTIuNWMtMS40IDAtMi41IDEuMS0yLjUgMi41djEuOSBjLTAuNyAwLTEuMiAwLjUtMS4yIDEuMnYzLjFDMS4zIDkuNCAxLjggMTAgMi41IDEwaDQuOWMwLjcgMCAxLjItMC42IDEuMi0xLjJWNS42IEM4LjcgNC45IDguMSA0LjQgNy40IDQuNHogTTUuMyA3LjR2MS4wYzAgMC4yLTAuMSAwLjMtMC4zIDAuM2MtMC4yIDAtMC4zLTAuMS0wLjMtMC4zVjcuNCBjLTAuMi0wLjEtMC4zLTAuMy0wLjMtMC41YzAtMC4zIDAuMy0wLjYgMC42LTAuNmMwLjMgMCAwLjYgMC4zIDAuNiAwLjYgQzUuNiA3LjEgNS41IDcuMyA1LjMgNy40eiBNNi4yIDQuNEgzLjdWMi41YzAtMC43IDAuNS0xLjIgMS4yLTEuMmMwLjcgMCAxLjIgMC42IDEuMiAxLjIgVjQuNHoiLz48L3N2Zz4=');
}
.ui-keyboard.ui-keyboard-disabled button:not(.ui-keyboard-toggle), .ui-keyboard.ui-keyboard-disabled input {
opacity: 0.5;
}
/*** Alt-Keys Popup extension ***/
/* clickable overlay on top of keyboard to hide the popup */
.ui-keyboard-overlay {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(0, 0, 0, 0.5);
}
/* the actual popup styling, class names from the css.container option are also added */
.ui-keyboard-popup {
display: inline-block;
/* default buttons are 2em wide + .1em margin on either side (set in .ui-keyboard-button definition);
so use multiples of 2.2em for a max-width if you don't want any extra white space on the sides,
e.g. 5 buttons * 2.2em = 11em, 6 buttons * 2.2em = 13.2em, etc */
max-width: 22em;
/* 10 buttons */
}
/*** Extender keyboard extension ***/
div.ui-keyboard-extender {
float: right;
margin-left: 5px;
margin-right: 10px;
}
button.ui-keyboard-extender span {
width: .9em;
height: .9em;
display: inline-block;
margin-bottom: 3px;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
/* light theme extender icon - fill: #111 */
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgc3R5bGU9ImZpbGw6IzExMSI+PGc+PHBhdGggc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzExMSIgZD0iTSAwLjUsNC41IDE1LjUsNC41IDE1LjUsMTUuNSAwLjUsMTUuNSBaIj48L3BhdGg+PHJlY3Qgd2lkdGg9IjIiIGhlaWdodD0iMiIgeD0iMiIgeT0iNiI+PC9yZWN0PjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjUiIHk9IjYiPjwvcmVjdD48cmVjdCB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB4PSI4IiB5PSI2Ij48L3JlY3Q+PHBhdGggZD0ibSAxMSw2IDMsMCAwLDUgLTIsMCAwLC0zIC0xLDAgeiI+PC9wYXRoPjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjEyIiB5PSIxMiI+PC9yZWN0PjxyZWN0IHdpZHRoPSI2IiBoZWlnaHQ9IjIiIHg9IjUiIHk9IjEyIj48L3JlY3Q+PHJlY3Qgd2lkdGg9IjIiIGhlaWdodD0iMiIgeD0iOSIgeT0iOSI+PC9yZWN0PjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjYiIHk9IjkiPjwvcmVjdD48cmVjdCB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB4PSIyIiB5PSIxMiI+PC9yZWN0PjxyZWN0IHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHg9IjIiIHk9IjkiPjwvcmVjdD48L2c+PC9zdmc+');
}
.ui-keyboard-dark-theme button.ui-keyboard-extender span {
/* dark theme extender icon - fill: #eee */
background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgc3R5bGU9ImZpbGw6I2VlZSI+PGc+PHBhdGggc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6I2VlZSIgZD0iTSAwLjUsNC41IDE1LjUsNC41IDE1LjUsMTUuNSAwLjUsMTUuNSBaIj48L3BhdGg+PHJlY3Qgd2lkdGg9IjIiIGhlaWdodD0iMiIgeD0iMiIgeT0iNiI+PC9yZWN0PjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjUiIHk9IjYiPjwvcmVjdD48cmVjdCB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB4PSI4IiB5PSI2Ij48L3JlY3Q+PHBhdGggZD0ibSAxMSw2IDMsMCAwLDUgLTIsMCAwLC0zIC0xLDAgeiI+PC9wYXRoPjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjEyIiB5PSIxMiI+PC9yZWN0PjxyZWN0IHdpZHRoPSI2IiBoZWlnaHQ9IjIiIHg9IjUiIHk9IjEyIj48L3JlY3Q+PHJlY3Qgd2lkdGg9IjIiIGhlaWdodD0iMiIgeD0iOSIgeT0iOSI+PC9yZWN0PjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjYiIHk9IjkiPjwvcmVjdD48cmVjdCB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB4PSIyIiB5PSIxMiI+PC9yZWN0PjxyZWN0IHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHg9IjIiIHk9IjkiPjwvcmVjdD48L2c+PC9zdmc+');
}

View file

@ -0,0 +1,44 @@
/* basic previewKeyset setup - modify as desired */
.ui-keyboard-keyset .ui-keyboard-button {
position: relative;
}
/* show mini-shift keyset with normal keyset */
.ui-keyboard-keyset-normal .ui-keyboard-button::after {
content: attr(data-shift);
font-size: 0.6em;
color: #999;
position: absolute;
top: -1em;
left: 2px;
z-index: 200;
}
/* show mini-normal keyset with shift keyset */
.ui-keyboard-keyset-shift .ui-keyboard-button::after {
content: attr(data-normal);
font-size: 0.6em;
color: #999;
position: absolute;
top: -1em;
left: 2px;
z-index: 200;
}
/* show mini-normal keyset with alt keyset */
.ui-keyboard-keyset-alt .ui-keyboard-button::after {
content: attr(data-alt-shift);
font-size: 0.6em;
color: #999;
position: absolute;
top: -1em;
left: 2px;
z-index: 200;
}
/* show mini-alt-shift keyset with alt-shift keyset */
.ui-keyboard-keyset-alt-shift .ui-keyboard-button::after {
content: attr(data-alt);
font-size: 0.6em;
color: #999;
position: absolute;
top: -1em;
left: 2px;
z-index: 200;
}

161
web_app/css/external/keyboard.css vendored Normal file
View file

@ -0,0 +1,161 @@
/* keyboard - jQuery UI Widget */
.ui-keyboard { padding: .3em; position: absolute; left: 0; top: 0; z-index: 16000; }
.ui-keyboard-has-focus { z-index: 16001; }
.ui-keyboard div { font-size: 1.1em; }
.ui-keyboard-button { height: 2em; min-width: 2em; margin: .1em; cursor: pointer; overflow: hidden; line-height: 2em; -moz-user-focus: ignore; }
.ui-keyboard-button span { padding: 0; margin: 0; white-space:nowrap; display: inline-block; }
.ui-keyboard-button-endrow { clear: left; }
.ui-keyboard-space { width: 15em; }
/* see http://nicolasgallagher.com/another-css-image-replacement-technique/ */
.ui-keyboard-space span, .ui-keyboard-empty span { font: 0/0 a; text-shadow: none; color: transparent; }
.ui-keyboard-preview-wrapper { text-align: center; position: relative; overflow: hidden; }
/* width is calculated in IE, since 99% = 99% full browser width =( */
.ui-keyboard-preview { text-align: left; margin: 0 0 3px 0; display: inline; width: 99%;}
.ui-keyboard-keyset { text-align: center; white-space: nowrap; }
.ui-keyboard-input { text-align: left; }
.ui-keyboard-input-current { -moz-box-shadow: 0 0 5px #4d90fe; -webkit-box-shadow: 0 0 5px #4d90fe; box-shadow: 0 0 5px #4d90fe; }
.ui-keyboard-placeholder { color: #888; }
/* disabled or readonly inputs, or use input[disabled='disabled'] { color: #f00; } */
.ui-keyboard-nokeyboard { color: #888; border-color: #888; }
.ui-keyboard-spacer { display: inline-block; width: 1px; height: 0; cursor: default; }
.ui-keyboard-NBSP span, .ui-keyboard-ZWSP span, .ui-keyboard-ZWNJ span, .ui-keyboard-ZWJ span, .ui-keyboard-LRM span, .ui-keyboard-RLM span {
font-size: 0.5em;
line-height: 1.5em;
white-space: normal;
}
/* combo key styling - toggles diacritics on/off */
.ui-keyboard-button.ui-keyboard-combo.ui-state-default { border-color: #ffaf0f; }
/* (in)valid inputs */
button.ui-keyboard-accept.ui-keyboard-valid-input { border-color: #0c0; background: #080; color: #fff; }
button.ui-keyboard-accept.ui-keyboard-valid-input:hover { background: #0a0; }
button.ui-keyboard-accept.ui-keyboard-invalid-input { border-color: #c00; background: #800; color: #fff; opacity: 0.5; filter: alpha(opacity=50); }
button.ui-keyboard-accept.ui-keyboard-invalid-input:hover { background: #a00; }
/*** Caret extension definition ***/
/* margin-top => is added to the caret height (top & bottom) */
.ui-keyboard-caret { background: #c00; width: 1px; margin-top: 3px; }
/*** jQuery Mobile definitions ***/
/* jQuery Mobile styles - need wider buttons because of font size and text-overflow:ellipsis */
div.ui-body.ui-keyboard button.ui-keyboard-button.ui-btn { padding: 0.5em 1em; border-color: transparent; }
.ui-body .ui-keyboard-button { width: 3em; height: 3em; display: inline-block; }
.ui-body .ui-keyboard-widekey { width: 5.5em; }
.ui-body .ui-keyboard-space { width: 15em; }
.ui-body .ui-keyboard-space span { visibility: hidden; } /* hides the ellipsis */
.ui-body .ui-keyboard-keyset { line-height: 0.5em; }
.ui-body input.ui-input-text, .ui-body textarea.ui-input-text { width: 95%; }
/* over-ride padding set by mobile ui theme - needed because the mobile script wraps button text with several more spans */
.ui-body .ui-btn-inner { height: 2em; padding: 0.2em 0; margin: 0; }
.ui-body .ui-btn { margin: 0; font-size: 13px; } /* mobile default size is 13px */
/* override Bootstrap excessive button padding */
button.ui-keyboard-button.btn { padding: 1px 6px; }
/* enable/disable icons */
button.ui-keyboard-toggle span {
width: .8em;
height: .8em;
display: inline-block;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
}
/* unlocked icon (keyboard enabled) */
button.ui-keyboard-toggle span {
/* light theme unlocked icon - fill: #111 */
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgZmlsbD0iIzExMSI+PHBhdGggZD0iTTguNyw0LjRINy41SDUuMHYtMS45YzAtMS40LTEuMS0yLjUtMi41LTIuNWMtMS40LDAtMi41LDEuMS0yLjUsMi41djEuOWgxLjIgdi0xLjljMC0wLjcsMC42LTEuMiwxLjItMS4yczEuMiwwLjYsMS4yLDEuMnYxLjljLTAuNywwLTEuMiwwLjYtMS4yLDEuMlY4LjggYzAsMC43LDAuNiwxLjIsMS4yLDEuMmg1LjBDOS40LDEwLDEwLDkuNCwxMCw4LjhWNS42QzEwLDUuMCw5LjQsNC40LDguOCw0LjR6IE02LjYsNy40djEuMCBjMCwwLjItMC4xLDAuMy0wLjMsMC4zUzYuMCw4LjYsNi4wLDguNFY3LjRjLTAuMi0wLjEtMC4zLTAuMy0wLjMtMC41YzAtMC4zLDAuMy0wLjYsMC42LTAuNiBTNi45LDYuNiw2LjksNi45QzYuOSw3LjEsNi44LDcuMyw2LjYsNy40eiIvPjwvc3ZnPg==);
}
.ui-keyboard-dark-theme button.ui-keyboard-toggle span {
/* dark theme unlocked icon - fill: #eee */
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgZmlsbD0iI2VlZSI+PHBhdGggZD0iTTguNyw0LjRINy41SDUuMHYtMS45YzAtMS40LTEuMS0yLjUtMi41LTIuNWMtMS40LDAtMi41LDEuMS0yLjUsMi41djEuOWgxLjIgdi0xLjljMC0wLjcsMC42LTEuMiwxLjItMS4yczEuMiwwLjYsMS4yLDEuMnYxLjljLTAuNywwLTEuMiwwLjYtMS4yLDEuMlY4LjggYzAsMC43LDAuNiwxLjIsMS4yLDEuMmg1LjBDOS40LDEwLDEwLDkuNCwxMCw4LjhWNS42QzEwLDUuMCw5LjQsNC40LDguOCw0LjR6IE02LjYsNy40djEuMCBjMCwwLjItMC4xLDAuMy0wLjMsMC4zUzYuMCw4LjYsNi4wLDguNFY3LjRjLTAuMi0wLjEtMC4zLTAuMy0wLjMtMC41YzAtMC4zLDAuMy0wLjYsMC42LTAuNiBTNi45LDYuNiw2LjksNi45QzYuOSw3LjEsNi44LDcuMyw2LjYsNy40eiIvPjwvc3ZnPg==);
}
/* locked icon (keyboard disabled) */
button.ui-keyboard-toggle.ui-keyboard-disabled span {
/* light theme locked icon - fill: #111 */
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgZmlsbD0iIzExMSI+PHBhdGggZD0iTTcuNCA0LjRWMi41YzAtMS40LTEuMS0yLjUtMi41LTIuNWMtMS40IDAtMi41IDEuMS0yLjUgMi41djEuOSBjLTAuNyAwLTEuMiAwLjUtMS4yIDEuMnYzLjFDMS4zIDkuNCAxLjggMTAgMi41IDEwaDQuOWMwLjcgMCAxLjItMC42IDEuMi0xLjJWNS42IEM4LjcgNC45IDguMSA0LjQgNy40IDQuNHogTTUuMyA3LjR2MS4wYzAgMC4yLTAuMSAwLjMtMC4zIDAuM2MtMC4yIDAtMC4zLTAuMS0wLjMtMC4zVjcuNCBjLTAuMi0wLjEtMC4zLTAuMy0wLjMtMC41YzAtMC4zIDAuMy0wLjYgMC42LTAuNmMwLjMgMCAwLjYgMC4zIDAuNiAwLjYgQzUuNiA3LjEgNS41IDcuMyA1LjMgNy40eiBNNi4yIDQuNEgzLjdWMi41YzAtMC43IDAuNS0xLjIgMS4yLTEuMmMwLjcgMCAxLjIgMC42IDEuMiAxLjIgVjQuNHoiLz48L3N2Zz4=);
}
.ui-keyboard-dark-theme button.ui-keyboard-toggle.ui-keyboard-disabled span {
/* dark theme locked icon - fill: #eee */
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSIxMCIgZmlsbD0iI2VlZSI+PHBhdGggZD0iTTcuNCA0LjRWMi41YzAtMS40LTEuMS0yLjUtMi41LTIuNWMtMS40IDAtMi41IDEuMS0yLjUgMi41djEuOSBjLTAuNyAwLTEuMiAwLjUtMS4yIDEuMnYzLjFDMS4zIDkuNCAxLjggMTAgMi41IDEwaDQuOWMwLjcgMCAxLjItMC42IDEuMi0xLjJWNS42IEM4LjcgNC45IDguMSA0LjQgNy40IDQuNHogTTUuMyA3LjR2MS4wYzAgMC4yLTAuMSAwLjMtMC4zIDAuM2MtMC4yIDAtMC4zLTAuMS0wLjMtMC4zVjcuNCBjLTAuMi0wLjEtMC4zLTAuMy0wLjMtMC41YzAtMC4zIDAuMy0wLjYgMC42LTAuNmMwLjMgMCAwLjYgMC4zIDAuNiAwLjYgQzUuNiA3LjEgNS41IDcuMyA1LjMgNy40eiBNNi4yIDQuNEgzLjdWMi41YzAtMC43IDAuNS0xLjIgMS4yLTEuMmMwLjcgMCAxLjIgMC42IDEuMiAxLjIgVjQuNHoiLz48L3N2Zz4=);
}
.ui-keyboard.ui-keyboard-disabled button:not(.ui-keyboard-toggle),
.ui-keyboard.ui-keyboard-disabled input {
opacity: 0.5;
}
/*** Alt-Keys Popup extension ***/
/* clickable overlay on top of keyboard to hide the popup */
.ui-keyboard-overlay {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: rgba(0, 0, 0, 0.5);
}
/* the actual popup styling, class names from the css.container option are also added */
.ui-keyboard-popup {
display: inline-block;
/* default buttons are 2em wide + .1em margin on either side (set in .ui-keyboard-button definition);
so use multiples of 2.2em for a max-width if you don't want any extra white space on the sides,
e.g. 5 buttons * 2.2em = 11em, 6 buttons * 2.2em = 13.2em, etc */
max-width: 22em; /* 10 buttons */
}
/*** Extender keyboard extension ***/
div.ui-keyboard-extender { float: right; margin-left: 5px; }
button.ui-keyboard-extender span {
width: .9em;
height: .9em;
display: inline-block;
margin-bottom: 3px;
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
/* light theme extender icon - fill: #111 */
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgc3R5bGU9ImZpbGw6IzExMSI+PGc+PHBhdGggc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6IzExMSIgZD0iTSAwLjUsNC41IDE1LjUsNC41IDE1LjUsMTUuNSAwLjUsMTUuNSBaIj48L3BhdGg+PHJlY3Qgd2lkdGg9IjIiIGhlaWdodD0iMiIgeD0iMiIgeT0iNiI+PC9yZWN0PjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjUiIHk9IjYiPjwvcmVjdD48cmVjdCB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB4PSI4IiB5PSI2Ij48L3JlY3Q+PHBhdGggZD0ibSAxMSw2IDMsMCAwLDUgLTIsMCAwLC0zIC0xLDAgeiI+PC9wYXRoPjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjEyIiB5PSIxMiI+PC9yZWN0PjxyZWN0IHdpZHRoPSI2IiBoZWlnaHQ9IjIiIHg9IjUiIHk9IjEyIj48L3JlY3Q+PHJlY3Qgd2lkdGg9IjIiIGhlaWdodD0iMiIgeD0iOSIgeT0iOSI+PC9yZWN0PjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjYiIHk9IjkiPjwvcmVjdD48cmVjdCB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB4PSIyIiB5PSIxMiI+PC9yZWN0PjxyZWN0IHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHg9IjIiIHk9IjkiPjwvcmVjdD48L2c+PC9zdmc+);
}
.ui-keyboard-dark-theme button.ui-keyboard-extender span {
/* dark theme extender icon - fill: #eee */
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgc3R5bGU9ImZpbGw6I2VlZSI+PGc+PHBhdGggc3R5bGU9ImZpbGw6bm9uZTtzdHJva2U6I2VlZSIgZD0iTSAwLjUsNC41IDE1LjUsNC41IDE1LjUsMTUuNSAwLjUsMTUuNSBaIj48L3BhdGg+PHJlY3Qgd2lkdGg9IjIiIGhlaWdodD0iMiIgeD0iMiIgeT0iNiI+PC9yZWN0PjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjUiIHk9IjYiPjwvcmVjdD48cmVjdCB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB4PSI4IiB5PSI2Ij48L3JlY3Q+PHBhdGggZD0ibSAxMSw2IDMsMCAwLDUgLTIsMCAwLC0zIC0xLDAgeiI+PC9wYXRoPjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjEyIiB5PSIxMiI+PC9yZWN0PjxyZWN0IHdpZHRoPSI2IiBoZWlnaHQ9IjIiIHg9IjUiIHk9IjEyIj48L3JlY3Q+PHJlY3Qgd2lkdGg9IjIiIGhlaWdodD0iMiIgeD0iOSIgeT0iOSI+PC9yZWN0PjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIHg9IjYiIHk9IjkiPjwvcmVjdD48cmVjdCB3aWR0aD0iMiIgaGVpZ2h0PSIyIiB4PSIyIiB5PSIxMiI+PC9yZWN0PjxyZWN0IHdpZHRoPSIzIiBoZWlnaHQ9IjIiIHg9IjIiIHk9IjkiPjwvcmVjdD48L2c+PC9zdmc+);
}
/* Media Queries (optimized for jQuery UI themes; may be slightly off in jQuery Mobile themes) */
/* 240 x 320 (small phone) */
@media all and (max-width: 319px) {
.ui-keyboard div { font-size: 9px; }
.ui-keyboard .ui-keyboard-input { font-size: 12px; }
/* I don't own an iPhone so I have no idea how small this really is... is it even clickable with your finger? */
.ui-body .ui-btn { margin: 0; font-size: 9px; }
.ui-body .ui-keyboard-button { width: 1.8em; height: 2.5em; }
.ui-body .ui-keyboard-widekey { width: 4em; }
.ui-body .ui-keyboard-space { width: 8em; }
.ui-body .ui-btn-inner { height: 2.5em; padding: 0.3em 0; }
}
/* 320 x 480 (iPhone) */
@media all and (min-width: 320px) and (max-width: 479px) {
.ui-keyboard div { font-size: 9px; }
.ui-keyboard .ui-keyboard-input { font-size: 14px; }
/* I don't own an iPhone so I have no idea how small this really is... is it even clickable with your finger? */
.ui-body .ui-btn { margin: 0; font-size: 11px; }
.ui-body .ui-keyboard-button { width: 1.8em; height: 3em; }
.ui-body .ui-keyboard-widekey { width: 4.5em; }
.ui-body .ui-keyboard-space { width: 10em; }
.ui-body .ui-btn-inner { height: 3em; padding: 0.7em 0; }
}
/* 480 x 640 (small tablet) */
@media all and (min-width: 480px) and (max-width: 767px) {
.ui-keyboard div { font-size: 13px; }
.ui-keyboard .ui-keyboard-input { font-size: 14px; }
.ui-body .ui-btn { margin: 0; font-size: 10px; }
.ui-body .ui-keyboard-button { height: 2.5em; }
.ui-body .ui-btn-inner { height: 2.5em; padding: 0.5em 0; }
}

View file

@ -1,12 +1,13 @@
body {
font: 100% "Trebuchet MS", sans-serif;
font-family: Verdana,Arial,sans-serif;
font-size: 14px;
margin: 0px;
margin: 50px;
background-color: #000000;
color: #ffffff;
/*height: 100%;*/
/*min-height: 480px;*/
}
body {
font-family: Verdana,Arial,sans-serif;
font-size: 14px;
}
p {

View file

@ -1,13 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta name="generator" content="HTML Tidy for HTML5 for Mac OS X version 5.0.0">
<title>SIFTools</title>
<meta http-equiv="refresh" content="5;URL=sif_tools.html">
</head>
<body bgcolor="#FFFFFF">
<center>
This is not the web page you're looking for... <a href="sif_tools.html">move along, move along</a>
</center>
</body>
<head>
<title>SIFTools</title>
<META http-equiv="refresh" content="5;URL=sif_tools.html">
</head>
<body bgcolor="#ffffff">
<center>This is not the web page you're looking for... <A HREF="sif_tools.html">move along, move along</a>
</center>
</body>
</html>

104
web_app/js/external/date.js vendored Normal file
View file

@ -0,0 +1,104 @@
/**
* Version: 1.0 Alpha-1
* Build Date: 13-Nov-2007
* Copyright (c) 2006-2007, Coolite Inc. (http://www.coolite.com/). All rights reserved.
* License: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
* Website: http://www.datejs.com/ or http://www.coolite.com/datejs/
*/
Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|after|from)/i,subtract:/^(\-|before|ago)/i,yesterday:/^yesterday/i,today:/^t(oday)?/i,tomorrow:/^tomorrow/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^min(ute)?s?/i,hour:/^h(ou)?rs?/i,week:/^w(ee)?k/i,month:/^m(o(nth)?s?)?/i,day:/^d(ays?)?/i,year:/^y((ea)?rs?)?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a|p)/i},abbreviatedTimeZoneStandard:{GMT:"-000",EST:"-0400",CST:"-0500",MST:"-0600",PST:"-0700"},abbreviatedTimeZoneDST:{GMT:"-000",EDT:"-0500",CDT:"-0600",MDT:"-0700",PDT:"-0800"}};
Date.getMonthNumberFromName=function(name){var n=Date.CultureInfo.monthNames,m=Date.CultureInfo.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
return-1;};Date.getDayNumberFromName=function(name){var n=Date.CultureInfo.dayNames,m=Date.CultureInfo.abbreviatedDayNames,o=Date.CultureInfo.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
return-1;};Date.isLeapYear=function(year){return(((year%4===0)&&(year%100!==0))||(year%400===0));};Date.getDaysInMonth=function(year,month){return[31,(Date.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};Date.getTimezoneOffset=function(s,dst){return(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST[s.toUpperCase()]:Date.CultureInfo.abbreviatedTimeZoneStandard[s.toUpperCase()];};Date.getTimezoneAbbreviation=function(offset,dst){var n=(dst||false)?Date.CultureInfo.abbreviatedTimeZoneDST:Date.CultureInfo.abbreviatedTimeZoneStandard,p;for(p in n){if(n[p]===offset){return p;}}
return null;};Date.prototype.clone=function(){return new Date(this.getTime());};Date.prototype.compareTo=function(date){if(isNaN(this)){throw new Error(this);}
if(date instanceof Date&&!isNaN(date)){return(this>date)?1:(this<date)?-1:0;}else{throw new TypeError(date);}};Date.prototype.equals=function(date){return(this.compareTo(date)===0);};Date.prototype.between=function(start,end){var t=this.getTime();return t>=start.getTime()&&t<=end.getTime();};Date.prototype.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};Date.prototype.addSeconds=function(value){return this.addMilliseconds(value*1000);};Date.prototype.addMinutes=function(value){return this.addMilliseconds(value*60000);};Date.prototype.addHours=function(value){return this.addMilliseconds(value*3600000);};Date.prototype.addDays=function(value){return this.addMilliseconds(value*86400000);};Date.prototype.addWeeks=function(value){return this.addMilliseconds(value*604800000);};Date.prototype.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,this.getDaysInMonth()));return this;};Date.prototype.addYears=function(value){return this.addMonths(value*12);};Date.prototype.add=function(config){if(typeof config=="number"){this._orient=config;return this;}
var x=config;if(x.millisecond||x.milliseconds){this.addMilliseconds(x.millisecond||x.milliseconds);}
if(x.second||x.seconds){this.addSeconds(x.second||x.seconds);}
if(x.minute||x.minutes){this.addMinutes(x.minute||x.minutes);}
if(x.hour||x.hours){this.addHours(x.hour||x.hours);}
if(x.month||x.months){this.addMonths(x.month||x.months);}
if(x.year||x.years){this.addYears(x.year||x.years);}
if(x.day||x.days){this.addDays(x.day||x.days);}
return this;};Date._validate=function(value,min,max,name){if(typeof value!="number"){throw new TypeError(value+" is not a Number.");}else if(value<min||value>max){throw new RangeError(value+" is not a valid value for "+name+".");}
return true;};Date.validateMillisecond=function(n){return Date._validate(n,0,999,"milliseconds");};Date.validateSecond=function(n){return Date._validate(n,0,59,"seconds");};Date.validateMinute=function(n){return Date._validate(n,0,59,"minutes");};Date.validateHour=function(n){return Date._validate(n,0,23,"hours");};Date.validateDay=function(n,year,month){return Date._validate(n,1,Date.getDaysInMonth(year,month),"days");};Date.validateMonth=function(n){return Date._validate(n,0,11,"months");};Date.validateYear=function(n){return Date._validate(n,1,9999,"seconds");};Date.prototype.set=function(config){var x=config;if(!x.millisecond&&x.millisecond!==0){x.millisecond=-1;}
if(!x.second&&x.second!==0){x.second=-1;}
if(!x.minute&&x.minute!==0){x.minute=-1;}
if(!x.hour&&x.hour!==0){x.hour=-1;}
if(!x.day&&x.day!==0){x.day=-1;}
if(!x.month&&x.month!==0){x.month=-1;}
if(!x.year&&x.year!==0){x.year=-1;}
if(x.millisecond!=-1&&Date.validateMillisecond(x.millisecond)){this.addMilliseconds(x.millisecond-this.getMilliseconds());}
if(x.second!=-1&&Date.validateSecond(x.second)){this.addSeconds(x.second-this.getSeconds());}
if(x.minute!=-1&&Date.validateMinute(x.minute)){this.addMinutes(x.minute-this.getMinutes());}
if(x.hour!=-1&&Date.validateHour(x.hour)){this.addHours(x.hour-this.getHours());}
if(x.month!==-1&&Date.validateMonth(x.month)){this.addMonths(x.month-this.getMonth());}
if(x.year!=-1&&Date.validateYear(x.year)){this.addYears(x.year-this.getFullYear());}
if(x.day!=-1&&Date.validateDay(x.day,this.getFullYear(),this.getMonth())){this.addDays(x.day-this.getDate());}
if(x.timezone){this.setTimezone(x.timezone);}
if(x.timezoneOffset){this.setTimezoneOffset(x.timezoneOffset);}
return this;};Date.prototype.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};Date.prototype.isLeapYear=function(){var y=this.getFullYear();return(((y%4===0)&&(y%100!==0))||(y%400===0));};Date.prototype.isWeekday=function(){return!(this.is().sat()||this.is().sun());};Date.prototype.getDaysInMonth=function(){return Date.getDaysInMonth(this.getFullYear(),this.getMonth());};Date.prototype.moveToFirstDayOfMonth=function(){return this.set({day:1});};Date.prototype.moveToLastDayOfMonth=function(){return this.set({day:this.getDaysInMonth()});};Date.prototype.moveToDayOfWeek=function(day,orient){var diff=(day-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};Date.prototype.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};Date.prototype.getDayOfYear=function(){return Math.floor((this-new Date(this.getFullYear(),0,1))/86400000);};Date.prototype.getWeekOfYear=function(firstDayOfWeek){var y=this.getFullYear(),m=this.getMonth(),d=this.getDate();var dow=firstDayOfWeek||Date.CultureInfo.firstDayOfWeek;var offset=7+1-new Date(y,0,1).getDay();if(offset==8){offset=1;}
var daynum=((Date.UTC(y,m,d,0,0,0)-Date.UTC(y,0,1,0,0,0))/86400000)+1;var w=Math.floor((daynum-offset+7)/7);if(w===dow){y--;var prevOffset=7+1-new Date(y,0,1).getDay();if(prevOffset==2||prevOffset==8){w=53;}else{w=52;}}
return w;};Date.prototype.isDST=function(){console.log('isDST');return this.toString().match(/(E|C|M|P)(S|D)T/)[2]=="D";};Date.prototype.getTimezone=function(){return Date.getTimezoneAbbreviation(this.getUTCOffset,this.isDST());};Date.prototype.setTimezoneOffset=function(s){var here=this.getTimezoneOffset(),there=Number(s)*-6/10;this.addMinutes(there-here);return this;};Date.prototype.setTimezone=function(s){return this.setTimezoneOffset(Date.getTimezoneOffset(s));};Date.prototype.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r[0]+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};Date.prototype.getDayName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedDayNames[this.getDay()]:Date.CultureInfo.dayNames[this.getDay()];};Date.prototype.getMonthName=function(abbrev){return abbrev?Date.CultureInfo.abbreviatedMonthNames[this.getMonth()]:Date.CultureInfo.monthNames[this.getMonth()];};Date.prototype._toString=Date.prototype.toString;Date.prototype.toString=function(format){var self=this;var p=function p(s){return(s.toString().length==1)?"0"+s:s;};return format?format.replace(/dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?/g,function(format){switch(format){case"hh":return p(self.getHours()<13?self.getHours():(self.getHours()-12));case"h":return self.getHours()<13?self.getHours():(self.getHours()-12);case"HH":return p(self.getHours());case"H":return self.getHours();case"mm":return p(self.getMinutes());case"m":return self.getMinutes();case"ss":return p(self.getSeconds());case"s":return self.getSeconds();case"yyyy":return self.getFullYear();case"yy":return self.getFullYear().toString().substring(2,4);case"dddd":return self.getDayName();case"ddd":return self.getDayName(true);case"dd":return p(self.getDate());case"d":return self.getDate().toString();case"MMMM":return self.getMonthName();case"MMM":return self.getMonthName(true);case"MM":return p((self.getMonth()+1));case"M":return self.getMonth()+1;case"t":return self.getHours()<12?Date.CultureInfo.amDesignator.substring(0,1):Date.CultureInfo.pmDesignator.substring(0,1);case"tt":return self.getHours()<12?Date.CultureInfo.amDesignator:Date.CultureInfo.pmDesignator;case"zzz":case"zz":case"z":return"";}}):this._toString();};
Date.now=function(){return new Date();};Date.today=function(){return Date.now().clearTime();};Date.prototype._orient=+1;Date.prototype.next=function(){this._orient=+1;return this;};Date.prototype.last=Date.prototype.prev=Date.prototype.previous=function(){this._orient=-1;return this;};Date.prototype._is=false;Date.prototype.is=function(){this._is=true;return this;};Number.prototype._dateElement="day";Number.prototype.fromNow=function(){var c={};c[this._dateElement]=this;return Date.now().add(c);};Number.prototype.ago=function(){var c={};c[this._dateElement]=this*-1;return Date.now().add(c);};(function(){var $D=Date.prototype,$N=Number.prototype;var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),de;var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;}
return this.moveToDayOfWeek(n,this._orient);};};for(var i=0;i<dx.length;i++){$D[dx[i]]=$D[dx[i].substring(0,3)]=df(i);}
var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
return this.moveToMonth(n,this._orient);};};for(var j=0;j<mx.length;j++){$D[mx[j]]=$D[mx[j].substring(0,3)]=mf(j);}
var ef=function(j){return function(){if(j.substring(j.length-1)!="s"){j+="s";}
return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$D[de]=$D[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}}());Date.prototype.toJSONString=function(){return this.toString("yyyy-MM-ddThh:mm:ssZ");};Date.prototype.toShortDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortDatePattern);};Date.prototype.toLongDateString=function(){return this.toString(Date.CultureInfo.formatPatterns.longDatePattern);};Date.prototype.toShortTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.shortTimePattern);};Date.prototype.toLongTimeString=function(){return this.toString(Date.CultureInfo.formatPatterns.longTimePattern);};Date.prototype.getOrdinal=function(){switch(this.getDate()){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};
(function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
break;}
return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
rx.push(r[0]);s=r[1];}
return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
try{r=(px[i].call(this,s));}catch(e){r=null;}
if(r){return r;}}
throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
rx.push(r[0]);s=r[1];}
return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
s=q[1];}
if(!r){throw new $P.Exception(s);}
if(q){throw new $P.Exception(q[1]);}
if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
if(!last&&q[1].length===0){last=true;}
if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
if(rx[1].length<best[1].length){best=rx;}
if(best[1].length===0){break;}}
if(best[0].length===0){return best;}
if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
best[1]=q[1];}
return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
return rx;};Date.Grammar={};Date.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=((s.length==3)?Date.getMonthNumberFromName(s):(Number(s)-1));};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<Date.CultureInfo.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];var now=new Date();this.year=now.getFullYear();this.month=now.getMonth();this.day=1;this.hour=0;this.minute=0;this.second=0;for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
this.hour=(this.meridian=="p"&&this.hour<13)?this.hour+12:this.hour;if(this.day>Date.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");}
var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
if(this.now){return new Date();}
var today=Date.today();var method=null;var expression=!!(this.days!=null||this.orient||this.operator);if(expression){var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(this.weekday){this.unit="day";gap=(Date.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
if(this.month){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
if(!this.unit){this.unit="day";}
if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
if(this.unit=="week"){this.unit="day";this.value=this.value*7;}
this[this.unit+"s"]=this.value*orient;}
return today.add(this);}else{if(this.meridian&&this.hour){this.hour=(this.hour<13&&this.meridian=="p")?this.hour+12:this.hour;}
if(this.weekday&&!this.day){this.day=(today.addDays((Date.getDayNumberFromName(this.weekday)-today.getDay()))).getDate();}
if(this.month&&!this.day){this.day=1;}
return today.set(this);}}};var _=Date.Parsing.Operators,g=Date.Grammar,t=Date.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=Date.CultureInfo.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
fn=_C[keys]=_.any.apply(null,px);}
return fn;};g.ctoken2=function(key){return _.rtoken(Date.CultureInfo.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.mm,g.ss],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^(\+|\-)?\s*\d\d\d\d?/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^(\+|\-)\s*\d\d\d\d/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[Date.CultureInfo.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw Date.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["yyyy-MM-ddTHH:mm:ss","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
return g._start.call({},s);};}());Date._parse=Date.parse;Date.parse=function(s){var r=null;if(!s){return null;}
try{r=Date.Grammar.start.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};Date.getParseFunction=function(fx){var fn=Date.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
return((r[1].length===0)?r[0]:null);};};Date.parseExact=function(s,fx){return Date.getParseFunction(fx)(s);};

View file

@ -1,117 +0,0 @@
/*!
* jQuery Cookie Plugin v1.4.1
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// CommonJS
factory(require('jquery'));
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setTime(+t + days * 864e+5);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) === undefined) {
return false;
}
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};
}));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,209 @@
/*! jQuery UI Virtual Keyboard Alt Key Popup v1.0.1 *//*
* for Keyboard v1.18+ only (7/7/2015)
*
* By Rob Garrison (aka Mottie)
* Licensed under the MIT License
*
*/
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;( function( factory ) {
if ( typeof define === 'function' && define.amd ) {
define( [ 'jquery' ], factory );
} else if ( typeof module === 'object' && typeof module.exports === 'object' ) {
module.exports = factory( require( 'jquery' ) );
} else {
factory( jQuery );
}
}( function( $ ) {
'use strict';
var $keyboard = $.keyboard;
$.extend( $keyboard.css, {
altKeyPopup : 'ui-keyboard-popup',
altKeyOverlay : 'ui-keyboard-overlay'
});
$keyboard.altKeys = $.extend({
a : '\u00e5 \u00e6 \u0101 \u0103 \u0105 \u00e0 \u00e1 \u00e2 \u00e3 \u00e4', // å æ ā ă ą à á â ã ä
A : '\u00c5 \u00c6 \u0100 \u0102 \u0104 \u00c0 \u00c1 \u00c2 \u00c3 \u00c4', // Å Æ Ā Ă Ą À Á Â Ã Ä
c : '\u00e7 \u0107 \u0109 \u010b \u010d', // ç ć ĉ ċ č
C : '\u00c7 \u0106 \u0108 \u010a \u010c', // Ç Ć Ĉ Ċ Č
d : '\u010f \u00f0 \u010f', // ď ð ď
D : '\u010e \u00d0 \u010e', // Ď Ð Ď
e : '\u0117 \u0119 \u0115 \u011b \u0259 \u00e8 \u00e9 \u00ea \u00eb \u0113', // ė ę ĕ ě ə è é ê ë ē
E : '\u0116 \u0118 \u0114 \u011a \u018e \u00c8 \u00c9 \u00ca \u00cb \u0112', // Ė Ę Ĕ Ě Ǝ È É Ê Ë Ē
g : '\u0123 \u011f \u011d \u0121', // ģ ğ ĝ ġ
G : '\u0122 \u011e \u011c \u0120', // Ģ Ğ Ĝ Ġ
h : '\u0125 \u0127', // ĥ ħ
H : '\u0124 \u0126', // Ĥ Ħ
i : '\u0131 \u012f \u012b \u00ef \u00ee \u00ed \u00ec \u0129 \u012d', // ı į ī ï î í ì ĩ ĭ
I : '\u0130 \u012e \u012a \u00cf \u00ce \u00cd \u00cc \u0128 \u012c', // İ Į Ī Ï Î Í Ì Ĩ Ĭ
j : '\u0135', // ĵ
J : '\u0134', // Ĵ
k : '\u0137', // ķ
K : '\u0136', // Ķ
l : '\u0141 \u013d \u013b \u0139 \u013f', // Ł Ľ Ļ Ĺ Ŀ
L : '\u0142 \u013e \u013c \u013a \u0140', // ł ľ ļ ĺ ŀ
n : '\u0149 \u0148 \u0146 \u0144 \u00f1', // ʼn ň ņ ń ñ
N : '\u0149 \u0147 \u0145 \u0143 \u00d1', // ʼn Ň Ņ Ń Ñ
o : '\u0153 \u0151 \u00f8 \u00f6 \u00f5 \u00f4 \u00f3 \u00f2 \u014d \u014f', // œ ő ø ö õ ô ó ò ō ŏ
O : '\u0152 \u0150 \u00d8 \u00d6 \u00d5 \u00d4 \u00d3 \u00d2 \u014c \u014e', // Œ Ő Ø Ö Õ Ô Ó Ò Ō Ŏ
r : '\u0155 \u0159 \u0157', // ŕ ř ŗ
R : '\u0154 \u0158 \u0156', // Ŕ Ř Ŗ
s : '\u015b \u0161 \u015f \u00df \u00a7 \u015d', // ś š ş ß § ŝ
S : '\u015a \u0160 \u015e \u1e9e \u00a7 \u015c', // Ś Š Ş ẞ § Ŝ
t : '\u00fe \u0165 \u021b \u0163 \u0167', // þ ť ț ţ ŧ
T : '\u00de \u0164 \u021a \u0162 \u0166', // Þ Ť Ț Ţ Ŧ
u : '\u0173 \u0171 \u016f \u016b \u00fc \u00fb \u00fa \u00f9 \u0169 \u016d', // ų ű ů ū ü û ú ù ũ ŭ
U : '\u0172 \u0170 \u016e \u016a \u00dc \u00db \u00da \u00d9 \u0168 \u016c', // Ų Ű Ů Ū Ü Û Ú Ù Ũ Ŭ
w : '\u0175', // ŵ
W : '\u0174', // Ŵ
y : '\u00fd', // ý
Y : '\u00dd', // Ý
z : '\u017a \u017c \u017e', // ź ż ž
Z : '\u0179 \u017b \u017d', // Ź Ż Ž
'!' : '\u00a1', // ¡
'$' : '\u20ac \u00a3 \u00a4 \u00a5 \u00a2 \u20a1 \u20b1 \u20a9 \u20b9 \u20aa \u20ad \u20ae \u20a6 \u20a4', // €£¤¥¢₡₱₩₹₪₭₮₦₤
'?' : '\u00bf', // ¿
"'" : '\u3008 \u300c \u300e \u201c', // 〈 「 『 “
'"' : '\u3009 \u300d \u300f \u201d', // 〉 」 』 ”
'(' : '\u300a \u3010 \u3014', // « 【
')' : '\u300b \u3011 \u3015' // » 】
}, $keyboard.altKeys );
$.fn.addAltKeyPopup = function( options ) {
//Set the default values, use comma to separate the settings, example:
var defaults = {
// time to hold down a button in ms to trigger a popup
holdTime : 500,
// event triggered when popup is visible
popupVisible : 'popup-visible'
};
return this.each( function() {
// make sure a keyboard is attached
var namespace,
base = $( this ).data( 'keyboard' );
if (!base) { return; }
// variables
base.altkeypopup_options = $.extend( {}, defaults, options );
namespace = base.altkeypopup_namespace = base.namespace + 'AltKeyPopup';
base.extensionNamespace.push( namespace );
base.altkeypopup_setup = function() {
var timer,
start = 'mousedown touchstart '.split( ' ' ).join( namespace + ' ' ),
end = 'mouseup touchend touchcancel '.split( ' ' ).join( namespace + ' ' );
// force disable repeat keys
base.options.repeatRate = 0;
// add hold key functionality for popups
base.$allKeys
.bind( start, function() {
clearTimeout( timer );
var $key = $( this ),
key = $key.attr( 'data-action' ) || '';
if ( key in $keyboard.altKeys ) {
timer = setTimeout( function() {
base.altKeyPopup_popup( key, $key );
}, base.altkeypopup_options.holdTime );
}
})
.bind( end, function() {
clearTimeout( timer );
});
};
base.altKeyPopup_popup = function( key, $key ) {
var keys, $keys, positionHoriz, positionVert, top,
kbcss = $keyboard.css,
kbWidth = base.$keyboard.outerWidth(),
kbHeight = base.$keyboard.outerHeight();
// overlay keyboard
base.altKeyPopup_$overlay = $( '<div class="' + kbcss.altKeyOverlay + '" />' )
.css({
width : kbWidth,
height: kbHeight
})
.appendTo( base.$keyboard )
.bind( 'click touchstart', function() {
base.altKeyPopup_$overlay.remove();
});
// remove character added when key was initially pressed, unless it was a backspace key
if ( key !== 'bksp' ) {
$keyboard.keyaction.bksp( base );
}
// make popup; use the same classes as the keyboard container
$keys = $( '<div class="' + kbcss.altKeyPopup + ' ' + base.options.css.container + '" />' );
keys = $keyboard.altKeys[ key ].split( /\s+/ );
// make popup keys
base.buildRow( $keys, 0, keys, [] );
// add popup & add bindings
$keys
.appendTo( base.altKeyPopup_$overlay )
.children()
.bind( 'mousedown touchstart', function( event ) {
var action = $( this ).attr( 'data-action' );
// make action keys work in popup
if ( action in $keyboard.keyaction && $.isFunction($keyboard.keyaction[ action ] ) ) {
$keyboard.keyaction[ action ]( base, this, event );
} else {
base.insertText( action );
}
base.altKeyPopup_$overlay.remove();
})
.bind( 'mouseover mouseleave', function( event ){
$( this ).toggleClass( base.options.css.buttonHover, event.type === 'mouseover' );
});
// position popup within $keyboard container
positionHoriz = $key.position().left - ( $keys.outerWidth() / 2 ) + ( $key.outerWidth() / 2 );
if ( positionHoriz + $keys.outerWidth() > kbWidth ) {
positionHoriz = kbWidth - $keys.outerWidth();
if ( positionHoriz < 0 ) {
$keys.css({
width : kbWidth,
height : 'auto'
});
}
}
positionVert = $key.position().top - $key.outerHeight() - 5;
top = base.$keyboard.find( '.' + kbcss.keySet ).position().top;
if ( positionVert + $keys.outerHeight() > kbHeight ) {
positionVert = kbHeight - $keys.outerHeight();
if ( positionVert < top ) {
$keys.css({
height : kbHeight - top,
width : 'auto'
});
}
}
$keys.css({
position : 'relative',
top : positionVert < top ? top : positionVert,
left : positionHoriz < 0 ? 0 : positionHoriz
});
// trigger popup visible event
base.$el.trigger( base.altkeypopup_options.popupVisible, [ base ] );
};
// visible event is fired before this extension is initialized, so check!
if ( base.options.alwaysOpen && base.isVisible() ) {
base.altkeypopup_setup();
}
// setup altkey popup
base.$el
.unbind( $keyboard.events.kbBeforeVisible + namespace )
.bind( $keyboard.events.kbBeforeVisible + namespace, function() {
base.altkeypopup_setup();
});
});
};
}));

View file

@ -0,0 +1,147 @@
/*! jQuery UI Virtual Keyboard Autocomplete v1.9.2 *//*
* for Keyboard v1.18+ only (8/17/2015)
*
* By Rob Garrison (aka Mottie & Fudgey)
* Licensed under the MIT License
*
* Use this extension with the Virtual Keyboard to get
* the jQuery UI Autocomplete widget to work seamlessly
*
* Requires:
* jQuery
* jQuery UI & css
* Keyboard plugin : https://github.com/Mottie/Keyboard
*
* Setup:
* $('.ui-keyboard-input')
* .keyboard(options)
* .autocomplete(options)
* .addAutoComplete();
*
* // or if targeting a specific keyboard
* $('#keyboard1')
* .keyboard(options) // keyboard plugin
* .autocomplete(options) // jQuery UI autocomplete
* .addAutoComplete(); // this keyboard extension
*
*/
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
'use strict';
$.fn.addAutocomplete = function(options){
var defaults = {
position : {
of : null,
my : 'right top',
at : 'left top',
collision: 'flip'
}
};
return this.each(function(){
// make sure a keyboard is attached
var o, base = $(this).data('keyboard');
if (!base) { return; }
base.autocomplete_namespace = base.namespace + 'Autocomplete';
base.extensionNamespace.push( base.autocomplete_namespace );
// Setup
base.autocomplete_init = function(){
// variables
o = base.autocomplete_options = $.extend( true, {}, defaults, options );
// visible event is fired before this extension is initialized, so check!
if (base.options.alwaysOpen && base.isVisible()) {
base.autocomplete_setup();
}
base.$el
.unbind(base.autocomplete_namespace)
.bind($.keyboard.events.kbVisible + base.autocomplete_namespace,function(){
base.autocomplete_setup();
})
.bind($.keyboard.events.kbChange + base.autocomplete_namespace,function(){
if (base.hasAutocomplete && base.isVisible()) {
base.$el
.val(base.$preview.val())
.trigger('keydown.autocomplete');
}
})
.bind($.keyboard.events.kbHidden + base.autocomplete_namespace, function(){
base.$el.autocomplete('close');
})
.bind('autocompleteopen' + base.autocomplete_namespace, function() {
if (base.hasAutocomplete){
// default to $keyboard if no position.of defined
var position = $.extend( {}, o.position );
// refresh base.$keyboard (it gets destroyed after use); fixes #382
position.of = position.of || base.$keyboard;
// reposition autocomplete window next to the keyboard
base.$autocomplete.menu.element.position( position );
}
})
.bind('autocompleteselect' + base.autocomplete_namespace, function(e, ui){
var v = ui.item && ui.item.value || '';
if (base.hasAutocomplete && v !== ''){
base.$preview
.val( v )
.focus();
// see issue #95 - thanks banku!
base.last.start = v.length;
base.last.end = v.length;
}
});
};
// set up after keyboard is visible
base.autocomplete_setup = function(){
// look for autocomplete
base.$autocomplete = base.$el.data('autocomplete') || base.$el.data('uiAutocomplete') || base.$el.data('ui-autocomplete');
base.hasAutocomplete = (typeof(base.$autocomplete) === 'undefined') ? false : (base.$autocomplete.options.disabled) ? false : true;
// only bind to keydown once
if (base.hasAutocomplete) {
base.$preview.bind('keydown' + base.autocomplete_namespace, function(e){
// send keys to the autocomplete widget (arrow, pageup/down, etc)
base.$el.val( base.$preview.val() ).triggerHandler(e);
});
base.$allKeys.bind('mouseup mousedown mouseleave touchstart touchend touchcancel '.split(' ').join(base.autocomplete_namespace + ' '),function(event){
clearTimeout( base.$autocomplete.searching );
var evt = event;
base.$autocomplete.searching = setTimeout(function() {
// only search if the value has changed
if ( base.$autocomplete.term !== base.$autocomplete.element.val() ) {
base.$autocomplete.selectedItem = null;
base.$autocomplete.search( null, evt );
}
}, base.$autocomplete.options.delay );
});
}
};
base.origEscClose = base.escClose;
// replace original function with this one
base.escClose = function(e){
// prevent selecting an item in autocomplete from closing keyboard
if ( base.hasAutocomplete && (e.target.id === 'ui-active-menuitem' || $(e.target).closest('ul').hasClass('ui-autocomplete')) ) { return; }
base.origEscClose(e);
};
base.autocomplete_init();
});
};
}));

View file

@ -0,0 +1,197 @@
/*! jQuery UI Virtual Keyboard Virtual Caret v1.1.3 (beta) *//*
* for Keyboard v1.18+ only (9/24/2015)
* modified from https://github.com/component/textarea-caret-position
*
* By Rob Garrison (aka Mottie)
* Licensed under the MIT License
*
* CSS changes
* NOTE: caret margin-top => is added to the caret height (top & bottom)
* .ui-keyboard-preview-wrapper { position: relative; overflow: hidden; }
* .ui-keyboard-caret { background: red; width: 1px; margin-top: 3px; }
*/
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;( function( factory ) {
if ( typeof define === 'function' && define.amd ) {
define( [ 'jquery' ], factory );
} else if ( typeof module === 'object' && typeof module.exports === 'object' ) {
module.exports = factory( require( 'jquery' ) );
} else {
factory( jQuery );
}
}( function( $ ) {
'use strict';
var $keyboard = $.keyboard;
$keyboard.firefox = typeof window.mozInnerScreenX !== 'undefined';
$.extend( $keyboard.css, {
caret : 'ui-keyboard-caret'
});
$.fn.addCaret = function( options ) {
var defaults = {
caretClass : '',
// *** for future use ***
// data-attribute containing the character(s) next to the caret
charAttr : 'data-character',
// # character(s) next to the caret (can be negative for RTL)
charIndex : 1,
offsetX : 0,
offsetY : 0,
adjustHt : 0
};
return this.each( function() {
// make sure a keyboard is attached
var o, namespace,
kbevents = $keyboard.events,
base = $( this ).data( 'keyboard' );
if ( !base ) { return; }
// variables
o = base.caret_options = $.extend( {}, defaults, options );
namespace = base.caret_namespace = base.namespace + 'caret';
base.extensionNamespace.push( namespace );
// modified from https://github.com/component/textarea-caret-position
// The properties that we copy into a mirrored div.
// Note that some browsers, such as Firefox,
// do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
// so we have to do every single property specifically.
base.textareaCaretProperties = [
'direction', 'boxSizing', 'width', 'height', 'overflowX', 'overflowY',
'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth', 'borderStyle',
'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft',
'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontSizeAdjust',
'lineHeight', 'fontFamily', 'textAlign', 'textTransform', 'textIndent', 'textDecoration',
'letterSpacing', 'wordSpacing', 'tabSize', 'MozTabSize'
];
base.caret_setup = function() {
var events = 'keyup keypress mouseup mouseleave '.split( ' ' ).join( namespace + ' ' ),
style = 'position:absolute;visibility:hidden;top:-9999em;left:-9999em;' +
'white-space:pre-wrap;' +
( base.preview.nodeName === 'INPUT' ? '' : 'word-wrap:break-word;' );
// add mirrored div
base.caret_$div = $( '<div class="ui-keyboard-mirror-div" style="' + style + '">' )
.appendTo( base.$keyboard );
// remove caret, just-in-case
if (base.$caret) { base.$caret.remove(); }
base.$caret = $( '<div class="ui-keyboard-caret ' + o.caretClass + '" style="position:absolute;">' )
.insertAfter( base.$preview );
base.$el
.unbind( kbevents.kbChange + namespace )
.bind( kbevents.kbChange + namespace, function() {
base.findCaretPos();
});
base.$preview
.unbind( events )
.bind( events, function() {
base.findCaretPos();
});
};
// getCaretCoordinatesFn = function (element, position, recalculate) {
base.findCaretPos = function() {
if ( !base.caret_$div ) { return; }
var style, computed, margin, pos, position, txt, span, offset,
element = base.preview,
isInput = element.nodeName === 'INPUT',
div = base.caret_$div[0];
style = div.style;
// getComputedStyle with null - fixes #384
computed = window.getComputedStyle ? getComputedStyle( element, null ) : element.currentStyle;
// get caret position based on text-direction
pos = $keyboard.caret( base.$preview );
position = Math[ computed.direction === 'ltr' ? 'max' : 'min' ]( pos.start, pos.end );
// transfer the element's properties to the div
base.textareaCaretProperties.forEach(function ( prop ) {
style[ prop ] = computed[ prop ];
});
if ( $keyboard.firefox ) {
// Firefox adds 2 pixels to the padding - https://bugzilla.mozilla.org/show_bug.cgi?id=753662
style.width = parseInt( computed.width, 10 ) - 2 + 'px';
// Firefox lies about the overflow property for textareas:
// https://bugzilla.mozilla.org/show_bug.cgi?id=984275
if ( element.scrollHeight > parseInt( computed.height, 10 ) ) {
style.overflowY = 'scroll';
}
} else {
// for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
// style.overflow = 'hidden';
style.width = parseInt( isInput ? element.scrollWidth : computed.width, 10 ) +
// add 50 extra px if it's an input to prevent wrap
( isInput ? 50 : 0 ) + 'px';
}
div.textContent = element.value.substring( 0, position );
// the second special handling for input type="text" vs textarea:
// spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
if ( element.nodeName === 'INPUT' ) {
div.textContent = div.textContent.replace( /\x20/g, '\xa0' );
}
span = document.createElement( 'span' );
// Wrapping must be replicated *exactly*, including when a long word gets
// onto the next line, with whitespace at the end of the line before (#7).
// The *only* reliable way to do that is to copy the *entire* rest of the
// textarea's content into the <span> created at the caret position.
// for inputs, just '.' would be enough, but why bother?
// || because a completely empty faux span doesn't render at all
span.textContent = element.value.substring( position ) || '.';
div.appendChild( span );
offset = $(span).position();
base.caretPos = {
top: offset.top + parseInt( computed.borderTopWidth, 10 ) + o.offsetY,
left: offset.left + parseInt( computed.borderLeftWidth, 10 ) + o.offsetX
};
// make caret height = font-size + any margin-top x2 added by the css
margin = parseInt( base.$caret.css( 'margin-top' ), 10 );
style = Math.round( parseFloat( base.$preview.css( 'font-size' ) ) + margin * 2 ) + o.adjustHt;
offset = base.$preview.position();
base.$caret.css({
top: offset.top - element.scrollTop + base.caretPos.top - margin,
left: offset.left - element.scrollLeft + base.caretPos.left,
height: style
});
txt = element.value.substring( position, position + o.charIndex ).replace(/\s/, '\xa0' ) || '\xa0';
base.$caret.attr( o.charAttr, txt );
};
// setup caret when keyboard is visible
base.$el
.unbind( namespace )
.bind( kbevents.kbBeforeVisible + namespace, function() {
base.caret_setup();
})
.bind( kbevents.kbVisible + namespace, function() {
base.findCaretPos();
})
.bind( kbevents.kbHidden + namespace, function() {
// unbind events in case usePreview: false; see #376
var events = 'keyup keypress mouseup mouseleave '.split( ' ' ).join( namespace + ' ' );
base.$preview.unbind( events );
base.$caret.remove();
base.$caret = null;
base.caret_$div = null;
});
// visible event is fired before this extension is initialized, so check!
if ( base.options.alwaysOpen && base.isVisible() ) {
base.caret_setup();
base.findCaretPos();
}
});
};
}));

View file

@ -0,0 +1,119 @@
/*! jQuery UI Virtual Keyboard Extender v1.0.1 *//*
* for Keyboard v1.18+ only (7/7/2015)
*
* By Rob Garrison (aka Mottie)
* Licensed under the MIT License
*
*/
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
'use strict';
var $keyboard = $.keyboard;
$.extend( $keyboard.css, {
extender : 'ui-keyboard-extender'
});
$.extend( $keyboard.layouts, {
'numpad' : {
'normal' : [
'{clear} / * -',
'7 8 9 +',
'4 5 6 %',
'1 2 3 =',
'0 {dec} {left} {right}'
]
}
});
// add {extender} keyaction
$.extend( $keyboard.keyaction, {
extender: function( base, el ) {
base.extender_toggle();
return false;
}
});
$.fn.addExtender = function(options) {
//Set the default values, use comma to separate the settings, example:
var defaults = {
layout : 'numpad',
showing : false,
reposition : true
};
return this.each( function() {
// make sure a keyboard is attached
var o, base = $( this ).data( 'keyboard' );
if (!base) { return; }
// variables
o = base.extender_options = $.extend( {}, defaults, options );
$.extend( true, $keyboard.language.en, {
display : {
'extender' : ' '
}
});
base.extender_namespace = base.namespace + 'extender';
base.extensionNamespace.push( base.extender_namespace );
base.extender_setup = function() {
var $kb,
layout = o.layout;
if ( typeof $keyboard.builtLayouts[ layout ] === 'undefined' ) {
base.buildKeyboard( layout );
}
$kb = $keyboard.builtLayouts[ layout ].$keyboard.find( '.' + $keyboard.css.keySet + '-normal' ).clone();
$kb
.removeClass()
.removeAttr('name')
.addClass( $keyboard.css.extender )
.toggle( o.showing )
.children('button')
.removeAttr('data-pos');
base.$keyboard.append( $kb );
base.extender_toggle( base.extender_options.showing );
base.bindKeys();
};
base.extender_toggle = function(set) {
base.extender_options.showing = typeof set === 'undefined' ? !base.extender_options.showing : set;
base.$keyboard
.find( 'div.' + $keyboard.css.extender )
.toggle( base.extender_options.showing )
.end()
.find( '.' + $keyboard.css.keySet )
.css('float', base.extender_options.showing ? 'left' : 'none')
.end()
.find( 'button.' + $keyboard.css.extender )
.toggleClass( base.options.css.buttonActive, base.extender_options.showing );
// force keyboard reposition
if (base.extender_options.reposition) {
$(window).trigger('resize');
}
};
// visible event is fired before this extension is initialized, so check!
if (base.options.alwaysOpen && base.isVisible()) {
base.extender_setup();
}
// setup extender
base.$el
.unbind( $keyboard.events.kbBeforeVisible + base.extender_namespace )
.bind( $keyboard.events.kbBeforeVisible + base.extender_namespace, function() {
base.extender_setup();
});
});
};
}));

View file

@ -0,0 +1,188 @@
/*! jQuery UI Virtual Keyboard for jQuery Mobile Themes v1.4.1 *//*
* for Keyboard v1.18+ (updated 7/7/2015)
*
* By Rob Garrison (aka Mottie & Fudgey)
* Licensed under the MIT License
*
* Use this extension with the Virtual Keyboard to apply
* the necessary themes to make the keyboard compatible with
* jQuery Mobile themes
*
* Requires:
* jQuery - http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js
* jQuery Mobile - http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js
* jQuery Mobile themes - http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.css
*
* Setup:
* $('.ui-keyboard-input')
* .keyboard(options)
* .addMobile(mobile-options);
*
* // or if targeting a specific keyboard
* $('#keyboard1')
* .keyboard(options) // keyboard plugin
* .addMobile(mobile-options); // this keyboard extension
*
*/
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
$.fn.addMobile = function(options){
var o, defaults = {
// keyboard wrapper theme
container : { theme:'b', cssClass:'ui-body' },
// keyboard duplicate input
input : { theme:'b', cssClass:'' },
// theme added to all regular buttons
buttonMarkup : { theme:'b', cssClass:'ui-btn', shadow:'true', corners:'true' },
// theme added to all buttons when they are being hovered
buttonHover : { theme:'b', cssClass:'ui-btn-hover' },
// theme added to action buttons (e.g. tab, shift, accept, cancel);
// parameters here will override the settings in the buttonMarkup
buttonAction : { theme:'b', cssClass:'ui-btn-active' },
// theme added to button when it is active (e.g. shift is down)
// All extra parameters will be ignored
buttonActive : { theme:'b', cssClass:'ui-btn-active' },
// if more than 3 mobile themes are used, add them here
allThemes : 'a b c'
};
return this.each(function(){
var base = $(this).data('keyboard');
// Stop if no keyboard attached or if jQuery Mobile isn't loaded
if (!base || typeof($.fn.textinput) === 'undefined') { return; }
base.mobile_options = o = $.extend(true, {}, defaults, options);
// create a list of theme class names to remove
base.mobile_themes = $.trim(
(' ' + o.allThemes).split(' ').join(' ' + o.buttonMarkup.cssClass + '-') +
(' ' + o.allThemes).split(' ').join(' ' + o.buttonAction.cssClass + '-') +
(' ' + o.allThemes).split(' ').join(' ' + o.buttonActive.cssClass + '-')
);
// save original action class because it gets removed when this theme switches swatches
if (typeof base.options.mobile_savedActiveClass === 'undefined') {
base.options.mobile_savedActiveClass = '' + base.options.css.buttonActive;
}
// Setup
base.mobile_init = function() {
var namespace = base.namespace + 'Mobile';
// Add theme to input - if not already done through the markup
$('.' + $.keyboard.css.input).textinput();
// visible event is fired before this extension is initialized, so check!
if (base.options.alwaysOpen && base.isVisible) {
base.mobile_setup();
}
base.extensionNamespace.push( namespace );
// Setup mobile theme on keyboard once it is visible.
// Note: There is a 10ms delay after the keyboard is displayed before it actually fires 'visible.keyboard'.
// Since we are restyling here, the user will experience FlashOfUnstyledContent (FOUC).
// This is avoided by first setting the visibility to hidden, then after the mobile styles are applied we
// set it visible.
base.$el
.unbind(namespace)
.bind($.keyboard.events.kbBeforeVisible + namespace, function() {
if ( base && base.el.active && base.$keyboard.length ) {
base.$keyboard.css('visibility', 'hidden');
}
})
.bind($.keyboard.events.kbVisible + namespace, function() {
if ( base && base.el.active && base.$keyboard.length ) {
base.mobile_setup();
base.$keyboard.css('visibility', 'visible');
base.$preview.focus();
}
});
};
base.mobile_setup = function(){
var p,
kbcss = $.keyboard.css,
opts = base.options,
themes = base.mobile_themes;
base.mobile_$actionKeys = base.$keyboard.find('.' + base.options.css.buttonAction);
opts.css.buttonActive = opts.mobile_savedActiveClass + ' ' + base.modOptions(o.buttonActive, o.buttonMarkup);
base.$keyboard
// 'ui-body ui-body-a' classes to apply swatch theme
.addClass( base.modOptions(o.container, o.container) )
// preview input
.find('.' + kbcss.preview)
// removing 'ui-widget-content' will prevent jQuery UI theme from applying to the keyboard
.removeClass('ui-widget ui-widget-content')
.addClass( base.modOptions(o.input, o.input) ).end()
// apply jQuery Mobile button markup
// removed call to jQuery Mobile buttonMarkup function; replaced with base.modOptions
.find('button')
.removeClass( $.trim('ui-corner-all ui-state-default ' + themes) )
.addClass( base.modOptions(o.buttonMarkup, o.buttonMarkup) )
.not( base.mobile_$actionKeys )
.hover(function(){
$(this)
.removeClass( themes )
.addClass( base.modOptions(o.buttonHover, o.buttonMarkup) );
},function(){
$(this)
.removeClass( themes + ' ' + o.buttonHover.cssClass )
.addClass( base.modOptions(o.buttonMarkup, o.buttonMarkup) );
});
base.mobile_$actionKeys
.removeClass( themes )
.addClass( base.modOptions(o.buttonAction, o.buttonMarkup) );
// update keyboard width if preview is showing... after applying mobile theme
if (base.msie && base.$preview[0] !== base.el) {
base.$preview.hide();
base.$keyboard.css('width','');
base.width = base.$keyboard.outerWidth();
// add about 1em to input width for extra padding
base.$keyboard.width(base.width + parseInt(base.$preview.css('fontSize'),10));
base.$preview.width(base.width);
base.$preview.show();
}
// adjust keyboard position after applying mobile theme
if ($.ui && $.ui.position) {
p = opts.position;
p.of = p.of || base.$el.data('keyboardPosition') || base.$el;
p.collision = p.collision || 'flipfit flipfit';
base.$keyboard.position(p);
}
};
base.modOptions = function(t, btn){
var css = ' ' + ( t.cssClass || '' );
// Using this instead of the jQuery Mobile buttonMarkup because it is expecting <a>'s instead of <button>
// theme:'a', shadow:'true', inline:'true', corners:'false'
return css + ' ' + (btn && btn.cssClass ? btn.cssClass + '-' + (t.theme || '') : '') +
(t.shadow == 'true' ? ' ui-shadow' : '') +
(t.corners == 'true' ? ' ui-corner-all' : '');
};
base.mobile_init();
});
};
}));

View file

@ -0,0 +1,218 @@
/*! jQuery UI Virtual Keyboard Navigation v1.6.1 *//*
* for Keyboard v1.18+ only (updated 7/7/2015)
*
* By Rob Garrison (aka Mottie & Fudgey)
* Licensed under the MIT License
*
* Use this extension with the Virtual Keyboard to navigate
* the virtual keyboard keys using the arrow, page, home and end keys
* Using this extension WILL prevent keyboard navigation inside of all
* input and textareas
*
* Requires:
* jQuery
* Keyboard plugin : https://github.com/Mottie/Keyboard
*
* Setup:
* $('.ui-keyboard-input')
* .keyboard(options)
* .addNavigation();
*
* // or if targeting a specific keyboard
* $('#keyboard1')
* .keyboard(options) // keyboard plugin
* .addNavigation(); // this keyboard extension
*
*/
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
'use strict';
$.keyboard = $.keyboard || {};
$.keyboard.navigationKeys = {
// all keys
toggle : 112, // toggle key; F1 = 112 (event.which value for function 1 key)
enter : 13,
pageup : 33,
pagedown : 34,
end : 35,
home : 36,
left : 37,
up : 38,
right : 39,
down : 40,
// move caret WITH navigate toggle active
caretrt : 45, // Insert key
caretlt : 46, // delete key
// ** custom navigationKeys functions **
// move caret without navigate toggle active
caretright : function(kb){
$.keyboard.keyaction.right(kb);
},
caretleft : function(kb){
$.keyboard.keyaction.left(kb);
}
};
$.fn.addNavigation = function(options){
return this.each(function(){
// make sure a keyboard is attached
var o, k,
base = $(this).data('keyboard'),
opts = base.options,
defaults = {
position : [0,0], // set start position [row-number, key-index]
toggleMode : false, // true = navigate the virtual keyboard, false = navigate in input/textarea
focusClass : 'hasFocus',// css class added when toggle mode is on
toggleKey : null // defaults to $.keyboard.navigationKeys.toggle value
},
kbevents = $.keyboard.events;
if (!base) { return; }
base.navigation_options = o = $.extend({}, defaults, options);
base.navigation_keys = k = $.extend({}, $.keyboard.navigationKeys);
base.navigation_namespace = base.namespace + 'Nav';
base.extensionNamespace.push( base.navigation_namespace );
// save navigation settings - disabled when the toggled
base.saveNav = [ base.options.tabNavigation, base.options.enterNavigation ];
base.allNavKeys = $.map(k, function(v,i){ return v; });
// Setup
base.navigation_init = function(){
var kbcss = $.keyboard.css;
base.$keyboard.toggleClass(o.focusClass, o.toggleMode)
.find('.' + kbcss.keySet + ':visible')
.find('.' + kbcss.keyButton + '[data-pos="' + o.position[0] + ',' + o.position[1] + '"]')
.addClass(opts.css.buttonHover);
base.$preview
.unbind(base.navigation_namespace)
.bind('keydown' + base.navigation_namespace,function(e){
return base.checkKeys(e.which);
});
};
base.checkKeys = function(key, disable){
if (typeof(key) === "undefined") {
return;
}
var k = base.navigation_keys,
kbcss = $.keyboard.css;
if (key === ( o.toggleKey || k.toggle ) || disable) {
o.toggleMode = (disable) ? false : !o.toggleMode;
base.options.tabNavigation = (o.toggleMode) ? false : base.saveNav[0];
base.options.enterNavigation = (o.toggleMode) ? false : base.saveNav[1];
}
base.$keyboard.toggleClass(o.focusClass, o.toggleMode);
if ( o.toggleMode && key === k.enter ) {
base.$keyboard
.find('.' + kbcss.keySet + ':visible')
.find('.' + kbcss.keyButton + '[data-pos="' + o.position[0] + ',' + o.position[1] + '"]')
.trigger(kbevents.kbRepeater);
return false;
}
if ( o.toggleMode && $.inArray(key, base.allNavKeys) >= 0 ) {
base.navigateKeys(key);
return false;
}
};
base.navigateKeys = function(key, row, indx){
indx = typeof indx === 'number' ? indx : o.position[1];
row = typeof row === 'number' ? row : o.position[0];
var kbcss = $.keyboard.css,
vis = base.$keyboard.find('.' + kbcss.keySet + ':visible'),
maxRow = vis.find('.' + kbcss.endRow).length - 1,
maxIndx = vis.find('.' + kbcss.keyButton + '[data-pos^="' + row + ',"]').length - 1,
p = base.last,
l = base.$preview.val().length,
k = base.navigation_keys;
switch(key){
case k.pageup : row = 0; break; // pageUp
case k.pagedown : row = maxRow; break; // pageDown
case k.end : indx = maxIndx; break; // End
case k.home : indx = 0; break; // Home
case k.left : indx += (indx > 0) ? -1 : 0; break; // Left
case k.up : row += (row > 0) ? -1 : 0; break; // Up
case k.right : indx += 1; break; // Right
case k.down : row += (row + 1 > maxRow) ? 0 : 1; break; // Down
case k.caretrt : p.start++; break; // caret right
case k.caretlt : p.start--; break; // caret left
}
// move caret
if (key === k.caretrt || key === k.caretlt) {
p.start = p.start < 0 ? 0 : p.start > l ? l : p.start;
base.last.start = base.last.end = p.end = p.start;
$.keyboard.caret( base.$preview, base.last );
}
// get max index of new row
maxIndx = vis.find('.' + kbcss.keyButton + '[data-pos^="' + row + ',"]').length - 1;
if (indx > maxIndx) { indx = maxIndx; }
vis.find('.' + opts.css.buttonHover).removeClass(opts.css.buttonHover);
vis.find('.' + kbcss.keyButton + '[data-pos="' + row + ',' + indx + '"]').addClass(opts.css.buttonHover);
o.position = [ row, indx ];
};
// visible event is fired before this extension is initialized, so check!
if (base.options.alwaysOpen && base.isVisible) {
base.$keyboard.find('.' + opts.css.buttonHover).removeClass(opts.css.buttonHover);
base.navigation_init();
}
// navigation bindings
base.$el
.unbind(base.navigation_namespace)
.bind(kbevents.kbVisible, function(){
base.$keyboard.find('.' + opts.css.buttonHover).removeClass(opts.css.buttonHover);
base.navigation_init();
})
.bind(kbevents.kbInactive + ' ' + kbevents.kbHidden, function(e){
base.checkKeys(e.which, true); // disable toggle mode & revert navigation options
})
.bind(kbevents.kbKeysetChange, function(){
base.navigateKeys(null);
})
.bind('navigate navigateTo', function(e, row, indx){
var key;
// no row given, check if it's a navigation key or keyaction
row = isNaN(row) ? row.toLowerCase() : row;
if (row in base.navigation_keys) {
key = base.navigation_keys[row];
if (isNaN(key) && key in $.keyboard.keyaction) {
// defined navigation_keys string name is a defined keyaction
$.keyboard.keyaction[key]( base, this, e );
} else if ($.isFunction(key)) {
// custom function defined in navigation_keys
key(base);
} else {
// key (e.which value) is defined in navigation_keys
base.checkKeys(key);
}
} else if ( typeof row === 'string' && row in $.keyboard.keyaction ) {
// navigate called directly with a keyaction name
$.keyboard.keyaction[row]( base, this, e );
} else {
base.navigateKeys(null, row, indx);
}
});
});
};
}));

View file

@ -0,0 +1,91 @@
/*! jQuery UI Virtual Keyboard previewKeyset v1.1.1 *//*
* for Keyboard v1.18+ only (updated 7/7/2015)
*
* By Rob Garrison (aka Mottie & Fudgey)
* Licensed under the MIT License
*
* Use this extension with the Virtual Keyboard to add a preview
* of other keysets to the main keyboard.
*
* Requires:
* jQuery
* Keyboard plugin : https://github.com/Mottie/Keyboard
*
* Setup:
* $('.ui-keyboard-input')
* .keyboard(options)
* .previewKeyset();
*
* // or if targeting a specific keyboard
* $('#keyboard1')
* .keyboard(options) // keyboard plugin
* .previewKeyset(); // this keyboard extension
*
*/
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
'use strict';
$.keyboard = $.keyboard || {};
$.fn.previewKeyset = function( options ) {
return this.each( function() {
// make sure a keyboard is attached
var base = $( this ).data( 'keyboard' ),
namespace = base.namespace + 'Preview',
defaults = {
sets : [ 'normal', 'shift', 'alt', 'alt-shift' ]
};
if ( !base ) { return; }
base.previewKeyset_options = $.extend( {}, defaults, options );
base.extensionNamespace.push( namespace );
base.previewKeyset = function() {
var kbcss = $.keyboard.css,
sets = base.previewKeyset_options.sets,
// only target option defined sets
$sets = base.$keyboard.find( '.' + kbcss.keySet ).filter( '[name="' + sets.join('"],[name="') + '"]' );
if ( $sets.length > 1 ) {
// start with normal keyset & find all non-action buttons
$sets.eq( 0 ).find( '.' + kbcss.keyButton ).not( '.' + kbcss.keyAction ).each(function(){
var indx, nam,
data = {},
len = sets.length,
// find all keys with the same position
$sibs = $sets.find( 'button[data-pos="' + $(this).attr('data-pos') + '"]' );
for ( indx = 0; indx < len; indx++ ) {
nam = $sibs.eq( indx ).parent().attr( 'name' );
if ( $.inArray( nam, sets ) >= 0 ) {
data[ 'data-' + nam ] = $sibs.eq( indx ).find( '.' + kbcss.keyText ).text();
}
}
$sibs.attr( data );
});
}
};
// visible event is fired before this extension is initialized, so check!
if (base.options.alwaysOpen && base.isVisible()) {
base.previewKeyset();
} else {
base.$el
.unbind($.keyboard.events.kbBeforeVisible + namespace)
.bind($.keyboard.events.kbBeforeVisible + namespace, function() {
base.previewKeyset();
});
}
});
};
}));

View file

@ -0,0 +1,230 @@
/*
* jQuery UI Virtual Keyboard Scramble Extension v1.6.1 for Keyboard v1.18+ (updated 7/7/2015)
*
* By Rob Garrison (aka Mottie & Fudgey)
* Licensed under the MIT License
*
* Use this extension with the Virtual Keyboard to scramble the
* specified keyboard keys
*
* Requires:
* jQuery v1.4.4+
* Keyboard v1.17.14+ - https://github.com/Mottie/Keyboard
*
* Setup:
* $('.ui-keyboard-input')
* .keyboard(options)
* .addScramble();
*
* // or if targeting a specific keyboard
* $('#keyboard1')
* .keyboard(options) // keyboard plugin
* .addScramble(); // this keyboard extension
*
*/
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
'use strict';
$.keyboard = $.keyboard || {};
$.fn.addScramble = function(options) {
//Set the default values, use comma to separate the settings, example:
var savedLayout,
defaults = {
targetKeys : /[a-z\d]/i, // keys to randomize
byRow : true, // randomize by row, otherwise randomize all keys
byKeySet : false, // if true, randomize one keyset & duplicate
randomizeOnce : true, // if true, randomize only once on keyboard visible
sameForAll : false, // use the same scrambled keyboard for all targetted keyboards - not fully implemented!
init : null // function(keyboard){}
};
return this.each(function() {
// make sure a keyboard is attached
var o,
base = $(this).data('keyboard'),
namespace = base.namespace + 'Scramble',
opts = base.options;
if (!base || base.scramble_options) { return; }
o = base.scramble_options = $.extend({}, defaults, options);
base.extensionNamespace.push( namespace );
// save create callback
o.orig_create = opts.create;
base.scramble_setup = function($keyboard) {
var $sets, set, $keys, key, index, tmp,
rowIndex, keyboardmap, map, keyboard, row;
$sets = $keyboard.find('.' + $.keyboard.css.keySet);
if ($keyboard.length) {
if (o.byKeySet) {
$sets = $sets.eq(0);
}
for (set = 0; set < $sets.length; set++) {
/*jshint loopfunc:true */
$keys = $sets.eq(set);
rowIndex = 0;
row = [];
map = [];
keyboardmap = [];
keyboard = [];
$keys.children('button, span, br').each(function() {
if (this.tagName === 'BR') {
if (o.byRow) {
row.push(this);
map.push(false);
keyboard[rowIndex] = row;
keyboardmap[rowIndex] = map;
row = [];
map = [];
rowIndex++;
} else {
keyboard[rowIndex] = this;
keyboardmap[rowIndex] = false;
rowIndex++;
}
} else {
tmp = $(this).attr('data-value') || '';
tmp = tmp.length === 1 && o.targetKeys.test(tmp) ? tmp : false;
if (o.byRow) {
row.push( this );
map.push ( tmp );
} else {
keyboard[rowIndex] = this;
keyboardmap[rowIndex] = tmp;
rowIndex++;
}
}
});
// remove original <br> elements
$keys.find('.' + $.keyboard.css.endRow).remove();
// re-map keys
if (!o.byRow) {
row = base.shuffle( keyboard, keyboardmap );
for (key = 0; key < row.length; key++) {
$keys.append(row[key]);
}
} else {
for (index = 0; index < keyboard.length; index++) {
row = base.shuffle( keyboard[index], keyboardmap[index] );
for (key = 0; key < row.length; key++) {
$keys.append(row[key]);
}
}
}
}
if (o.byKeySet) {
$keyboard = base.realign($keyboard);
}
return $keyboard;
}
};
// modified from Fisher-Yates shuffle ( http://bost.ocks.org/mike/shuffle/ )
// to allow not shuffling specifically mapped array elements
base.shuffle = function(array, map) {
var swap, random,
index = array.length;
// While there remain elements to shuffle...
while (index > 0) {
// Pick a remaining element...
random = Math.floor(Math.random() * index);
if (map[index - 1] === false) {
index--;
}
// skip elements that are mapped to false
if (map[index - 1] !== false && map[random] !== false) {
// And swap it with the current element
index--;
swap = array[index];
array[index] = array[random];
array[random] = swap;
}
}
return array;
};
// make other keysets "line-up" with scrambled keyset
base.realign = function($keyboard) {
var selector, typ, pos,
$sets = $keyboard.find('.' + $.keyboard.css.keySet),
$orig = $sets.eq(0);
$sets = $sets.filter(':gt(0)');
$orig.children().each(function(i, cell){
typ = cell.tagName === 'BR';
pos = $(cell).attr('data-pos');
/*jshint loopfunc:true */
$sets.each(function(j, k){
selector = typ ? 'br:first' : 'button[data-pos="' + pos + '"]';
$(k).find(selector).appendTo( k );
});
});
return $keyboard;
};
// create scrambled keyboard layout
opts.create = function() {
var layout = opts.layout;
$.keyboard.builtLayouts[layout] = {
mappedKeys : {},
acceptedKeys : [],
$keyboard: null
};
if ( typeof $.keyboard.builtLayouts[base.orig_layout] === 'undefined' ) {
base.layout = opts.layout = base.orig_layout;
// build original layout, if not already built, e.g. "qwerty"
base.buildKeyboard( base.layout, true );
base.layout = opts.layout = layout;
}
// clone, scramble then save layout
$.keyboard.builtLayouts[layout] = $.extend(true, {}, $.keyboard.builtLayouts[base.orig_layout]);
if (o.randomizeOnce) {
$.keyboard.builtLayouts[layout].$keyboard = base.scramble_setup( $.keyboard.builtLayouts[base.orig_layout].$keyboard.clone() );
}
base.$keyboard = $.keyboard.builtLayouts[layout].$keyboard;
if ( !o.randomizeOnce ) {
base.$el
.unbind($.keyboard.events.kbBeforeVisible + namespace)
.bind($.keyboard.events.kbBeforeVisible + namespace, function(e, kb) {
kb.$keyboard = kb.scramble_setup(kb.$keyboard);
});
}
if ( $.isFunction( o.orig_create ) ) {
o.orig_create( base );
}
};
// scrambled layout already initialized
if (!/^scrambled/.test(opts.layout)) {
base.orig_layout = opts.layout;
savedLayout = savedLayout || 'scrambled' + Math.round(Math.random() * 10000);
opts.layout = o.sameForAll ? savedLayout : 'scrambled' + Math.round(Math.random() * 10000);
}
// special case when keyboard is set to always be open
if (opts.alwaysOpen && base.$keyboard.length) {
setTimeout(function(){
base.$keyboard = base.scramble_setup(base.$keyboard);
if ($.isFunction(o.init)) {
o.init(base);
}
}, 0);
} else {
if ($.isFunction(o.init)) {
o.init(base);
}
}
});
};
}));

View file

@ -0,0 +1,323 @@
/*! jQuery UI Virtual Keyboard Typing Simulator v1.10.0 *//*
* for Keyboard v1.18+ only (9/14/2015)
*
* By Rob Garrison (aka Mottie & Fudgey)
* Licensed under the MIT License
*
* Use this extension with the Virtual Keyboard to simulate
* typing for tutorials or whatever else use you can find
*
* Requires:
* jQuery
* Keyboard plugin : https://github.com/Mottie/Keyboard
*
* Setup:
* $('.ui-keyboard-input')
* .keyboard(options)
* .addTyping(typing-options);
*
* // or if targeting a specific keyboard
* $('#keyboard1')
* .keyboard(options)
* .addTyping(typing-options);
*
* Basic Usage:
* // To disable manual typing on the virtual keyboard, just set "showTyping" option to false
* $('#keyboard-input').keyboard(options).addTyping({ showTyping: false });
*
* // Change the default typing delay (time the virtual keyboard highlights the manually typed key) - default = 250 milliseconds
* $('#keyboard-input').keyboard(options).addTyping({ delay: 500 });
*
* // get keyboard object, open it, then start typing simulation
* $('#keyboard-input').getkeyboard().reveal().typeIn('Hello World', 700);
*
* // get keyboard object, open it, type in "This is a test" with 700ms delay between types, then accept & close the keyboard
* $('#keyboard-input').getkeyboard().reveal().typeIn('This is a test', 700, function(){ $('#keyboard-input').getkeyboard().close(true); });
*/
// EXAMPLES:
// $('#inter').getkeyboard().reveal().typeIn('\tHello \b\n\tWorld', 500);
// $('#meta').getkeyboard().reveal().typeIn('abCDd11123\u2648\u2649\u264A\u264B', 700, function(){ alert('all done!'); });
/*jshint browser:true, jquery:true, unused:false */
/*global require:false, define:false, module:false */
;(function(factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
}(function($) {
$.fn.addTyping = function(options){
//Set the default values, use comma to separate the settings, example:
var defaults = {
showTyping : true,
lockTypeIn : false,
delay : 250
},
$keyboard = $.keyboard;
return this.each(function(){
// make sure a keyboard is attached
var o, base = $(this).data('keyboard');
if (!base) { return; }
// variables
o = base.typing_options = $.extend({}, defaults, options);
base.typing_keymap = {
' ' : 'space',
'"' : '34',
"'" : '39',
'&nbsp;' : 'space',
'\b' : 'bksp', // delete character to the left
'{b}' : 'bksp',
'{d}' : 'del', // delete character to the right
'{l}' : 'left', // move caret left
'{r}' : 'right', // move caret right
'\n' : 'enter',
'\r' : 'enter',
'{e}' : 'enter',
'\t' : 'tab',
'{t}' : 'tab'
};
base.typing_xref = {
8 : 'bksp',
9 : 'tab',
13 : 'enter',
32 : 'space',
46 : 'del'
};
base.typing_event = false;
base.typing_namespace = base.namespace + 'typing';
base.extensionNamespace.push( base.typing_namespace );
// save lockInput setting
o.savedLockInput = base.options.lockInput;
base.typing_setup = function(){
var kbevents = $keyboard.events,
namespace = base.typing_namespace;
base.$el.add( base.$preview ).unbind(namespace);
base.$el
.bind([ kbevents.kbHidden, kbevents.kbInactive, '' ].join( namespace + ' ' ), function(e){
base.typing_reset();
})
.bind( $keyboard.events.kbBeforeVisible + namespace, function(){
base.typing_setup();
});
base.$allKeys
.bind('mousedown' + namespace, function(){
base.typing_reset();
});
base.$preview
.bind('keyup' + namespace, function(e){
if (o.init && o.lockTypeIn) { return false; }
if (e.which >= 37 && e.which <=40) { return; } // ignore arrow keys
if (e.which === 16) { base.shiftActive = false; }
if (e.which === 18) { base.altActive = false; }
if (e.which === 16 || e.which === 18) {
base.showSet();
// Alt key will shift focus to the menu - doesn't work in Windows
setTimeout(function(){ base.$preview.focus(); }, 200);
return;
}
})
// change keyset when either shift or alt is held down
.bind('keydown' + namespace, function(e){
if (o.init && o.lockTypeIn) { return false; }
e.temp = false; // prevent repetitive calls while keydown repeats.
if (e.which === 16) { e.temp = !base.shiftActive; base.shiftActive = true; }
// it should be ok to reset e.temp, since both alt and shift will call this function separately
if (e.which === 18) { e.temp = !base.altActive; base.altActive = true; }
if (e.temp) {
base.showSet();
base.$preview.focus(); // Alt shift focus to the menu
}
base.typing_event = true;
// Simulate key press for tab and backspace since they don't fire the keypress event
if (e.which === 8 || e.which === 9) {
base.typing_findKey( '', e ); // pass event object
}
})
.bind('keypress' + namespace, function(e){
if (o.init && o.lockTypeIn) { return false; }
// Simulate key press on virtual keyboard
if (base.typing_event && !base.options.lockInput) {
base.typing_reset();
base.typing_event = true;
base.typing_findKey( '', e ); // pass event object
}
});
};
base.typing_reset = function(){
base.typing_event = o.init = false;
o.text = '';
o.len = o.current = 0;
base.options.lockInput = o.savedLockInput;
// clearTimeout(base.typing_timer);
};
// Store typing text
base.typeIn = function(txt, delay, callback, e){
if (!base.isVisible()) {
// keyboard was closed
clearTimeout(base.typing_timer);
base.typing_reset();
return;
}
if (!base.typing_event){
if (o.init !== true) {
o.init = true;
base.options.lockInput = o.lockTypeIn;
o.text = txt || o.text || '';
o.len = o.text.length;
o.delay = delay || 300;
o.current = 0; // position in text string
if (callback) {
o.callback = callback;
}
}
// function that loops through and types each character
txt = o.text.substring( o.current, ++o.current );
// add support for curly-wrapped single character: {l}, {r}, {d}, etc.
if ( txt === '{' && o.text.substring( o.current + 1, o.current + 2 ) === '}' ) {
txt += o.text.substring( o.current, o.current += 2 );
}
base.typing_findKey( txt, e );
} else if (typeof txt === 'undefined') {
// typeIn called by user input
base.typing_event = false;
base.options.lockInput = o.savedLockInput;
return;
}
};
base.typing_findKey = function(txt, e){
var tar, m, n, k, key, ks, meta, set,
kbcss = $keyboard.css,
mappedKeys = $keyboard.builtLayouts[base.layout].mappedKeys;
// stop if keyboard is closed
if ( !base.isOpen || !base.$keyboard.length ) { return; }
ks = base.$keyboard.find('.' + kbcss.keySet);
k = txt in base.typing_keymap ? base.typing_keymap[txt] : txt;
// typing_event is true when typing on the actual keyboard - look for actual key
// All of this breaks when the CapLock is on... unable to find a cross-browser method that works.
tar = '.' + kbcss.keyButton + '[data-action="' + k + '"]';
if (base.typing_event && e) {
if (base.typing_xref.hasOwnProperty(e.keyCode || e.which)) {
// special named keys: bksp, tab and enter
tar = '.' + kbcss.keyPrefix + base.typing_xref[e.keyCode || e.which];
} else {
m = String.fromCharCode(e.charCode || e.which);
tar = (mappedKeys.hasOwnProperty(m)) ?
'.' + kbcss.keyButton + '[data-action="' + mappedKeys[m] + '"]' :
'.' + kbcss.keyPrefix + m;
}
}
// find key
key = ks.filter(':visible').find(tar);
if (key.length) {
// key is visible, simulate typing
base.typing_simulateKey(key, txt, e);
} else {
// key not found, check if it is in the keymap (tab, space, enter, etc)
if (base.typing_event) {
key = ks.find(tar);
} else {
// key not found, check if it is in the keymap (tab, space, enter, etc)
n = txt in base.typing_keymap ? base.typing_keymap[txt] : base.processName( txt );
// find actual key on keyboard
key = ks.find('.' + kbcss.keyPrefix + n);
}
// find the keyset
set = key.closest('.' + kbcss.keySet);
// figure out which keyset the key is in then simulate clicking on that meta key, then on the key
if (set.attr('name')) {
// get meta key name
meta = set.attr('name');
// show correct key set
base.shiftActive = /shift/.test(meta);
base.altActive = /alt/.test(meta);
base.metaActive = base.last.keyset[2] = (meta).match(/meta\d+/) || false;
// make the plugin think we're passing it a jQuery object with a name
base.showSet( base.metaActive );
// Add the key
base.typing_simulateKey(key, txt, e);
} else {
if (!base.typing_event) {
// Key doesn't exist on the keyboard, so just enter it
if (txt in base.typing_keymap && base.typing_keymap[txt] in $keyboard.keyaction) {
$keyboard.keyaction[base.typing_keymap[txt]](base, key, e);
} else {
base.insertText(txt);
}
base.checkCombos();
base.$el.trigger( $keyboard.events.kbChange, [ base, base.el ] );
}
}
}
if (o.current <= o.len && o.len !== 0){
if (!base.isVisible()) { return; } // keyboard was closed, abort!!
setTimeout(function(){ base.typeIn(); }, o.delay);
} else if (o.len !== 0){
// o.len is zero when the user typed on the actual keyboard during simulation
base.typing_reset();
if ($.isFunction(o.callback)) {
// ensure all typing animation is done before the callback
setTimeout(function(){
// if the user typed during the key simulation, the "o" variable may sometimes be undefined
if ($.isFunction(o.callback)) {
o.callback(base);
}
}, o.delay);
}
return;
} else {
base.typing_reset();
}
};
// mouseover the key, add the text directly, then mouseout on the key
base.typing_simulateKey = function(el, txt, e){
var len = el.length;
if (len) { el.filter(':visible').trigger('mouseenter' + base.namespace); }
base.typing_timer = setTimeout(function(){
var len = el.length;
if (len) { setTimeout(function(){ el.trigger('mouseleave' + base.namespace); }, o.delay/3); }
if (!base.isVisible()) { return; }
if (!base.typing_event) {
if (txt in base.typing_keymap && base.typing_keymap[txt] in $keyboard.keyaction) {
e = e || $.Event('keypress');
e.target = el; // "Enter" checks for the e.target
$keyboard.keyaction[base.typing_keymap[txt]](base, el, e );
} else {
base.insertText(txt);
}
base.checkCombos();
base.$el.trigger( $keyboard.events.kbChange, [ base, base.el ] );
}
}, o.delay/3);
};
if (o.showTyping) {
// visible event is fired before this extension is initialized, so check!
if (base.options.alwaysOpen && base.isVisible()) {
base.typing_setup();
}
// capture and simulate typing
base.$el
.unbind( $keyboard.events.kbBeforeVisible + base.typing_namespace )
.bind( $keyboard.events.kbBeforeVisible + base.typing_namespace, function(){
base.typing_setup();
});
}
});
};
}));

2460
web_app/js/external/jquery.keyboard.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,920 +0,0 @@
/* http://keith-wood.name/keypad.html
Keypad field entry extension for jQuery v2.0.1.
Written by Keith Wood (kbwood{at}iinet.com.au) August 2008.
Available under the MIT (https://github.com/jquery/jquery/blob/master/LICENSE.txt) license.
Please attribute the author if you use it. */
(function($) { // hide the namespace
var pluginName = 'keypad';
var layoutStandard = [' BSCECA', '_1_2_3_+@X', '_4_5_6_-@U', '_7_8_9_*@E', '_0_._=_/'];
/** Create the keypad plugin.
<p>Sets an input field to popup a keypad for keystroke entry,
or creates an inline keypad in a <code>div</code> or <code>span</code>.</p>
<p>Expects HTML like:</p>
<pre>&lt;input type="text"> or
&lt;div&gt;&lt;/div&gt;</pre>
<p>Provide inline configuration like:</p>
<pre>&lt;input type="text" data-keypad="name: 'value'"/></pre>
@module Keypad
@augments JQPlugin
@example $(selector).keypad() */
$.JQPlugin.createPlugin({
/** The name of the plugin. */
name: pluginName,
/** Keypad before show callback.
Triggered before the keypad is shown.
@callback beforeShowCallback
@param div {jQuery} The div to be shown.
@param inst {object} The current instance settings. */
/** Keypad on keypress callback.
Triggered when a key on the keypad is pressed.
@callback keypressCallback
@param key {string} The key just pressed.
@param value {string} The full value entered so far.
@param inst {object} The current instance settings. */
/** Keypad on close callback.
Triggered when the keypad is closed.
@callback closeCallback
@param value {string} The full value entered so far.
@param inst {object} The current instance settings. */
/** Keypad is alphabetic callback.
Triggered when an alphabetic key needs to be identified.
@callback isAlphabeticCallback
@param ch {string} The key to check.
@return {boolean} True if this key is alphabetic, false if not.
@example isAlphabetic: function(ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
} */
/** Keypad is numeric callback.
Triggered when an numeric key needs to be identified.
@callback isNumericCallback
@param ch {string} The key to check.
@return {boolean} True if this key is numeric, false if not.
@example isNumeric: function(ch) {
return (ch >= '0' && ch <= '9');
} */
/** Keypad to upper callback.
Triggered to convert keys to upper case.
@callback toUpperCallback
@param ch {string} The key to convert.
@return {string} The upper case version of this key.
@example toUpper: function(ch) {
return ch.toUpperCase();
} */
/** Default settings for the plugin.
@property [showOn='focus'] {string} 'focus' for popup on focus, 'button' for trigger button, or 'both' for either.
@property [buttonImage=''] {string} URL for trigger button image.
@property [buttonImageOnly=false] {boolean} True if the image appears alone, false if it appears on a button.
@property [showAnim='show'] {string} Name of jQuery animation for popup.
@property [showOptions=null] {object} Options for enhanced animations.
@property [duration='normal'] {string|number} Duration of display/closure.
@property [appendText=''] {string} Display text following the text field, e.g. showing the format.
@property [useThemeRoller=false] {boolean} True to add ThemeRoller classes.
@property [keypadClass=''] {string} Additional CSS class for the keypad for an instance.
@property [prompt=''] {string} Display text at the top of the keypad.
@property [layout=this.numericLayout] {string} Layout of keys.
@property [separator=''] {string} Separator character between keys.
@property [target=null] {string|jQuery|Element} Input target for an inline keypad.
@property [keypadOnly=true] {boolean} True for entry only via the keypad, false for real keyboard too.
@property [randomiseAlphabetic=false] {boolean} True to randomise the alphabetic key positions, false to keep in order.
@property [randomiseNumeric=false] {boolean} True to randomise the numeric key positions, false to keep in order.
@property [randomiseOther=false] {boolean} True to randomise the other key positions, false to keep in order.
@property [randomiseAll=false] {boolean} True to randomise all key positions, false to keep in order.
@property [beforeShow=null] {beforeShowCallback} Callback before showing the keypad.
@property [onKeypress=null] {keypressCallback} Callback when a key is selected.
@property [onClose=null] {closeCallback} Callback when the panel is closed. */
defaultOptions: {
showOn: 'focus',
buttonImage: '',
buttonImageOnly: false,
showAnim: 'show',
showOptions: null,
duration: 'normal',
appendText: '',
useThemeRoller: false,
keypadClass: '',
prompt: '',
layout: [], // Set at the end
separator: '',
target: null,
keypadOnly: true,
randomiseAlphabetic: false,
randomiseNumeric: false,
randomiseOther: false,
randomiseAll: false,
beforeShow: null,
onKeypress: null,
onClose: null
},
/** Localisations for the plugin.
Entries are objects indexed by the language code ('' being the default US/English).
Each object has the following attributes.
@property [buttonText='...'] {string} Display text for trigger button.
@property [buttonStatus='Open the keypad'] {string} Status text for trigger button.
@property [closeText='Close'] {string} Display text for close link.
@property [closeStatus='Close the keypad'] {string} Status text for close link.
@property [clearText='Clear'] {string} Display text for clear link.
@property [clearStatus='Erase all the text'] {string} Status text for clear link.
@property [backText='Back'] {string} Display text for back link.
@property [backStatus='Erase the previous character'] {string} Status text for back link.
@property [spacebarText='&#160;'] {string} Display text for space bar.
@property [spacebarStatus='Space'] {string} Status text for space bar.
@property [enterText='Enter'] {string} Display text for carriage return.
@property [enterStatus='Carriage return'] {string} Status text for carriage return.
@property [tabText='→'] {string} Display text for tab.
@property [tabStatus='Horizontal tab'] {string} Status text for tab.
@property [shiftText='Shift'] {string} Display text for shift link.
@property [shiftStatus='Toggle upper/lower case characters'] {string} Status text for shift link.
@property [alphabeticLayout=this.qwertyAlphabetic] {string} Default layout for alphabetic characters.
@property [fullLayout=this.qwertyLayout] {string} Default layout for full keyboard.
@property [isAlphabetic=this.isAlphabetic] {isAlphabeticCallback} Function to determine if character is alphabetic.
@property [isNumeric=this.isNumeric] {isNumericCallback} Function to determine if character is numeric.
@property [toUpper=this.toUpper] {toUpperCallback} Function to convert characters to upper case.
@property [isRTL=false] {boolean} True if right-to-left language, false if left-to-right. */
regionalOptions: { // Available regional settings, indexed by language/country code
'': { // Default regional settings - English/US
buttonText: '...',
buttonStatus: 'Open the keypad',
closeText: 'Close',
closeStatus: 'Close the keypad',
clearText: 'Clear',
clearStatus: 'Erase all the text',
backText: 'Back',
backStatus: 'Erase the previous character',
spacebarText: '&#160;',
spacebarStatus: 'Space',
enterText: 'Enter',
enterStatus: 'Carriage return',
tabText: '→',
tabStatus: 'Horizontal tab',
shiftText: 'Shift',
shiftStatus: 'Toggle upper/lower case characters',
alphabeticLayout: [], // Set at the end
fullLayout: [],
isAlphabetic: null,
isNumeric: null,
toUpper: null,
isRTL: false
}
},
/** Names of getter methods - those that can't be chained. */
_getters: ['isDisabled'],
_curInst: null, // The current instance in use
_disabledFields: [], // List of keypad fields that have been disabled
_keypadShowing: false, // True if the popup panel is showing , false if not
_keyCode: 0,
_specialKeys: [],
_mainDivClass: pluginName + '-popup', // The main keypad division class
_inlineClass: pluginName + '-inline', // The inline marker class
_appendClass: pluginName + '-append', // The append marker class
_triggerClass: pluginName + '-trigger', // The trigger marker class
_disableClass: pluginName + '-disabled', // The disabled covering marker class
_inlineEntryClass: pluginName + '-keyentry', // The inline entry marker class
_rtlClass: pluginName + '-rtl', // The right-to-left marker class
_rowClass: pluginName + '-row', // The keypad row marker class
_promptClass: pluginName + '-prompt', // The prompt marker class
_specialClass: pluginName + '-special', // The special key marker class
_namePrefixClass: pluginName + '-', // The key name marker class prefix
_keyClass: pluginName + '-key', // The key marker class
_keyDownClass: pluginName + '-key-down', // The key down marker class
// Standard US keyboard alphabetic layout
qwertyAlphabetic: ['qwertyuiop', 'asdfghjkl', 'zxcvbnm'],
// Standard US keyboard layout
qwertyLayout: ['!@#$%^&*()_=' + this.HALF_SPACE + this.SPACE + this.CLOSE,
this.HALF_SPACE + '`~[]{}<>\\|/' + this.SPACE + '789',
'qwertyuiop\'"' + this.HALF_SPACE + '456',
this.HALF_SPACE + 'asdfghjkl;:' + this.SPACE + '123',
this.SPACE + 'zxcvbnm,.?' + this.SPACE + this.HALF_SPACE + '-0+',
'' + this.TAB + this.ENTER + this.SPACE_BAR + this.SHIFT +
this.HALF_SPACE + this.BACK + this.CLEAR],
/** Add the definition of a special key.
@param id {string} The identifier for this key - access via <code>$.keypad.xxx</code>.<id>.
@param name {string} The prefix for localisation strings and the suffix for a class name.
@param action {function} The action performed for this key - receives <code>inst</code> as a parameter.
@param noHighlight {boolean} True to suppress highlight when using ThemeRoller.
@return {Keypad} The keypad object for chaining further calls.
@example $.keypad.addKeyDef('CLEAR', 'clear', function(inst) { plugin._clearValue(inst); }); */
addKeyDef: function(id, name, action, noHighlight) {
if (this._keyCode == 32) {
throw 'Only 32 special keys allowed';
}
this[id] = String.fromCharCode(this._keyCode++);
this._specialKeys.push({code: this[id], id: id, name: name,
action: action, noHighlight: noHighlight});
return this;
},
/** Additional setup for the keypad.
Create popup div. */
_init: function() {
this.mainDiv = $('<div class="' + this._mainDivClass + '" style="display: none;"></div>');
this._super();
},
_instSettings: function(elem, options) {
var inline = !elem[0].nodeName.toLowerCase().match(/input|textarea/);
return {_inline: inline, ucase: false,
_mainDiv: (inline ? $('<div class="' + this._inlineClass + '"></div>') : plugin.mainDiv)};
},
_postAttach: function(elem, inst) {
if (inst._inline) {
elem.append(inst._mainDiv).
on('click.' + inst.name, function() { inst._input.focus(); });
this._updateKeypad(inst);
}
else if (elem.is(':disabled')) {
this.disable(elem);
}
},
/** Determine the input field for the keypad.
@private
@param elem {jQuery} The target control.
@param inst {object} The instance settings. */
_setInput: function(elem, inst) {
inst._input = $(!inst._inline ? elem : inst.options.target ||
'<input type="text" class="' + this._inlineEntryClass + '" disabled/>');
if (inst._inline) {
elem.find('input').remove();
if (!inst.options.target) {
elem.append(inst._input);
}
}
},
_optionsChanged: function(elem, inst, options) {
$.extend(inst.options, options);
elem.off('.' + inst.name).
siblings('.' + this._appendClass).remove().end().
siblings('.' + this._triggerClass).remove();
var appendText = inst.options.appendText;
if (appendText) {
elem[inst.options.isRTL ? 'before' : 'after'](
'<span class="' + this._appendClass + '">' + appendText + '</span>');
}
if (!inst._inline) {
if (inst.options.showOn == 'focus' || inst.options.showOn == 'both') {
// pop-up keypad when in the marked field
elem.on('focus.' + inst.name, this.show).
on('keydown.' + inst.name, this._doKeyDown);
}
if (inst.options.showOn == 'button' || inst.options.showOn == 'both') {
// pop-up keypad when button clicked
var buttonStatus = inst.options.buttonStatus;
var buttonImage = inst.options.buttonImage;
var trigger = $(inst.options.buttonImageOnly ?
$('<img src="' + buttonImage + '" alt="' +
buttonStatus + '" title="' + buttonStatus + '"/>') :
$('<button type="button" title="' + buttonStatus + '"></button>').
html(buttonImage == '' ? inst.options.buttonText :
$('<img src="' + buttonImage + '" alt="' +
buttonStatus + '" title="' + buttonStatus + '"/>')));
elem[inst.options.isRTL ? 'before' : 'after'](trigger);
trigger.addClass(this._triggerClass).click(function() {
if (plugin._keypadShowing && plugin._lastField == elem[0]) {
plugin.hide();
}
else {
plugin.show(elem[0]);
}
return false;
});
}
}
inst.saveReadonly = elem.attr('readonly');
elem[inst.options.keypadOnly ? 'attr' : 'removeAttr']('readonly', true).
on('setData.' + inst.name, function(event, key, value) {
inst.options[key] = value;
}).
on('getData.' + inst.name, function(event, key) {
return inst.options[key];
});
this._setInput(elem, inst);
this._updateKeypad(inst);
},
_preDestroy: function(elem, inst) {
if (this._curInst == inst) {
this.hide();
}
elem.siblings('.' + this._appendClass).remove().end().
siblings('.' + this._triggerClass).remove().end().
prev('.' + this._inlineEntryClass).remove();
elem.empty().off('.' + inst.name)
[inst.saveReadonly ? 'attr' : 'removeAttr']('readonly', true);
inst._input.removeData(inst.name);
},
/** Enable the keypad for a jQuery selection.
@param elem {Element} The target text field.
@example $(selector).keypad('enable'); */
enable: function(elem) {
elem = $(elem);
if (!elem.hasClass(this._getMarker())) {
return;
}
var nodeName = elem[0].nodeName.toLowerCase();
if (nodeName.match(/input|textarea/)) {
elem.prop('disabled', false).
siblings('button.' + this._triggerClass).prop('disabled', false).end().
siblings('img.' + this._triggerClass).css({opacity: '1.0', cursor: ''});
}
else if (nodeName.match(/div|span/)) {
elem.children('.' + this._disableClass).remove();
this._getInst(elem)._mainDiv.find('button').prop('disabled', false);
}
this._disabledFields = $.map(this._disabledFields,
function(value) { return (value == elem[0] ? null : value); }); // delete entry
},
/** Disable the keypad for a jQuery selection.
@param elem {Element} The target text field.
@example $(selector).keypad('disable'); */
disable: function(elem) {
elem = $(elem);
if (!elem.hasClass(this._getMarker())) {
return;
}
var nodeName = elem[0].nodeName.toLowerCase();
if (nodeName.match(/input|textarea/)) {
elem.prop('disabled', true).
siblings('button.' + this._triggerClass).prop('disabled', true).end().
siblings('img.' + this._triggerClass).css({opacity: '0.5', cursor: 'default'});
}
else if (nodeName.match(/div|span/)) {
var inline = elem.children('.' + this._inlineClass);
var offset = inline.offset();
var relOffset = {left: 0, top: 0};
inline.parents().each(function() {
if ($(this).css('position') == 'relative') {
relOffset = $(this).offset();
return false;
}
});
elem.prepend('<div class="' + this._disableClass + '" style="width: ' +
inline.outerWidth() + 'px; height: ' + inline.outerHeight() +
'px; left: ' + (offset.left - relOffset.left) +
'px; top: ' + (offset.top - relOffset.top) + 'px;"></div>');
this._getInst(elem)._mainDiv.find('button').prop('disabled', true);
}
this._disabledFields = $.map(this._disabledFields,
function(value) { return (value == elem[0] ? null : value); }); // delete entry
this._disabledFields[this._disabledFields.length] = elem[0];
},
/** Is the text field disabled as a keypad?
@param elem {Element} The target text field.
@return {boolean} True if disabled, false if enabled.
@example var disabled = $(selector).keypad('isDisabled'); */
isDisabled: function(elem) {
return (elem && $.inArray(elem, this._disabledFields) > -1);
},
/** Pop-up the keypad for a given text field.
@param elem {Element|Event} The text field attached to the keypad or event if triggered by focus.
@example $(selector).keypad('show'); */
show: function(elem) {
elem = elem.target || elem;
if (plugin.isDisabled(elem) || plugin._lastField == elem) { // already here
return;
}
var inst = plugin._getInst(elem);
plugin.hide(null, '');
plugin._lastField = elem;
plugin._pos = plugin._findPos(elem);
plugin._pos[1] += elem.offsetHeight; // add the height
var isFixed = false;
$(elem).parents().each(function() {
isFixed |= $(this).css('position') == 'fixed';
return !isFixed;
});
var offset = {left: plugin._pos[0], top: plugin._pos[1]};
plugin._pos = null;
// determine sizing offscreen
inst._mainDiv.css({position: 'absolute', display: 'block', top: '-1000px', width: 'auto'});
plugin._updateKeypad(inst);
// and adjust position before showing
offset = plugin._checkOffset(inst, offset, isFixed);
inst._mainDiv.css({position: (isFixed ? 'fixed' : 'absolute'), display: 'none',
left: offset.left + 'px', top: offset.top + 'px'});
var duration = inst.options.duration;
var showAnim = inst.options.showAnim;
var postProcess = function() {
plugin._keypadShowing = true;
};
if ($.effects && ($.effects[showAnim] || ($.effects.effect && $.effects.effect[showAnim]))) {
var data = inst._mainDiv.data(); // Update old effects data
for (var key in data) {
if (key.match(/^ec\.storage\./)) {
data[key] = inst._mainDiv.css(key.replace(/ec\.storage\./, ''));
}
}
inst._mainDiv.data(data).show(showAnim,
inst.options.showOptions || {}, duration, postProcess);
}
else {
inst._mainDiv[showAnim || 'show']((showAnim ? duration : 0), postProcess);
}
if (inst._input[0].type != 'hidden') {
inst._input[0].focus();
}
plugin._curInst = inst;
},
/** Generate the keypad content.
@private
@param inst {object} The instance settings. */
_updateKeypad: function(inst) {
var borders = this._getBorders(inst._mainDiv);
inst._mainDiv.empty().append(this._generateHTML(inst)).
removeClass().addClass(inst.options.keypadClass +
(inst.options.useThemeRoller ? ' ui-widget ui-widget-content' : '') +
(inst.options.isRTL ? ' ' + this._rtlClass : '') + ' ' +
(inst._inline ? this._inlineClass : this._mainDivClass));
if ($.isFunction(inst.options.beforeShow)) {
inst.options.beforeShow.apply((inst._input ? inst._input[0] : null),
[inst._mainDiv, inst]);
}
},
/** Retrieve the size of left and top borders for an element.
@private
@param elem {jQuery} The element of interest.
@return {number[]} The left and top borders. */
_getBorders: function(elem) {
var convert = function(value) {
return {thin: 1, medium: 3, thick: 5}[value] || value;
};
return [parseFloat(convert(elem.css('border-left-width'))),
parseFloat(convert(elem.css('border-top-width')))];
},
/** Check positioning to remain on screen.
@private
@param inst {object} The instance settings.
@param offset {object} The current offset.
@param isFixed {boolean} True if the text field is fixed in position.
@return {object} The updated offset. */
_checkOffset: function(inst, offset, isFixed) {
var pos = inst._input ? this._findPos(inst._input[0]) : null;
var browserWidth = window.innerWidth || document.documentElement.clientWidth;
var browserHeight = window.innerHeight || document.documentElement.clientHeight;
var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
var scrollY = document.documentElement.scrollTop || document.body.scrollTop;
// recalculate width as otherwise set to 100%
var width = 0;
inst._mainDiv.find(':not(div)').each(function() {
width = Math.max(width, this.offsetLeft + $(this).outerWidth(true));
});
inst._mainDiv.css('width', width + 1);
// reposition keypad panel horizontally if outside the browser window
if (inst.options.isRTL ||
(offset.left + inst._mainDiv.outerWidth() - scrollX) > browserWidth) {
offset.left = Math.max((isFixed ? 0 : scrollX),
pos[0] + (inst._input ? inst._input.outerWidth() : 0) -
(isFixed ? scrollX : 0) - inst._mainDiv.outerWidth());
}
else {
offset.left = Math.max((isFixed ? 0 : scrollX), offset.left - (isFixed ? scrollX : 0));
}
// reposition keypad panel vertically if outside the browser window
if ((offset.top + inst._mainDiv.outerHeight() - scrollY) > browserHeight) {
offset.top = Math.max((isFixed ? 0 : scrollY),
pos[1] - (isFixed ? scrollY : 0) - inst._mainDiv.outerHeight());
}
else {
offset.top = Math.max((isFixed ? 0 : scrollY), offset.top - (isFixed ? scrollY : 0));
}
return offset;
},
/** Find an object's position on the screen.
@private
@param obj {Element} The element to find the position for.
@return {number[]} The element's position. */
_findPos: function(obj) {
while (obj && (obj.type == 'hidden' || obj.nodeType != 1)) {
obj = obj.nextSibling;
}
var position = $(obj).offset();
return [position.left, position.top];
},
/** Hide the keypad from view.
@param elem {Element} The text field attached to the keypad.
@param duration {string} The duration over which to close the keypad.
@example $(selector).keypad('hide') */
hide: function(elem, duration) {
var inst = this._curInst;
if (!inst || (elem && inst != $.data(elem, this.name))) {
return;
}
if (this._keypadShowing) {
duration = (duration != null ? duration : inst.options.duration);
var showAnim = inst.options.showAnim;
if ($.effects && ($.effects[showAnim] || ($.effects.effect && $.effects.effect[showAnim]))) {
inst._mainDiv.hide(showAnim, inst.options.showOptions || {}, duration);
}
else {
inst._mainDiv[(showAnim == 'slideDown' ? 'slideUp' :
(showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))](showAnim ? duration : 0);
}
}
if ($.isFunction(inst.options.onClose)) {
inst.options.onClose.apply((inst._input ? inst._input[0] : null), // trigger custom callback
[inst._input.val(), inst]);
}
if (this._keypadShowing) {
this._keypadShowing = false;
this._lastField = null;
}
if (inst._inline) {
inst._input.val('');
}
this._curInst = null;
},
/** Handle keystrokes.
@private
@param event {Event} The key event. */
_doKeyDown: function(event) {
if (event.keyCode == 9) { // Tab out
plugin.mainDiv.stop(true, true);
plugin.hide();
}
},
/** Close keypad if clicked elsewhere.
@private
@param event {Event} The mouseclick details. */
_checkExternalClick: function(event) {
if (!plugin._curInst) {
return;
}
var target = $(event.target);
if (target.closest('.' + plugin._mainDivClass).length === 0 &&
!target.hasClass(plugin._getMarker()) &&
target.closest('.' + plugin._triggerClass).length === 0 &&
plugin._keypadShowing) {
plugin.hide();
}
},
/** Toggle between upper and lower case.
@private
@param inst {object} The instance settings. */
_shiftKeypad: function(inst) {
inst.ucase = !inst.ucase;
this._updateKeypad(inst);
inst._input.focus(); // for further typing
},
/** Erase the text field.
@private
@param inst {object} The instance settings. */
_clearValue: function(inst) {
this._setValue(inst, '', 0);
this._notifyKeypress(inst, plugin.DEL);
},
/** Erase the last character.
@private
@param inst {object} The instance settings. */
_backValue: function(inst) {
var elem = inst._input[0];
var value = inst._input.val();
var range = [value.length, value.length];
range = (inst._input.prop('readonly') || inst._input.prop('disabled') ? range :
(elem.setSelectionRange /* Mozilla */ ? [elem.selectionStart, elem.selectionEnd] :
(elem.createTextRange /* IE */ ? this._getIERange(elem) : range)));
this._setValue(inst, (value.length == 0 ? '' :
value.substr(0, range[0] - 1) + value.substr(range[1])), range[0] - 1);
this._notifyKeypress(inst, plugin.BS);
},
/** Update the text field with the selected value.
@private
@param inst {object} The instance settings.
@param value {string} The new character to add. */
_selectValue: function(inst, value) {
this.insertValue(inst._input[0], value);
this._setValue(inst, inst._input.val());
this._notifyKeypress(inst, value);
},
/** Update the text field with the selected value.
@param input {string|Element|jQuery} The jQuery selector, input field, or jQuery collection.
@param value {string} The new character to add.
@example $.keypad.insertValue(field, 'abc'); */
insertValue: function(input, value) {
input = (input.jquery ? input : $(input));
var elem = input[0];
var newValue = input.val();
var range = [newValue.length, newValue.length];
range = (input.attr('readonly') || input.attr('disabled') ? range :
(elem.setSelectionRange /* Mozilla */ ? [elem.selectionStart, elem.selectionEnd] :
(elem.createTextRange /* IE */ ? this._getIERange(elem) : range)));
input.val(newValue.substr(0, range[0]) + value + newValue.substr(range[1]));
pos = range[0] + value.length;
if (input.is(':visible')) {
input.focus(); // for further typing
}
if (elem.setSelectionRange) { // Mozilla
if (input.is(':visible')) {
elem.setSelectionRange(pos, pos);
}
}
else if (elem.createTextRange) { // IE
range = elem.createTextRange();
range.move('character', pos);
range.select();
}
},
/** Get the coordinates for the selected area in the text field in IE.
@private
@param elem {Element} The target text field.
@return {number[]} The start and end positions of the selection. */
_getIERange: function(elem) {
elem.focus();
var selectionRange = document.selection.createRange().duplicate();
// Use two ranges: before and selection
var beforeRange = this._getIETextRange(elem);
beforeRange.setEndPoint('EndToStart', selectionRange);
// Check each range for trimmed newlines by shrinking the range by one
// character and seeing if the text property has changed. If it has not
// changed then we know that IE has trimmed a \r\n from the end.
var checkCRLF = function(range) {
var origText = range.text;
var text = origText;
var finished = false;
while (true) {
if (range.compareEndPoints('StartToEnd', range) == 0) {
break;
}
else {
range.moveEnd('character', -1);
if (range.text == origText) {
text += '\r\n';
}
else {
break;
}
}
}
return text;
};
var beforeText = checkCRLF(beforeRange);
var selectionText = checkCRLF(selectionRange);
return [beforeText.length, beforeText.length + selectionText.length];
},
/** Create an IE text range for the text field.
@private
@param elem {Element} The target text field.
@return {object} The corresponding text range. */
_getIETextRange: function(elem) {
var isInput = (elem.nodeName.toLowerCase() == 'input');
var range = (isInput ? elem.createTextRange() : document.body.createTextRange());
if (!isInput) {
range.moveToElementText(elem); // Selects all the text for a textarea
}
return range;
},
/** Set the text field to the selected value, and trigger any on change event.
@private
@param inst {object} The instance settings.
@param value {string} The new value for the text field. */
_setValue: function(inst, value) {
var maxlen = inst._input.attr('maxlength');
if (maxlen > -1) {
value = value.substr(0, maxlen);
}
inst._input.val(value);
if (!$.isFunction(inst.options.onKeypress)) {
inst._input.trigger('change'); // fire the change event
}
},
/** Notify clients of a keypress.
@private
@param inst {object} The instance settings.
@param key {string} The character pressed. */
_notifyKeypress: function(inst, key) {
if ($.isFunction(inst.options.onKeypress)) { // trigger custom callback
inst.options.onKeypress.apply((inst._input ? inst._input[0] : null),
[key, inst._input.val(), inst]);
}
},
/** Generate the HTML for the current state of the keypad.
@private
@param inst {object} The instance settings.
@return {jQuery} The HTML for this keypad. */
_generateHTML: function(inst) {
var html = (!inst.options.prompt ? '' : '<div class="' + this._promptClass +
(inst.options.useThemeRoller ? ' ui-widget-header ui-corner-all' : '') + '">' +
inst.options.prompt + '</div>');
var layout = this._randomiseLayout(inst);
for (var i = 0; i < layout.length; i++) {
html += '<div class="' + this._rowClass + '">';
var keys = layout[i].split(inst.options.separator);
for (var j = 0; j < keys.length; j++) {
if (inst.ucase) {
keys[j] = inst.options.toUpper(keys[j]);
}
var keyDef = this._specialKeys[keys[j].charCodeAt(0)];
if (keyDef) {
html += (keyDef.action ? '<button type="button" class="' +
this._specialClass + ' ' + this._namePrefixClass + keyDef.name +
(inst.options.useThemeRoller ? ' ui-corner-all ui-state-default' +
(keyDef.noHighlight ? '' : ' ui-state-highlight') : '') +
'" title="' + inst.options[keyDef.name + 'Status'] + '">' +
(inst.options[keyDef.name + 'Text'] || '&#160;') + '</button>' :
'<div class="' + this._namePrefixClass + keyDef.name + '"></div>');
}
else {
html += '<button type="button" class="' + this._keyClass +
(inst.options.useThemeRoller ? ' ui-corner-all ui-state-default' : '') +
'">' + (keys[j] == ' ' ? '&#160;' : keys[j]) + '</button>';
}
}
html += '</div>';
}
html = $(html);
var thisInst = inst;
var activeClasses = this._keyDownClass +
(inst.options.useThemeRoller ? ' ui-state-active' : '');
html.find('button').mousedown(function() { $(this).addClass(activeClasses); }).
mouseup(function() { $(this).removeClass(activeClasses); }).
mouseout(function() { $(this).removeClass(activeClasses); }).
filter('.' + this._keyClass).
click(function() { plugin._selectValue(thisInst, $(this).text()); });
$.each(this._specialKeys, function(i, keyDef) {
html.find('.' + plugin._namePrefixClass + keyDef.name).click(function() {
keyDef.action.apply(thisInst._input, [thisInst]);
});
});
return html;
},
/** Check whether characters should be randomised, and, if so, produce the randomised layout.
@private
@param inst {object} The instance settings.
@return {string[]} The layout with any requested randomisations applied. */
_randomiseLayout: function(inst) {
if (!inst.options.randomiseNumeric && !inst.options.randomiseAlphabetic &&
!inst.options.randomiseOther && !inst.options.randomiseAll) {
return inst.options.layout;
}
var numerics = [];
var alphas = [];
var others = [];
var newLayout = [];
// Find characters of different types
for (var i = 0; i < inst.options.layout.length; i++) {
newLayout[i] = '';
var keys = inst.options.layout[i].split(inst.options.separator);
for (var j = 0; j < keys.length; j++) {
if (this._isControl(keys[j])) {
continue;
}
if (inst.options.randomiseAll) {
others.push(keys[j]);
}
else if (inst.options.isNumeric(keys[j])) {
numerics.push(keys[j]);
}
else if (inst.options.isAlphabetic(keys[j])) {
alphas.push(keys[j]);
}
else {
others.push(keys[j]);
}
}
}
// Shuffle them
if (inst.options.randomiseNumeric) {
this._shuffle(numerics);
}
if (inst.options.randomiseAlphabetic) {
this._shuffle(alphas);
}
if (inst.options.randomiseOther || inst.options.randomiseAll) {
this._shuffle(others);
}
var n = 0;
var a = 0;
var o = 0;
// And replace them in the layout
for (var i = 0; i < inst.options.layout.length; i++) {
var keys = inst.options.layout[i].split(inst.options.separator);
for (var j = 0; j < keys.length; j++) {
newLayout[i] += (this._isControl(keys[j]) ? keys[j] :
(inst.options.randomiseAll ? others[o++] :
(inst.options.isNumeric(keys[j]) ? numerics[n++] :
(inst.options.isAlphabetic(keys[j]) ? alphas[a++] :
others[o++])))) + inst.options.separator;
}
}
return newLayout;
},
/** Is a given character a control character?
@private
@param ch {string} The character to test.
@return {boolean} True if a control character, false if not. */
_isControl: function(ch) {
return ch < ' ';
},
/** Is a given character alphabetic?
@param ch {string} The character to test.
@return {boolean} True if alphabetic, false if not. */
isAlphabetic: function(ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
},
/** Is a given character numeric?
@param ch {string} The character to test.
@return {boolean} True if numeric, false if not. */
isNumeric: function(ch) {
return (ch >= '0' && ch <= '9');
},
/** Convert a character to upper case.
@param ch {string} The character to convert.
@return {string} Its uppercase version. */
toUpper: function(ch) {
return ch.toUpperCase();
},
/** Randomise the contents of an array.
@private
@param values {string[]} The array to rearrange. */
_shuffle: function(values) {
for (var i = values.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * values.length);
var ch = values[i];
values[i] = values[j];
values[j] = ch;
}
}
});
var plugin = $.keypad;
// Initialise the key definitions
plugin.addKeyDef('CLOSE', 'close', function(inst) {
plugin._curInst = (inst._inline ? inst : plugin._curInst);
plugin.hide();
});
plugin.addKeyDef('CLEAR', 'clear', function(inst) { plugin._clearValue(inst); });
plugin.addKeyDef('BACK', 'back', function(inst) { plugin._backValue(inst); });
plugin.addKeyDef('SHIFT', 'shift', function(inst) { plugin._shiftKeypad(inst); });
plugin.addKeyDef('SPACE_BAR', 'spacebar', function(inst) { plugin._selectValue(inst, ' '); }, true);
plugin.addKeyDef('SPACE', 'space');
plugin.addKeyDef('HALF_SPACE', 'half-space');
plugin.addKeyDef('ENTER', 'enter', function(inst) { plugin._selectValue(inst, '\x0D'); }, true);
plugin.addKeyDef('TAB', 'tab', function(inst) { plugin._selectValue(inst, '\x09'); }, true);
// Initialise the layouts and settings
plugin.numericLayout = ['123' + plugin.CLOSE, '456' + plugin.CLEAR, '789' + plugin.BACK, plugin.SPACE + '0'];
plugin.qwertyLayout = ['!@#$%^&*()_=' + plugin.HALF_SPACE + plugin.SPACE + plugin.CLOSE,
plugin.HALF_SPACE + '`~[]{}<>\\|/' + plugin.SPACE + '789',
'qwertyuiop\'"' + plugin.HALF_SPACE + '456',
plugin.HALF_SPACE + 'asdfghjkl;:' + plugin.SPACE + '123',
plugin.SPACE + 'zxcvbnm,.?' + plugin.SPACE + plugin.HALF_SPACE + '-0+',
'' + plugin.TAB + plugin.ENTER + plugin.SPACE_BAR + plugin.SHIFT +
plugin.HALF_SPACE + plugin.BACK + plugin.CLEAR],
$.extend(plugin.regionalOptions[''],
{alphabeticLayout: plugin.qwertyAlphabetic, fullLayout: plugin.qwertyLayout,
isAlphabetic: plugin.isAlphabetic, isNumeric: plugin.isNumeric, toUpper: plugin.toUpper});
plugin.setDefaults($.extend({layout: plugin.numericLayout}, plugin.regionalOptions['']));
// Add the keypad division and external click check
$(function() {
$(document.body).append(plugin.mainDiv).
on('mousedown.' + pluginName, plugin._checkExternalClick);
});
})(jQuery);

Binary file not shown.

221
web_app/js/external/jquery.mousewheel.js vendored Normal file
View file

@ -0,0 +1,221 @@
/* Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh)
* Licensed under the MIT License (LICENSE.txt).
*
* Version: 3.1.12
*
* Requires: jQuery 1.2.2+
*/
/*! Mousewheel version: 3.1.12 * (c) 2014 Brandon Aaron * MIT License */
(function (factory) {
if ( typeof define === 'function' && define.amd ) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node/CommonJS style for Browserify
module.exports = factory;
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
var toFix = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'],
toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ?
['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
slice = Array.prototype.slice,
nullLowestDeltaTimeout, lowestDelta;
if ( $.event.fixHooks ) {
for ( var i = toFix.length; i; ) {
$.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
}
}
var special = $.event.special.mousewheel = {
version: '3.1.12',
setup: function() {
if ( this.addEventListener ) {
for ( var i = toBind.length; i; ) {
this.addEventListener( toBind[--i], handler, false );
}
} else {
this.onmousewheel = handler;
}
// Store the line height and page height for this particular element
$.data(this, 'mousewheel-line-height', special.getLineHeight(this));
$.data(this, 'mousewheel-page-height', special.getPageHeight(this));
},
teardown: function() {
if ( this.removeEventListener ) {
for ( var i = toBind.length; i; ) {
this.removeEventListener( toBind[--i], handler, false );
}
} else {
this.onmousewheel = null;
}
// Clean up the data we added to the element
$.removeData(this, 'mousewheel-line-height');
$.removeData(this, 'mousewheel-page-height');
},
getLineHeight: function(elem) {
var $elem = $(elem),
$parent = $elem['offsetParent' in $.fn ? 'offsetParent' : 'parent']();
if (!$parent.length) {
$parent = $('body');
}
return parseInt($parent.css('fontSize'), 10) || parseInt($elem.css('fontSize'), 10) || 16;
},
getPageHeight: function(elem) {
return $(elem).height();
},
settings: {
adjustOldDeltas: true, // see shouldAdjustOldDeltas() below
normalizeOffset: true // calls getBoundingClientRect for each event
}
};
$.fn.extend({
mousewheel: function(fn) {
return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel');
},
unmousewheel: function(fn) {
return this.unbind('mousewheel', fn);
}
});
function handler(event) {
var orgEvent = event || window.event,
args = slice.call(arguments, 1),
delta = 0,
deltaX = 0,
deltaY = 0,
absDelta = 0,
offsetX = 0,
offsetY = 0;
event = $.event.fix(orgEvent);
event.type = 'mousewheel';
// Old school scrollwheel delta
if ( 'detail' in orgEvent ) { deltaY = orgEvent.detail * -1; }
if ( 'wheelDelta' in orgEvent ) { deltaY = orgEvent.wheelDelta; }
if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY; }
if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; }
// Firefox < 17 horizontal scrolling related to DOMMouseScroll event
if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
deltaX = deltaY * -1;
deltaY = 0;
}
// Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
delta = deltaY === 0 ? deltaX : deltaY;
// New school wheel delta (wheel event)
if ( 'deltaY' in orgEvent ) {
deltaY = orgEvent.deltaY * -1;
delta = deltaY;
}
if ( 'deltaX' in orgEvent ) {
deltaX = orgEvent.deltaX;
if ( deltaY === 0 ) { delta = deltaX * -1; }
}
// No change actually happened, no reason to go any further
if ( deltaY === 0 && deltaX === 0 ) { return; }
// Need to convert lines and pages to pixels if we aren't already in pixels
// There are three delta modes:
// * deltaMode 0 is by pixels, nothing to do
// * deltaMode 1 is by lines
// * deltaMode 2 is by pages
if ( orgEvent.deltaMode === 1 ) {
var lineHeight = $.data(this, 'mousewheel-line-height');
delta *= lineHeight;
deltaY *= lineHeight;
deltaX *= lineHeight;
} else if ( orgEvent.deltaMode === 2 ) {
var pageHeight = $.data(this, 'mousewheel-page-height');
delta *= pageHeight;
deltaY *= pageHeight;
deltaX *= pageHeight;
}
// Store lowest absolute delta to normalize the delta values
absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );
if ( !lowestDelta || absDelta < lowestDelta ) {
lowestDelta = absDelta;
// Adjust older deltas if necessary
if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
lowestDelta /= 40;
}
}
// Adjust older deltas if necessary
if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
// Divide all the things by 40!
delta /= 40;
deltaX /= 40;
deltaY /= 40;
}
// Get a whole, normalized value for the deltas
delta = Math[ delta >= 1 ? 'floor' : 'ceil' ](delta / lowestDelta);
deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);
// Normalise offsetX and offsetY properties
if ( special.settings.normalizeOffset && this.getBoundingClientRect ) {
var boundingRect = this.getBoundingClientRect();
offsetX = event.clientX - boundingRect.left;
offsetY = event.clientY - boundingRect.top;
}
// Add information to the event object
event.deltaX = deltaX;
event.deltaY = deltaY;
event.deltaFactor = lowestDelta;
event.offsetX = offsetX;
event.offsetY = offsetY;
// Go ahead and set deltaMode to 0 since we converted to pixels
// Although this is a little odd since we overwrite the deltaX/Y
// properties with normalized deltas.
event.deltaMode = 0;
// Add event and delta to the front of the arguments
args.unshift(event, delta, deltaX, deltaY);
// Clearout lowestDelta after sometime to better
// handle multiple device types that give different
// a different lowestDelta
// Ex: trackpad = 3 and mouse wheel = 120
if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);
return ($.event.dispatch || $.event.handle).apply(this, args);
}
function nullLowestDelta() {
lowestDelta = null;
}
function shouldAdjustOldDeltas(orgEvent, absDelta) {
// If this is an older event and the delta is divisable by 120,
// then we are assuming that the browser is treating this as an
// older mouse wheel event and that we should divide the deltas
// by 40 to try and get a more usable deltaFactor.
// Side note, this actually impacts the reported scroll distance
// in older browsers and can cause scrolling to be slower than native.
// Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
}
}));

View file

@ -1,344 +0,0 @@
/* Simple JavaScript Inheritance
* By John Resig http://ejohn.org/
* MIT Licensed.
*/
// Inspired by base2 and Prototype
(function(){
var initializing = false;
// The base JQClass implementation (does nothing)
window.JQClass = function(){};
// Collection of derived classes
JQClass.classes = {};
// Create a new JQClass that inherits from this class
JQClass.extend = function extender(prop) {
var base = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == 'function' &&
typeof base[name] == 'function' ?
(function(name, fn){
return function() {
var __super = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = function(args) {
return base[name].apply(this, args || []);
};
var ret = fn.apply(this, arguments);
// The method only need to be bound temporarily, so we
// remove it when we're done executing
this._super = __super;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function JQClass() {
// All construction is actually done in the init method
if (!initializing && this._init) {
this._init.apply(this, arguments);
}
}
// Populate our constructed prototype object
JQClass.prototype = prototype;
// Enforce the constructor to be what we expect
JQClass.prototype.constructor = JQClass;
// And make this class extendable
JQClass.extend = extender;
return JQClass;
};
})();
(function($) { // Ensure $, encapsulate
/** Abstract base class for collection plugins v1.0.1.
Written by Keith Wood (kbwood{at}iinet.com.au) December 2013.
Licensed under the MIT (https://github.com/jquery/jquery/blob/master/LICENSE.txt) license.
@module $.JQPlugin
@abstract */
JQClass.classes.JQPlugin = JQClass.extend({
/** Name to identify this plugin.
@example name: 'tabs' */
name: 'plugin',
/** Default options for instances of this plugin (default: {}).
@example defaultOptions: {
selectedClass: 'selected',
triggers: 'click'
} */
defaultOptions: {},
/** Options dependent on the locale.
Indexed by language and (optional) country code, with '' denoting the default language (English/US).
@example regionalOptions: {
'': {
greeting: 'Hi'
}
} */
regionalOptions: {},
/** Names of getter methods - those that can't be chained (default: []).
@example _getters: ['activeTab'] */
_getters: [],
/** Retrieve a marker class for affected elements.
@private
@return {string} The marker class. */
_getMarker: function() {
return 'is-' + this.name;
},
/** Initialise the plugin.
Create the jQuery bridge - plugin name <code>xyz</code>
produces <code>$.xyz</code> and <code>$.fn.xyz</code>. */
_init: function() {
// Apply default localisations
$.extend(this.defaultOptions, (this.regionalOptions && this.regionalOptions['']) || {});
// Camel-case the name
var jqName = camelCase(this.name);
// Expose jQuery singleton manager
$[jqName] = this;
// Expose jQuery collection plugin
$.fn[jqName] = function(options) {
var otherArgs = Array.prototype.slice.call(arguments, 1);
if ($[jqName]._isNotChained(options, otherArgs)) {
return $[jqName][options].apply($[jqName], [this[0]].concat(otherArgs));
}
return this.each(function() {
if (typeof options === 'string') {
if (options[0] === '_' || !$[jqName][options]) {
throw 'Unknown method: ' + options;
}
$[jqName][options].apply($[jqName], [this].concat(otherArgs));
}
else {
$[jqName]._attach(this, options);
}
});
};
},
/** Set default values for all subsequent instances.
@param options {object} The new default options.
@example $.plugin.setDefauls({name: value}) */
setDefaults: function(options) {
$.extend(this.defaultOptions, options || {});
},
/** Determine whether a method is a getter and doesn't permit chaining.
@private
@param name {string} The method name.
@param otherArgs {any[]} Any other arguments for the method.
@return {boolean} True if this method is a getter, false otherwise. */
_isNotChained: function(name, otherArgs) {
if (name === 'option' && (otherArgs.length === 0 ||
(otherArgs.length === 1 && typeof otherArgs[0] === 'string'))) {
return true;
}
return $.inArray(name, this._getters) > -1;
},
/** Initialise an element. Called internally only.
Adds an instance object as data named for the plugin.
@param elem {Element} The element to enhance.
@param options {object} Overriding settings. */
_attach: function(elem, options) {
elem = $(elem);
if (elem.hasClass(this._getMarker())) {
return;
}
elem.addClass(this._getMarker());
options = $.extend({}, this.defaultOptions, this._getMetadata(elem), options || {});
var inst = $.extend({name: this.name, elem: elem, options: options},
this._instSettings(elem, options));
elem.data(this.name, inst); // Save instance against element
this._postAttach(elem, inst);
this.option(elem, options);
},
/** Retrieve additional instance settings.
Override this in a sub-class to provide extra settings.
@param elem {jQuery} The current jQuery element.
@param options {object} The instance options.
@return {object} Any extra instance values.
@example _instSettings: function(elem, options) {
return {nav: elem.find(options.navSelector)};
} */
_instSettings: function(elem, options) {
return {};
},
/** Plugin specific post initialisation.
Override this in a sub-class to perform extra activities.
@param elem {jQuery} The current jQuery element.
@param inst {object} The instance settings.
@example _postAttach: function(elem, inst) {
elem.on('click.' + this.name, function() {
...
});
} */
_postAttach: function(elem, inst) {
},
/** Retrieve metadata configuration from the element.
Metadata is specified as an attribute:
<code>data-&lt;plugin name>="&lt;setting name>: '&lt;value>', ..."</code>.
Dates should be specified as strings in this format: 'new Date(y, m-1, d)'.
@private
@param elem {jQuery} The source element.
@return {object} The inline configuration or {}. */
_getMetadata: function(elem) {
try {
var data = elem.data(this.name.toLowerCase()) || '';
data = data.replace(/'/g, '"');
data = data.replace(/([a-zA-Z0-9]+):/g, function(match, group, i) {
var count = data.substring(0, i).match(/"/g); // Handle embedded ':'
return (!count || count.length % 2 === 0 ? '"' + group + '":' : group + ':');
});
data = $.parseJSON('{' + data + '}');
for (var name in data) { // Convert dates
var value = data[name];
if (typeof value === 'string' && value.match(/^new Date\((.*)\)$/)) {
data[name] = eval(value);
}
}
return data;
}
catch (e) {
return {};
}
},
/** Retrieve the instance data for element.
@param elem {Element} The source element.
@return {object} The instance data or {}. */
_getInst: function(elem) {
return $(elem).data(this.name) || {};
},
/** Retrieve or reconfigure the settings for a plugin.
@param elem {Element} The source element.
@param name {object|string} The collection of new option values or the name of a single option.
@param [value] {any} The value for a single named option.
@return {any|object} If retrieving a single value or all options.
@example $(selector).plugin('option', 'name', value)
$(selector).plugin('option', {name: value, ...})
var value = $(selector).plugin('option', 'name')
var options = $(selector).plugin('option') */
option: function(elem, name, value) {
elem = $(elem);
var inst = elem.data(this.name);
if (!name || (typeof name === 'string' && value == null)) {
var options = (inst || {}).options;
return (options && name ? options[name] : options);
}
if (!elem.hasClass(this._getMarker())) {
return;
}
var options = name || {};
if (typeof name === 'string') {
options = {};
options[name] = value;
}
this._optionsChanged(elem, inst, options);
$.extend(inst.options, options);
},
/** Plugin specific options processing.
Old value available in <code>inst.options[name]</code>, new value in <code>options[name]</code>.
Override this in a sub-class to perform extra activities.
@param elem {jQuery} The current jQuery element.
@param inst {object} The instance settings.
@param options {object} The new options.
@example _optionsChanged: function(elem, inst, options) {
if (options.name != inst.options.name) {
elem.removeClass(inst.options.name).addClass(options.name);
}
} */
_optionsChanged: function(elem, inst, options) {
},
/** Remove all trace of the plugin.
Override <code>_preDestroy</code> for plugin-specific processing.
@param elem {Element} The source element.
@example $(selector).plugin('destroy') */
destroy: function(elem) {
elem = $(elem);
if (!elem.hasClass(this._getMarker())) {
return;
}
this._preDestroy(elem, this._getInst(elem));
elem.removeData(this.name).removeClass(this._getMarker());
},
/** Plugin specific pre destruction.
Override this in a sub-class to perform extra activities and undo everything that was
done in the <code>_postAttach</code> or <code>_optionsChanged</code> functions.
@param elem {jQuery} The current jQuery element.
@param inst {object} The instance settings.
@example _preDestroy: function(elem, inst) {
elem.off('.' + this.name);
} */
_preDestroy: function(elem, inst) {
}
});
/** Convert names from hyphenated to camel-case.
@private
@param value {string} The original hyphenated name.
@return {string} The camel-case version. */
function camelCase(name) {
return name.replace(/-([a-z])/g, function(match, group) {
return group.toUpperCase();
});
}
/** Expose the plugin base.
@namespace "$.JQPlugin" */
$.JQPlugin = {
/** Create a new collection plugin.
@memberof "$.JQPlugin"
@param [superClass='JQPlugin'] {string} The name of the parent class to inherit from.
@param overrides {object} The property/function overrides for the new class.
@example $.JQPlugin.createPlugin({
name: 'tabs',
defaultOptions: {selectedClass: 'selected'},
_initSettings: function(elem, options) { return {...}; },
_postAttach: function(elem, inst) { ... }
}); */
createPlugin: function(superClass, overrides) {
if (typeof superClass === 'object') {
overrides = superClass;
superClass = 'JQPlugin';
}
superClass = camelCase(superClass);
var className = camelCase(overrides.name);
JQClass.classes[className] = JQClass.classes[superClass].extend(overrides);
new JQClass.classes[className]();
}
};
})(jQuery);

View file

@ -99,9 +99,9 @@
var list = self.data('timepicker-list');
// check if input is readonly
// if (self.prop('readonly')) {
// return;
// }
if (self.prop('readonly')) {
return;
}
// check if list needs to be rendered
if (!list || list.length === 0 || typeof settings.durationTime === 'function') {

File diff suppressed because it is too large Load diff

View file

@ -1,238 +1,153 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta name="generator" content="HTML Tidy for HTML5 for Mac OS X version 5.0.0">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<meta name="apple-mobile-web-app-capable" content="yes"><!-- width=320 -->
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, minimum-scale=1.0, maximum-scale=1.0, minimal-ui">
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<meta name="viewport" content="width=320, initial-scale=1, user-scalable=no">
<meta charset="utf-8">
<title>SIF Tools</title>
<link href="css/external/jquery-ui.css" rel="stylesheet">
<link href="css/external/jquery-ui-timepicker-addon.css" rel="stylesheet">
<link href="css/external/keyboard.css" rel="stylesheet">
<link href="css/external/keyboard-previewkeyset.css" rel="stylesheet">
<link href="css/external/jquery.timepicker.css" rel="stylesheet">
<link href="css/external/jquery.keypad.css" rel="stylesheet">
<link href="css/sif_tools.css" rel="stylesheet">
<script src="js/external/jquery.js">
</script>
<script src="js/external/jquery.cookie.js">
</script>
<script src="js/external/jquery.plugin.js">
</script>
<script src="js/external/jquery.keypad.js">
</script>
<script src="js/external/jquery-ui.js">
</script>
<script src="js/external/jquery.timepicker.js">
</script>
<script src="js/external/jquery-ui-sliderAccess.js">
</script>
<script src="js/external/moment.js">
</script>
<script src="js/external/sprintf.js">
</script>
<script src="js/external/twitterFetcher.js">
</script>
<script src="js/sif_tools.js">
</script>
<link href="css/sif-tools.css" rel="stylesheet">
<script src="js/external/jquery.js"></script>
<script src="js/external/jquery.keyboard.js"></script>
<script src="js/external/jquery.keyboard.extension-all.js"></script>
<script src="js/external/jquery-ui.js"></script>
<script src="js/external/jquery.timepicker.js"></script>
<script src="js/external/jquery-ui-sliderAccess.js"></script>
<script src="js/external/moment.js"></script>
<script src="js/external/sprintf.js"></script>
<script src="js/external/twitterFetcher.js"></script>
<script src="js/sif_tools.js"></script>
</head>
<body>
<div id="header" align="center">
<h1>SIF Tools</h1>
</div>
<div id="tabs">
<ul>
<li>
<a href="#tab-rank">Rank</a>
</li>
<li>
<a href="#tab-love-gem">Gems</a>
</li>
<li>
<a href="#tab-card-level">Cards</a>
</li>
<li>
<a href="#tab-event">Events</a>
</li>
</ul>
<div id="tab-rank">
<div id="the-form" align="center">
Current Rank:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="current_rank" name="current_rank" placeholder="rank" value=""><br>
Current EXP:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="current_exp" name="current_exp" placeholder="optional" value=""><br>
Desired Rank:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="desired_rank" name="desired_rank" placeholder="rank" value=""><br>
Game Version:&nbsp;&nbsp;&nbsp; <select id="game_version" name="game_version">
<option value="EN">
EN
</option>
<option value="JP">
JP
</option>
</select><br>
<br>
<div id="button-calculate-rank">
Calculate
</div><br>
<br>
<div id="rank-calc-result-area">
<h1>Results</h1>EXP required:&nbsp;&nbsp;&nbsp;<span id="rank-result-exp">-</span><br>
<br>
You will need to play the following number of songs in order to get this amount of EXP:<br>
<br>(Single / 3xMedFes / 3xMedFes w/EXP boost)<br /><br />
EASY:&nbsp;&nbsp;&nbsp;<span id="rank-result-songs-easy">-</span> / <span id="rank-result-songs-easy-mf">-</span> / <span id="rank-result-songs-easy-mfb">-</span><br>
NORMAL:&nbsp;&nbsp;&nbsp;<span id="rank-result-songs-normal">-</span> / <span id="rank-result-songs-normal-mf">-</span> / <span id="rank-result-songs-normal-mfb">-</span><br>
HARD:&nbsp;&nbsp;&nbsp;<span id="rank-result-songs-hard">-</span> / <span id="rank-result-songs-hard-mf">-</span> / <span id="rank-result-songs-hard-mfb">-</span><br>
EXPERT:&nbsp;&nbsp;&nbsp;<span id="rank-result-songs-expert">-</span> / <span id="rank-result-songs-expert-mf">-</span> / <span id="rank-result-songs-expert-mfb">-</span><br>
<br>
At this rank you will have <span id="rank-results-lp">-</span> LP and <span id="rank-results-fp">-</span> friend slots.<br>
<br>
<div id="button-reset-rank">
Reset
</div>
</div>
</div>
<div id="header" align="center">
<h1>SIF Tools</h1>
</div>
<div id="tab-love-gem">
<div id="love-gem-form" align="center">
Current Gems:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="current_gems" name="current_gems" placeholder="optional" value=""><br>
Game Version:&nbsp;&nbsp;&nbsp; <select id="gem_game_version" name="gem_game_version">
<option value="EN">
EN
</option>
<option value="JP">
JP
</option>
</select><br>
<br>
<span id="gem_jp_daily_gems"><input type="checkbox" name="gems_include_daily_gems" id="gems_include_daily_gems" value="YES">Include daily "quest" gems?
(<a href="#" id="gem_quest_readme">What is this?</a>)<br></span>
<div id="gem_quest_readme_dialog" title="About JP Daily 'Quest' Gems" style="display: none;">
<span id="gem_jp_daily_gems"></span>
<p><span id="gem_jp_daily_gems">On JP, you can now obtain a gem every day by completing the following steps, in the following order:</span></p>
<ol>
<li><span id="gem_jp_daily_gems">Scout three Normal students. (The daily free scout counts.)</span></li>
<li><span id="gem_jp_daily_gems">Clear five live shows (any difficulty.)</span></li>
<li><span id="gem_jp_daily_gems">Practice ("feed") 5 cards of any kind.</span></li>
<li><span id="gem_jp_daily_gems">Play a live show (any difficulty) and get a Full Combo.</span></li>
</ol>
</div><input type="checkbox" name="gems_include_events" id="gems_include_events" value="YES">Include gems from events? (<a href="#" id=
"gem_event_readme">Read this first</a>)<br>
<div id="gem-event-options-area" align="center">
<div id="gem_event_readme_dialog" title="About the Event Gem Calculator" style="display: none;">
<p>Due to the variable nature of events, calculation of gems gotten through events is at best an approximation. To make the calculations simpler,
it assumes that each month has 2 events, ending on the 1st and the 15th, and events alternate between token events and score matches (and medley
festivals if on JP.) Also, if you are tiering, it assumes that you will spend some gems to tier, and will use an estimate based on your average
tier, but this will only be an estimate.)</p>
</div>Average tier:&nbsp;&nbsp;&nbsp; <select id="gems_tier_level" name="gems_tier_level">
<option value="1">
Tier 1
</option>
<option value="2">
Tier 2
</option>
<option value="0">
Event SR only
</option>
</select>
</div><br>
Mode:<br>
<input type="radio" name="gem-mode" id="gem-mode" class="option-button" value="DATE" checked>Number of gems you'll have on a date?<br>
<input type="radio" name="gem-mode" id="gem-mode" class="option-button" value="GEMS">Date you will have this many gems?<br>
<br>
<div id="gem-date-area">
Date:&nbsp;&nbsp;&nbsp;<input type="text" size="10" id="gem_desired_date" name="gem_desired_date" readonly="true" placeholder="MM/DD/YYYY" value="">
</div>
<div id="gem-desired-gems-area">
Desired gems:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="gem_desired_gems" name="gem_desired_gems" placeholder="gems" value="">
</div><br>
<br>
<input type="checkbox" name="gems_verbose" id="gems_verbose" value="YES">Verbose Mode<br>
<br>
<br>
<div id="button-calculate-gems">
Calculate
</div><br>
<br>
<div id="gem-calc-result-area">
<h1>Results</h1><span id="gem-result-summary">-</span><br>
<br>
<div id="gem-result-verbose-area">
<div id="gem-result-textarea">
-
<div id="tabs">
<ul>
<li><a href="#tab-rank">Rank</a></li>
<li><a href="#tab-love-gem">Gem</a></li>
<li><a href="#tab-card-level">Card</a></li>
<li><a href="#tab-event">Event</a></li>
</ul>
<div id="tab-rank">
<div id="the-form" align="center">
Current Rank:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="current_rank" name="current_rank" placeholder="rank" value="" />
<br />
Current EXP:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="current_exp" name="current_exp" placeholder="optional" value="" />
<br />
Desired Rank:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="desired_rank" name="desired_rank" placeholder="rank" value="" />
<br />
Game Version:&nbsp;&nbsp;&nbsp;
<select id="game_version" name="game_version">
<option value="EN">EN</option>
<option value="JP">JP</option>
</select>
<br /><br />
<div id="button-calculate-rank">Calculate</div>
<br /><br />
<div id="rank-calc-result-area">
<h1>Results</h1>
EXP required:&nbsp;&nbsp;&nbsp;<span id="rank-result-exp">-</span><br /><br />
You will need to play the following number of songs in order to get this amount of EXP:<br /><br />
EASY:&nbsp;&nbsp;&nbsp;<span id="rank-result-songs-easy">-</span><br />
NORMAL:&nbsp;&nbsp;&nbsp;<span id="rank-result-songs-normal">-</span><br />
HARD:&nbsp;&nbsp;&nbsp;<span id="rank-result-songs-hard">-</span><br />
EXPERT:&nbsp;&nbsp;&nbsp;<span id="rank-result-songs-expert">-</span><br /><br/>
At this rank you will have <span id="rank-results-lp">-</span> LP and <span id="rank-results-fp">-</span> friend slots.<br /><br />
<div id="button-reset-rank">Reset</div>
</div>
</div>
</div>
<div id="tab-love-gem">
<div id="love-gem-form" align="center">
Current Gems:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="current_gems" name="current_gems" placeholder="optional" value="" />
<br /><br />
Mode:<br />
<input type="radio" name="gem-mode" id="gem-mode" value="DATE" checked />Number of gems on date?<br />
<input type="radio" name="gem-mode" id="gem-mode" value="GEMS" />Date when you get <i>x</i> gems?<br /><br />
<div id="gem-date-area">
Date:&nbsp;&nbsp;&nbsp;<input type="text" size="10" id="gem_desired_date" name="gem_desired_date" placeholder="MM/DD/YYYY" value="" />
</div>
<div id="gem-desired-gems-area">
Desired gems:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="gem_desired_gems" name="gem_desired_gems" placeholder="gems" value="" />
</div>
<br /><br />
<input type="checkbox" name="gems_verbose" id="gems_verbose" value="YES" />Verbose Mode<br />
<br /><br />
<div id="button-calculate-gems">Calculate</div>
<br /><br />
<div id="gem-calc-result-area">
<h1>Results</h1>
<span id="gem-result-summary">-</span><br /><br />
<div id="gem-result-verbose-area">
<div id="gem-result-textarea">-</div>
</div>
</div>
<div id="button-reset-gems">
Reset
</div>
</div>
<div id="button-reset-gems">Reset</div>
</div>
</div>
<div id="tab-card-level">
<div id="love-gem-form" align="center">
Card Rarity:&nbsp;&nbsp;&nbsp; <select id="card_rarity" name="card_rarity">
<option value="N">
N
</option>
<option value="R">
R
</option>
<option value="SR">
SR
</option>
<option value="UR">
UR
</option>
</select><br>
Current Level:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="card_current_level" name="card_current_level" placeholder="level"
value=""><br>
Current EXP:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="card_current_exp" name="card_current_exp" placeholder="optional" value=
""><br>
<br>
Mode:<br>
<input type="radio" name="card-mode" id="card-mode" class="option-button" value="LEVEL" checked>EXP needed to get card to a level?<br>
<input type="radio" name="card-mode" id="card-mode" class="option-button" value="EXP">Final level after feeding an amount of EXP?<br>
<br>
<div id="card-level-area">
Desired level:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="card_desired_level" name="card_desired_level" placeholder="level"
value="">
<div id="button-card-max-level">Max</div>
</div>
<div id="card-exp-area">
EXP:&nbsp;&nbsp;&nbsp;<input type="text" readonly="true" size="5" id="card_feed_exp" name="card_feed_exp" placeholder="exp" value="">
</div><br>
<input type="checkbox" name="card_same_attribute" id="card_same_attribute" value="YES">Assume cards are same attribute<br>
<br>
<div id="button-calculate-card">
Calculate
</div><br>
<br>
<div id="card-calc-result-area">
<h1>Results</h1><span id="card-result-summary">-</span><br>
<br>
<div id="button-reset-card">
Reset
</div>
</div>
</div>
</div>
<div id="tab-event">
<div id="event-timer-form" align="center">
<h3>Enter Event End Date/Time (in UTC):</h3><br>
<input type="text" size="10" id="event_end_date" name="event_end_date" readonly="true" placeholder="MM/DD/YYYY" value=""> <input type="text" size="5"
id="event_end_time" name="event_end_time" readonly="true" placeholder="HH:MM" value=""><br>
<br>
<div id="button-start-stop-timer">
Start Timer
</div>
<div id="timer_output_area" align="center">
<h1>Timer Not Running</h1>
</div>
<div id="tier_info_output_area" align="center"></div>
<div id="button-clear-timer">
Clear Timer
</div>
</div>
<div id="error-dialog" title="Error">
<p><span id="error-text"></span></p>
</div>
</div>
</div>
<div id="tab-card-level">
<div id="love-gem-form" align="center">
Card Rarity:&nbsp;&nbsp;&nbsp;
<select id="card_rarity" name="card_rarity">
<option value="N">N</option>
<option value="R">R</option>
<option value="SR">SR</option>
<option value="UR">UR</option>
</select>
<br />
Current Level:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="card_current_level" name="card_current_level" placeholder="level" value="" />
<br />
Current EXP:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="card_current_exp" name="card_current_exp" placeholder="optional" value="" />
<br /><br />
Mode:<br />
<input type="radio" name="card-mode" id="card-mode" value="LEVEL" checked />EXP needed to level?<br />
<input type="radio" name="card-mode" id="card-mode" value="EXP" />Final level after feeding?<br /><br />
<div id="card-level-area">
Desired level:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="card_desired_level" name="card_desired_level" placeholder="level" value="" />
</div>
<div id="card-exp-area">
EXP:&nbsp;&nbsp;&nbsp;<input type="text" size="5" id="card_feed_exp" name="card_feed_exp" placeholder="exp" value="" />
</div>
<br /><br />
<div id="button-calculate-card">Calculate</div>
<br /><br />
<div id="card-calc-result-area">
<h1>Results</h1>
<span id="card-result-summary">-</span><br /><br />
<div id="button-reset-card">Reset</div>
</div>
</div>
</div>
<div id="tab-event">
<div id="event-timer-form" align="center">
<h3>Enter Event End Date/Time (in UTC):</h3><br />
<input type="text" size="10" id="event_end_date" name="event_end_date" placeholder="MM/DD/YYYY" value="" />
<input type="text" size="5" id="event_end_time" name="event_end_time" placeholder="HH:MM" value="" />
<br /><br />
<div id="button-start-stop-timer">Start Timer</div>
<div id="timer_output_area" align="center">
<h1>Timer Not Running</h1>
</div>
<div id="tier_info_output_area" align="center">
</div>
<div id="button-clear-timer">Clear Timer</div>
</div>
<div id="error-dialog" title="Error">
<p><span id="error-text" /></p>
</div>
</div>
</div>
</body>
</html>