diff --git a/README.md b/README.md index 840929f..7ba5f46 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,19 @@ 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 (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.) +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)) ## Bugs? Need help? Got any suggestions/ideas for new features? Or want to chat? diff --git a/web_app/css/external/images/disabled.svg b/web_app/css/external/images/disabled.svg deleted file mode 100644 index 8a91c4d..0000000 --- a/web_app/css/external/images/disabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/web_app/css/external/images/enabled.svg b/web_app/css/external/images/enabled.svg deleted file mode 100644 index 44b8ebe..0000000 --- a/web_app/css/external/images/enabled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web_app/css/external/images/keyboard.svg b/web_app/css/external/images/keyboard.svg deleted file mode 100644 index 0d8fcb5..0000000 --- a/web_app/css/external/images/keyboard.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web_app/css/external/jquery-ui-timepicker-addon.css b/web_app/css/external/jquery-ui-timepicker-addon.css deleted file mode 100644 index 2d9e031..0000000 --- a/web_app/css/external/jquery-ui-timepicker-addon.css +++ /dev/null @@ -1,27 +0,0 @@ -.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; } \ No newline at end of file diff --git a/web_app/css/external/jquery.keypad.css b/web_app/css/external/jquery.keypad.css new file mode 100644 index 0000000..3424627 --- /dev/null +++ b/web_app/css/external/jquery.keypad.css @@ -0,0 +1,106 @@ +/* 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; +} diff --git a/web_app/css/external/jquery.timepicker.css b/web_app/css/external/jquery.timepicker.css old mode 100755 new mode 100644 diff --git a/web_app/css/external/keyboard-basic.css b/web_app/css/external/keyboard-basic.css deleted file mode 100644 index fb91ec4..0000000 --- a/web_app/css/external/keyboard-basic.css +++ /dev/null @@ -1,170 +0,0 @@ -/* *** 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+'); -} diff --git a/web_app/css/external/keyboard-previewkeyset.css b/web_app/css/external/keyboard-previewkeyset.css deleted file mode 100644 index c93ba1e..0000000 --- a/web_app/css/external/keyboard-previewkeyset.css +++ /dev/null @@ -1,44 +0,0 @@ -/* 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; -} diff --git a/web_app/css/external/keyboard.css b/web_app/css/external/keyboard.css deleted file mode 100644 index 0729c03..0000000 --- a/web_app/css/external/keyboard.css +++ /dev/null @@ -1,161 +0,0 @@ -/* 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; } -} diff --git a/web_app/css/sif-tools.css b/web_app/css/sif_tools.css similarity index 66% rename from web_app/css/sif-tools.css rename to web_app/css/sif_tools.css index 67a87c1..a1d7680 100644 --- a/web_app/css/sif-tools.css +++ b/web_app/css/sif_tools.css @@ -1,13 +1,12 @@ body { font: 100% "Trebuchet MS", sans-serif; - margin: 50px; + font-family: Verdana,Arial,sans-serif; + font-size: 14px; + margin: 0px; background-color: #000000; color: #ffffff; -} - -body { - font-family: Verdana,Arial,sans-serif; - font-size: 14px; + /*height: 100%;*/ + /*min-height: 480px;*/ } p { diff --git a/web_app/index.html b/web_app/index.html index 7c11c2c..9c94c0e 100644 --- a/web_app/index.html +++ b/web_app/index.html @@ -1,10 +1,13 @@ + - - SIFTools - - - -
This is not the web page you're looking for... move along, move along -
- + + + SIFTools + + + +
+ This is not the web page you're looking for... move along, move along +
+ diff --git a/web_app/js/external/date.js b/web_app/js/external/date.js deleted file mode 100644 index 2d52e9a..0000000 --- a/web_app/js/external/date.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * 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;idate)?1:(this=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(valuemax){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;i0&&!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;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} -if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} -if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)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' ) - .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 = $( '
' ); - 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(); - }); - - }); - }; - -})); - -/*! 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(); - - }); -}; - -})); - -/*! 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 = $( '
' ) - .appendTo( base.$keyboard ); - - // remove caret, just-in-case - if (base.$caret) { base.$caret.remove(); } - base.$caret = $( '
' ) - .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 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(); - } - - }); - }; - -})); - -/*! 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(); - }); - - }); - }; - -})); - -/*! 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 's instead of '). + html(buttonImage == '' ? inst.options.buttonText : + $('' +
+						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('
'); + 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 ? '' : '
' + + inst.options.prompt + '
'); + var layout = this._randomiseLayout(inst); + for (var i = 0; i < layout.length; i++) { + html += '
'; + 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 ? '' : + '
'); + } + else { + html += ''; + } + } + html += '
'; + } + 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); diff --git a/web_app/js/external/jquery.keypad.package-2.0.1.zip b/web_app/js/external/jquery.keypad.package-2.0.1.zip deleted file mode 100644 index 913a4da..0000000 Binary files a/web_app/js/external/jquery.keypad.package-2.0.1.zip and /dev/null differ diff --git a/web_app/js/external/jquery.mousewheel.js b/web_app/js/external/jquery.mousewheel.js deleted file mode 100644 index df9ade2..0000000 --- a/web_app/js/external/jquery.mousewheel.js +++ /dev/null @@ -1,221 +0,0 @@ -/* 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; - } - -})); diff --git a/web_app/js/external/jquery.plugin.js b/web_app/js/external/jquery.plugin.js new file mode 100644 index 0000000..7757f09 --- /dev/null +++ b/web_app/js/external/jquery.plugin.js @@ -0,0 +1,344 @@ +/* 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 xyz + produces $.xyz and $.fn.xyz. */ + _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: + data-<plugin name>="<setting name>: '<value>', ...". + 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 inst.options[name], new value in options[name]. + 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 _preDestroy 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 _postAttach or _optionsChanged 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); \ No newline at end of file diff --git a/web_app/js/external/jquery.timepicker.js b/web_app/js/external/jquery.timepicker.js index cfe7dce..1861978 100644 --- a/web_app/js/external/jquery.timepicker.js +++ b/web_app/js/external/jquery.timepicker.js @@ -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') { diff --git a/web_app/js/sif_tools.js b/web_app/js/sif_tools.js index eba51d8..bc89759 100644 --- a/web_app/js/sif_tools.js +++ b/web_app/js/sif_tools.js @@ -20,116 +20,240 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -// +// TODO: +// ===== +// * General improvements: +// - needs a lot more error/bounds checking in general +// * Card Level Calc: +// - maybe also the super (1.5) and ultra (2.0) success bonuses too? +// * Event Tracker: +// - @sifen_trackbot update code gets "stuck" sometimes - not sure if the problem +// is in the twitter fetcher or elsewhere // Set to 0 to disable debugging, 1+ to enable debugging (higher = more verbose) var DEBUG_LEVEL = 0; // EXP tables -var exp_table_n = [-1, 0, 6, 18, 28, 40, 51, 61, 72, 82, 93, 104, 114, 124, 135, 145, 156, 165, 176, 187, 196, 207, 217, 226, 238, 247, 257, 268, 277, 288, 297, 308, 317, 328, 337, 348, 358, 367, 377, 388, 397]; -var exp_table_r = [-1, 0, 14, 31, 45, 55, 67, 76, 85, 94, 103, 110, 119, 125, 134, 140, 148, 155, 161, 168, 174, 181, 187, 193, 199, 206, 211, 217, 223, 228, 235, 240, 245, 251, 256, 262, 267, 272, 277, 283, 288, 292, 298, 303, 308, 313, 317, 323, 327, 332, 337, 342, 346, 351, 356, 360, 365, 370, 374, 378, 383]; -var exp_table_sr = [-1, 0, 54, 98, 127, 150, 169, 187, 203, 218, 232, 245, 257, 269, 281, 291, 302, 311, 322, 331, 340, 349, 358, 366, 374, 383, 391, 398, 406, 413, 421, 428, 435, 442, 449, 456, 462, 469, 475, 482, 488, 494, 500, 507, 512, 518, 524, 530, 536, 541, 547, 552, 558, 563, 568, 574, 579, 584, 590, 594, 600, 605, 609, 615, 619, 625, 629, 634, 639, 643, 648, 653, 657, 662, 667, 670, 676, 680, 684, 689, 693]; -var exp_table_ur = [-1, 0, 201, 294, 345, 382, 411, 438, 460, 481, 499, 517, 532, 547, 561, 574, 587, 598, 611, 621, 631, 642, 651, 661, 670, 679, 687, 696, 704, 712, 720, 727, 734, 742, 749, 755, 763, 769, 775, 782, 788, 794, 800, 806, 812, 818, 823, 829, 834, 840, 845, 850, 856, 860, 866, 870, 875, 880, 885, 890, 894, 899, 903, 908, 912, 917, 921, 925, 930, 933, 938, 942, 946, 950, 954, 959, 961, 966, 970, 974, 977, 981, 985, 988, 992, 996, 999, 1003, 1006, 1010, 1013, 1017, 1020, 1024, 1027, 1030, 1034, 1037, 1040, 1043, 1047]; +// from: http://www59.atwiki.jp/lovelive-sif/pages/32.html +var exp_table_n = [ -1, 0, 6, 18, 28, 40, 51, 61, 72, 82, 93, 104, 114, 124, 135, 145, 156, 165, 176, 187, 196, 207, 217, 226, 238, 247, 257, 268, 277, 288, 297, 308, 317, 328, 337, 348, 358, 367, 377, 388, 397 ]; + +var exp_table_r = [ -1, 0, 14, 31, 45, 55, 67, 76, 85, 94, 103, 110, 119, 125, 134, 140, 148, 155, 161, 168, 174, 181, 187, 193, 199, 206, 211, 217, 223, 228, 235, 240, 245, 251, 256, 262, 267, 272, 277, 283, 288, 292, 298, 303, 308, 313, 317, 323, 327, 332, 337, 342, 346, 351, 356, 360, 365, 370, 374, 378, 383 ]; + +var exp_table_sr = [ -1, 0, 54, 98, 127, 150, 169, 187, 203, 218, 232, 245, 257, 269, 281, 291, 302, 311, 322, 331, 340, 349, 358, 366, 374, 383, 391, 398, 406, 413, 421, 428, 435, 442, 449, 456, 462, 469, 475, 482, 488, 494, 500, 507, 512, 518, 524, 530, 536, 541, 547, 552, 558, 563, 568, 574, 579, 584, 590, 594, 600, 605, 609, 615, 619, 625, 629, 634, 639, 643, 648, 653, 657, 662, 667, 670, 676, 680, 684, 689, 693 ]; + +var exp_table_ur = [ -1, 0, 201, 294, 345, 382, 411, 438, 460, 481, 499, 517, 532, 547, 561, 574, 587, 598, 611, 621, 631, 642, 651, 661, 670, 679, 687, 696, 704, 712, 720, 727, 734, 742, 749, 755, 763, 769, 775, 782, 788, 794, 800, 806, 812, 818, 823, 829, 834, 840, 845, 850, 856, 860, 866, 870, 875, 880, 885, 890, 894, 899, 903, 908, 912, 917, 921, 925, 930, 933, 938, 942, 946, 950, 954, 959, 961, 966, 970, 974, 977, 981, 985, 988, 992, 996, 999, 1003, 1006, 1010, 1013, 1017, 1020, 1024, 1027, 1030, 1034, 1037, 1040, 1043, 1047 ]; + +// global variable to keep event state, because we need it to live between function calls. +// YES I KNOW THIS IS BAD. SO SUE ME. IT WORKS THOUGH. :P +// 1 = token event, 2 = score match, 3 = medfes +var current_type_of_event = 1; // debug logging -function LOG(level, msg) -{ - if (DEBUG_LEVEL > 0 && DEBUG_LEVEL >= level) { - console.log(msg); - } +function LOG(level, msg) { + if (DEBUG_LEVEL > 0 && DEBUG_LEVEL >= level) { + console.log(msg); + } } // Main function, runs automatically at document-ready (i.e. when the page is finished loading) -$(document).ready(function(){ - // set up UI (buttons, etc.) - setup_ui_elements(); - // set up button handlers - setup_button_handlers(); - // set up slider handlers +$(document).ready(function() { + // Hide the address bar on mobile browsers + setTimeout(function() { + // some sites suggest 0,0 and others 0,1 - not sure which is correct + window.scrollTo(0, 0); + }, 0); + // set up UI (buttons, etc.) + setup_ui_elements(); + // set up button handlers + setup_button_handlers(); + // set up slider handlers window.timerInterval = 0; }); // Set up UI elements (tabs, buttons, etc.) -function setup_ui_elements() -{ - LOG(1, "setup_ui_elements()"); - - $( "#tabs" ).tabs({ - active: 0, - create: function(event, ui) { - var theTab = ui.tab.index(); - LOG(3, "INIT tab created " + theTab); - set_up_tab(theTab); - }, - activate: function(event, ui) { - var theTab = ui.newTab.index(); - LOG(3, "INIT tab selected " + theTab); - set_up_tab(theTab); - } - }); - - // set up keypads - /* - $( "#current_rank" ).keypad(); // {prompt: 'Enter here'} - $( "#current_exp" ).keypad(); // {prompt: 'Enter here'} - $( "#desired_rank" ).keypad(); // {prompt: 'Enter here'} - $( "#current_gems" ).keypad(); // {prompt: 'Enter here'} - $( "#" ).keypad(); // {prompt: 'Enter here'} - $( "#" ).keypad(); // {prompt: 'Enter here'} - $( "#" ).keypad(); // {prompt: 'Enter here'} - $( "#" ).keypad(); // {prompt: 'Enter here'} - $( "#" ).keypad(); // {prompt: 'Enter here'} - */ - ["current_rank", "current_exp", "desired_rank", "current_gems", "gem_desired_gems", "card_current_level", "card_current_exp", "card_desired_level", "card_feed_exp"].forEach(function(entry) { - var selector = "#" + entry; - LOG(1, "setting up " + selector); - // $(selector).keypad() - $(selector) - .keyboard({ - layout : 'custom', - customLayout: { 'default': ['1 2 3', '4 5 6', '7 8 9', '{empty} 0 {empty}', '{b} {c} {a}'] }, - restrictInput : true, // Prevent keys not in the displayed keyboard from being typed in - preventPaste : true, // prevent ctrl-v and right click - autoAccept : true - }) - .addTyping(); +function setup_ui_elements() { + LOG(1, "setup_ui_elements()"); + var QueryString = function() { + // http://stackoverflow.com/a/979995 + // This function is anonymous, is executed immediately and + // the return value is assigned to QueryString! + var query_string = {}; + var query = window.location.search.substring(1); + var vars = query.split("&"); + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + // If first entry with this name + if (typeof query_string[pair[0]] === "undefined") { + query_string[pair[0]] = decodeURIComponent(pair[1]); + } else if (typeof query_string[pair[0]] === "string") { + var arr = [ query_string[pair[0]], decodeURIComponent(pair[1]) ]; + query_string[pair[0]] = arr; + } else { + query_string[pair[0]].push(decodeURIComponent(pair[1])); + } + } + return query_string; + }(); + var default_tab = 0; + if (!isNaN(QueryString.tab)) { + if (QueryString.tab >= 1 && QueryString.tab <= 4) { + default_tab = QueryString.tab - 1; + } + } else { + var default_tab_from_cookie = $.cookie("default_tab"); + if (isNaN(default_tab_from_cookie)) { + default_tab = 0; + } else { + default_tab = default_tab_from_cookie; + } + } + $("#tabs").tabs({ + active: default_tab, + create: function(event, ui) { + var theTab = ui.tab.index(); + LOG(3, "INIT tab created " + theTab); + set_up_tab(theTab); + }, + activate: function(event, ui) { + var theTab = ui.newTab.index(); + LOG(3, "INIT tab selected " + theTab); + set_up_tab(theTab); + } }); - - + // only set up keypads on mobile browsers + // not sure what the best way of doing this is + var pageWidth = $(window).width(); + if (pageWidth < 1024) { + $("#current_rank").prop("readonly", true); + $("#current_rank").keypad(); + // {prompt: 'Enter here'} + $("#current_exp").prop("readonly", true); + $("#current_exp").keypad(); + // {prompt: 'Enter here'} + $("#desired_rank").prop("readonly", true); + $("#desired_rank").keypad(); + // {prompt: 'Enter here'} + $("#current_gems").prop("readonly", true); + $("#current_gems").keypad(); + // {prompt: 'Enter here'} + $("#gem_desired_gems").prop("readonly", true); + $("#gem_desired_gems").keypad(); + // {prompt: 'Enter here'} + $("#card_current_level").prop("readonly", true); + $("#card_current_level").keypad(); + // {prompt: 'Enter here'} + $("#card_current_exp").prop("readonly", true); + $("#card_current_exp").keypad(); + // {prompt: 'Enter here'} + $("#card_desired_level").prop("readonly", true); + $("#card_desired_level").keypad(); + // {prompt: 'Enter here'} + $("#card_feed_exp").prop("readonly", true); + $("#card_feed_exp").keypad(); + } else { + $("#current_rank").prop("readonly", false); + $("#current_exp").prop("readonly", false); + $("#desired_rank").prop("readonly", false); + $("#current_gems").prop("readonly", false); + $("#gem_desired_gems").prop("readonly", false); + $("#card_current_level").prop("readonly", false); + $("#card_current_exp").prop("readonly", false); + $("#card_desired_level").prop("readonly", false); + $("#card_feed_exp").prop("readonly", false); + } // set up date/time pickers - $( "#gem_desired_date" ).datepicker(); - $( "#event_end_date" ).datepicker(); + $("#gem_desired_date").datepicker(); + $("#event_end_date").datepicker(); // $( "#event_end_time" ).timepicker(); - $('#event_end_time').timepicker({ 'timeFormat': 'H:i' }); - - // set up buttons - ["calculate-rank", "reset-rank", "calculate-gems", "reset-gems", "calculate-card", "reset-card", "start-stop-timer", "clear-timer"].forEach(function(entry) { - var selector = "#button-" + entry; - LOG(1, "setting up " + selector); - $(selector).button(); - }); - + $("#event_end_time").timepicker({ + timeFormat: "H:i", + disableTextInput: true + }); + // set up buttons + [ "calculate-rank", "reset-rank", "calculate-gems", "reset-gems", "card-max-level", "calculate-card", "reset-card", "start-stop-timer", "clear-timer" ].forEach(function(entry) { + var selector = "#button-" + entry; + LOG(1, "setting up " + selector); + $(selector).button(); + }); // hide result divs - ["rank-calc-result-area", "gem-calc-result-area", "card-calc-result-area"].forEach(function(entry) { + [ "rank-calc-result-area", "gem-calc-result-area", "card-calc-result-area" ].forEach(function(entry) { var selector = "#" + entry; LOG(1, "setting up " + selector); $(selector).hide(); }); - // set up radio button listeners $("input[name=gem-mode]").change(handle_gem_mode_select); $("input[name=card-mode]").change(handle_card_mode_select); - - // hide non-selected option divs - ["gem-desired-gems-area", "card-exp-area"].forEach(function(entry) { - var selector = "#" + entry; - LOG(1, "setting up " + selector); - $(selector).hide(); + // set up checkbox change event handler + $("#gems_include_events").change(function() { + update_ui(); }); + // set up gem event calc note dialog + // $("#dialog").dialog({ autoOpen: false }); + $("a#gem_event_readme").click(function(e) { + e.preventDefault(); + $("#gem_event_readme_dialog").dialog({ + height: 300 + }); + }); + $("a#gem_quest_readme").click(function(e) { + e.preventDefault(); + $("#gem_quest_readme_dialog").dialog({ + height: 300 + }); + }); + // set up show/hide JP-only options + $("#gem_game_version").on("change", function(e) { + update_ui(); + }); + // update the UI based on what is selected + update_ui(); } -function setup_button_handlers() +// show/hide option div's based on which modes are selected +function update_ui() { + // gem screen - show/hide the options based on game version + var gem_game_version = $("#gem_game_version").val(); + if (gem_game_version === "JP") { + $("#gem_jp_daily_gems").show(); + } else { + $("#gem_jp_daily_gems").hide(); + } + // gem screen - hide event tier selector + var gem_events_selected = $("#gems_include_events").is(":checked"); + if (gem_events_selected) { + $("#gem-event-options-area").show(); + } else { + $("#gem-event-options-area").hide(); + } + // gem screen - mode + var gem_mode = $("#gem-mode").val(); + switch(gem_mode) { + case "DATE": + $("#gem-date-area").show(); + $("#gem-desired-gems-area").hide(); + break; + case "GEMS": + $("#gem-date-area").hide(); + $("#gem-desired-gems-area").show(); + break; + } + // card screen - mode + var card_mode = $("#card-mode").val(); + switch(card_mode) { + case "LEVEL": + $("#card-level-area").show(); + $("#card-exp-area").hide(); + break; + case "EXP": + $("#card-level-area").hide(); + $("#card-exp-area").show(); + break; + } +} + +function setup_button_handlers() { $("#button-calculate-rank").click(function(evt) { calculate_rank(); }); @@ -142,6 +266,9 @@ function setup_button_handlers() $("#button-reset-gems").click(function(evt) { reset_gems(); }); + $("#button-card-max-level").click(function(evt) { + card_set_max_level(); + }); $("#button-calculate-card").click(function(evt) { calculate_card(); }); @@ -157,39 +284,47 @@ function setup_button_handlers() } // tab functions +function set_up_tab(tab) { + switch (tab) { + case 0: + rank_calc_tab_selected(); + break; -function set_up_tab(tab) -{ - switch(tab) { - case 0: rank_calc_tab_selected(); break; - case 1: love_gem_calc_tab_selected(); break; - case 2: card_level_calc_tab_selected(); break; - case 3: event_end_calc_tab_selected(); break; + case 1: + love_gem_calc_tab_selected(); + break; + + case 2: + card_level_calc_tab_selected(); + break; + + case 3: + event_end_calc_tab_selected(); + break; } } -function rank_calc_tab_selected() -{ +function rank_calc_tab_selected() { LOG(1, "rank_calc_tab_selected"); + $.cookie("default_tab", 0); } -function love_gem_calc_tab_selected() -{ - LOG(1, "love_gem_calc_tab_selected"); +function love_gem_calc_tab_selected() { + LOG(1, "love_gem_calc_tab_selected"); + $.cookie("default_tab", 1); } -function card_level_calc_tab_selected() -{ - LOG(1, "card_level_calc_tab_selected"); +function card_level_calc_tab_selected() { + LOG(1, "card_level_calc_tab_selected"); + $.cookie("default_tab", 2); } -function event_end_calc_tab_selected() -{ - LOG(1, "event_end_calc_tab_selected"); +function event_end_calc_tab_selected() { + LOG(1, "event_end_calc_tab_selected"); + $.cookie("default_tab", 3); } -function calculate_rank() -{ +function calculate_rank() { // validate data var current_rank = parseInt($("#current_rank").val()); var current_exp_input = $("#current_exp").val(); @@ -208,17 +343,17 @@ function calculate_rank() } else if (desired_rank <= current_rank) { window.alert("Error: desired rank must be greater than current rank."); } else { - var required_exp = 0 - for (rank = current_rank ; rank < desired_rank ; rank++) { - var required_exp_for_next_rank = Math.round(34.45 * rank - 551) + var required_exp = 0; + for (rank = current_rank; rank < desired_rank; rank++) { + var required_exp_for_next_rank = Math.round(34.45 * rank - 551); // account for half EXP on JP (only if rank < 100) if (game_version === "JP" && rank < 100) { required_exp_for_next_rank /= 2; } - required_exp = required_exp + required_exp_for_next_rank + required_exp = required_exp + required_exp_for_next_rank; } // account for exp we already have - required_exp -= current_exp + required_exp -= current_exp; // convert to integer required_exp = Math.round(required_exp); // now calc the # of songs needed @@ -227,17 +362,33 @@ function calculate_rank() var normal_count = Math.round(required_exp / 26); var hard_count = Math.round(required_exp / 46); var expert_count = Math.round(required_exp / 83); + var m3_easy_count = Math.round(required_exp / (12*3)); + var m3_normal_count = Math.round(required_exp / (26*3)); + var m3_hard_count = Math.round(required_exp / (46*3)); + var m3_expert_count = Math.round(required_exp / (83*3)); + // Increases the amount of EXP gained by 10%. + var m3b_easy_count = Math.round(required_exp / (12*1.1*3)); + var m3b_normal_count = Math.round(required_exp / (26*1.1*3)); + var m3b_hard_count = Math.round(required_exp / (46*1.1*3)); + var m3b_expert_count = Math.round(required_exp / (83*1.1*3)); // calc LP and FP - var LP = 25 + Math.floor(Math.min(desired_rank, 300) / 2) + Math.floor(Math.max(desired_rank - 300, 0) / 3) + var LP = 25 + Math.floor(Math.min(desired_rank, 300) / 2) + Math.floor(Math.max(desired_rank - 300, 0) / 3); // calc friend slots - var friend_slots = 10 + Math.floor(Math.min(desired_rank, 50) / 5) + Math.floor(Math.max(desired_rank - 50, 0) / 10) - + var friend_slots = 10 + Math.floor(Math.min(desired_rank, 50) / 5) + Math.floor(Math.max(desired_rank - 50, 0) / 10); // display the results $("#rank-result-exp").text(required_exp); $("#rank-result-songs-easy").text(easy_count); $("#rank-result-songs-normal").text(normal_count); $("#rank-result-songs-hard").text(hard_count); $("#rank-result-songs-expert").text(expert_count); + $("#rank-result-songs-easy-mf").text(m3_easy_count); + $("#rank-result-songs-normal-mf").text(m3_normal_count); + $("#rank-result-songs-hard-mf").text(m3_hard_count); + $("#rank-result-songs-expert-mf").text(m3_expert_count); + $("#rank-result-songs-easy-mfb").text(m3b_easy_count); + $("#rank-result-songs-normal-mfb").text(m3b_normal_count); + $("#rank-result-songs-hard-mfb").text(m3b_hard_count); + $("#rank-result-songs-expert-mfb").text(m3b_expert_count); // rank-results-lp">-
LP and 2 || game_version === "JP" && current_type_of_event > 3) { + current_type_of_event = 1; + } + } + LOG(1, "end event type is " + current_type_of_event); + LOG(1, return_tuple); + // now return what we got + return return_tuple; +} + +function calculate_gems() { + // reset current event indicator (start out with a token event) + current_type_of_event = 1; + var verbose = $("#gems_verbose").is(":checked"); var current_gems_text = $("#current_gems").val(); var current_gems = 0; if (current_gems_text != "") { @@ -384,36 +641,43 @@ function calculate_gems() alert("Error: invalid number of current gems. Please check your input and try again."); return; } + var tier = parseInt($("#gems_tier_level").val()); + var game_version = $("#gem_game_version").val(); + var calc_daily_quest_gems = false; + var calc_event_gems = $("#gems_include_events").is(":checked"); + if (game_version === "JP") { + calc_daily_quest_gems = $("#gems_include_daily_gems").is(":checked"); + } var mode = $("input[name=gem-mode]:checked").val(); - if (mode === "DATE") { + if (mode === "DATE") { var target_date = $("#gem_desired_date").val(); if (target_date === "") { alert("Error: invalid date. Please check and try again."); return; } var target_date_object = moment(new Date(target_date)); - if (!target_date_object.isValid()) { + if (!target_date_object.isValid()) { alert("Error: invalid date. Please check and try again."); return; } - var now = moment(new Date()); if (target_date_object.isBefore(now) || is_same_day(now, target_date_object)) { window.alert("Error: the date must be in the future."); return; } - // ready to rock var resultsString = sprintf("Today is %02d/%02d/%04d and you currently have %d love gems.
(Assuming you collected any gems you got today and already counted those.)", month(now), day(now), year(now), current_gems); var verboseText = ""; - var gems = current_gems - now = now.add(1, 'days') + if (calc_daily_quest_gems) { + verboseText = "(Including daily 'quest' gems in the calculation. There will not be a separate daily entry for each one.)

"; + } + var gems = current_gems; + now = now.add(1, "days"); while (now.isBefore(target_date_object) || is_same_day(now, target_date_object)) { // is it a login bonus? if (is_gem_day(now)) { gems += 1; } - // is it a birthday? var birthday_tuple = is_muse_members_birthday(now); var is_bday = birthday_tuple[0]; @@ -421,25 +685,72 @@ function calculate_gems() if (is_bday) { gems += 5; } - - // record verbose output if desired - if (verbose) { - if (is_gem_day(now) && is_bday) { - verboseText += sprintf("%02d/%02d/%04d
Free gem as login bonus AND it's %s's birthday! You get 6 gems, which brings you to %d gems.

", month(now), day(now), year(now), name, gems); - } - - if (is_bday && !is_gem_day(now)) { - verboseText += sprintf("%02d/%02d/%04d
It's %s's birthday! You get 5 gems, which brings you to %d gems.

", month(now), day(now), year(now), name, gems); - } - - if (is_gem_day(now) && !is_bday) { - verboseText = verboseText + sprintf("%02d/%02d/%04d
Free gem as login bonus, which brings you to %d gems.

", month(now), day(now), year(now), gems); + // account for daily login quest gem + if (calc_daily_quest_gems) { + gems++; + } + if (calc_event_gems) { + // account for event + // format of returned tuple: + // tuple[0] - was this an event day? (boolean, duh) + // tuple[1] - name of event, or "" if none (string) + // tuple[2] - amount of gems spent (int) + // tuple[3] - amount of gems gained (int) + var event_results = calculate_event(day(now), game_version, tier); + var is_event = event_results[0]; + var event_name = ""; + var spent_gems = 0; + var won_gems = 0; + if (is_event) { + event_name = event_results[1]; + spent_gems = event_results[2]; + won_gems = event_results[3]; + // did any gems get spent? + if (spent_gems > 0) { + // do we have enough to cover it? + if (gems >= spent_gems) { + // spend the gems + gems -= spent_gems; + // now reap the winnings + gems += won_gems; + } else { + // flag to indicate that we didn't have the gems + spent_gems = -1; + } + } else { + gems += won_gems; + } } } - - now = now.add(1, 'days') + // record verbose output if desired + if (verbose) { + if (is_gem_day(now) || is_bday || is_event) { + verboseText += sprintf("%02d/%02d/%04d
", month(now), day(now), year(now)); + if (is_gem_day(now)) { + verboseText += "Free gem as login bonus!
"; + } + if (is_bday) { + verboseText += sprintf("It's %s's birthday! You get 5 gems!
", name); + } + // account for events + if (is_event) { + verboseText += sprintf("A " + event_name + " just ended!
", month(now), day(now), year(now), event_name); + if (spent_gems == -1) { + verboseText += "You didn't have enough gems to participate.
"; + } else { + if (spent_gems == 0) { + verboseText += sprintf("You didn't have to spend any gems, and you won %d gems!
", won_gems); + } else { + verboseText += sprintf("You spent %d gems, and you won %d gems.
", spent_gems, won_gems); + } + } + } + // add a newline + verboseText += sprintf("That brings you to %d gems!

", gems); + } + } + now = now.add(1, "days"); } - resultsString = resultsString + sprintf("
You will have %d love gems on %02d/%02d/%04d. Good things come to those who wait!", gems, month(target_date_object), day(target_date_object), year(target_date_object)); $("#gem-result-summary").html(resultsString); if (verbose) { @@ -449,27 +760,29 @@ function calculate_gems() $("#gem-result-verbose-area").html(verboseText); $("#gem-result-textarea").hide(); } - } else if (mode === "GEMS") { + } else if (mode === "GEMS") { var target_gems = parseInt($("#gem_desired_gems").val()); if (isNaN(target_gems)) { window.alert("Error: you have entered an invalid (non-numeric) value. Please check your input and try again."); return; } - var now = moment(new Date()); + // arbitrary limit to make sure we don't go wander off into infinity + var cutoff = moment(now).add(5, "years"); var resultsString = sprintf("Today is %02d/%02d/%04d and you currently have %d love gems.
(Assuming you collected any gems you got today and already counted those.)", month(now), day(now), year(now), current_gems); var verboseText = ""; - - var gems = current_gems - - while (gems < target_gems) { - now = now.add(1, 'days') - + if (calc_daily_quest_gems) { + verboseText = "(Including daily 'quest' gems in the calculation. There will not be a separate daily entry for each one.)

"; + } + // make sure we don't go off into infinity (i.e. user gives input that is impossible to calculate) + var abort = false; + var gems = current_gems; + while (gems < target_gems && !abort) { + now = now.add(1, "days"); // is it a login bonus? if (is_gem_day(now)) { gems += 1; } - // is it a birthday? var birthday_tuple = is_muse_members_birthday(now); var is_bday = birthday_tuple[0]; @@ -477,25 +790,80 @@ function calculate_gems() if (is_bday) { gems += 5; } - - // record verbose output if desired - if (verbose) { - if (is_gem_day(now) && is_bday) { - verboseText += sprintf("%02d/%02d/%04d
Free gem as login bonus AND it's %s's birthday! You get 6 gems, which brings you to %d gems.

", month(now), day(now), year(now), name, gems); - } - - if (is_bday && !is_gem_day(now)) { - verboseText += sprintf("%02d/%02d/%04d
It's %s's birthday! You get 5 gems, which brings you to %d gems.

", month(now), day(now), year(now), name, gems); - } - - if (is_gem_day(now) && !is_bday) { - verboseText = verboseText + sprintf("%02d/%02d/%04d
Free gem as login bonus, which brings you to %d gems.

", month(now), day(now), year(now), gems); + // account for daily login quest gem + if (calc_daily_quest_gems) { + gems++; + } + if (calc_event_gems) { + // account for event + // format of returned tuple: + // tuple[0] - was this an event day? (boolean, duh) + // tuple[1] - name of event, or "" if none (string) + // tuple[2] - amount of gems spent (int) + // tuple[3] - amount of gems gained (int) + var event_results = calculate_event(day(now), game_version, tier); + var is_event = event_results[0]; + var event_name = ""; + var spent_gems = 0; + var won_gems = 0; + if (is_event) { + event_name = event_results[1]; + spent_gems = event_results[2]; + won_gems = event_results[3]; + // did any gems get spent? + if (spent_gems > 0) { + // do we have enough to cover it? + if (gems >= spent_gems) { + // spend the gems + gems -= spent_gems; + // now reap the winnings + gems += won_gems; + } else { + // flag to indicate that we didn't have the gems + spent_gems = -1; + } + } else { + gems += won_gems; + } } } + // record verbose output if desired + if (verbose) { + if (is_gem_day(now) || is_bday || is_event) { + verboseText += sprintf("%02d/%02d/%04d
", month(now), day(now), year(now)); + if (is_gem_day(now)) { + verboseText += "Free gem as login bonus!
"; + } + if (is_bday) { + verboseText += sprintf("It's %s's birthday! You get 5 gems!
", name); + } + // account for events + if (is_event) { + verboseText += sprintf("A " + event_name + " just ended!
", month(now), day(now), year(now), event_name); + if (spent_gems == -1) { + verboseText += "You didn't have enough gems to participate.
"; + } else { + if (spent_gems == 0) { + verboseText += sprintf("You didn't have to spend any gems, and you won %d gems!
", won_gems); + } else { + verboseText += sprintf("You spent %d gems, and you won %d gems.
", spent_gems, won_gems); + } + } + } + // add a newline + verboseText += sprintf("That brings you to %d gems!

", gems); + } + } + // do we abort? + if (now.isAfter(cutoff)) { + verboseText += sprintf("(Cannot proceed, this appears to be an unattainable amount of gems given your constraints.)"); + abort = true; + } } - resultsString = resultsString + sprintf("
You will have %d love gems on %02d/%02d/%04d. Good things come to those who wait!", gems, month(now), day(now), year(now)); - + if (abort) { + resultsString += "
(Note: the calculation was stopped because it appears that you will be unable to attain the desired amount of gems given your constraints.)"; + } $("#gem-result-summary").html(resultsString); if (verbose) { $("#gem-result-verbose-area").html(verboseText); @@ -505,27 +873,24 @@ function calculate_gems() $("#gem-result-textarea").hide(); } } - $("#gem-calc-result-area").show(); } -function reset_gems() -{ +function reset_gems() { $("#gem-calc-result-area").hide(); $("#gem-result-summary").text("-"); $("#gem-desired-gems-area").hide(); $("#gem-date-area").hide(); $("#current_gems").val(0); - var $radios = $('input:radio[name=gem-mode]'); - $radios.filter('[value=DATE]').prop('checked', true); + var $radios = $("input:radio[name=gem-mode]"); + $radios.filter("[value=DATE]").prop("checked", true); $("#gem_desired_date").val(""); $("#gem_desired_gems").val(""); - $('#gems_verbose').prop('checked', false); + $("#gems_verbose").prop("checked", false); $("#gem-date-area").show(); } -function get_level_cap(rarity) -{ +function get_level_cap(rarity) { if (rarity === "N") { return 40; } else if (rarity === "R") { @@ -539,40 +904,51 @@ function get_level_cap(rarity) return -1; } -function is_valid_level(rarity, level) -{ +function is_valid_level(rarity, level) { var return_value = false; if (level >= 1) { - return_value = (level <= get_level_cap(rarity)); + return_value = level <= get_level_cap(rarity); } return return_value; } -function is_valid_exp(rarity, level, exp) -{ +function is_valid_exp(rarity, level, exp) { var return_value = false; if (rarity === "N" && is_valid_level(rarity, level)) { - return_value = (exp >= 0 && exp < exp_table_n[level+1]); + return_value = exp >= 0 && exp < exp_table_n[level + 1]; } else if (rarity === "R" && is_valid_level(rarity, level)) { - return_value = (exp >= 0 && exp < exp_table_r[level+1]); - } else if (rarity === "SR" && is_valid_level(rarity, level+1)) { - return_value = (exp >= 0 && exp < exp_table_sr[level+1]); + return_value = exp >= 0 && exp < exp_table_r[level + 1]; + } else if (rarity === "SR" && is_valid_level(rarity, level + 1)) { + return_value = exp >= 0 && exp < exp_table_sr[level + 1]; } else if (rarity === "UR" && is_valid_level(rarity, level)) { - return_value = (exp >= 0 && exp < exp_table_ur[level+1]); + return_value = exp >= 0 && exp < exp_table_ur[level + 1]; } - return return_value + return return_value; } -function calculate_card() -{ +function get_exp_table_entry(rarity, level) { + var exp = 0; + if (rarity === "N") { + exp = exp_table_n[level]; + } else if (rarity === "R") { + exp = exp_table_r[level]; + } else if (rarity === "SR") { + exp = exp_table_sr[level]; + } else if (rarity === "UR") { + exp = exp_table_ur[level]; + } + return exp; +} + +function calculate_card() { var current_level = parseInt($("#card_current_level").val()); var current_exp_input = $("#card_current_exp").val(); + var same_attribute = $("#card_same_attribute").is(":checked"); // 1.2x bonus for feeding same attribute cards var current_exp = 0; if (current_exp_input != "") { current_exp = parseInt(current_exp_input); } var rarity = $("#card_rarity").val(); - if (isNaN(current_level) || !is_valid_level(rarity, current_level)) { alert("Error: invalid level. Please check your input and try again."); return; @@ -581,96 +957,79 @@ function calculate_card() alert("Error: invalid EXP. Please check your input and try again."); return; } - var mode = $("input[name=card-mode]:checked").val(); - if (mode === "LEVEL") { + if (mode === "LEVEL") { var target_level = parseInt($("#card_desired_level").val()); if (isNaN(target_level) || !is_valid_level(rarity, target_level)) { window.alert("Error: the desired level is invalid."); return; } - // ready to rock var required_exp = 0; var resultsString = ""; - for (level = current_level+1; level <= target_level; level++) { - if (rarity === "N") { - required_exp += exp_table_n[level]; - } else if (rarity === "R") { - required_exp += exp_table_r[level]; - } else if (rarity === "SR") { - required_exp += exp_table_sr[level]; - } else if (rarity === "UR") { - required_exp += exp_table_ur[level]; - } + for (level = current_level + 1; level <= target_level; level++) { + required_exp += get_exp_table_entry(rarity, level); } - // subtract what we have required_exp -= current_exp; - var resultString = sprintf("To get a %s card from level %d (with %d EXP) to %d requires %d EXP.
", rarity, current_level, current_exp, target_level, required_exp); - // calculate equiv N cards - var number_of_n_cards = Math.round(required_exp / 100) + 1; + // FINDME + if (same_attribute) { + n_card_factor = 120; + } else { + n_card_factor = 100; + } + var number_of_n_cards = Math.round(required_exp / n_card_factor) + 1; resultString += sprintf("(the equivalent of about %d level-1 N cards fed to it)", number_of_n_cards); - // output the result $("#card-result-summary").html(resultString); - } else if (mode === "EXP") { + } else if (mode === "EXP") { var exp_to_feed = parseInt($("#card_feed_exp").val()); if (isNaN(exp_to_feed)) { window.alert("Error: you have entered an invalid (non-numeric) value. Please check your input and try again."); return; } - + var real_exp_to_feed = exp_to_feed; // ready to rock - var resultsString = "FOO"; - + var resultsString = ""; // XXX do some calculating var exp_tally = current_exp; var level = 0; - for (level = current_level+1; level <= get_level_cap(rarity); level++) { - if (rarity === "N") { - exp_tally += exp_table_n[level]; - } else if (rarity === "R") { - exp_tally += exp_table_r[level]; - } else if (rarity === "SR") { - exp_tally += exp_table_sr[level]; - } else if (rarity === "UR") { - exp_tally += exp_table_ur[level]; - } - if (exp_tally > exp_to_feed) { + for (level = current_level + 1; level <= get_level_cap(rarity); level++) { + exp_tally += get_exp_table_entry(rarity, level); + if (exp_tally > real_exp_to_feed) { break; } } - level--; - var resultString = sprintf("If you feed a %s card at level %d (with %d EXP) a total of %d EXP,
it will end up at level %d.%s", rarity, current_level, current_exp, exp_to_feed, level, (level == get_level_cap(rarity) ? " (MAX LEVEL!)" : "")); - + var resultString = ""; + if (exp_to_feed > exp_tally) { + resultString = sprintf("If you feed a %s card at level %d (with %d EXP) a total of %d EXP,
it will end up at level %d.%s
This is way overkill, you fed %d more EXP than was necessary.", rarity, current_level, current_exp, exp_to_feed, level, (level == get_level_cap(rarity) ? " (MAX LEVEL!)" : ""), exp_to_feed - exp_tally); + } else { + resultString = sprintf("If you feed a %s card at level %d (with %d EXP) a total of %d EXP,
it will end up at level %d.%s", rarity, current_level, current_exp, exp_to_feed, level, level == get_level_cap(rarity) ? " (MAX LEVEL!)" : ""); + } // output the result $("#card-result-summary").html(resultString); } - $("#card-calc-result-area").show(); } -function reset_card() -{ +function reset_card() { $("#card-calc-result-area").hide(); $("#card-result-summary").text("-"); $("#card-level-area").hide(); $("#card-exp-area").hide(); $("#card_current_level").val(""); $("#card_current_exp").val(""); - var $radios = $('input:radio[name=card-mode]'); - $radios.filter('[value=LEVEL]').prop('checked', true); + var $radios = $("input:radio[name=card-mode]"); + $radios.filter("[value=LEVEL]").prop("checked", true); $("#card_desired_level").val(""); $("#card_feed_exp").val(""); $("#card-level-area").show(); } -function start_stop_timer() -{ +function start_stop_timer() { if (window.timerInterval != 0) { // stop it clearInterval(window.timerInterval); @@ -680,7 +1039,7 @@ function start_stop_timer() // 2013-02-08 09:30 # An hour and minute time part var dateString = $("#event_end_date").val() + " " + $("#event_end_time").val() + "Z"; window.the_end = moment(dateString, "MM/DD/YYYY HH:mmZ"); - window.timerInterval = setInterval(run_timer, 1000); + window.timerInterval = setInterval(run_timer, 1e3); $("#button-start-stop-timer span").text("Stop Timer"); // run the first update ourselves, so that it doesn't stay blank until the timer kicks in window.immediately_refresh_tier_cutoffs = true; @@ -688,97 +1047,80 @@ function start_stop_timer() } } -function run_timer() -{ +function run_timer() { var now = moment(new Date()); var end = window.the_end; var string = "

CURRENT TIME

" + now.utc().format("MM/DD/YYYY HH:mm:ss") + " UTC

EVENT ENDS:

" + end.utc().format("MM/DD/YYYY HH:mm:ss") + " UTC

"; - if (now.isBefore(end)) { + if (now.isBefore(end)) { var ms = end.diff(now.utc()); var t = moment.duration(ms).asMilliseconds(); - var seconds = Math.floor( (t/1000) % 60 ); - var minutes = Math.floor( (t/1000/60) % 60 ); - var hours = Math.floor( (t/(1000*60*60)) % 24 ); - var days = Math.floor( t/(1000*60*60*24) ); - var total_hours = (days * 24) + hours; - + var seconds = Math.floor(t / 1e3 % 60); + var minutes = Math.floor(t / 1e3 / 60 % 60); + var hours = Math.floor(t / (1e3 * 60 * 60) % 24); + var days = Math.floor(t / (1e3 * 60 * 60 * 24)); + var total_hours = days * 24 + hours; var time_till = "

"; - if (total_hours >= 24) { time_till += sprintf("%d hours
", total_hours); } - time_till += "("; + if (total_hours >= 24) { + time_till += "("; + } if (days >= 1) { - time_till += sprintf("%d day%s ", days, (days > 1 ? "s" : "")); + time_till += sprintf("%d day%s ", days, days > 1 ? "s" : ""); } if (hours >= 1) { - time_till += sprintf("%d hour%s ", hours, (hours > 1 ? "s" : "")); + time_till += sprintf("%d hour%s ", hours, hours > 1 ? "s" : ""); } - time_till += sprintf("%d minute%s %d second%s)

", minutes, (minutes > 1? "s" : ""), seconds, (seconds > 1 ? "s" : "")); + time_till += sprintf("%d minute%s %d second%s%s", minutes, minutes > 1 ? "s" : "", seconds, (seconds > 1 ? "s" : ""), (total_hours >= 24) ? ")" : ""); // d.asDays() + " days " + d.asHours() + moment.utc(ms).format(":mm:ss"); - // var time_till = moment.utc(end.diff(now)).format("HH:mm:ss");//.format("HH:mm:ss"); string += "

TIME REMAINING:

" + time_till + "

"; } else { string += "

EVENT IS OVER

"; } $("#timer_output_area").html(string); - - // dumb ass way to fetch and display @sifen_trackbot tier cutoff tweets hourly - // using this twitter fetcher: http://jasonmayes.com/projects/twitterApi/#sthash.budgYosd.dpbs - var config5 = { - "id": '654587648904794112', - "domId": '', - "maxTweets": 1, - "enableLinks": false, - "showUser": true, - "showTime": true, - "dateFunction": '', - "showRetweet": false, - "customCallback": handleTweets, - "showInteraction": false - }; - - function handleTweets(tweets) { - if (tweets.length > 0) { - var tweet = tweets[0]; - // parse it - console.log("GOT TWEET: " + tweet); - // this is kinda crappy - var splitTweet = tweet.split("\n"); - // this is VERY LAZY - // we always assume Tier1 is on line 11, Tier2 is line 12 and Date is 13 - var tier1 = splitTweet[11]; - var tier2 = splitTweet[12]; - var updateTime = splitTweet[13]; - $("#tier_info_output_area").html("

Latest Tier Cutoffs as of " + updateTime + ":
" + tier1 + "
" + tier2 + "

"); - } - } - - // @sifen_trackbot updates come out at 36 minutes past the hour, fetch them at 37 minutes to allow for some slop - if (minute(now) == 37 || window.immediately_refresh_tier_cutoffs) { + // @sifen_trackbot updates come out at 36 minutes past the hour, fetch them at 38 minutes to allow for some slop + if (minute(now) == 38 && second(now) == 0 || window.immediately_refresh_tier_cutoffs) { $("#tier_info_output_area").html("

Updating tier cutoff data, please wait...

"); - twitterFetcher.fetch(config5); + // dumb ass way to fetch and display @sifen_trackbot tier cutoff tweets hourly + // using this twitter fetcher: http://jasonmayes.com/projects/twitterApi/#sthash.budgYosd.dpbs + twitterFetcher.fetch({ + id: "654587648904794112", + domId: "", + maxTweets: 1, + enableLinks: false, + showUser: true, + showTime: true, + dateFunction: "", + showRetweet: false, + customCallback: function(tweets) { + if (tweets.length > 0) { + // we only care about the first one + var tweet = tweets[0]; + LOG(1, "GOT TWEET: " + tweet); + // now parse it + // !!! QUICK & DIRTY HACK ALERT !!! + // this is kinda crappy, @sifen_trackbot tweets come out as multiple lines separated by newlines + // so for now we just split the incoming tweet into an array of strings (one per line) and pull the values out using explicit line numbers + // we always assume Tier1 value is on line 11, Tier2 is line 12 and Date/time/% complete is line 13 + // this may (in fact it probably will) change in the future + // eventually I would like to come up with a better (i.e. less crappy) algorithm to parse the string and extract the values dynamically + // but for now this will do :P + var splitTweet = tweet.split("\n"); + var tier1 = splitTweet[11]; + var tier2 = splitTweet[12]; + var updateTime = splitTweet[13]; + $("#tier_info_output_area").html("

Latest Tier Cutoffs as of UTC " + updateTime + ":
" + tier1 + "
" + tier2 + "

"); + } + }, + showInteraction: false + }); window.immediately_refresh_tier_cutoffs = false; } } -function clear_timer() -{ +function clear_timer() { $("#timer_output_area").html("

Timer Not Running

"); -} - - - - - - - - - - - - - - - + $("#tier_info_output_area").html(""); +} \ No newline at end of file diff --git a/web_app/sif_tools.html b/web_app/sif_tools.html index b041d76..8bc120a 100644 --- a/web_app/sif_tools.html +++ b/web_app/sif_tools.html @@ -1,153 +1,238 @@ - + - - + + + + SIF Tools - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - -
- - -
-
- Current Rank:    -
- Current EXP:    -
- Desired Rank:    -
- Game Version:    - -

-
Calculate
-

-
-

Results

- EXP required:   -

- You will need to play the following number of songs in order to get this amount of EXP:

- EASY:   -
- NORMAL:   -
- HARD:   -
- EXPERT:   -

- At this rank you will have - LP and - friend slots.

-
Reset
-
-
-
-
-
- Current Gems:    -

- Mode:
- Number of gems on date?
- Date when you get x gems?

-
- Date:    -
-
- Desired gems:    -
-

- Verbose Mode
-

-
Calculate
-

-
-

Results

- -

-
-
-
-
-
-
Reset
-
+ - -
-
- Card Rarity:    - -
- Current Level:    -
- Current EXP:    -

- Mode:
- EXP needed to level?
- Final level after feeding?

-
- Desired level:    -
-
- EXP:    -
-

-
Calculate
-

-
-

Results

- -

-
Reset
-
-
-
- -
-
-

Enter Event End Date/Time (in UTC):


- - -

-
Start Timer
-
-

Timer Not Running

+
+ +
+
+ Current Rank:   
+ Current EXP:   
+ Desired Rank:   
+ Game Version:   
+
+
+ Calculate +

+
+
+

Results

EXP required:   -
+
+ You will need to play the following number of songs in order to get this amount of EXP:
+
(Single / 3xMedFes / 3xMedFes w/EXP boost)

+ EASY:   - / - / -
+ NORMAL:   - / - / -
+ HARD:   - / - / -
+ EXPERT:   - / - / -
+
+ At this rank you will have - LP and - friend slots.
+
+
+ Reset +
+
+
-
+
+
+ Current Gems:   
+ Game Version:   
+
+ Include daily "quest" gems? + (What is this?)
+ Include gems from events? (Read this first)
+
+ Average tier:    +

+ Mode:
+ Number of gems you'll have on a date?
+ Date you will have this many gems?
+
+
+ Date:    +
+
+ Desired gems:    +

+
+ Verbose Mode
+
+
+
+ Calculate +

+
+
+

Results

-
+
+
+
+ - +
+
+
+ Reset +
+
+
-
Clear Timer
-
- -
-

-
-
-
- +
+
+ Card Rarity:   
+ Current Level:   
+ Current EXP:   
+
+ Mode:
+ EXP needed to get card to a level?
+ Final level after feeding an amount of EXP?
+
+
+ Desired level:    +
Max
+
+
+ EXP:    +

+ Assume cards are same attribute
+
+
+ Calculate +

+
+
+

Results

-
+
+
+ Reset +
+
+
+
+
+
+

Enter Event End Date/Time (in UTC):


+
+
+
+ Start Timer +
+
+

Timer Not Running

+
+
+
+ Clear Timer +
+
+
+

+
+
+
\ No newline at end of file