diff --git a/.gitignore b/.gitignore index 3f2d49e5..9ce2a024 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ includes/.DS_Store includes/react/frontend/node_modules docs/_site/* playwright/.auth/user.json -playwright/playwright/.auth/user.json +playwright/playwright/.auth/user.json \ No newline at end of file diff --git a/README.md b/README.md index f1b105af..c6362acd 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Weight Tracker -For further information regarding this plugin, please use the following links: +[Weight Tracker](https://wordpress.org/plugins/weight-loss-tracker/) is a [WordPress](https://wordpress.org) plugin that allows your users to track their weight, measurements, photos and custom data! Support for BMI, BMR, Calorie Intake, Macronutrients and much more! -Plugin support site: https://docs.yeken.uk -WordPress Plugin Repo: https://wordpress.org/plugins/weight-loss-tracker/ +The plugin can be found on the WordPress plugin directory: https://wordpress.org/plugins/weight-loss-tracker/ + +## Documentation + +Documentation for this plugin (for the end user) can be found at https://docs.yeken.uk \ No newline at end of file diff --git a/assets/.DS_Store b/assets/.DS_Store index 3a994c06..18287417 100644 Binary files a/assets/.DS_Store and b/assets/.DS_Store differ diff --git a/assets/css/fonts/FontAwesome.otf b/assets/css/fonts/FontAwesome.otf new file mode 100644 index 00000000..401ec0f3 Binary files /dev/null and b/assets/css/fonts/FontAwesome.otf differ diff --git a/assets/css/fonts/fontawesome-webfont.eot b/assets/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000..e9f60ca9 Binary files /dev/null and b/assets/css/fonts/fontawesome-webfont.eot differ diff --git a/assets/css/fonts/fontawesome-webfont.svg b/assets/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000..855c845e --- /dev/null +++ b/assets/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/css/fonts/fontawesome-webfont.ttf b/assets/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000..35acda2f Binary files /dev/null and b/assets/css/fonts/fontawesome-webfont.ttf differ diff --git a/assets/css/fonts/fontawesome-webfont.woff b/assets/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000..400014a4 Binary files /dev/null and b/assets/css/fonts/fontawesome-webfont.woff differ diff --git a/assets/css/fonts/fontawesome-webfont.woff2 b/assets/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000..4d13fc60 Binary files /dev/null and b/assets/css/fonts/fontawesome-webfont.woff2 differ diff --git a/assets/css/libraries/fontawesome-4.7.0.css b/assets/css/libraries/fontawesome-4.7.0.css new file mode 100644 index 00000000..ac1581e0 --- /dev/null +++ b/assets/css/libraries/fontawesome-4.7.0.css @@ -0,0 +1,2339 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +/* FONT PATH + * -------------------------- */ + @font-face { + font-family: 'FontAwesome'; + src: url('../fonts/fontawesome-webfont.eot?v=4.7.0'); + src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg'); + font-weight: normal; + font-style: normal; + } + .fa { + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + /* makes the font 33% larger relative to the icon container */ + .fa-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; + } + .fa-2x { + font-size: 2em; + } + .fa-3x { + font-size: 3em; + } + .fa-4x { + font-size: 4em; + } + .fa-5x { + font-size: 5em; + } + .fa-fw { + width: 1.28571429em; + text-align: center; + } + .fa-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; + } + .fa-ul > li { + position: relative; + } + .fa-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; + } + .fa-li.fa-lg { + left: -1.85714286em; + } + .fa-border { + padding: .2em .25em .15em; + border: solid 0.08em #eeeeee; + border-radius: .1em; + } + .fa-pull-left { + float: left; + } + .fa-pull-right { + float: right; + } + .fa.fa-pull-left { + margin-right: .3em; + } + .fa.fa-pull-right { + margin-left: .3em; + } + /* Deprecated as of 4.4.0 */ + .pull-right { + float: right; + } + .pull-left { + float: left; + } + .fa.pull-left { + margin-right: .3em; + } + .fa.pull-right { + margin-left: .3em; + } + .fa-spin { + -webkit-animation: fa-spin 2s infinite linear; + animation: fa-spin 2s infinite linear; + } + .fa-pulse { + -webkit-animation: fa-spin 1s infinite steps(8); + animation: fa-spin 1s infinite steps(8); + } + @-webkit-keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } + } + @keyframes fa-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } + } + .fa-rotate-90 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); + } + .fa-rotate-180 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)"; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); + } + .fa-rotate-270 { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)"; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); + } + .fa-flip-horizontal { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)"; + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); + } + .fa-flip-vertical { + -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"; + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); + } + :root .fa-rotate-90, + :root .fa-rotate-180, + :root .fa-rotate-270, + :root .fa-flip-horizontal, + :root .fa-flip-vertical { + filter: none; + } + .fa-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; + } + .fa-stack-1x, + .fa-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; + } + .fa-stack-1x { + line-height: inherit; + } + .fa-stack-2x { + font-size: 2em; + } + .fa-inverse { + color: #ffffff; + } + /* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + .fa-glass:before { + content: "\f000"; + } + .fa-music:before { + content: "\f001"; + } + .fa-search:before { + content: "\f002"; + } + .fa-envelope-o:before { + content: "\f003"; + } + .fa-heart:before { + content: "\f004"; + } + .fa-star:before { + content: "\f005"; + } + .fa-star-o:before { + content: "\f006"; + } + .fa-user:before { + content: "\f007"; + } + .fa-film:before { + content: "\f008"; + } + .fa-th-large:before { + content: "\f009"; + } + .fa-th:before { + content: "\f00a"; + } + .fa-th-list:before { + content: "\f00b"; + } + .fa-check:before { + content: "\f00c"; + } + .fa-remove:before, + .fa-close:before, + .fa-times:before { + content: "\f00d"; + } + .fa-search-plus:before { + content: "\f00e"; + } + .fa-search-minus:before { + content: "\f010"; + } + .fa-power-off:before { + content: "\f011"; + } + .fa-signal:before { + content: "\f012"; + } + .fa-gear:before, + .fa-cog:before { + content: "\f013"; + } + .fa-trash-o:before { + content: "\f014"; + } + .fa-home:before { + content: "\f015"; + } + .fa-file-o:before { + content: "\f016"; + } + .fa-clock-o:before { + content: "\f017"; + } + .fa-road:before { + content: "\f018"; + } + .fa-download:before { + content: "\f019"; + } + .fa-arrow-circle-o-down:before { + content: "\f01a"; + } + .fa-arrow-circle-o-up:before { + content: "\f01b"; + } + .fa-inbox:before { + content: "\f01c"; + } + .fa-play-circle-o:before { + content: "\f01d"; + } + .fa-rotate-right:before, + .fa-repeat:before { + content: "\f01e"; + } + .fa-refresh:before { + content: "\f021"; + } + .fa-list-alt:before { + content: "\f022"; + } + .fa-lock:before { + content: "\f023"; + } + .fa-flag:before { + content: "\f024"; + } + .fa-headphones:before { + content: "\f025"; + } + .fa-volume-off:before { + content: "\f026"; + } + .fa-volume-down:before { + content: "\f027"; + } + .fa-volume-up:before { + content: "\f028"; + } + .fa-qrcode:before { + content: "\f029"; + } + .fa-barcode:before { + content: "\f02a"; + } + .fa-tag:before { + content: "\f02b"; + } + .fa-tags:before { + content: "\f02c"; + } + .fa-book:before { + content: "\f02d"; + } + .fa-bookmark:before { + content: "\f02e"; + } + .fa-print:before { + content: "\f02f"; + } + .fa-camera:before { + content: "\f030"; + } + .fa-font:before { + content: "\f031"; + } + .fa-bold:before { + content: "\f032"; + } + .fa-italic:before { + content: "\f033"; + } + .fa-text-height:before { + content: "\f034"; + } + .fa-text-width:before { + content: "\f035"; + } + .fa-align-left:before { + content: "\f036"; + } + .fa-align-center:before { + content: "\f037"; + } + .fa-align-right:before { + content: "\f038"; + } + .fa-align-justify:before { + content: "\f039"; + } + .fa-list:before { + content: "\f03a"; + } + .fa-dedent:before, + .fa-outdent:before { + content: "\f03b"; + } + .fa-indent:before { + content: "\f03c"; + } + .fa-video-camera:before { + content: "\f03d"; + } + .fa-photo:before, + .fa-image:before, + .fa-picture-o:before { + content: "\f03e"; + } + .fa-pencil:before { + content: "\f040"; + } + .fa-map-marker:before { + content: "\f041"; + } + .fa-adjust:before { + content: "\f042"; + } + .fa-tint:before { + content: "\f043"; + } + .fa-edit:before, + .fa-pencil-square-o:before { + content: "\f044"; + } + .fa-share-square-o:before { + content: "\f045"; + } + .fa-check-square-o:before { + content: "\f046"; + } + .fa-arrows:before { + content: "\f047"; + } + .fa-step-backward:before { + content: "\f048"; + } + .fa-fast-backward:before { + content: "\f049"; + } + .fa-backward:before { + content: "\f04a"; + } + .fa-play:before { + content: "\f04b"; + } + .fa-pause:before { + content: "\f04c"; + } + .fa-stop:before { + content: "\f04d"; + } + .fa-forward:before { + content: "\f04e"; + } + .fa-fast-forward:before { + content: "\f050"; + } + .fa-step-forward:before { + content: "\f051"; + } + .fa-eject:before { + content: "\f052"; + } + .fa-chevron-left:before { + content: "\f053"; + } + .fa-chevron-right:before { + content: "\f054"; + } + .fa-plus-circle:before { + content: "\f055"; + } + .fa-minus-circle:before { + content: "\f056"; + } + .fa-times-circle:before { + content: "\f057"; + } + .fa-check-circle:before { + content: "\f058"; + } + .fa-question-circle:before { + content: "\f059"; + } + .fa-info-circle:before { + content: "\f05a"; + } + .fa-crosshairs:before { + content: "\f05b"; + } + .fa-times-circle-o:before { + content: "\f05c"; + } + .fa-check-circle-o:before { + content: "\f05d"; + } + .fa-ban:before { + content: "\f05e"; + } + .fa-arrow-left:before { + content: "\f060"; + } + .fa-arrow-right:before { + content: "\f061"; + } + .fa-arrow-up:before { + content: "\f062"; + } + .fa-arrow-down:before { + content: "\f063"; + } + .fa-mail-forward:before, + .fa-share:before { + content: "\f064"; + } + .fa-expand:before { + content: "\f065"; + } + .fa-compress:before { + content: "\f066"; + } + .fa-plus:before { + content: "\f067"; + } + .fa-minus:before { + content: "\f068"; + } + .fa-asterisk:before { + content: "\f069"; + } + .fa-exclamation-circle:before { + content: "\f06a"; + } + .fa-gift:before { + content: "\f06b"; + } + .fa-leaf:before { + content: "\f06c"; + } + .fa-fire:before { + content: "\f06d"; + } + .fa-eye:before { + content: "\f06e"; + } + .fa-eye-slash:before { + content: "\f070"; + } + .fa-warning:before, + .fa-exclamation-triangle:before { + content: "\f071"; + } + .fa-plane:before { + content: "\f072"; + } + .fa-calendar:before { + content: "\f073"; + } + .fa-random:before { + content: "\f074"; + } + .fa-comment:before { + content: "\f075"; + } + .fa-magnet:before { + content: "\f076"; + } + .fa-chevron-up:before { + content: "\f077"; + } + .fa-chevron-down:before { + content: "\f078"; + } + .fa-retweet:before { + content: "\f079"; + } + .fa-shopping-cart:before { + content: "\f07a"; + } + .fa-folder:before { + content: "\f07b"; + } + .fa-folder-open:before { + content: "\f07c"; + } + .fa-arrows-v:before { + content: "\f07d"; + } + .fa-arrows-h:before { + content: "\f07e"; + } + .fa-bar-chart-o:before, + .fa-bar-chart:before { + content: "\f080"; + } + .fa-twitter-square:before { + content: "\f081"; + } + .fa-facebook-square:before { + content: "\f082"; + } + .fa-camera-retro:before { + content: "\f083"; + } + .fa-key:before { + content: "\f084"; + } + .fa-gears:before, + .fa-cogs:before { + content: "\f085"; + } + .fa-comments:before { + content: "\f086"; + } + .fa-thumbs-o-up:before { + content: "\f087"; + } + .fa-thumbs-o-down:before { + content: "\f088"; + } + .fa-star-half:before { + content: "\f089"; + } + .fa-heart-o:before { + content: "\f08a"; + } + .fa-sign-out:before { + content: "\f08b"; + } + .fa-linkedin-square:before { + content: "\f08c"; + } + .fa-thumb-tack:before { + content: "\f08d"; + } + .fa-external-link:before { + content: "\f08e"; + } + .fa-sign-in:before { + content: "\f090"; + } + .fa-trophy:before { + content: "\f091"; + } + .fa-github-square:before { + content: "\f092"; + } + .fa-upload:before { + content: "\f093"; + } + .fa-lemon-o:before { + content: "\f094"; + } + .fa-phone:before { + content: "\f095"; + } + .fa-square-o:before { + content: "\f096"; + } + .fa-bookmark-o:before { + content: "\f097"; + } + .fa-phone-square:before { + content: "\f098"; + } + .fa-twitter:before { + content: "\f099"; + } + .fa-facebook-f:before, + .fa-facebook:before { + content: "\f09a"; + } + .fa-github:before { + content: "\f09b"; + } + .fa-unlock:before { + content: "\f09c"; + } + .fa-credit-card:before { + content: "\f09d"; + } + .fa-feed:before, + .fa-rss:before { + content: "\f09e"; + } + .fa-hdd-o:before { + content: "\f0a0"; + } + .fa-bullhorn:before { + content: "\f0a1"; + } + .fa-bell:before { + content: "\f0f3"; + } + .fa-certificate:before { + content: "\f0a3"; + } + .fa-hand-o-right:before { + content: "\f0a4"; + } + .fa-hand-o-left:before { + content: "\f0a5"; + } + .fa-hand-o-up:before { + content: "\f0a6"; + } + .fa-hand-o-down:before { + content: "\f0a7"; + } + .fa-arrow-circle-left:before { + content: "\f0a8"; + } + .fa-arrow-circle-right:before { + content: "\f0a9"; + } + .fa-arrow-circle-up:before { + content: "\f0aa"; + } + .fa-arrow-circle-down:before { + content: "\f0ab"; + } + .fa-globe:before { + content: "\f0ac"; + } + .fa-wrench:before { + content: "\f0ad"; + } + .fa-tasks:before { + content: "\f0ae"; + } + .fa-filter:before { + content: "\f0b0"; + } + .fa-briefcase:before { + content: "\f0b1"; + } + .fa-arrows-alt:before { + content: "\f0b2"; + } + .fa-group:before, + .fa-users:before { + content: "\f0c0"; + } + .fa-chain:before, + .fa-link:before { + content: "\f0c1"; + } + .fa-cloud:before { + content: "\f0c2"; + } + .fa-flask:before { + content: "\f0c3"; + } + .fa-cut:before, + .fa-scissors:before { + content: "\f0c4"; + } + .fa-copy:before, + .fa-files-o:before { + content: "\f0c5"; + } + .fa-paperclip:before { + content: "\f0c6"; + } + .fa-save:before, + .fa-floppy-o:before { + content: "\f0c7"; + } + .fa-square:before { + content: "\f0c8"; + } + .fa-navicon:before, + .fa-reorder:before, + .fa-bars:before { + content: "\f0c9"; + } + .fa-list-ul:before { + content: "\f0ca"; + } + .fa-list-ol:before { + content: "\f0cb"; + } + .fa-strikethrough:before { + content: "\f0cc"; + } + .fa-underline:before { + content: "\f0cd"; + } + .fa-table:before { + content: "\f0ce"; + } + .fa-magic:before { + content: "\f0d0"; + } + .fa-truck:before { + content: "\f0d1"; + } + .fa-pinterest:before { + content: "\f0d2"; + } + .fa-pinterest-square:before { + content: "\f0d3"; + } + .fa-google-plus-square:before { + content: "\f0d4"; + } + .fa-google-plus:before { + content: "\f0d5"; + } + .fa-money:before { + content: "\f0d6"; + } + .fa-caret-down:before { + content: "\f0d7"; + } + .fa-caret-up:before { + content: "\f0d8"; + } + .fa-caret-left:before { + content: "\f0d9"; + } + .fa-caret-right:before { + content: "\f0da"; + } + .fa-columns:before { + content: "\f0db"; + } + .fa-unsorted:before, + .fa-sort:before { + content: "\f0dc"; + } + .fa-sort-down:before, + .fa-sort-desc:before { + content: "\f0dd"; + } + .fa-sort-up:before, + .fa-sort-asc:before { + content: "\f0de"; + } + .fa-envelope:before { + content: "\f0e0"; + } + .fa-linkedin:before { + content: "\f0e1"; + } + .fa-rotate-left:before, + .fa-undo:before { + content: "\f0e2"; + } + .fa-legal:before, + .fa-gavel:before { + content: "\f0e3"; + } + .fa-dashboard:before, + .fa-tachometer:before { + content: "\f0e4"; + } + .fa-comment-o:before { + content: "\f0e5"; + } + .fa-comments-o:before { + content: "\f0e6"; + } + .fa-flash:before, + .fa-bolt:before { + content: "\f0e7"; + } + .fa-sitemap:before { + content: "\f0e8"; + } + .fa-umbrella:before { + content: "\f0e9"; + } + .fa-paste:before, + .fa-clipboard:before { + content: "\f0ea"; + } + .fa-lightbulb-o:before { + content: "\f0eb"; + } + .fa-exchange:before { + content: "\f0ec"; + } + .fa-cloud-download:before { + content: "\f0ed"; + } + .fa-cloud-upload:before { + content: "\f0ee"; + } + .fa-user-md:before { + content: "\f0f0"; + } + .fa-stethoscope:before { + content: "\f0f1"; + } + .fa-suitcase:before { + content: "\f0f2"; + } + .fa-bell-o:before { + content: "\f0a2"; + } + .fa-coffee:before { + content: "\f0f4"; + } + .fa-cutlery:before { + content: "\f0f5"; + } + .fa-file-text-o:before { + content: "\f0f6"; + } + .fa-building-o:before { + content: "\f0f7"; + } + .fa-hospital-o:before { + content: "\f0f8"; + } + .fa-ambulance:before { + content: "\f0f9"; + } + .fa-medkit:before { + content: "\f0fa"; + } + .fa-fighter-jet:before { + content: "\f0fb"; + } + .fa-beer:before { + content: "\f0fc"; + } + .fa-h-square:before { + content: "\f0fd"; + } + .fa-plus-square:before { + content: "\f0fe"; + } + .fa-angle-double-left:before { + content: "\f100"; + } + .fa-angle-double-right:before { + content: "\f101"; + } + .fa-angle-double-up:before { + content: "\f102"; + } + .fa-angle-double-down:before { + content: "\f103"; + } + .fa-angle-left:before { + content: "\f104"; + } + .fa-angle-right:before { + content: "\f105"; + } + .fa-angle-up:before { + content: "\f106"; + } + .fa-angle-down:before { + content: "\f107"; + } + .fa-desktop:before { + content: "\f108"; + } + .fa-laptop:before { + content: "\f109"; + } + .fa-tablet:before { + content: "\f10a"; + } + .fa-mobile-phone:before, + .fa-mobile:before { + content: "\f10b"; + } + .fa-circle-o:before { + content: "\f10c"; + } + .fa-quote-left:before { + content: "\f10d"; + } + .fa-quote-right:before { + content: "\f10e"; + } + .fa-spinner:before { + content: "\f110"; + } + .fa-circle:before { + content: "\f111"; + } + .fa-mail-reply:before, + .fa-reply:before { + content: "\f112"; + } + .fa-github-alt:before { + content: "\f113"; + } + .fa-folder-o:before { + content: "\f114"; + } + .fa-folder-open-o:before { + content: "\f115"; + } + .fa-smile-o:before { + content: "\f118"; + } + .fa-frown-o:before { + content: "\f119"; + } + .fa-meh-o:before { + content: "\f11a"; + } + .fa-gamepad:before { + content: "\f11b"; + } + .fa-keyboard-o:before { + content: "\f11c"; + } + .fa-flag-o:before { + content: "\f11d"; + } + .fa-flag-checkered:before { + content: "\f11e"; + } + .fa-terminal:before { + content: "\f120"; + } + .fa-code:before { + content: "\f121"; + } + .fa-mail-reply-all:before, + .fa-reply-all:before { + content: "\f122"; + } + .fa-star-half-empty:before, + .fa-star-half-full:before, + .fa-star-half-o:before { + content: "\f123"; + } + .fa-location-arrow:before { + content: "\f124"; + } + .fa-crop:before { + content: "\f125"; + } + .fa-code-fork:before { + content: "\f126"; + } + .fa-unlink:before, + .fa-chain-broken:before { + content: "\f127"; + } + .fa-question:before { + content: "\f128"; + } + .fa-info:before { + content: "\f129"; + } + .fa-exclamation:before { + content: "\f12a"; + } + .fa-superscript:before { + content: "\f12b"; + } + .fa-subscript:before { + content: "\f12c"; + } + .fa-eraser:before { + content: "\f12d"; + } + .fa-puzzle-piece:before { + content: "\f12e"; + } + .fa-microphone:before { + content: "\f130"; + } + .fa-microphone-slash:before { + content: "\f131"; + } + .fa-shield:before { + content: "\f132"; + } + .fa-calendar-o:before { + content: "\f133"; + } + .fa-fire-extinguisher:before { + content: "\f134"; + } + .fa-rocket:before { + content: "\f135"; + } + .fa-maxcdn:before { + content: "\f136"; + } + .fa-chevron-circle-left:before { + content: "\f137"; + } + .fa-chevron-circle-right:before { + content: "\f138"; + } + .fa-chevron-circle-up:before { + content: "\f139"; + } + .fa-chevron-circle-down:before { + content: "\f13a"; + } + .fa-html5:before { + content: "\f13b"; + } + .fa-css3:before { + content: "\f13c"; + } + .fa-anchor:before { + content: "\f13d"; + } + .fa-unlock-alt:before { + content: "\f13e"; + } + .fa-bullseye:before { + content: "\f140"; + } + .fa-ellipsis-h:before { + content: "\f141"; + } + .fa-ellipsis-v:before { + content: "\f142"; + } + .fa-rss-square:before { + content: "\f143"; + } + .fa-play-circle:before { + content: "\f144"; + } + .fa-ticket:before { + content: "\f145"; + } + .fa-minus-square:before { + content: "\f146"; + } + .fa-minus-square-o:before { + content: "\f147"; + } + .fa-level-up:before { + content: "\f148"; + } + .fa-level-down:before { + content: "\f149"; + } + .fa-check-square:before { + content: "\f14a"; + } + .fa-pencil-square:before { + content: "\f14b"; + } + .fa-external-link-square:before { + content: "\f14c"; + } + .fa-share-square:before { + content: "\f14d"; + } + .fa-compass:before { + content: "\f14e"; + } + .fa-toggle-down:before, + .fa-caret-square-o-down:before { + content: "\f150"; + } + .fa-toggle-up:before, + .fa-caret-square-o-up:before { + content: "\f151"; + } + .fa-toggle-right:before, + .fa-caret-square-o-right:before { + content: "\f152"; + } + .fa-euro:before, + .fa-eur:before { + content: "\f153"; + } + .fa-gbp:before { + content: "\f154"; + } + .fa-dollar:before, + .fa-usd:before { + content: "\f155"; + } + .fa-rupee:before, + .fa-inr:before { + content: "\f156"; + } + .fa-cny:before, + .fa-rmb:before, + .fa-yen:before, + .fa-jpy:before { + content: "\f157"; + } + .fa-ruble:before, + .fa-rouble:before, + .fa-rub:before { + content: "\f158"; + } + .fa-won:before, + .fa-krw:before { + content: "\f159"; + } + .fa-bitcoin:before, + .fa-btc:before { + content: "\f15a"; + } + .fa-file:before { + content: "\f15b"; + } + .fa-file-text:before { + content: "\f15c"; + } + .fa-sort-alpha-asc:before { + content: "\f15d"; + } + .fa-sort-alpha-desc:before { + content: "\f15e"; + } + .fa-sort-amount-asc:before { + content: "\f160"; + } + .fa-sort-amount-desc:before { + content: "\f161"; + } + .fa-sort-numeric-asc:before { + content: "\f162"; + } + .fa-sort-numeric-desc:before { + content: "\f163"; + } + .fa-thumbs-up:before { + content: "\f164"; + } + .fa-thumbs-down:before { + content: "\f165"; + } + .fa-youtube-square:before { + content: "\f166"; + } + .fa-youtube:before { + content: "\f167"; + } + .fa-xing:before { + content: "\f168"; + } + .fa-xing-square:before { + content: "\f169"; + } + .fa-youtube-play:before { + content: "\f16a"; + } + .fa-dropbox:before { + content: "\f16b"; + } + .fa-stack-overflow:before { + content: "\f16c"; + } + .fa-instagram:before { + content: "\f16d"; + } + .fa-flickr:before { + content: "\f16e"; + } + .fa-adn:before { + content: "\f170"; + } + .fa-bitbucket:before { + content: "\f171"; + } + .fa-bitbucket-square:before { + content: "\f172"; + } + .fa-tumblr:before { + content: "\f173"; + } + .fa-tumblr-square:before { + content: "\f174"; + } + .fa-long-arrow-down:before { + content: "\f175"; + } + .fa-long-arrow-up:before { + content: "\f176"; + } + .fa-long-arrow-left:before { + content: "\f177"; + } + .fa-long-arrow-right:before { + content: "\f178"; + } + .fa-apple:before { + content: "\f179"; + } + .fa-windows:before { + content: "\f17a"; + } + .fa-android:before { + content: "\f17b"; + } + .fa-linux:before { + content: "\f17c"; + } + .fa-dribbble:before { + content: "\f17d"; + } + .fa-skype:before { + content: "\f17e"; + } + .fa-foursquare:before { + content: "\f180"; + } + .fa-trello:before { + content: "\f181"; + } + .fa-female:before { + content: "\f182"; + } + .fa-male:before { + content: "\f183"; + } + .fa-gittip:before, + .fa-gratipay:before { + content: "\f184"; + } + .fa-sun-o:before { + content: "\f185"; + } + .fa-moon-o:before { + content: "\f186"; + } + .fa-archive:before { + content: "\f187"; + } + .fa-bug:before { + content: "\f188"; + } + .fa-vk:before { + content: "\f189"; + } + .fa-weibo:before { + content: "\f18a"; + } + .fa-renren:before { + content: "\f18b"; + } + .fa-pagelines:before { + content: "\f18c"; + } + .fa-stack-exchange:before { + content: "\f18d"; + } + .fa-arrow-circle-o-right:before { + content: "\f18e"; + } + .fa-arrow-circle-o-left:before { + content: "\f190"; + } + .fa-toggle-left:before, + .fa-caret-square-o-left:before { + content: "\f191"; + } + .fa-dot-circle-o:before { + content: "\f192"; + } + .fa-wheelchair:before { + content: "\f193"; + } + .fa-vimeo-square:before { + content: "\f194"; + } + .fa-turkish-lira:before, + .fa-try:before { + content: "\f195"; + } + .fa-plus-square-o:before { + content: "\f196"; + } + .fa-space-shuttle:before { + content: "\f197"; + } + .fa-slack:before { + content: "\f198"; + } + .fa-envelope-square:before { + content: "\f199"; + } + .fa-wordpress:before { + content: "\f19a"; + } + .fa-openid:before { + content: "\f19b"; + } + .fa-institution:before, + .fa-bank:before, + .fa-university:before { + content: "\f19c"; + } + .fa-mortar-board:before, + .fa-graduation-cap:before { + content: "\f19d"; + } + .fa-yahoo:before { + content: "\f19e"; + } + .fa-google:before { + content: "\f1a0"; + } + .fa-reddit:before { + content: "\f1a1"; + } + .fa-reddit-square:before { + content: "\f1a2"; + } + .fa-stumbleupon-circle:before { + content: "\f1a3"; + } + .fa-stumbleupon:before { + content: "\f1a4"; + } + .fa-delicious:before { + content: "\f1a5"; + } + .fa-digg:before { + content: "\f1a6"; + } + .fa-pied-piper-pp:before { + content: "\f1a7"; + } + .fa-pied-piper-alt:before { + content: "\f1a8"; + } + .fa-drupal:before { + content: "\f1a9"; + } + .fa-joomla:before { + content: "\f1aa"; + } + .fa-language:before { + content: "\f1ab"; + } + .fa-fax:before { + content: "\f1ac"; + } + .fa-building:before { + content: "\f1ad"; + } + .fa-child:before { + content: "\f1ae"; + } + .fa-paw:before { + content: "\f1b0"; + } + .fa-spoon:before { + content: "\f1b1"; + } + .fa-cube:before { + content: "\f1b2"; + } + .fa-cubes:before { + content: "\f1b3"; + } + .fa-behance:before { + content: "\f1b4"; + } + .fa-behance-square:before { + content: "\f1b5"; + } + .fa-steam:before { + content: "\f1b6"; + } + .fa-steam-square:before { + content: "\f1b7"; + } + .fa-recycle:before { + content: "\f1b8"; + } + .fa-automobile:before, + .fa-car:before { + content: "\f1b9"; + } + .fa-cab:before, + .fa-taxi:before { + content: "\f1ba"; + } + .fa-tree:before { + content: "\f1bb"; + } + .fa-spotify:before { + content: "\f1bc"; + } + .fa-deviantart:before { + content: "\f1bd"; + } + .fa-soundcloud:before { + content: "\f1be"; + } + .fa-database:before { + content: "\f1c0"; + } + .fa-file-pdf-o:before { + content: "\f1c1"; + } + .fa-file-word-o:before { + content: "\f1c2"; + } + .fa-file-excel-o:before { + content: "\f1c3"; + } + .fa-file-powerpoint-o:before { + content: "\f1c4"; + } + .fa-file-photo-o:before, + .fa-file-picture-o:before, + .fa-file-image-o:before { + content: "\f1c5"; + } + .fa-file-zip-o:before, + .fa-file-archive-o:before { + content: "\f1c6"; + } + .fa-file-sound-o:before, + .fa-file-audio-o:before { + content: "\f1c7"; + } + .fa-file-movie-o:before, + .fa-file-video-o:before { + content: "\f1c8"; + } + .fa-file-code-o:before { + content: "\f1c9"; + } + .fa-vine:before { + content: "\f1ca"; + } + .fa-codepen:before { + content: "\f1cb"; + } + .fa-jsfiddle:before { + content: "\f1cc"; + } + .fa-life-bouy:before, + .fa-life-buoy:before, + .fa-life-saver:before, + .fa-support:before, + .fa-life-ring:before { + content: "\f1cd"; + } + .fa-circle-o-notch:before { + content: "\f1ce"; + } + .fa-ra:before, + .fa-resistance:before, + .fa-rebel:before { + content: "\f1d0"; + } + .fa-ge:before, + .fa-empire:before { + content: "\f1d1"; + } + .fa-git-square:before { + content: "\f1d2"; + } + .fa-git:before { + content: "\f1d3"; + } + .fa-y-combinator-square:before, + .fa-yc-square:before, + .fa-hacker-news:before { + content: "\f1d4"; + } + .fa-tencent-weibo:before { + content: "\f1d5"; + } + .fa-qq:before { + content: "\f1d6"; + } + .fa-wechat:before, + .fa-weixin:before { + content: "\f1d7"; + } + .fa-send:before, + .fa-paper-plane:before { + content: "\f1d8"; + } + .fa-send-o:before, + .fa-paper-plane-o:before { + content: "\f1d9"; + } + .fa-history:before { + content: "\f1da"; + } + .fa-circle-thin:before { + content: "\f1db"; + } + .fa-header:before { + content: "\f1dc"; + } + .fa-paragraph:before { + content: "\f1dd"; + } + .fa-sliders:before { + content: "\f1de"; + } + .fa-share-alt:before { + content: "\f1e0"; + } + .fa-share-alt-square:before { + content: "\f1e1"; + } + .fa-bomb:before { + content: "\f1e2"; + } + .fa-soccer-ball-o:before, + .fa-futbol-o:before { + content: "\f1e3"; + } + .fa-tty:before { + content: "\f1e4"; + } + .fa-binoculars:before { + content: "\f1e5"; + } + .fa-plug:before { + content: "\f1e6"; + } + .fa-slideshare:before { + content: "\f1e7"; + } + .fa-twitch:before { + content: "\f1e8"; + } + .fa-yelp:before { + content: "\f1e9"; + } + .fa-newspaper-o:before { + content: "\f1ea"; + } + .fa-wifi:before { + content: "\f1eb"; + } + .fa-calculator:before { + content: "\f1ec"; + } + .fa-paypal:before { + content: "\f1ed"; + } + .fa-google-wallet:before { + content: "\f1ee"; + } + .fa-cc-visa:before { + content: "\f1f0"; + } + .fa-cc-mastercard:before { + content: "\f1f1"; + } + .fa-cc-discover:before { + content: "\f1f2"; + } + .fa-cc-amex:before { + content: "\f1f3"; + } + .fa-cc-paypal:before { + content: "\f1f4"; + } + .fa-cc-stripe:before { + content: "\f1f5"; + } + .fa-bell-slash:before { + content: "\f1f6"; + } + .fa-bell-slash-o:before { + content: "\f1f7"; + } + .fa-trash:before { + content: "\f1f8"; + } + .fa-copyright:before { + content: "\f1f9"; + } + .fa-at:before { + content: "\f1fa"; + } + .fa-eyedropper:before { + content: "\f1fb"; + } + .fa-paint-brush:before { + content: "\f1fc"; + } + .fa-birthday-cake:before { + content: "\f1fd"; + } + .fa-area-chart:before { + content: "\f1fe"; + } + .fa-pie-chart:before { + content: "\f200"; + } + .fa-line-chart:before { + content: "\f201"; + } + .fa-lastfm:before { + content: "\f202"; + } + .fa-lastfm-square:before { + content: "\f203"; + } + .fa-toggle-off:before { + content: "\f204"; + } + .fa-toggle-on:before { + content: "\f205"; + } + .fa-bicycle:before { + content: "\f206"; + } + .fa-bus:before { + content: "\f207"; + } + .fa-ioxhost:before { + content: "\f208"; + } + .fa-angellist:before { + content: "\f209"; + } + .fa-cc:before { + content: "\f20a"; + } + .fa-shekel:before, + .fa-sheqel:before, + .fa-ils:before { + content: "\f20b"; + } + .fa-meanpath:before { + content: "\f20c"; + } + .fa-buysellads:before { + content: "\f20d"; + } + .fa-connectdevelop:before { + content: "\f20e"; + } + .fa-dashcube:before { + content: "\f210"; + } + .fa-forumbee:before { + content: "\f211"; + } + .fa-leanpub:before { + content: "\f212"; + } + .fa-sellsy:before { + content: "\f213"; + } + .fa-shirtsinbulk:before { + content: "\f214"; + } + .fa-simplybuilt:before { + content: "\f215"; + } + .fa-skyatlas:before { + content: "\f216"; + } + .fa-cart-plus:before { + content: "\f217"; + } + .fa-cart-arrow-down:before { + content: "\f218"; + } + .fa-diamond:before { + content: "\f219"; + } + .fa-ship:before { + content: "\f21a"; + } + .fa-user-secret:before { + content: "\f21b"; + } + .fa-motorcycle:before { + content: "\f21c"; + } + .fa-street-view:before { + content: "\f21d"; + } + .fa-heartbeat:before { + content: "\f21e"; + } + .fa-venus:before { + content: "\f221"; + } + .fa-mars:before { + content: "\f222"; + } + .fa-mercury:before { + content: "\f223"; + } + .fa-intersex:before, + .fa-transgender:before { + content: "\f224"; + } + .fa-transgender-alt:before { + content: "\f225"; + } + .fa-venus-double:before { + content: "\f226"; + } + .fa-mars-double:before { + content: "\f227"; + } + .fa-venus-mars:before { + content: "\f228"; + } + .fa-mars-stroke:before { + content: "\f229"; + } + .fa-mars-stroke-v:before { + content: "\f22a"; + } + .fa-mars-stroke-h:before { + content: "\f22b"; + } + .fa-neuter:before { + content: "\f22c"; + } + .fa-genderless:before { + content: "\f22d"; + } + .fa-facebook-official:before { + content: "\f230"; + } + .fa-pinterest-p:before { + content: "\f231"; + } + .fa-whatsapp:before { + content: "\f232"; + } + .fa-server:before { + content: "\f233"; + } + .fa-user-plus:before { + content: "\f234"; + } + .fa-user-times:before { + content: "\f235"; + } + .fa-hotel:before, + .fa-bed:before { + content: "\f236"; + } + .fa-viacoin:before { + content: "\f237"; + } + .fa-train:before { + content: "\f238"; + } + .fa-subway:before { + content: "\f239"; + } + .fa-medium:before { + content: "\f23a"; + } + .fa-yc:before, + .fa-y-combinator:before { + content: "\f23b"; + } + .fa-optin-monster:before { + content: "\f23c"; + } + .fa-opencart:before { + content: "\f23d"; + } + .fa-expeditedssl:before { + content: "\f23e"; + } + .fa-battery-4:before, + .fa-battery:before, + .fa-battery-full:before { + content: "\f240"; + } + .fa-battery-3:before, + .fa-battery-three-quarters:before { + content: "\f241"; + } + .fa-battery-2:before, + .fa-battery-half:before { + content: "\f242"; + } + .fa-battery-1:before, + .fa-battery-quarter:before { + content: "\f243"; + } + .fa-battery-0:before, + .fa-battery-empty:before { + content: "\f244"; + } + .fa-mouse-pointer:before { + content: "\f245"; + } + .fa-i-cursor:before { + content: "\f246"; + } + .fa-object-group:before { + content: "\f247"; + } + .fa-object-ungroup:before { + content: "\f248"; + } + .fa-sticky-note:before { + content: "\f249"; + } + .fa-sticky-note-o:before { + content: "\f24a"; + } + .fa-cc-jcb:before { + content: "\f24b"; + } + .fa-cc-diners-club:before { + content: "\f24c"; + } + .fa-clone:before { + content: "\f24d"; + } + .fa-balance-scale:before { + content: "\f24e"; + } + .fa-hourglass-o:before { + content: "\f250"; + } + .fa-hourglass-1:before, + .fa-hourglass-start:before { + content: "\f251"; + } + .fa-hourglass-2:before, + .fa-hourglass-half:before { + content: "\f252"; + } + .fa-hourglass-3:before, + .fa-hourglass-end:before { + content: "\f253"; + } + .fa-hourglass:before { + content: "\f254"; + } + .fa-hand-grab-o:before, + .fa-hand-rock-o:before { + content: "\f255"; + } + .fa-hand-stop-o:before, + .fa-hand-paper-o:before { + content: "\f256"; + } + .fa-hand-scissors-o:before { + content: "\f257"; + } + .fa-hand-lizard-o:before { + content: "\f258"; + } + .fa-hand-spock-o:before { + content: "\f259"; + } + .fa-hand-pointer-o:before { + content: "\f25a"; + } + .fa-hand-peace-o:before { + content: "\f25b"; + } + .fa-trademark:before { + content: "\f25c"; + } + .fa-registered:before { + content: "\f25d"; + } + .fa-creative-commons:before { + content: "\f25e"; + } + .fa-gg:before { + content: "\f260"; + } + .fa-gg-circle:before { + content: "\f261"; + } + .fa-tripadvisor:before { + content: "\f262"; + } + .fa-odnoklassniki:before { + content: "\f263"; + } + .fa-odnoklassniki-square:before { + content: "\f264"; + } + .fa-get-pocket:before { + content: "\f265"; + } + .fa-wikipedia-w:before { + content: "\f266"; + } + .fa-safari:before { + content: "\f267"; + } + .fa-chrome:before { + content: "\f268"; + } + .fa-firefox:before { + content: "\f269"; + } + .fa-opera:before { + content: "\f26a"; + } + .fa-internet-explorer:before { + content: "\f26b"; + } + .fa-tv:before, + .fa-television:before { + content: "\f26c"; + } + .fa-contao:before { + content: "\f26d"; + } + .fa-500px:before { + content: "\f26e"; + } + .fa-amazon:before { + content: "\f270"; + } + .fa-calendar-plus-o:before { + content: "\f271"; + } + .fa-calendar-minus-o:before { + content: "\f272"; + } + .fa-calendar-times-o:before { + content: "\f273"; + } + .fa-calendar-check-o:before { + content: "\f274"; + } + .fa-industry:before { + content: "\f275"; + } + .fa-map-pin:before { + content: "\f276"; + } + .fa-map-signs:before { + content: "\f277"; + } + .fa-map-o:before { + content: "\f278"; + } + .fa-map:before { + content: "\f279"; + } + .fa-commenting:before { + content: "\f27a"; + } + .fa-commenting-o:before { + content: "\f27b"; + } + .fa-houzz:before { + content: "\f27c"; + } + .fa-vimeo:before { + content: "\f27d"; + } + .fa-black-tie:before { + content: "\f27e"; + } + .fa-fonticons:before { + content: "\f280"; + } + .fa-reddit-alien:before { + content: "\f281"; + } + .fa-edge:before { + content: "\f282"; + } + .fa-credit-card-alt:before { + content: "\f283"; + } + .fa-codiepie:before { + content: "\f284"; + } + .fa-modx:before { + content: "\f285"; + } + .fa-fort-awesome:before { + content: "\f286"; + } + .fa-usb:before { + content: "\f287"; + } + .fa-product-hunt:before { + content: "\f288"; + } + .fa-mixcloud:before { + content: "\f289"; + } + .fa-scribd:before { + content: "\f28a"; + } + .fa-pause-circle:before { + content: "\f28b"; + } + .fa-pause-circle-o:before { + content: "\f28c"; + } + .fa-stop-circle:before { + content: "\f28d"; + } + .fa-stop-circle-o:before { + content: "\f28e"; + } + .fa-shopping-bag:before { + content: "\f290"; + } + .fa-shopping-basket:before { + content: "\f291"; + } + .fa-hashtag:before { + content: "\f292"; + } + .fa-bluetooth:before { + content: "\f293"; + } + .fa-bluetooth-b:before { + content: "\f294"; + } + .fa-percent:before { + content: "\f295"; + } + .fa-gitlab:before { + content: "\f296"; + } + .fa-wpbeginner:before { + content: "\f297"; + } + .fa-wpforms:before { + content: "\f298"; + } + .fa-envira:before { + content: "\f299"; + } + .fa-universal-access:before { + content: "\f29a"; + } + .fa-wheelchair-alt:before { + content: "\f29b"; + } + .fa-question-circle-o:before { + content: "\f29c"; + } + .fa-blind:before { + content: "\f29d"; + } + .fa-audio-description:before { + content: "\f29e"; + } + .fa-volume-control-phone:before { + content: "\f2a0"; + } + .fa-braille:before { + content: "\f2a1"; + } + .fa-assistive-listening-systems:before { + content: "\f2a2"; + } + .fa-asl-interpreting:before, + .fa-american-sign-language-interpreting:before { + content: "\f2a3"; + } + .fa-deafness:before, + .fa-hard-of-hearing:before, + .fa-deaf:before { + content: "\f2a4"; + } + .fa-glide:before { + content: "\f2a5"; + } + .fa-glide-g:before { + content: "\f2a6"; + } + .fa-signing:before, + .fa-sign-language:before { + content: "\f2a7"; + } + .fa-low-vision:before { + content: "\f2a8"; + } + .fa-viadeo:before { + content: "\f2a9"; + } + .fa-viadeo-square:before { + content: "\f2aa"; + } + .fa-snapchat:before { + content: "\f2ab"; + } + .fa-snapchat-ghost:before { + content: "\f2ac"; + } + .fa-snapchat-square:before { + content: "\f2ad"; + } + .fa-pied-piper:before { + content: "\f2ae"; + } + .fa-first-order:before { + content: "\f2b0"; + } + .fa-yoast:before { + content: "\f2b1"; + } + .fa-themeisle:before { + content: "\f2b2"; + } + .fa-google-plus-circle:before, + .fa-google-plus-official:before { + content: "\f2b3"; + } + .fa-fa:before, + .fa-font-awesome:before { + content: "\f2b4"; + } + .fa-handshake-o:before { + content: "\f2b5"; + } + .fa-envelope-open:before { + content: "\f2b6"; + } + .fa-envelope-open-o:before { + content: "\f2b7"; + } + .fa-linode:before { + content: "\f2b8"; + } + .fa-address-book:before { + content: "\f2b9"; + } + .fa-address-book-o:before { + content: "\f2ba"; + } + .fa-vcard:before, + .fa-address-card:before { + content: "\f2bb"; + } + .fa-vcard-o:before, + .fa-address-card-o:before { + content: "\f2bc"; + } + .fa-user-circle:before { + content: "\f2bd"; + } + .fa-user-circle-o:before { + content: "\f2be"; + } + .fa-user-o:before { + content: "\f2c0"; + } + .fa-id-badge:before { + content: "\f2c1"; + } + .fa-drivers-license:before, + .fa-id-card:before { + content: "\f2c2"; + } + .fa-drivers-license-o:before, + .fa-id-card-o:before { + content: "\f2c3"; + } + .fa-quora:before { + content: "\f2c4"; + } + .fa-free-code-camp:before { + content: "\f2c5"; + } + .fa-telegram:before { + content: "\f2c6"; + } + .fa-thermometer-4:before, + .fa-thermometer:before, + .fa-thermometer-full:before { + content: "\f2c7"; + } + .fa-thermometer-3:before, + .fa-thermometer-three-quarters:before { + content: "\f2c8"; + } + .fa-thermometer-2:before, + .fa-thermometer-half:before { + content: "\f2c9"; + } + .fa-thermometer-1:before, + .fa-thermometer-quarter:before { + content: "\f2ca"; + } + .fa-thermometer-0:before, + .fa-thermometer-empty:before { + content: "\f2cb"; + } + .fa-shower:before { + content: "\f2cc"; + } + .fa-bathtub:before, + .fa-s15:before, + .fa-bath:before { + content: "\f2cd"; + } + .fa-podcast:before { + content: "\f2ce"; + } + .fa-window-maximize:before { + content: "\f2d0"; + } + .fa-window-minimize:before { + content: "\f2d1"; + } + .fa-window-restore:before { + content: "\f2d2"; + } + .fa-times-rectangle:before, + .fa-window-close:before { + content: "\f2d3"; + } + .fa-times-rectangle-o:before, + .fa-window-close-o:before { + content: "\f2d4"; + } + .fa-bandcamp:before { + content: "\f2d5"; + } + .fa-grav:before { + content: "\f2d6"; + } + .fa-etsy:before { + content: "\f2d7"; + } + .fa-imdb:before { + content: "\f2d8"; + } + .fa-ravelry:before { + content: "\f2d9"; + } + .fa-eercast:before { + content: "\f2da"; + } + .fa-microchip:before { + content: "\f2db"; + } + .fa-snowflake-o:before { + content: "\f2dc"; + } + .fa-superpowers:before { + content: "\f2dd"; + } + .fa-wpexplorer:before { + content: "\f2de"; + } + .fa-meetup:before { + content: "\f2e0"; + } + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; + } + .sr-only-focusable:active, + .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } + + \ No newline at end of file diff --git a/assets/css/libraries/fontawesome-4.7.0.min.css b/assets/css/libraries/fontawesome-4.7.0.min.css new file mode 100644 index 00000000..3a926783 --- /dev/null +++ b/assets/css/libraries/fontawesome-4.7.0.min.css @@ -0,0 +1,5 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} + diff --git a/assets/js/admin.data-notes-add.js b/assets/js/admin.data-notes-add.js new file mode 100644 index 00000000..9aedd82b --- /dev/null +++ b/assets/js/admin.data-notes-add.js @@ -0,0 +1,74 @@ +jQuery( document ).ready( function ( $ ) { + + let button_id = '#' + ws_notes_add_config[ 'component_id' ] + '_button'; + let textarea_id = '#' + ws_notes_add_config[ 'component_id' ] + '_textarea'; + let most_recent_id = '#' + ws_notes_add_config[ 'component_id' ] + '_most_recent'; + let errormessage_id = '#' + ws_notes_add_config[ 'component_id' ] + '_errormessage'; + let successmessage_id = '#' + ws_notes_add_config[ 'component_id' ] + '_successmessage'; + + $( button_id ).click( function( event ) { + + event.preventDefault(); + + let note = $( textarea_id ).val(); + + $( button_id ).addClass( 'ws-ls-loading-button'); + $( errormessage_id ).addClass( 'ws-ls-hide'); + $( successmessage_id ).addClass( 'ws-ls-hide' ); + + let data = { 'action' : 'ws_ls_add_note', + 'security' : ws_notes_add_config[ 'nonce' ], + 'user-id' : ws_notes_add_config[ 'user_id' ], + 'note' : note, + 'send-email' : $( '#' + ws_notes_add_config[ 'component_id' ] + '_send_email' ).is(':checked'), + 'visible-to-user' : $( '#' + ws_notes_add_config[ 'component_id' ] + '_visible_to_user' ).is(':checked') + }; + + jQuery.post( ws_notes_add_config[ 'url' ], data, function ( response ) { + + if ( parseInt( response ) === 0 ) { + $( errormessage_id ).removeClass( 'ws-ls-hide' ); + return; + } + + $( most_recent_id ).val( $( textarea_id ).val() ); + + $( textarea_id ).val( '' ); + + $( "#" + ws_notes_add_config[ 'component_id' ] + "_count" ).text( response ); + $( successmessage_id ).removeClass( 'ws-ls-hide' ); + + }).fail(function() { + $( errormessage_id ).removeClass( 'ws-ls-hide' ); + }) + .always(function() { + $( button_id ).removeClass( 'ws-ls-loading-button'); + });; + }); + + let hide_most_recent_id = '#' + ws_notes_add_config[ 'component_id' ] + '_hide_most_read'; + let view_most_recent_id = '#' + ws_notes_add_config[ 'component_id' ] + '_view_most_read'; + let view_most_recent_div_id = '#' + ws_notes_add_config[ 'component_id' ] + '_most_recent_comment_div'; + let view_add_new_div_id = '#' + ws_notes_add_config[ 'component_id' ] + '_add_new_div'; + + $( hide_most_recent_id ).click( function( event ) { + + event.preventDefault(); + + $( view_most_recent_id ).removeClass( 'ws-ls-hide' ); + $( hide_most_recent_id ).addClass( 'ws-ls-hide' ); + $( view_most_recent_div_id ).addClass( 'ws-ls-hide' ); + $( view_add_new_div_id ).removeClass( 'ws-ls-hide' ); + }); + + $( view_most_recent_id ).click( function( event ) { + + event.preventDefault(); + + $( hide_most_recent_id ).removeClass( 'ws-ls-hide' ); + $( view_most_recent_id ).addClass( 'ws-ls-hide' ); + $( view_most_recent_div_id ).removeClass( 'ws-ls-hide' ); + $( view_add_new_div_id ).addClass( 'ws-ls-hide' ); + }); + +}); \ No newline at end of file diff --git a/assets/js/admin.data-notes.js b/assets/js/admin.data-notes.js new file mode 100644 index 00000000..cdfe1c88 --- /dev/null +++ b/assets/js/admin.data-notes.js @@ -0,0 +1,26 @@ +jQuery( document ).ready( function ( $ ) { + + $( '.ws-note-delete-action' ).click( function( event ) { + + event.preventDefault(); + + let to_delete_div = $( this ).data( 'div-id' ); + + let data = { 'action' : 'ws_ls_delete_note', + 'security' : ws_notes_config[ 'nonce'], + 'id' : $( this ).data( 'id' ) + }; + + jQuery.post( ws_notes_config[ 'url'], data, function ( response ) { + + if ( parseInt( response ) !== 1 ) { + return; + } + + $( '#' + to_delete_div ).addClass( 'ws-ls-hide' ); + + }).fail(function() { + alert( ws_notes_config[ 'error-message'] ); + }) + }); +}); \ No newline at end of file diff --git a/assets/js/libraries/additional-methods.js b/assets/js/libraries/additional-methods.js new file mode 100644 index 00000000..880b485b --- /dev/null +++ b/assets/js/libraries/additional-methods.js @@ -0,0 +1,1513 @@ +/*! + * jQuery Validation Plugin v1.14.0 + * + * https://jqueryvalidation.org/ + * + * Copyright (c) 2015 Jörn Zaefferer + * Released under the MIT license +*/ +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + define( ["jquery", "./jquery.validate"], factory ); + } else if (typeof module === "object" && module.exports) { + module.exports = factory( require( "jquery" ) ); + } else { + factory( jQuery ); + } +}(function( $ ) { + +( function() { + + function stripHtml( value ) { + + // Remove html tags and space chars + return value.replace( /<.[^<>]*?>/g, " " ).replace( / | /gi, " " ) + + // Remove punctuation + .replace( /[.(),;:!?%#$'\"_+=\/\-“”’]*/g, "" ); + } + + $.validator.addMethod( "maxWords", function( value, element, params ) { + return this.optional( element ) || stripHtml( value ).match( /\b\w+\b/g ).length <= params; + }, $.validator.format( "Please enter {0} words or less." ) ); + + $.validator.addMethod( "minWords", function( value, element, params ) { + return this.optional( element ) || stripHtml( value ).match( /\b\w+\b/g ).length >= params; + }, $.validator.format( "Please enter at least {0} words." ) ); + + $.validator.addMethod( "rangeWords", function( value, element, params ) { + var valueStripped = stripHtml( value ), + regex = /\b\w+\b/g; + return this.optional( element ) || valueStripped.match( regex ).length >= params[ 0 ] && valueStripped.match( regex ).length <= params[ 1 ]; + }, $.validator.format( "Please enter between {0} and {1} words." ) ); + +}() ); + +/** + * This is used in the United States to process payments, deposits, + * or transfers using the Automated Clearing House (ACH) or Fedwire + * systems. A very common use case would be to validate a form for + * an ACH bill payment. + */ +$.validator.addMethod( "abaRoutingNumber", function( value ) { + var checksum = 0; + var tokens = value.split( "" ); + var length = tokens.length; + + // Length Check + if ( length !== 9 ) { + return false; + } + + // Calc the checksum + // https://en.wikipedia.org/wiki/ABA_routing_transit_number + for ( var i = 0; i < length; i += 3 ) { + checksum += parseInt( tokens[ i ], 10 ) * 3 + + parseInt( tokens[ i + 1 ], 10 ) * 7 + + parseInt( tokens[ i + 2 ], 10 ); + } + + // If not zero and divisible by 10 then valid + if ( checksum !== 0 && checksum % 10 === 0 ) { + return true; + } + + return false; +}, "Please enter a valid routing number." ); + +// Accept a value from a file input based on a required mimetype +$.validator.addMethod( "accept", function( value, element, param ) { + + // Split mime on commas in case we have multiple types we can accept + var typeParam = typeof param === "string" ? param.replace( /\s/g, "" ) : "image/*", + optionalValue = this.optional( element ), + i, file, regex; + + // Element is optional + if ( optionalValue ) { + return optionalValue; + } + + if ( $( element ).attr( "type" ) === "file" ) { + + // Escape string to be used in the regex + // see: https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + // Escape also "/*" as "/.*" as a wildcard + typeParam = typeParam + .replace( /[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, "\\$&" ) + .replace( /,/g, "|" ) + .replace( /\/\*/g, "/.*" ); + + // Check if the element has a FileList before checking each file + if ( element.files && element.files.length ) { + regex = new RegExp( ".?(" + typeParam + ")$", "i" ); + for ( i = 0; i < element.files.length; i++ ) { + file = element.files[ i ]; + + // Grab the mimetype from the loaded file, verify it matches + if ( !file.type.match( regex ) ) { + return false; + } + } + } + } + + // Either return true because we've validated each file, or because the + // browser does not support element.files and the FileList feature + return true; +}, $.validator.format( "Please enter a value with a valid mimetype." ) ); + +$.validator.addMethod( "alphanumeric", function( value, element ) { + return this.optional( element ) || /^\w+$/i.test( value ); +}, "Letters, numbers, and underscores only please." ); + +/* + * Dutch bank account numbers (not 'giro' numbers) have 9 digits + * and pass the '11 check'. + * We accept the notation with spaces, as that is common. + * acceptable: 123456789 or 12 34 56 789 + */ +$.validator.addMethod( "bankaccountNL", function( value, element ) { + if ( this.optional( element ) ) { + return true; + } + if ( !( /^[0-9]{9}|([0-9]{2} ){3}[0-9]{3}$/.test( value ) ) ) { + return false; + } + + // Now '11 check' + var account = value.replace( / /g, "" ), // Remove spaces + sum = 0, + len = account.length, + pos, factor, digit; + for ( pos = 0; pos < len; pos++ ) { + factor = len - pos; + digit = account.substring( pos, pos + 1 ); + sum = sum + factor * digit; + } + return sum % 11 === 0; +}, "Please specify a valid bank account number." ); + +$.validator.addMethod( "bankorgiroaccountNL", function( value, element ) { + return this.optional( element ) || + ( $.validator.methods.bankaccountNL.call( this, value, element ) ) || + ( $.validator.methods.giroaccountNL.call( this, value, element ) ); +}, "Please specify a valid bank or giro account number." ); + +/** + * BIC is the business identifier code (ISO 9362). This BIC check is not a guarantee for authenticity. + * + * BIC pattern: BBBBCCLLbbb (8 or 11 characters long; bbb is optional) + * + * Validation is case-insensitive. Please make sure to normalize input yourself. + * + * BIC definition in detail: + * - First 4 characters - bank code (only letters) + * - Next 2 characters - ISO 3166-1 alpha-2 country code (only letters) + * - Next 2 characters - location code (letters and digits) + * a. shall not start with '0' or '1' + * b. second character must be a letter ('O' is not allowed) or digit ('0' for test (therefore not allowed), '1' denoting passive participant, '2' typically reverse-billing) + * - Last 3 characters - branch code, optional (shall not start with 'X' except in case of 'XXX' for primary office) (letters and digits) + */ +$.validator.addMethod( "bic", function( value, element ) { + return this.optional( element ) || /^([A-Z]{6}[A-Z2-9][A-NP-Z1-9])(X{3}|[A-WY-Z0-9][A-Z0-9]{2})?$/.test( value.toUpperCase() ); +}, "Please specify a valid BIC code." ); + +/* + * Código de identificación fiscal ( CIF ) is the tax identification code for Spanish legal entities + * Further rules can be found in Spanish on http://es.wikipedia.org/wiki/C%C3%B3digo_de_identificaci%C3%B3n_fiscal + * + * Spanish CIF structure: + * + * [ T ][ P ][ P ][ N ][ N ][ N ][ N ][ N ][ C ] + * + * Where: + * + * T: 1 character. Kind of Organization Letter: [ABCDEFGHJKLMNPQRSUVW] + * P: 2 characters. Province. + * N: 5 characters. Secuencial Number within the province. + * C: 1 character. Control Digit: [0-9A-J]. + * + * [ T ]: Kind of Organizations. Possible values: + * + * A. Corporations + * B. LLCs + * C. General partnerships + * D. Companies limited partnerships + * E. Communities of goods + * F. Cooperative Societies + * G. Associations + * H. Communities of homeowners in horizontal property regime + * J. Civil Societies + * K. Old format + * L. Old format + * M. Old format + * N. Nonresident entities + * P. Local authorities + * Q. Autonomous bodies, state or not, and the like, and congregations and religious institutions + * R. Congregations and religious institutions (since 2008 ORDER EHA/451/2008) + * S. Organs of State Administration and regions + * V. Agrarian Transformation + * W. Permanent establishments of non-resident in Spain + * + * [ C ]: Control Digit. It can be a number or a letter depending on T value: + * [ T ] --> [ C ] + * ------ ---------- + * A Number + * B Number + * E Number + * H Number + * K Letter + * P Letter + * Q Letter + * S Letter + * + */ +$.validator.addMethod( "cifES", function( value, element ) { + "use strict"; + + if ( this.optional( element ) ) { + return true; + } + + var cifRegEx = new RegExp( /^([ABCDEFGHJKLMNPQRSUVW])(\d{7})([0-9A-J])$/gi ); + var letter = value.substring( 0, 1 ), // [ T ] + number = value.substring( 1, 8 ), // [ P ][ P ][ N ][ N ][ N ][ N ][ N ] + control = value.substring( 8, 9 ), // [ C ] + all_sum = 0, + even_sum = 0, + odd_sum = 0, + i, n, + control_digit, + control_letter; + + function isOdd( n ) { + return n % 2 === 0; + } + + // Quick format test + if ( value.length !== 9 || !cifRegEx.test( value ) ) { + return false; + } + + for ( i = 0; i < number.length; i++ ) { + n = parseInt( number[ i ], 10 ); + + // Odd positions + if ( isOdd( i ) ) { + + // Odd positions are multiplied first. + n *= 2; + + // If the multiplication is bigger than 10 we need to adjust + odd_sum += n < 10 ? n : n - 9; + + // Even positions + // Just sum them + } else { + even_sum += n; + } + } + + all_sum = even_sum + odd_sum; + control_digit = ( 10 - ( all_sum ).toString().substr( -1 ) ).toString(); + control_digit = parseInt( control_digit, 10 ) > 9 ? "0" : control_digit; + control_letter = "JABCDEFGHI".substr( control_digit, 1 ).toString(); + + // Control must be a digit + if ( letter.match( /[ABEH]/ ) ) { + return control === control_digit; + + // Control must be a letter + } else if ( letter.match( /[KPQS]/ ) ) { + return control === control_letter; + } + + // Can be either + return control === control_digit || control === control_letter; + +}, "Please specify a valid CIF number." ); + +/* + * Brazillian CNH number (Carteira Nacional de Habilitacao) is the License Driver number. + * CNH numbers have 11 digits in total: 9 numbers followed by 2 check numbers that are being used for validation. + */ +$.validator.addMethod( "cnhBR", function( value ) { + + // Removing special characters from value + value = value.replace( /([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g, "" ); + + // Checking value to have 11 digits only + if ( value.length !== 11 ) { + return false; + } + + var sum = 0, dsc = 0, firstChar, + firstCN, secondCN, i, j, v; + + firstChar = value.charAt( 0 ); + + if ( new Array( 12 ).join( firstChar ) === value ) { + return false; + } + + // Step 1 - using first Check Number: + for ( i = 0, j = 9, v = 0; i < 9; ++i, --j ) { + sum += +( value.charAt( i ) * j ); + } + + firstCN = sum % 11; + if ( firstCN >= 10 ) { + firstCN = 0; + dsc = 2; + } + + sum = 0; + for ( i = 0, j = 1, v = 0; i < 9; ++i, ++j ) { + sum += +( value.charAt( i ) * j ); + } + + secondCN = sum % 11; + if ( secondCN >= 10 ) { + secondCN = 0; + } else { + secondCN = secondCN - dsc; + } + + return ( String( firstCN ).concat( secondCN ) === value.substr( -2 ) ); + +}, "Please specify a valid CNH number." ); + +/* + * Brazillian value number (Cadastrado de Pessoas Juridica). + * value numbers have 14 digits in total: 12 numbers followed by 2 check numbers that are being used for validation. + */ +$.validator.addMethod( "cnpjBR", function( value, element ) { + "use strict"; + + if ( this.optional( element ) ) { + return true; + } + + // Removing no number + value = value.replace( /[^\d]+/g, "" ); + + // Checking value to have 14 digits only + if ( value.length !== 14 ) { + return false; + } + + // Elimina values invalidos conhecidos + if ( value === "00000000000000" || + value === "11111111111111" || + value === "22222222222222" || + value === "33333333333333" || + value === "44444444444444" || + value === "55555555555555" || + value === "66666666666666" || + value === "77777777777777" || + value === "88888888888888" || + value === "99999999999999" ) { + return false; + } + + // Valida DVs + var tamanho = ( value.length - 2 ); + var numeros = value.substring( 0, tamanho ); + var digitos = value.substring( tamanho ); + var soma = 0; + var pos = tamanho - 7; + + for ( var i = tamanho; i >= 1; i-- ) { + soma += numeros.charAt( tamanho - i ) * pos--; + if ( pos < 2 ) { + pos = 9; + } + } + + var resultado = soma % 11 < 2 ? 0 : 11 - soma % 11; + + if ( resultado !== parseInt( digitos.charAt( 0 ), 10 ) ) { + return false; + } + + tamanho = tamanho + 1; + numeros = value.substring( 0, tamanho ); + soma = 0; + pos = tamanho - 7; + + for ( var il = tamanho; il >= 1; il-- ) { + soma += numeros.charAt( tamanho - il ) * pos--; + if ( pos < 2 ) { + pos = 9; + } + } + + resultado = soma % 11 < 2 ? 0 : 11 - soma % 11; + + if ( resultado !== parseInt( digitos.charAt( 1 ), 10 ) ) { + return false; + } + + return true; + +}, "Please specify a CNPJ value number." ); + +/* + * Brazillian CPF number (Cadastrado de Pessoas Físicas) is the equivalent of a Brazilian tax registration number. + * CPF numbers have 11 digits in total: 9 numbers followed by 2 check numbers that are being used for validation. + */ +$.validator.addMethod( "cpfBR", function( value, element ) { + "use strict"; + + if ( this.optional( element ) ) { + return true; + } + + // Removing special characters from value + value = value.replace( /([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g, "" ); + + // Checking value to have 11 digits only + if ( value.length !== 11 ) { + return false; + } + + var sum = 0, + firstCN, secondCN, checkResult, i; + + firstCN = parseInt( value.substring( 9, 10 ), 10 ); + secondCN = parseInt( value.substring( 10, 11 ), 10 ); + + checkResult = function( sum, cn ) { + var result = ( sum * 10 ) % 11; + if ( ( result === 10 ) || ( result === 11 ) ) { + result = 0; + } + return ( result === cn ); + }; + + // Checking for dump data + if ( value === "" || + value === "00000000000" || + value === "11111111111" || + value === "22222222222" || + value === "33333333333" || + value === "44444444444" || + value === "55555555555" || + value === "66666666666" || + value === "77777777777" || + value === "88888888888" || + value === "99999999999" + ) { + return false; + } + + // Step 1 - using first Check Number: + for ( i = 1; i <= 9; i++ ) { + sum = sum + parseInt( value.substring( i - 1, i ), 10 ) * ( 11 - i ); + } + + // If first Check Number (CN) is valid, move to Step 2 - using second Check Number: + if ( checkResult( sum, firstCN ) ) { + sum = 0; + for ( i = 1; i <= 10; i++ ) { + sum = sum + parseInt( value.substring( i - 1, i ), 10 ) * ( 12 - i ); + } + return checkResult( sum, secondCN ); + } + return false; + +}, "Please specify a valid CPF number." ); + +// https://jqueryvalidation.org/creditcard-method/ +// based on https://en.wikipedia.org/wiki/Luhn_algorithm +$.validator.addMethod( "creditcard", function( value, element ) { + if ( this.optional( element ) ) { + return "dependency-mismatch"; + } + + // Accept only spaces, digits and dashes + if ( /[^0-9 \-]+/.test( value ) ) { + return false; + } + + var nCheck = 0, + nDigit = 0, + bEven = false, + n, cDigit; + + value = value.replace( /\D/g, "" ); + + // Basing min and max length on + // https://dev.ean.com/general-info/valid-card-types/ + if ( value.length < 13 || value.length > 19 ) { + return false; + } + + for ( n = value.length - 1; n >= 0; n-- ) { + cDigit = value.charAt( n ); + nDigit = parseInt( cDigit, 10 ); + if ( bEven ) { + if ( ( nDigit *= 2 ) > 9 ) { + nDigit -= 9; + } + } + + nCheck += nDigit; + bEven = !bEven; + } + + return ( nCheck % 10 ) === 0; +}, "Please enter a valid credit card number." ); + +/* NOTICE: Modified version of Castle.Components.Validator.CreditCardValidator + * Redistributed under the Apache License 2.0 at http://www.apache.org/licenses/LICENSE-2.0 + * Valid Types: mastercard, visa, amex, dinersclub, enroute, discover, jcb, unknown, all (overrides all other settings) + */ +$.validator.addMethod( "creditcardtypes", function( value, element, param ) { + if ( /[^0-9\-]+/.test( value ) ) { + return false; + } + + value = value.replace( /\D/g, "" ); + + var validTypes = 0x0000; + + if ( param.mastercard ) { + validTypes |= 0x0001; + } + if ( param.visa ) { + validTypes |= 0x0002; + } + if ( param.amex ) { + validTypes |= 0x0004; + } + if ( param.dinersclub ) { + validTypes |= 0x0008; + } + if ( param.enroute ) { + validTypes |= 0x0010; + } + if ( param.discover ) { + validTypes |= 0x0020; + } + if ( param.jcb ) { + validTypes |= 0x0040; + } + if ( param.unknown ) { + validTypes |= 0x0080; + } + if ( param.all ) { + validTypes = 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080; + } + if ( validTypes & 0x0001 && ( /^(5[12345])/.test( value ) || /^(2[234567])/.test( value ) ) ) { // Mastercard + return value.length === 16; + } + if ( validTypes & 0x0002 && /^(4)/.test( value ) ) { // Visa + return value.length === 16; + } + if ( validTypes & 0x0004 && /^(3[47])/.test( value ) ) { // Amex + return value.length === 15; + } + if ( validTypes & 0x0008 && /^(3(0[012345]|[68]))/.test( value ) ) { // Dinersclub + return value.length === 14; + } + if ( validTypes & 0x0010 && /^(2(014|149))/.test( value ) ) { // Enroute + return value.length === 15; + } + if ( validTypes & 0x0020 && /^(6011)/.test( value ) ) { // Discover + return value.length === 16; + } + if ( validTypes & 0x0040 && /^(3)/.test( value ) ) { // Jcb + return value.length === 16; + } + if ( validTypes & 0x0040 && /^(2131|1800)/.test( value ) ) { // Jcb + return value.length === 15; + } + if ( validTypes & 0x0080 ) { // Unknown + return true; + } + return false; +}, "Please enter a valid credit card number." ); + +/** + * Validates currencies with any given symbols by @jameslouiz + * Symbols can be optional or required. Symbols required by default + * + * Usage examples: + * currency: ["£", false] - Use false for soft currency validation + * currency: ["$", false] + * currency: ["RM", false] - also works with text based symbols such as "RM" - Malaysia Ringgit etc + * + * + * + * Soft symbol checking + * currencyInput: { + * currency: ["$", false] + * } + * + * Strict symbol checking (default) + * currencyInput: { + * currency: "$" + * //OR + * currency: ["$", true] + * } + * + * Multiple Symbols + * currencyInput: { + * currency: "$,£,¢" + * } + */ +$.validator.addMethod( "currency", function( value, element, param ) { + var isParamString = typeof param === "string", + symbol = isParamString ? param : param[ 0 ], + soft = isParamString ? true : param[ 1 ], + regex; + + symbol = symbol.replace( /,/g, "" ); + symbol = soft ? symbol + "]" : symbol + "]?"; + regex = "^[" + symbol + "([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$"; + regex = new RegExp( regex ); + return this.optional( element ) || regex.test( value ); + +}, "Please specify a valid currency." ); + +$.validator.addMethod( "dateFA", function( value, element ) { + return this.optional( element ) || /^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test( value ); +}, $.validator.messages.date ); + +/** + * Return true, if the value is a valid date, also making this formal check dd/mm/yyyy. + * + * @example $.validator.methods.date("01/01/1900") + * @result true + * + * @example $.validator.methods.date("01/13/1990") + * @result false + * + * @example $.validator.methods.date("01.01.1900") + * @result false + * + * @example + * @desc Declares an optional input element whose value must be a valid date. + * + * @name $.validator.methods.dateITA + * @type Boolean + * @cat Plugins/Validate/Methods + */ +$.validator.addMethod( "dateITA", function( value, element ) { + var check = false, + re = /^\d{1,2}\/\d{1,2}\/\d{4}$/, + adata, gg, mm, aaaa, xdata; + if ( re.test( value ) ) { + adata = value.split( "/" ); + gg = parseInt( adata[ 0 ], 10 ); + mm = parseInt( adata[ 1 ], 10 ); + aaaa = parseInt( adata[ 2 ], 10 ); + xdata = new Date( Date.UTC( aaaa, mm - 1, gg, 12, 0, 0, 0 ) ); + if ( ( xdata.getUTCFullYear() === aaaa ) && ( xdata.getUTCMonth() === mm - 1 ) && ( xdata.getUTCDate() === gg ) ) { + check = true; + } else { + check = false; + } + } else { + check = false; + } + return this.optional( element ) || check; +}, $.validator.messages.date ); + +$.validator.addMethod( "dateNL", function( value, element ) { + return this.optional( element ) || /^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test( value ); +}, $.validator.messages.date ); + +// Older "accept" file extension method. Old docs: http://docs.jquery.com/Plugins/Validation/Methods/accept +$.validator.addMethod( "extension", function( value, element, param ) { + param = typeof param === "string" ? param.replace( /,/g, "|" ) : "png|jpe?g|gif"; + return this.optional( element ) || value.match( new RegExp( "\\.(" + param + ")$", "i" ) ); +}, $.validator.format( "Please enter a value with a valid extension." ) ); + +/** + * Dutch giro account numbers (not bank numbers) have max 7 digits + */ +$.validator.addMethod( "giroaccountNL", function( value, element ) { + return this.optional( element ) || /^[0-9]{1,7}$/.test( value ); +}, "Please specify a valid giro account number." ); + +$.validator.addMethod( "greaterThan", function( value, element, param ) { + var target = $( param ); + + if ( this.settings.onfocusout && target.not( ".validate-greaterThan-blur" ).length ) { + target.addClass( "validate-greaterThan-blur" ).on( "blur.validate-greaterThan", function() { + $( element ).valid(); + } ); + } + + return value > target.val(); +}, "Please enter a greater value." ); + +$.validator.addMethod( "greaterThanEqual", function( value, element, param ) { + var target = $( param ); + + if ( this.settings.onfocusout && target.not( ".validate-greaterThanEqual-blur" ).length ) { + target.addClass( "validate-greaterThanEqual-blur" ).on( "blur.validate-greaterThanEqual", function() { + $( element ).valid(); + } ); + } + + return value >= target.val(); +}, "Please enter a greater value." ); + +/** + * IBAN is the international bank account number. + * It has a country - specific format, that is checked here too + * + * Validation is case-insensitive. Please make sure to normalize input yourself. + */ +$.validator.addMethod( "iban", function( value, element ) { + + // Some quick simple tests to prevent needless work + if ( this.optional( element ) ) { + return true; + } + + // Remove spaces and to upper case + var iban = value.replace( / /g, "" ).toUpperCase(), + ibancheckdigits = "", + leadingZeroes = true, + cRest = "", + cOperator = "", + countrycode, ibancheck, charAt, cChar, bbanpattern, bbancountrypatterns, ibanregexp, i, p; + + // Check for IBAN code length. + // It contains: + // country code ISO 3166-1 - two letters, + // two check digits, + // Basic Bank Account Number (BBAN) - up to 30 chars + var minimalIBANlength = 5; + if ( iban.length < minimalIBANlength ) { + return false; + } + + // Check the country code and find the country specific format + countrycode = iban.substring( 0, 2 ); + bbancountrypatterns = { + "AL": "\\d{8}[\\dA-Z]{16}", + "AD": "\\d{8}[\\dA-Z]{12}", + "AT": "\\d{16}", + "AZ": "[\\dA-Z]{4}\\d{20}", + "BE": "\\d{12}", + "BH": "[A-Z]{4}[\\dA-Z]{14}", + "BA": "\\d{16}", + "BR": "\\d{23}[A-Z][\\dA-Z]", + "BG": "[A-Z]{4}\\d{6}[\\dA-Z]{8}", + "CR": "\\d{17}", + "HR": "\\d{17}", + "CY": "\\d{8}[\\dA-Z]{16}", + "CZ": "\\d{20}", + "DK": "\\d{14}", + "DO": "[A-Z]{4}\\d{20}", + "EE": "\\d{16}", + "FO": "\\d{14}", + "FI": "\\d{14}", + "FR": "\\d{10}[\\dA-Z]{11}\\d{2}", + "GE": "[\\dA-Z]{2}\\d{16}", + "DE": "\\d{18}", + "GI": "[A-Z]{4}[\\dA-Z]{15}", + "GR": "\\d{7}[\\dA-Z]{16}", + "GL": "\\d{14}", + "GT": "[\\dA-Z]{4}[\\dA-Z]{20}", + "HU": "\\d{24}", + "IS": "\\d{22}", + "IE": "[\\dA-Z]{4}\\d{14}", + "IL": "\\d{19}", + "IT": "[A-Z]\\d{10}[\\dA-Z]{12}", + "KZ": "\\d{3}[\\dA-Z]{13}", + "KW": "[A-Z]{4}[\\dA-Z]{22}", + "LV": "[A-Z]{4}[\\dA-Z]{13}", + "LB": "\\d{4}[\\dA-Z]{20}", + "LI": "\\d{5}[\\dA-Z]{12}", + "LT": "\\d{16}", + "LU": "\\d{3}[\\dA-Z]{13}", + "MK": "\\d{3}[\\dA-Z]{10}\\d{2}", + "MT": "[A-Z]{4}\\d{5}[\\dA-Z]{18}", + "MR": "\\d{23}", + "MU": "[A-Z]{4}\\d{19}[A-Z]{3}", + "MC": "\\d{10}[\\dA-Z]{11}\\d{2}", + "MD": "[\\dA-Z]{2}\\d{18}", + "ME": "\\d{18}", + "NL": "[A-Z]{4}\\d{10}", + "NO": "\\d{11}", + "PK": "[\\dA-Z]{4}\\d{16}", + "PS": "[\\dA-Z]{4}\\d{21}", + "PL": "\\d{24}", + "PT": "\\d{21}", + "RO": "[A-Z]{4}[\\dA-Z]{16}", + "SM": "[A-Z]\\d{10}[\\dA-Z]{12}", + "SA": "\\d{2}[\\dA-Z]{18}", + "RS": "\\d{18}", + "SK": "\\d{20}", + "SI": "\\d{15}", + "ES": "\\d{20}", + "SE": "\\d{20}", + "CH": "\\d{5}[\\dA-Z]{12}", + "TN": "\\d{20}", + "TR": "\\d{5}[\\dA-Z]{17}", + "AE": "\\d{3}\\d{16}", + "GB": "[A-Z]{4}\\d{14}", + "VG": "[\\dA-Z]{4}\\d{16}" + }; + + bbanpattern = bbancountrypatterns[ countrycode ]; + + // As new countries will start using IBAN in the + // future, we only check if the countrycode is known. + // This prevents false negatives, while almost all + // false positives introduced by this, will be caught + // by the checksum validation below anyway. + // Strict checking should return FALSE for unknown + // countries. + if ( typeof bbanpattern !== "undefined" ) { + ibanregexp = new RegExp( "^[A-Z]{2}\\d{2}" + bbanpattern + "$", "" ); + if ( !( ibanregexp.test( iban ) ) ) { + return false; // Invalid country specific format + } + } + + // Now check the checksum, first convert to digits + ibancheck = iban.substring( 4, iban.length ) + iban.substring( 0, 4 ); + for ( i = 0; i < ibancheck.length; i++ ) { + charAt = ibancheck.charAt( i ); + if ( charAt !== "0" ) { + leadingZeroes = false; + } + if ( !leadingZeroes ) { + ibancheckdigits += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf( charAt ); + } + } + + // Calculate the result of: ibancheckdigits % 97 + for ( p = 0; p < ibancheckdigits.length; p++ ) { + cChar = ibancheckdigits.charAt( p ); + cOperator = "" + cRest + "" + cChar; + cRest = cOperator % 97; + } + return cRest === 1; +}, "Please specify a valid IBAN." ); + +$.validator.addMethod( "integer", function( value, element ) { + return this.optional( element ) || /^-?\d+$/.test( value ); +}, "A positive or negative non-decimal number please." ); + +$.validator.addMethod( "ipv4", function( value, element ) { + return this.optional( element ) || /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test( value ); +}, "Please enter a valid IP v4 address." ); + +$.validator.addMethod( "ipv6", function( value, element ) { + return this.optional( element ) || /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test( value ); +}, "Please enter a valid IP v6 address." ); + +$.validator.addMethod( "lessThan", function( value, element, param ) { + var target = $( param ); + + if ( this.settings.onfocusout && target.not( ".validate-lessThan-blur" ).length ) { + target.addClass( "validate-lessThan-blur" ).on( "blur.validate-lessThan", function() { + $( element ).valid(); + } ); + } + + return value < target.val(); +}, "Please enter a lesser value." ); + +$.validator.addMethod( "lessThanEqual", function( value, element, param ) { + var target = $( param ); + + if ( this.settings.onfocusout && target.not( ".validate-lessThanEqual-blur" ).length ) { + target.addClass( "validate-lessThanEqual-blur" ).on( "blur.validate-lessThanEqual", function() { + $( element ).valid(); + } ); + } + + return value <= target.val(); +}, "Please enter a lesser value." ); + +$.validator.addMethod( "lettersonly", function( value, element ) { + return this.optional( element ) || /^[a-z]+$/i.test( value ); +}, "Letters only please." ); + +$.validator.addMethod( "letterswithbasicpunc", function( value, element ) { + return this.optional( element ) || /^[a-z\-.,()'"\s]+$/i.test( value ); +}, "Letters or punctuation only please." ); + +// Limit the number of files in a FileList. +$.validator.addMethod( "maxfiles", function( value, element, param ) { + if ( this.optional( element ) ) { + return true; + } + + if ( $( element ).attr( "type" ) === "file" ) { + if ( element.files && element.files.length > param ) { + return false; + } + } + + return true; +}, $.validator.format( "Please select no more than {0} files." ) ); + +// Limit the size of each individual file in a FileList. +$.validator.addMethod( "maxsize", function( value, element, param ) { + if ( this.optional( element ) ) { + return true; + } + + if ( $( element ).attr( "type" ) === "file" ) { + if ( element.files && element.files.length ) { + for ( var i = 0; i < element.files.length; i++ ) { + if ( element.files[ i ].size > param ) { + return false; + } + } + } + } + + return true; +}, $.validator.format( "File size must not exceed {0} bytes each." ) ); + +// Limit the size of all files in a FileList. +$.validator.addMethod( "maxsizetotal", function( value, element, param ) { + if ( this.optional( element ) ) { + return true; + } + + if ( $( element ).attr( "type" ) === "file" ) { + if ( element.files && element.files.length ) { + var totalSize = 0; + + for ( var i = 0; i < element.files.length; i++ ) { + totalSize += element.files[ i ].size; + if ( totalSize > param ) { + return false; + } + } + } + } + + return true; +}, $.validator.format( "Total size of all files must not exceed {0} bytes." ) ); + + +$.validator.addMethod( "mobileNL", function( value, element ) { + return this.optional( element ) || /^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)6((\s|\s?\-\s?)?[0-9]){8}$/.test( value ); +}, "Please specify a valid mobile number." ); + +$.validator.addMethod( "mobileRU", function( phone_number, element ) { + var ruPhone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); + return this.optional( element ) || ruPhone_number.length > 9 && /^((\+7|7|8)+([0-9]){10})$/.test( ruPhone_number ); +}, "Please specify a valid mobile number." ); + +/* For UK phone functions, do the following server side processing: + * Compare original input with this RegEx pattern: + * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ + * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' + * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. + * A number of very detailed GB telephone number RegEx patterns can also be found at: + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers + */ +$.validator.addMethod( "mobileUK", function( phone_number, element ) { + phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); + return this.optional( element ) || phone_number.length > 9 && + phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/ ); +}, "Please specify a valid mobile number." ); + +$.validator.addMethod( "netmask", function( value, element ) { + return this.optional( element ) || /^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0)/i.test( value ); +}, "Please enter a valid netmask." ); + +/* + * The NIE (Número de Identificación de Extranjero) is a Spanish tax identification number assigned by the Spanish + * authorities to any foreigner. + * + * The NIE is the equivalent of a Spaniards Número de Identificación Fiscal (NIF) which serves as a fiscal + * identification number. The CIF number (Certificado de Identificación Fiscal) is equivalent to the NIF, but applies to + * companies rather than individuals. The NIE consists of an 'X' or 'Y' followed by 7 or 8 digits then another letter. + */ +$.validator.addMethod( "nieES", function( value, element ) { + "use strict"; + + if ( this.optional( element ) ) { + return true; + } + + var nieRegEx = new RegExp( /^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi ); + var validChars = "TRWAGMYFPDXBNJZSQVHLCKET", + letter = value.substr( value.length - 1 ).toUpperCase(), + number; + + value = value.toString().toUpperCase(); + + // Quick format test + if ( value.length > 10 || value.length < 9 || !nieRegEx.test( value ) ) { + return false; + } + + // X means same number + // Y means number + 10000000 + // Z means number + 20000000 + value = value.replace( /^[X]/, "0" ) + .replace( /^[Y]/, "1" ) + .replace( /^[Z]/, "2" ); + + number = value.length === 9 ? value.substr( 0, 8 ) : value.substr( 0, 9 ); + + return validChars.charAt( parseInt( number, 10 ) % 23 ) === letter; + +}, "Please specify a valid NIE number." ); + +/* + * The Número de Identificación Fiscal ( NIF ) is the way tax identification used in Spain for individuals + */ +$.validator.addMethod( "nifES", function( value, element ) { + "use strict"; + + if ( this.optional( element ) ) { + return true; + } + + value = value.toUpperCase(); + + // Basic format test + if ( !value.match( "((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)" ) ) { + return false; + } + + // Test NIF + if ( /^[0-9]{8}[A-Z]{1}$/.test( value ) ) { + return ( "TRWAGMYFPDXBNJZSQVHLCKE".charAt( value.substring( 8, 0 ) % 23 ) === value.charAt( 8 ) ); + } + + // Test specials NIF (starts with K, L or M) + if ( /^[KLM]{1}/.test( value ) ) { + return ( value[ 8 ] === "TRWAGMYFPDXBNJZSQVHLCKE".charAt( value.substring( 8, 1 ) % 23 ) ); + } + + return false; + +}, "Please specify a valid NIF number." ); + +/* + * Numer identyfikacji podatkowej ( NIP ) is the way tax identification used in Poland for companies + */ +$.validator.addMethod( "nipPL", function( value ) { + "use strict"; + + value = value.replace( /[^0-9]/g, "" ); + + if ( value.length !== 10 ) { + return false; + } + + var arrSteps = [ 6, 5, 7, 2, 3, 4, 5, 6, 7 ]; + var intSum = 0; + for ( var i = 0; i < 9; i++ ) { + intSum += arrSteps[ i ] * value[ i ]; + } + var int2 = intSum % 11; + var intControlNr = ( int2 === 10 ) ? 0 : int2; + + return ( intControlNr === parseInt( value[ 9 ], 10 ) ); +}, "Please specify a valid NIP number." ); + +/** + * Created for project jquery-validation. + * @Description Brazillian PIS or NIS number (Número de Identificação Social Pis ou Pasep) is the equivalent of a + * Brazilian tax registration number NIS of PIS numbers have 11 digits in total: 10 numbers followed by 1 check numbers + * that are being used for validation. + * @copyright (c) 21/08/2018 13:14, Cleiton da Silva Mendonça + * @author Cleiton da Silva Mendonça + * @link http://gitlab.com/csmendonca Gitlab of Cleiton da Silva Mendonça + * @link http://github.com/csmendonca Github of Cleiton da Silva Mendonça + */ +$.validator.addMethod( "nisBR", function( value ) { + var number; + var cn; + var sum = 0; + var dv; + var count; + var multiplier; + + // Removing special characters from value + value = value.replace( /([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g, "" ); + + // Checking value to have 11 digits only + if ( value.length !== 11 ) { + return false; + } + + //Get check number of value + cn = parseInt( value.substring( 10, 11 ), 10 ); + + //Get number with 10 digits of the value + number = parseInt( value.substring( 0, 10 ), 10 ); + + for ( count = 2; count < 12; count++ ) { + multiplier = count; + if ( count === 10 ) { + multiplier = 2; + } + if ( count === 11 ) { + multiplier = 3; + } + sum += ( ( number % 10 ) * multiplier ); + number = parseInt( number / 10, 10 ); + } + dv = ( sum % 11 ); + + if ( dv > 1 ) { + dv = ( 11 - dv ); + } else { + dv = 0; + } + + if ( cn === dv ) { + return true; + } else { + return false; + } +}, "Please specify a valid NIS/PIS number." ); + +$.validator.addMethod( "notEqualTo", function( value, element, param ) { + return this.optional( element ) || !$.validator.methods.equalTo.call( this, value, element, param ); +}, "Please enter a different value, values must not be the same." ); + +$.validator.addMethod( "nowhitespace", function( value, element ) { + return this.optional( element ) || /^\S+$/i.test( value ); +}, "No white space please." ); + +/** +* Return true if the field value matches the given format RegExp +* +* @example $.validator.methods.pattern("AR1004",element,/^AR\d{4}$/) +* @result true +* +* @example $.validator.methods.pattern("BR1004",element,/^AR\d{4}$/) +* @result false +* +* @name $.validator.methods.pattern +* @type Boolean +* @cat Plugins/Validate/Methods +*/ +$.validator.addMethod( "pattern", function( value, element, param ) { + if ( this.optional( element ) ) { + return true; + } + if ( typeof param === "string" ) { + param = new RegExp( "^(?:" + param + ")$" ); + } + return param.test( value ); +}, "Invalid format." ); + +/** + * Dutch phone numbers have 10 digits (or 11 and start with +31). + */ +$.validator.addMethod( "phoneNL", function( value, element ) { + return this.optional( element ) || /^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test( value ); +}, "Please specify a valid phone number." ); + +/** + * Polish telephone numbers have 9 digits. + * + * Mobile phone numbers starts with following digits: + * 45, 50, 51, 53, 57, 60, 66, 69, 72, 73, 78, 79, 88. + * + * Fixed-line numbers starts with area codes: + * 12, 13, 14, 15, 16, 17, 18, 22, 23, 24, 25, 29, 32, 33, + * 34, 41, 42, 43, 44, 46, 48, 52, 54, 55, 56, 58, 59, 61, + * 62, 63, 65, 67, 68, 71, 74, 75, 76, 77, 81, 82, 83, 84, + * 85, 86, 87, 89, 91, 94, 95. + * + * Ministry of National Defence numbers and VoIP numbers starts with 26 and 39. + * + * Excludes intelligent networks (premium rate, shared cost, free phone numbers). + * + * Poland National Numbering Plan http://www.itu.int/oth/T02020000A8/en + */ +$.validator.addMethod( "phonePL", function( phone_number, element ) { + phone_number = phone_number.replace( /\s+/g, "" ); + var regexp = /^(?:(?:(?:\+|00)?48)|(?:\(\+?48\)))?(?:1[2-8]|2[2-69]|3[2-49]|4[1-68]|5[0-9]|6[0-35-9]|[7-8][1-9]|9[145])\d{7}$/; + return this.optional( element ) || regexp.test( phone_number ); +}, "Please specify a valid phone number." ); + +/* For UK phone functions, do the following server side processing: + * Compare original input with this RegEx pattern: + * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ + * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' + * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. + * A number of very detailed GB telephone number RegEx patterns can also be found at: + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers + */ + +// Matches UK landline + mobile, accepting only 01-3 for landline or 07 for mobile to exclude many premium numbers +$.validator.addMethod( "phonesUK", function( phone_number, element ) { + phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); + return this.optional( element ) || phone_number.length > 9 && + phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/ ); +}, "Please specify a valid uk phone number." ); + +/* For UK phone functions, do the following server side processing: + * Compare original input with this RegEx pattern: + * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ + * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' + * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. + * A number of very detailed GB telephone number RegEx patterns can also be found at: + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers + */ +$.validator.addMethod( "phoneUK", function( phone_number, element ) { + phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); + return this.optional( element ) || phone_number.length > 9 && + phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/ ); +}, "Please specify a valid phone number." ); + +/** + * Matches US phone number format + * + * where the area code may not start with 1 and the prefix may not start with 1 + * allows '-' or ' ' as a separator and allows parens around area code + * some people may want to put a '1' in front of their number + * + * 1(212)-999-2345 or + * 212 999 2344 or + * 212-999-0983 + * + * but not + * 111-123-5434 + * and not + * 212 123 4567 + */ +$.validator.addMethod( "phoneUS", function( phone_number, element ) { + phone_number = phone_number.replace( /\s+/g, "" ); + return this.optional( element ) || phone_number.length > 9 && + phone_number.match( /^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]\d{2}-?\d{4}$/ ); +}, "Please specify a valid phone number." ); + +/* +* Valida CEPs do brasileiros: +* +* Formatos aceitos: +* 99999-999 +* 99.999-999 +* 99999999 +*/ +$.validator.addMethod( "postalcodeBR", function( cep_value, element ) { + return this.optional( element ) || /^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test( cep_value ); +}, "Informe um CEP válido." ); + +/** + * Matches a valid Canadian Postal Code + * + * @example jQuery.validator.methods.postalCodeCA( "H0H 0H0", element ) + * @result true + * + * @example jQuery.validator.methods.postalCodeCA( "H0H0H0", element ) + * @result false + * + * @name jQuery.validator.methods.postalCodeCA + * @type Boolean + * @cat Plugins/Validate/Methods + */ +$.validator.addMethod( "postalCodeCA", function( value, element ) { + return this.optional( element ) || /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] *\d[ABCEGHJKLMNPRSTVWXYZ]\d$/i.test( value ); +}, "Please specify a valid postal code." ); + +/* Matches Italian postcode (CAP) */ +$.validator.addMethod( "postalcodeIT", function( value, element ) { + return this.optional( element ) || /^\d{5}$/.test( value ); +}, "Please specify a valid postal code." ); + +$.validator.addMethod( "postalcodeNL", function( value, element ) { + return this.optional( element ) || /^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test( value ); +}, "Please specify a valid postal code." ); + +// Matches UK postcode. Does not match to UK Channel Islands that have their own postcodes (non standard UK) +$.validator.addMethod( "postcodeUK", function( value, element ) { + return this.optional( element ) || /^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test( value ); +}, "Please specify a valid UK postcode." ); + +/* + * Lets you say "at least X inputs that match selector Y must be filled." + * + * The end result is that neither of these inputs: + * + * + * + * + * ...will validate unless at least one of them is filled. + * + * partnumber: {require_from_group: [1,".productinfo"]}, + * description: {require_from_group: [1,".productinfo"]} + * + * options[0]: number of fields that must be filled in the group + * options[1]: CSS selector that defines the group of conditionally required fields + */ +$.validator.addMethod( "require_from_group", function( value, element, options ) { + var $fields = $( options[ 1 ], element.form ), + $fieldsFirst = $fields.eq( 0 ), + validator = $fieldsFirst.data( "valid_req_grp" ) ? $fieldsFirst.data( "valid_req_grp" ) : $.extend( {}, this ), + isValid = $fields.filter( function() { + return validator.elementValue( this ); + } ).length >= options[ 0 ]; + + // Store the cloned validator for future validation + $fieldsFirst.data( "valid_req_grp", validator ); + + // If element isn't being validated, run each require_from_group field's validation rules + if ( !$( element ).data( "being_validated" ) ) { + $fields.data( "being_validated", true ); + $fields.each( function() { + validator.element( this ); + } ); + $fields.data( "being_validated", false ); + } + return isValid; +}, $.validator.format( "Please fill at least {0} of these fields." ) ); + +/* + * Lets you say "either at least X inputs that match selector Y must be filled, + * OR they must all be skipped (left blank)." + * + * The end result, is that none of these inputs: + * + * + * + * + * + * ...will validate unless either at least two of them are filled, + * OR none of them are. + * + * partnumber: {skip_or_fill_minimum: [2,".productinfo"]}, + * description: {skip_or_fill_minimum: [2,".productinfo"]}, + * color: {skip_or_fill_minimum: [2,".productinfo"]} + * + * options[0]: number of fields that must be filled in the group + * options[1]: CSS selector that defines the group of conditionally required fields + * + */ +$.validator.addMethod( "skip_or_fill_minimum", function( value, element, options ) { + var $fields = $( options[ 1 ], element.form ), + $fieldsFirst = $fields.eq( 0 ), + validator = $fieldsFirst.data( "valid_skip" ) ? $fieldsFirst.data( "valid_skip" ) : $.extend( {}, this ), + numberFilled = $fields.filter( function() { + return validator.elementValue( this ); + } ).length, + isValid = numberFilled === 0 || numberFilled >= options[ 0 ]; + + // Store the cloned validator for future validation + $fieldsFirst.data( "valid_skip", validator ); + + // If element isn't being validated, run each skip_or_fill_minimum field's validation rules + if ( !$( element ).data( "being_validated" ) ) { + $fields.data( "being_validated", true ); + $fields.each( function() { + validator.element( this ); + } ); + $fields.data( "being_validated", false ); + } + return isValid; +}, $.validator.format( "Please either skip these fields or fill at least {0} of them." ) ); + +/* Validates US States and/or Territories by @jdforsythe + * Can be case insensitive or require capitalization - default is case insensitive + * Can include US Territories or not - default does not + * Can include US Military postal abbreviations (AA, AE, AP) - default does not + * + * Note: "States" always includes DC (District of Colombia) + * + * Usage examples: + * + * This is the default - case insensitive, no territories, no military zones + * stateInput: { + * caseSensitive: false, + * includeTerritories: false, + * includeMilitary: false + * } + * + * Only allow capital letters, no territories, no military zones + * stateInput: { + * caseSensitive: false + * } + * + * Case insensitive, include territories but not military zones + * stateInput: { + * includeTerritories: true + * } + * + * Only allow capital letters, include territories and military zones + * stateInput: { + * caseSensitive: true, + * includeTerritories: true, + * includeMilitary: true + * } + * + */ +$.validator.addMethod( "stateUS", function( value, element, options ) { + var isDefault = typeof options === "undefined", + caseSensitive = ( isDefault || typeof options.caseSensitive === "undefined" ) ? false : options.caseSensitive, + includeTerritories = ( isDefault || typeof options.includeTerritories === "undefined" ) ? false : options.includeTerritories, + includeMilitary = ( isDefault || typeof options.includeMilitary === "undefined" ) ? false : options.includeMilitary, + regex; + + if ( !includeTerritories && !includeMilitary ) { + regex = "^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$"; + } else if ( includeTerritories && includeMilitary ) { + regex = "^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$"; + } else if ( includeTerritories ) { + regex = "^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$"; + } else { + regex = "^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$"; + } + + regex = caseSensitive ? new RegExp( regex ) : new RegExp( regex, "i" ); + return this.optional( element ) || regex.test( value ); +}, "Please specify a valid state." ); + +// TODO check if value starts with <, otherwise don't try stripping anything +$.validator.addMethod( "strippedminlength", function( value, element, param ) { + return $( value ).text().length >= param; +}, $.validator.format( "Please enter at least {0} characters." ) ); + +$.validator.addMethod( "time", function( value, element ) { + return this.optional( element ) || /^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test( value ); +}, "Please enter a valid time, between 00:00 and 23:59." ); + +$.validator.addMethod( "time12h", function( value, element ) { + return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value ); +}, "Please enter a valid time in 12-hour am/pm format." ); + +// Same as url, but TLD is optional +$.validator.addMethod( "url2", function( value, element ) { + return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[^\]\[?\/<~#`!@$^&*()+=}|:";',>{ ]|%[0-9A-Fa-f]{2})+(?::(?:[^\]\[?\/<~#`!@$^&*()+=}|:";',>{ ]|%[0-9A-Fa-f]{2})*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff]\.)+(?:[a-z\u00a1-\uffff]{2,}\.?)|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62})?[a-z0-9\u00a1-\uffff])|(?:(?:[a-z0-9\u00a1-\uffff][a-z0-9\u00a1-\uffff_-]{0,62}\.)))(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value ); +}, $.validator.messages.url ); + +/** + * Return true, if the value is a valid vehicle identification number (VIN). + * + * Works with all kind of text inputs. + * + * @example + * @desc Declares a required input element whose value must be a valid vehicle identification number. + * + * @name $.validator.methods.vinUS + * @type Boolean + * @cat Plugins/Validate/Methods + */ +$.validator.addMethod( "vinUS", function( v ) { + if ( v.length !== 17 ) { + return false; + } + + var LL = [ "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" ], + VL = [ 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 7, 9, 2, 3, 4, 5, 6, 7, 8, 9 ], + FL = [ 8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2 ], + rs = 0, + i, n, d, f, cd, cdv; + + for ( i = 0; i < 17; i++ ) { + f = FL[ i ]; + d = v.slice( i, i + 1 ); + if ( i === 8 ) { + cdv = d; + } + if ( !isNaN( d ) ) { + d *= f; + } else { + for ( n = 0; n < LL.length; n++ ) { + if ( d.toUpperCase() === LL[ n ] ) { + d = VL[ n ]; + d *= f; + if ( isNaN( cdv ) && n === 8 ) { + cdv = LL[ n ]; + } + break; + } + } + } + rs += d; + } + cd = rs % 11; + if ( cd === 10 ) { + cd = "X"; + } + if ( cd === cdv ) { + return true; + } + return false; +}, "The specified vehicle identification number (VIN) is invalid." ); + +$.validator.addMethod( "zipcodeUS", function( value, element ) { + return this.optional( element ) || /^\d{5}(-\d{4})?$/.test( value ); +}, "The specified US ZIP Code is invalid." ); + +$.validator.addMethod( "ziprange", function( value, element ) { + return this.optional( element ) || /^90[2-5]\d\{2\}-\d{4}$/.test( value ); +}, "Your ZIP-code must be in the range 902xx-xxxx to 905xx-xxxx." ); +return $; +})); + diff --git a/assets/js/libraries/chart-4.4.1.js b/assets/js/libraries/chart-4.4.1.js new file mode 100644 index 00000000..9485b0a6 --- /dev/null +++ b/assets/js/libraries/chart-4.4.1.js @@ -0,0 +1,11456 @@ +/*! + * Chart.js v4.4.1 + * https://www.chartjs.org + * (c) 2023 Chart.js Contributors + * Released under the MIT License + */ +import { r as requestAnimFrame, a as resolve, e as effects, c as color, i as isObject, d as defaults, b as isArray, v as valueOrDefault, u as unlistenArrayEvents, l as listenArrayEvents, f as resolveObjectKey, g as isNumberFinite, h as defined, s as sign, j as createContext, k as isNullOrUndef, _ as _arrayUnique, t as toRadians, m as toPercentage, n as toDimension, T as TAU, o as formatNumber, p as _angleBetween, H as HALF_PI, P as PI, q as _getStartAndCountOfVisiblePoints, w as _scaleRangesChanged, x as isNumber, y as _parseObjectDataRadialScale, z as getRelativePosition, A as _rlookupByKey, B as _lookupByKey, C as _isPointInArea, D as getAngleFromPoint, E as toPadding, F as each, G as getMaximumSize, I as _getParentNode, J as readUsedSize, K as supportsEventListenerOptions, L as throttled, M as _isDomSupported, N as _factorize, O as finiteOrDefault, Q as callback, R as _addGrace, S as _limitValue, U as toDegrees, V as _measureText, W as _int16Range, X as _alignPixel, Y as clipArea, Z as renderText, $ as unclipArea, a0 as toFont, a1 as _toLeftRightCenter, a2 as _alignStartEnd, a3 as overrides, a4 as merge, a5 as _capitalize, a6 as descriptors, a7 as isFunction, a8 as _attachContext, a9 as _createResolver, aa as _descriptors, ab as mergeIf, ac as uid, ad as debounce, ae as retinaScale, af as clearCanvas, ag as setsEqual, ah as _elementsEqual, ai as _isClickEvent, aj as _isBetween, ak as _readValueToProps, al as _updateBezierControlPoints, am as _computeSegments, an as _boundSegments, ao as _steppedInterpolation, ap as _bezierInterpolation, aq as _pointInLine, ar as _steppedLineTo, as as _bezierCurveTo, at as drawPoint, au as addRoundedRectPath, av as toTRBL, aw as toTRBLCorners, ax as _boundSegment, ay as _normalizeAngle, az as getRtlAdapter, aA as overrideTextDirection, aB as _textX, aC as restoreTextDirection, aD as drawPointLegend, aE as distanceBetweenPoints, aF as noop, aG as _setMinAndMaxByKey, aH as niceNum, aI as almostWhole, aJ as almostEquals, aK as _decimalPlaces, aL as Ticks, aM as log10, aN as _longestText, aO as _filterBetween, aP as _lookup } from './chunks/helpers.segment.js'; +import '@kurkle/color'; + +class Animator { + constructor(){ + this._request = null; + this._charts = new Map(); + this._running = false; + this._lastDate = undefined; + } + _notify(chart, anims, date, type) { + const callbacks = anims.listeners[type]; + const numSteps = anims.duration; + callbacks.forEach((fn)=>fn({ + chart, + initial: anims.initial, + numSteps, + currentStep: Math.min(date - anims.start, numSteps) + })); + } + _refresh() { + if (this._request) { + return; + } + this._running = true; + this._request = requestAnimFrame.call(window, ()=>{ + this._update(); + this._request = null; + if (this._running) { + this._refresh(); + } + }); + } + _update(date = Date.now()) { + let remaining = 0; + this._charts.forEach((anims, chart)=>{ + if (!anims.running || !anims.items.length) { + return; + } + const items = anims.items; + let i = items.length - 1; + let draw = false; + let item; + for(; i >= 0; --i){ + item = items[i]; + if (item._active) { + if (item._total > anims.duration) { + anims.duration = item._total; + } + item.tick(date); + draw = true; + } else { + items[i] = items[items.length - 1]; + items.pop(); + } + } + if (draw) { + chart.draw(); + this._notify(chart, anims, date, 'progress'); + } + if (!items.length) { + anims.running = false; + this._notify(chart, anims, date, 'complete'); + anims.initial = false; + } + remaining += items.length; + }); + this._lastDate = date; + if (remaining === 0) { + this._running = false; + } + } + _getAnims(chart) { + const charts = this._charts; + let anims = charts.get(chart); + if (!anims) { + anims = { + running: false, + initial: true, + items: [], + listeners: { + complete: [], + progress: [] + } + }; + charts.set(chart, anims); + } + return anims; + } + listen(chart, event, cb) { + this._getAnims(chart).listeners[event].push(cb); + } + add(chart, items) { + if (!items || !items.length) { + return; + } + this._getAnims(chart).items.push(...items); + } + has(chart) { + return this._getAnims(chart).items.length > 0; + } + start(chart) { + const anims = this._charts.get(chart); + if (!anims) { + return; + } + anims.running = true; + anims.start = Date.now(); + anims.duration = anims.items.reduce((acc, cur)=>Math.max(acc, cur._duration), 0); + this._refresh(); + } + running(chart) { + if (!this._running) { + return false; + } + const anims = this._charts.get(chart); + if (!anims || !anims.running || !anims.items.length) { + return false; + } + return true; + } + stop(chart) { + const anims = this._charts.get(chart); + if (!anims || !anims.items.length) { + return; + } + const items = anims.items; + let i = items.length - 1; + for(; i >= 0; --i){ + items[i].cancel(); + } + anims.items = []; + this._notify(chart, anims, Date.now(), 'complete'); + } + remove(chart) { + return this._charts.delete(chart); + } +} +var animator = /* #__PURE__ */ new Animator(); + +const transparent = 'transparent'; +const interpolators = { + boolean (from, to, factor) { + return factor > 0.5 ? to : from; + }, + color (from, to, factor) { + const c0 = color(from || transparent); + const c1 = c0.valid && color(to || transparent); + return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to; + }, + number (from, to, factor) { + return from + (to - from) * factor; + } +}; +class Animation { + constructor(cfg, target, prop, to){ + const currentValue = target[prop]; + to = resolve([ + cfg.to, + to, + currentValue, + cfg.from + ]); + const from = resolve([ + cfg.from, + currentValue, + to + ]); + this._active = true; + this._fn = cfg.fn || interpolators[cfg.type || typeof from]; + this._easing = effects[cfg.easing] || effects.linear; + this._start = Math.floor(Date.now() + (cfg.delay || 0)); + this._duration = this._total = Math.floor(cfg.duration); + this._loop = !!cfg.loop; + this._target = target; + this._prop = prop; + this._from = from; + this._to = to; + this._promises = undefined; + } + active() { + return this._active; + } + update(cfg, to, date) { + if (this._active) { + this._notify(false); + const currentValue = this._target[this._prop]; + const elapsed = date - this._start; + const remain = this._duration - elapsed; + this._start = date; + this._duration = Math.floor(Math.max(remain, cfg.duration)); + this._total += elapsed; + this._loop = !!cfg.loop; + this._to = resolve([ + cfg.to, + to, + currentValue, + cfg.from + ]); + this._from = resolve([ + cfg.from, + currentValue, + to + ]); + } + } + cancel() { + if (this._active) { + this.tick(Date.now()); + this._active = false; + this._notify(false); + } + } + tick(date) { + const elapsed = date - this._start; + const duration = this._duration; + const prop = this._prop; + const from = this._from; + const loop = this._loop; + const to = this._to; + let factor; + this._active = from !== to && (loop || elapsed < duration); + if (!this._active) { + this._target[prop] = to; + this._notify(true); + return; + } + if (elapsed < 0) { + this._target[prop] = from; + return; + } + factor = elapsed / duration % 2; + factor = loop && factor > 1 ? 2 - factor : factor; + factor = this._easing(Math.min(1, Math.max(0, factor))); + this._target[prop] = this._fn(from, to, factor); + } + wait() { + const promises = this._promises || (this._promises = []); + return new Promise((res, rej)=>{ + promises.push({ + res, + rej + }); + }); + } + _notify(resolved) { + const method = resolved ? 'res' : 'rej'; + const promises = this._promises || []; + for(let i = 0; i < promises.length; i++){ + promises[i][method](); + } + } +} + +class Animations { + constructor(chart, config){ + this._chart = chart; + this._properties = new Map(); + this.configure(config); + } + configure(config) { + if (!isObject(config)) { + return; + } + const animationOptions = Object.keys(defaults.animation); + const animatedProps = this._properties; + Object.getOwnPropertyNames(config).forEach((key)=>{ + const cfg = config[key]; + if (!isObject(cfg)) { + return; + } + const resolved = {}; + for (const option of animationOptions){ + resolved[option] = cfg[option]; + } + (isArray(cfg.properties) && cfg.properties || [ + key + ]).forEach((prop)=>{ + if (prop === key || !animatedProps.has(prop)) { + animatedProps.set(prop, resolved); + } + }); + }); + } + _animateOptions(target, values) { + const newOptions = values.options; + const options = resolveTargetOptions(target, newOptions); + if (!options) { + return []; + } + const animations = this._createAnimations(options, newOptions); + if (newOptions.$shared) { + awaitAll(target.options.$animations, newOptions).then(()=>{ + target.options = newOptions; + }, ()=>{ + }); + } + return animations; + } + _createAnimations(target, values) { + const animatedProps = this._properties; + const animations = []; + const running = target.$animations || (target.$animations = {}); + const props = Object.keys(values); + const date = Date.now(); + let i; + for(i = props.length - 1; i >= 0; --i){ + const prop = props[i]; + if (prop.charAt(0) === '$') { + continue; + } + if (prop === 'options') { + animations.push(...this._animateOptions(target, values)); + continue; + } + const value = values[prop]; + let animation = running[prop]; + const cfg = animatedProps.get(prop); + if (animation) { + if (cfg && animation.active()) { + animation.update(cfg, value, date); + continue; + } else { + animation.cancel(); + } + } + if (!cfg || !cfg.duration) { + target[prop] = value; + continue; + } + running[prop] = animation = new Animation(cfg, target, prop, value); + animations.push(animation); + } + return animations; + } + update(target, values) { + if (this._properties.size === 0) { + Object.assign(target, values); + return; + } + const animations = this._createAnimations(target, values); + if (animations.length) { + animator.add(this._chart, animations); + return true; + } + } +} +function awaitAll(animations, properties) { + const running = []; + const keys = Object.keys(properties); + for(let i = 0; i < keys.length; i++){ + const anim = animations[keys[i]]; + if (anim && anim.active()) { + running.push(anim.wait()); + } + } + return Promise.all(running); +} +function resolveTargetOptions(target, newOptions) { + if (!newOptions) { + return; + } + let options = target.options; + if (!options) { + target.options = newOptions; + return; + } + if (options.$shared) { + target.options = options = Object.assign({}, options, { + $shared: false, + $animations: {} + }); + } + return options; +} + +function scaleClip(scale, allowedOverflow) { + const opts = scale && scale.options || {}; + const reverse = opts.reverse; + const min = opts.min === undefined ? allowedOverflow : 0; + const max = opts.max === undefined ? allowedOverflow : 0; + return { + start: reverse ? max : min, + end: reverse ? min : max + }; +} +function defaultClip(xScale, yScale, allowedOverflow) { + if (allowedOverflow === false) { + return false; + } + const x = scaleClip(xScale, allowedOverflow); + const y = scaleClip(yScale, allowedOverflow); + return { + top: y.end, + right: x.end, + bottom: y.start, + left: x.start + }; +} +function toClip(value) { + let t, r, b, l; + if (isObject(value)) { + t = value.top; + r = value.right; + b = value.bottom; + l = value.left; + } else { + t = r = b = l = value; + } + return { + top: t, + right: r, + bottom: b, + left: l, + disabled: value === false + }; +} +function getSortedDatasetIndices(chart, filterVisible) { + const keys = []; + const metasets = chart._getSortedDatasetMetas(filterVisible); + let i, ilen; + for(i = 0, ilen = metasets.length; i < ilen; ++i){ + keys.push(metasets[i].index); + } + return keys; +} +function applyStack(stack, value, dsIndex, options = {}) { + const keys = stack.keys; + const singleMode = options.mode === 'single'; + let i, ilen, datasetIndex, otherValue; + if (value === null) { + return; + } + for(i = 0, ilen = keys.length; i < ilen; ++i){ + datasetIndex = +keys[i]; + if (datasetIndex === dsIndex) { + if (options.all) { + continue; + } + break; + } + otherValue = stack.values[datasetIndex]; + if (isNumberFinite(otherValue) && (singleMode || value === 0 || sign(value) === sign(otherValue))) { + value += otherValue; + } + } + return value; +} +function convertObjectDataToArray(data) { + const keys = Object.keys(data); + const adata = new Array(keys.length); + let i, ilen, key; + for(i = 0, ilen = keys.length; i < ilen; ++i){ + key = keys[i]; + adata[i] = { + x: key, + y: data[key] + }; + } + return adata; +} +function isStacked(scale, meta) { + const stacked = scale && scale.options.stacked; + return stacked || stacked === undefined && meta.stack !== undefined; +} +function getStackKey(indexScale, valueScale, meta) { + return `${indexScale.id}.${valueScale.id}.${meta.stack || meta.type}`; +} +function getUserBounds(scale) { + const { min , max , minDefined , maxDefined } = scale.getUserBounds(); + return { + min: minDefined ? min : Number.NEGATIVE_INFINITY, + max: maxDefined ? max : Number.POSITIVE_INFINITY + }; +} +function getOrCreateStack(stacks, stackKey, indexValue) { + const subStack = stacks[stackKey] || (stacks[stackKey] = {}); + return subStack[indexValue] || (subStack[indexValue] = {}); +} +function getLastIndexInStack(stack, vScale, positive, type) { + for (const meta of vScale.getMatchingVisibleMetas(type).reverse()){ + const value = stack[meta.index]; + if (positive && value > 0 || !positive && value < 0) { + return meta.index; + } + } + return null; +} +function updateStacks(controller, parsed) { + const { chart , _cachedMeta: meta } = controller; + const stacks = chart._stacks || (chart._stacks = {}); + const { iScale , vScale , index: datasetIndex } = meta; + const iAxis = iScale.axis; + const vAxis = vScale.axis; + const key = getStackKey(iScale, vScale, meta); + const ilen = parsed.length; + let stack; + for(let i = 0; i < ilen; ++i){ + const item = parsed[i]; + const { [iAxis]: index , [vAxis]: value } = item; + const itemStacks = item._stacks || (item._stacks = {}); + stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, index); + stack[datasetIndex] = value; + stack._top = getLastIndexInStack(stack, vScale, true, meta.type); + stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type); + const visualValues = stack._visualValues || (stack._visualValues = {}); + visualValues[datasetIndex] = value; + } +} +function getFirstScaleId(chart, axis) { + const scales = chart.scales; + return Object.keys(scales).filter((key)=>scales[key].axis === axis).shift(); +} +function createDatasetContext(parent, index) { + return createContext(parent, { + active: false, + dataset: undefined, + datasetIndex: index, + index, + mode: 'default', + type: 'dataset' + }); +} +function createDataContext(parent, index, element) { + return createContext(parent, { + active: false, + dataIndex: index, + parsed: undefined, + raw: undefined, + element, + index, + mode: 'default', + type: 'data' + }); +} +function clearStacks(meta, items) { + const datasetIndex = meta.controller.index; + const axis = meta.vScale && meta.vScale.axis; + if (!axis) { + return; + } + items = items || meta._parsed; + for (const parsed of items){ + const stacks = parsed._stacks; + if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) { + return; + } + delete stacks[axis][datasetIndex]; + if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) { + delete stacks[axis]._visualValues[datasetIndex]; + } + } +} +const isDirectUpdateMode = (mode)=>mode === 'reset' || mode === 'none'; +const cloneIfNotShared = (cached, shared)=>shared ? cached : Object.assign({}, cached); +const createStack = (canStack, meta, chart)=>canStack && !meta.hidden && meta._stacked && { + keys: getSortedDatasetIndices(chart, true), + values: null + }; +class DatasetController { + static defaults = {}; + static datasetElementType = null; + static dataElementType = null; + constructor(chart, datasetIndex){ + this.chart = chart; + this._ctx = chart.ctx; + this.index = datasetIndex; + this._cachedDataOpts = {}; + this._cachedMeta = this.getMeta(); + this._type = this._cachedMeta.type; + this.options = undefined; + this._parsing = false; + this._data = undefined; + this._objectData = undefined; + this._sharedOptions = undefined; + this._drawStart = undefined; + this._drawCount = undefined; + this.enableOptionSharing = false; + this.supportsDecimation = false; + this.$context = undefined; + this._syncList = []; + this.datasetElementType = new.target.datasetElementType; + this.dataElementType = new.target.dataElementType; + this.initialize(); + } + initialize() { + const meta = this._cachedMeta; + this.configure(); + this.linkScales(); + meta._stacked = isStacked(meta.vScale, meta); + this.addElements(); + if (this.options.fill && !this.chart.isPluginEnabled('filler')) { + console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options"); + } + } + updateIndex(datasetIndex) { + if (this.index !== datasetIndex) { + clearStacks(this._cachedMeta); + } + this.index = datasetIndex; + } + linkScales() { + const chart = this.chart; + const meta = this._cachedMeta; + const dataset = this.getDataset(); + const chooseId = (axis, x, y, r)=>axis === 'x' ? x : axis === 'r' ? r : y; + const xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x')); + const yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y')); + const rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r')); + const indexAxis = meta.indexAxis; + const iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid); + const vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid); + meta.xScale = this.getScaleForId(xid); + meta.yScale = this.getScaleForId(yid); + meta.rScale = this.getScaleForId(rid); + meta.iScale = this.getScaleForId(iid); + meta.vScale = this.getScaleForId(vid); + } + getDataset() { + return this.chart.data.datasets[this.index]; + } + getMeta() { + return this.chart.getDatasetMeta(this.index); + } + getScaleForId(scaleID) { + return this.chart.scales[scaleID]; + } + _getOtherScale(scale) { + const meta = this._cachedMeta; + return scale === meta.iScale ? meta.vScale : meta.iScale; + } + reset() { + this._update('reset'); + } + _destroy() { + const meta = this._cachedMeta; + if (this._data) { + unlistenArrayEvents(this._data, this); + } + if (meta._stacked) { + clearStacks(meta); + } + } + _dataCheck() { + const dataset = this.getDataset(); + const data = dataset.data || (dataset.data = []); + const _data = this._data; + if (isObject(data)) { + this._data = convertObjectDataToArray(data); + } else if (_data !== data) { + if (_data) { + unlistenArrayEvents(_data, this); + const meta = this._cachedMeta; + clearStacks(meta); + meta._parsed = []; + } + if (data && Object.isExtensible(data)) { + listenArrayEvents(data, this); + } + this._syncList = []; + this._data = data; + } + } + addElements() { + const meta = this._cachedMeta; + this._dataCheck(); + if (this.datasetElementType) { + meta.dataset = new this.datasetElementType(); + } + } + buildOrUpdateElements(resetNewElements) { + const meta = this._cachedMeta; + const dataset = this.getDataset(); + let stackChanged = false; + this._dataCheck(); + const oldStacked = meta._stacked; + meta._stacked = isStacked(meta.vScale, meta); + if (meta.stack !== dataset.stack) { + stackChanged = true; + clearStacks(meta); + meta.stack = dataset.stack; + } + this._resyncElements(resetNewElements); + if (stackChanged || oldStacked !== meta._stacked) { + updateStacks(this, meta._parsed); + } + } + configure() { + const config = this.chart.config; + const scopeKeys = config.datasetScopeKeys(this._type); + const scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true); + this.options = config.createResolver(scopes, this.getContext()); + this._parsing = this.options.parsing; + this._cachedDataOpts = {}; + } + parse(start, count) { + const { _cachedMeta: meta , _data: data } = this; + const { iScale , _stacked } = meta; + const iAxis = iScale.axis; + let sorted = start === 0 && count === data.length ? true : meta._sorted; + let prev = start > 0 && meta._parsed[start - 1]; + let i, cur, parsed; + if (this._parsing === false) { + meta._parsed = data; + meta._sorted = true; + parsed = data; + } else { + if (isArray(data[start])) { + parsed = this.parseArrayData(meta, data, start, count); + } else if (isObject(data[start])) { + parsed = this.parseObjectData(meta, data, start, count); + } else { + parsed = this.parsePrimitiveData(meta, data, start, count); + } + const isNotInOrderComparedToPrev = ()=>cur[iAxis] === null || prev && cur[iAxis] < prev[iAxis]; + for(i = 0; i < count; ++i){ + meta._parsed[i + start] = cur = parsed[i]; + if (sorted) { + if (isNotInOrderComparedToPrev()) { + sorted = false; + } + prev = cur; + } + } + meta._sorted = sorted; + } + if (_stacked) { + updateStacks(this, parsed); + } + } + parsePrimitiveData(meta, data, start, count) { + const { iScale , vScale } = meta; + const iAxis = iScale.axis; + const vAxis = vScale.axis; + const labels = iScale.getLabels(); + const singleScale = iScale === vScale; + const parsed = new Array(count); + let i, ilen, index; + for(i = 0, ilen = count; i < ilen; ++i){ + index = i + start; + parsed[i] = { + [iAxis]: singleScale || iScale.parse(labels[index], index), + [vAxis]: vScale.parse(data[index], index) + }; + } + return parsed; + } + parseArrayData(meta, data, start, count) { + const { xScale , yScale } = meta; + const parsed = new Array(count); + let i, ilen, index, item; + for(i = 0, ilen = count; i < ilen; ++i){ + index = i + start; + item = data[index]; + parsed[i] = { + x: xScale.parse(item[0], index), + y: yScale.parse(item[1], index) + }; + } + return parsed; + } + parseObjectData(meta, data, start, count) { + const { xScale , yScale } = meta; + const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing; + const parsed = new Array(count); + let i, ilen, index, item; + for(i = 0, ilen = count; i < ilen; ++i){ + index = i + start; + item = data[index]; + parsed[i] = { + x: xScale.parse(resolveObjectKey(item, xAxisKey), index), + y: yScale.parse(resolveObjectKey(item, yAxisKey), index) + }; + } + return parsed; + } + getParsed(index) { + return this._cachedMeta._parsed[index]; + } + getDataElement(index) { + return this._cachedMeta.data[index]; + } + applyStack(scale, parsed, mode) { + const chart = this.chart; + const meta = this._cachedMeta; + const value = parsed[scale.axis]; + const stack = { + keys: getSortedDatasetIndices(chart, true), + values: parsed._stacks[scale.axis]._visualValues + }; + return applyStack(stack, value, meta.index, { + mode + }); + } + updateRangeFromParsed(range, scale, parsed, stack) { + const parsedValue = parsed[scale.axis]; + let value = parsedValue === null ? NaN : parsedValue; + const values = stack && parsed._stacks[scale.axis]; + if (stack && values) { + stack.values = values; + value = applyStack(stack, parsedValue, this._cachedMeta.index); + } + range.min = Math.min(range.min, value); + range.max = Math.max(range.max, value); + } + getMinMax(scale, canStack) { + const meta = this._cachedMeta; + const _parsed = meta._parsed; + const sorted = meta._sorted && scale === meta.iScale; + const ilen = _parsed.length; + const otherScale = this._getOtherScale(scale); + const stack = createStack(canStack, meta, this.chart); + const range = { + min: Number.POSITIVE_INFINITY, + max: Number.NEGATIVE_INFINITY + }; + const { min: otherMin , max: otherMax } = getUserBounds(otherScale); + let i, parsed; + function _skip() { + parsed = _parsed[i]; + const otherValue = parsed[otherScale.axis]; + return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue; + } + for(i = 0; i < ilen; ++i){ + if (_skip()) { + continue; + } + this.updateRangeFromParsed(range, scale, parsed, stack); + if (sorted) { + break; + } + } + if (sorted) { + for(i = ilen - 1; i >= 0; --i){ + if (_skip()) { + continue; + } + this.updateRangeFromParsed(range, scale, parsed, stack); + break; + } + } + return range; + } + getAllParsedValues(scale) { + const parsed = this._cachedMeta._parsed; + const values = []; + let i, ilen, value; + for(i = 0, ilen = parsed.length; i < ilen; ++i){ + value = parsed[i][scale.axis]; + if (isNumberFinite(value)) { + values.push(value); + } + } + return values; + } + getMaxOverflow() { + return false; + } + getLabelAndValue(index) { + const meta = this._cachedMeta; + const iScale = meta.iScale; + const vScale = meta.vScale; + const parsed = this.getParsed(index); + return { + label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '', + value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : '' + }; + } + _update(mode) { + const meta = this._cachedMeta; + this.update(mode || 'default'); + meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow()))); + } + update(mode) {} + draw() { + const ctx = this._ctx; + const chart = this.chart; + const meta = this._cachedMeta; + const elements = meta.data || []; + const area = chart.chartArea; + const active = []; + const start = this._drawStart || 0; + const count = this._drawCount || elements.length - start; + const drawActiveElementsOnTop = this.options.drawActiveElementsOnTop; + let i; + if (meta.dataset) { + meta.dataset.draw(ctx, area, start, count); + } + for(i = start; i < start + count; ++i){ + const element = elements[i]; + if (element.hidden) { + continue; + } + if (element.active && drawActiveElementsOnTop) { + active.push(element); + } else { + element.draw(ctx, area); + } + } + for(i = 0; i < active.length; ++i){ + active[i].draw(ctx, area); + } + } + getStyle(index, active) { + const mode = active ? 'active' : 'default'; + return index === undefined && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(mode) : this.resolveDataElementOptions(index || 0, mode); + } + getContext(index, active, mode) { + const dataset = this.getDataset(); + let context; + if (index >= 0 && index < this._cachedMeta.data.length) { + const element = this._cachedMeta.data[index]; + context = element.$context || (element.$context = createDataContext(this.getContext(), index, element)); + context.parsed = this.getParsed(index); + context.raw = dataset.data[index]; + context.index = context.dataIndex = index; + } else { + context = this.$context || (this.$context = createDatasetContext(this.chart.getContext(), this.index)); + context.dataset = dataset; + context.index = context.datasetIndex = this.index; + } + context.active = !!active; + context.mode = mode; + return context; + } + resolveDatasetElementOptions(mode) { + return this._resolveElementOptions(this.datasetElementType.id, mode); + } + resolveDataElementOptions(index, mode) { + return this._resolveElementOptions(this.dataElementType.id, mode, index); + } + _resolveElementOptions(elementType, mode = 'default', index) { + const active = mode === 'active'; + const cache = this._cachedDataOpts; + const cacheKey = elementType + '-' + mode; + const cached = cache[cacheKey]; + const sharing = this.enableOptionSharing && defined(index); + if (cached) { + return cloneIfNotShared(cached, sharing); + } + const config = this.chart.config; + const scopeKeys = config.datasetElementScopeKeys(this._type, elementType); + const prefixes = active ? [ + `${elementType}Hover`, + 'hover', + elementType, + '' + ] : [ + elementType, + '' + ]; + const scopes = config.getOptionScopes(this.getDataset(), scopeKeys); + const names = Object.keys(defaults.elements[elementType]); + const context = ()=>this.getContext(index, active, mode); + const values = config.resolveNamedOptions(scopes, names, context, prefixes); + if (values.$shared) { + values.$shared = sharing; + cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing)); + } + return values; + } + _resolveAnimations(index, transition, active) { + const chart = this.chart; + const cache = this._cachedDataOpts; + const cacheKey = `animation-${transition}`; + const cached = cache[cacheKey]; + if (cached) { + return cached; + } + let options; + if (chart.options.animation !== false) { + const config = this.chart.config; + const scopeKeys = config.datasetAnimationScopeKeys(this._type, transition); + const scopes = config.getOptionScopes(this.getDataset(), scopeKeys); + options = config.createResolver(scopes, this.getContext(index, active, transition)); + } + const animations = new Animations(chart, options && options.animations); + if (options && options._cacheable) { + cache[cacheKey] = Object.freeze(animations); + } + return animations; + } + getSharedOptions(options) { + if (!options.$shared) { + return; + } + return this._sharedOptions || (this._sharedOptions = Object.assign({}, options)); + } + includeOptions(mode, sharedOptions) { + return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled; + } + _getSharedOptions(start, mode) { + const firstOpts = this.resolveDataElementOptions(start, mode); + const previouslySharedOptions = this._sharedOptions; + const sharedOptions = this.getSharedOptions(firstOpts); + const includeOptions = this.includeOptions(mode, sharedOptions) || sharedOptions !== previouslySharedOptions; + this.updateSharedOptions(sharedOptions, mode, firstOpts); + return { + sharedOptions, + includeOptions + }; + } + updateElement(element, index, properties, mode) { + if (isDirectUpdateMode(mode)) { + Object.assign(element, properties); + } else { + this._resolveAnimations(index, mode).update(element, properties); + } + } + updateSharedOptions(sharedOptions, mode, newOptions) { + if (sharedOptions && !isDirectUpdateMode(mode)) { + this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions); + } + } + _setStyle(element, index, mode, active) { + element.active = active; + const options = this.getStyle(index, active); + this._resolveAnimations(index, mode, active).update(element, { + options: !active && this.getSharedOptions(options) || options + }); + } + removeHoverStyle(element, datasetIndex, index) { + this._setStyle(element, index, 'active', false); + } + setHoverStyle(element, datasetIndex, index) { + this._setStyle(element, index, 'active', true); + } + _removeDatasetHoverStyle() { + const element = this._cachedMeta.dataset; + if (element) { + this._setStyle(element, undefined, 'active', false); + } + } + _setDatasetHoverStyle() { + const element = this._cachedMeta.dataset; + if (element) { + this._setStyle(element, undefined, 'active', true); + } + } + _resyncElements(resetNewElements) { + const data = this._data; + const elements = this._cachedMeta.data; + for (const [method, arg1, arg2] of this._syncList){ + this[method](arg1, arg2); + } + this._syncList = []; + const numMeta = elements.length; + const numData = data.length; + const count = Math.min(numData, numMeta); + if (count) { + this.parse(0, count); + } + if (numData > numMeta) { + this._insertElements(numMeta, numData - numMeta, resetNewElements); + } else if (numData < numMeta) { + this._removeElements(numData, numMeta - numData); + } + } + _insertElements(start, count, resetNewElements = true) { + const meta = this._cachedMeta; + const data = meta.data; + const end = start + count; + let i; + const move = (arr)=>{ + arr.length += count; + for(i = arr.length - 1; i >= end; i--){ + arr[i] = arr[i - count]; + } + }; + move(data); + for(i = start; i < end; ++i){ + data[i] = new this.dataElementType(); + } + if (this._parsing) { + move(meta._parsed); + } + this.parse(start, count); + if (resetNewElements) { + this.updateElements(data, start, count, 'reset'); + } + } + updateElements(element, start, count, mode) {} + _removeElements(start, count) { + const meta = this._cachedMeta; + if (this._parsing) { + const removed = meta._parsed.splice(start, count); + if (meta._stacked) { + clearStacks(meta, removed); + } + } + meta.data.splice(start, count); + } + _sync(args) { + if (this._parsing) { + this._syncList.push(args); + } else { + const [method, arg1, arg2] = args; + this[method](arg1, arg2); + } + this.chart._dataChanges.push([ + this.index, + ...args + ]); + } + _onDataPush() { + const count = arguments.length; + this._sync([ + '_insertElements', + this.getDataset().data.length - count, + count + ]); + } + _onDataPop() { + this._sync([ + '_removeElements', + this._cachedMeta.data.length - 1, + 1 + ]); + } + _onDataShift() { + this._sync([ + '_removeElements', + 0, + 1 + ]); + } + _onDataSplice(start, count) { + if (count) { + this._sync([ + '_removeElements', + start, + count + ]); + } + const newCount = arguments.length - 2; + if (newCount) { + this._sync([ + '_insertElements', + start, + newCount + ]); + } + } + _onDataUnshift() { + this._sync([ + '_insertElements', + 0, + arguments.length + ]); + } +} + +function getAllScaleValues(scale, type) { + if (!scale._cache.$bar) { + const visibleMetas = scale.getMatchingVisibleMetas(type); + let values = []; + for(let i = 0, ilen = visibleMetas.length; i < ilen; i++){ + values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale)); + } + scale._cache.$bar = _arrayUnique(values.sort((a, b)=>a - b)); + } + return scale._cache.$bar; +} + function computeMinSampleSize(meta) { + const scale = meta.iScale; + const values = getAllScaleValues(scale, meta.type); + let min = scale._length; + let i, ilen, curr, prev; + const updateMinAndPrev = ()=>{ + if (curr === 32767 || curr === -32768) { + return; + } + if (defined(prev)) { + min = Math.min(min, Math.abs(curr - prev) || min); + } + prev = curr; + }; + for(i = 0, ilen = values.length; i < ilen; ++i){ + curr = scale.getPixelForValue(values[i]); + updateMinAndPrev(); + } + prev = undefined; + for(i = 0, ilen = scale.ticks.length; i < ilen; ++i){ + curr = scale.getPixelForTick(i); + updateMinAndPrev(); + } + return min; +} + function computeFitCategoryTraits(index, ruler, options, stackCount) { + const thickness = options.barThickness; + let size, ratio; + if (isNullOrUndef(thickness)) { + size = ruler.min * options.categoryPercentage; + ratio = options.barPercentage; + } else { + size = thickness * stackCount; + ratio = 1; + } + return { + chunk: size / stackCount, + ratio, + start: ruler.pixels[index] - size / 2 + }; +} + function computeFlexCategoryTraits(index, ruler, options, stackCount) { + const pixels = ruler.pixels; + const curr = pixels[index]; + let prev = index > 0 ? pixels[index - 1] : null; + let next = index < pixels.length - 1 ? pixels[index + 1] : null; + const percent = options.categoryPercentage; + if (prev === null) { + prev = curr - (next === null ? ruler.end - ruler.start : next - curr); + } + if (next === null) { + next = curr + curr - prev; + } + const start = curr - (curr - Math.min(prev, next)) / 2 * percent; + const size = Math.abs(next - prev) / 2 * percent; + return { + chunk: size / stackCount, + ratio: options.barPercentage, + start + }; +} +function parseFloatBar(entry, item, vScale, i) { + const startValue = vScale.parse(entry[0], i); + const endValue = vScale.parse(entry[1], i); + const min = Math.min(startValue, endValue); + const max = Math.max(startValue, endValue); + let barStart = min; + let barEnd = max; + if (Math.abs(min) > Math.abs(max)) { + barStart = max; + barEnd = min; + } + item[vScale.axis] = barEnd; + item._custom = { + barStart, + barEnd, + start: startValue, + end: endValue, + min, + max + }; +} +function parseValue(entry, item, vScale, i) { + if (isArray(entry)) { + parseFloatBar(entry, item, vScale, i); + } else { + item[vScale.axis] = vScale.parse(entry, i); + } + return item; +} +function parseArrayOrPrimitive(meta, data, start, count) { + const iScale = meta.iScale; + const vScale = meta.vScale; + const labels = iScale.getLabels(); + const singleScale = iScale === vScale; + const parsed = []; + let i, ilen, item, entry; + for(i = start, ilen = start + count; i < ilen; ++i){ + entry = data[i]; + item = {}; + item[iScale.axis] = singleScale || iScale.parse(labels[i], i); + parsed.push(parseValue(entry, item, vScale, i)); + } + return parsed; +} +function isFloatBar(custom) { + return custom && custom.barStart !== undefined && custom.barEnd !== undefined; +} +function barSign(size, vScale, actualBase) { + if (size !== 0) { + return sign(size); + } + return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1); +} +function borderProps(properties) { + let reverse, start, end, top, bottom; + if (properties.horizontal) { + reverse = properties.base > properties.x; + start = 'left'; + end = 'right'; + } else { + reverse = properties.base < properties.y; + start = 'bottom'; + end = 'top'; + } + if (reverse) { + top = 'end'; + bottom = 'start'; + } else { + top = 'start'; + bottom = 'end'; + } + return { + start, + end, + reverse, + top, + bottom + }; +} +function setBorderSkipped(properties, options, stack, index) { + let edge = options.borderSkipped; + const res = {}; + if (!edge) { + properties.borderSkipped = res; + return; + } + if (edge === true) { + properties.borderSkipped = { + top: true, + right: true, + bottom: true, + left: true + }; + return; + } + const { start , end , reverse , top , bottom } = borderProps(properties); + if (edge === 'middle' && stack) { + properties.enableBorderRadius = true; + if ((stack._top || 0) === index) { + edge = top; + } else if ((stack._bottom || 0) === index) { + edge = bottom; + } else { + res[parseEdge(bottom, start, end, reverse)] = true; + edge = top; + } + } + res[parseEdge(edge, start, end, reverse)] = true; + properties.borderSkipped = res; +} +function parseEdge(edge, a, b, reverse) { + if (reverse) { + edge = swap(edge, a, b); + edge = startEnd(edge, b, a); + } else { + edge = startEnd(edge, a, b); + } + return edge; +} +function swap(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} +function startEnd(v, start, end) { + return v === 'start' ? start : v === 'end' ? end : v; +} +function setInflateAmount(properties, { inflateAmount }, ratio) { + properties.inflateAmount = inflateAmount === 'auto' ? ratio === 1 ? 0.33 : 0 : inflateAmount; +} +class BarController extends DatasetController { + static id = 'bar'; + static defaults = { + datasetElementType: false, + dataElementType: 'bar', + categoryPercentage: 0.8, + barPercentage: 0.9, + grouped: true, + animations: { + numbers: { + type: 'number', + properties: [ + 'x', + 'y', + 'base', + 'width', + 'height' + ] + } + } + }; + static overrides = { + scales: { + _index_: { + type: 'category', + offset: true, + grid: { + offset: true + } + }, + _value_: { + type: 'linear', + beginAtZero: true + } + } + }; + parsePrimitiveData(meta, data, start, count) { + return parseArrayOrPrimitive(meta, data, start, count); + } + parseArrayData(meta, data, start, count) { + return parseArrayOrPrimitive(meta, data, start, count); + } + parseObjectData(meta, data, start, count) { + const { iScale , vScale } = meta; + const { xAxisKey ='x' , yAxisKey ='y' } = this._parsing; + const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey; + const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey; + const parsed = []; + let i, ilen, item, obj; + for(i = start, ilen = start + count; i < ilen; ++i){ + obj = data[i]; + item = {}; + item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i); + parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i)); + } + return parsed; + } + updateRangeFromParsed(range, scale, parsed, stack) { + super.updateRangeFromParsed(range, scale, parsed, stack); + const custom = parsed._custom; + if (custom && scale === this._cachedMeta.vScale) { + range.min = Math.min(range.min, custom.min); + range.max = Math.max(range.max, custom.max); + } + } + getMaxOverflow() { + return 0; + } + getLabelAndValue(index) { + const meta = this._cachedMeta; + const { iScale , vScale } = meta; + const parsed = this.getParsed(index); + const custom = parsed._custom; + const value = isFloatBar(custom) ? '[' + custom.start + ', ' + custom.end + ']' : '' + vScale.getLabelForValue(parsed[vScale.axis]); + return { + label: '' + iScale.getLabelForValue(parsed[iScale.axis]), + value + }; + } + initialize() { + this.enableOptionSharing = true; + super.initialize(); + const meta = this._cachedMeta; + meta.stack = this.getDataset().stack; + } + update(mode) { + const meta = this._cachedMeta; + this.updateElements(meta.data, 0, meta.data.length, mode); + } + updateElements(bars, start, count, mode) { + const reset = mode === 'reset'; + const { index , _cachedMeta: { vScale } } = this; + const base = vScale.getBasePixel(); + const horizontal = vScale.isHorizontal(); + const ruler = this._getRuler(); + const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode); + for(let i = start; i < start + count; i++){ + const parsed = this.getParsed(i); + const vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? { + base, + head: base + } : this._calculateBarValuePixels(i); + const ipixels = this._calculateBarIndexPixels(i, ruler); + const stack = (parsed._stacks || {})[vScale.axis]; + const properties = { + horizontal, + base: vpixels.base, + enableBorderRadius: !stack || isFloatBar(parsed._custom) || index === stack._top || index === stack._bottom, + x: horizontal ? vpixels.head : ipixels.center, + y: horizontal ? ipixels.center : vpixels.head, + height: horizontal ? ipixels.size : Math.abs(vpixels.size), + width: horizontal ? Math.abs(vpixels.size) : ipixels.size + }; + if (includeOptions) { + properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode); + } + const options = properties.options || bars[i].options; + setBorderSkipped(properties, options, stack, index); + setInflateAmount(properties, options, ruler.ratio); + this.updateElement(bars[i], i, properties, mode); + } + } + _getStacks(last, dataIndex) { + const { iScale } = this._cachedMeta; + const metasets = iScale.getMatchingVisibleMetas(this._type).filter((meta)=>meta.controller.options.grouped); + const stacked = iScale.options.stacked; + const stacks = []; + const skipNull = (meta)=>{ + const parsed = meta.controller.getParsed(dataIndex); + const val = parsed && parsed[meta.vScale.axis]; + if (isNullOrUndef(val) || isNaN(val)) { + return true; + } + }; + for (const meta of metasets){ + if (dataIndex !== undefined && skipNull(meta)) { + continue; + } + if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) { + stacks.push(meta.stack); + } + if (meta.index === last) { + break; + } + } + if (!stacks.length) { + stacks.push(undefined); + } + return stacks; + } + _getStackCount(index) { + return this._getStacks(undefined, index).length; + } + _getStackIndex(datasetIndex, name, dataIndex) { + const stacks = this._getStacks(datasetIndex, dataIndex); + const index = name !== undefined ? stacks.indexOf(name) : -1; + return index === -1 ? stacks.length - 1 : index; + } + _getRuler() { + const opts = this.options; + const meta = this._cachedMeta; + const iScale = meta.iScale; + const pixels = []; + let i, ilen; + for(i = 0, ilen = meta.data.length; i < ilen; ++i){ + pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i)); + } + const barThickness = opts.barThickness; + const min = barThickness || computeMinSampleSize(meta); + return { + min, + pixels, + start: iScale._startPixel, + end: iScale._endPixel, + stackCount: this._getStackCount(), + scale: iScale, + grouped: opts.grouped, + ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage + }; + } + _calculateBarValuePixels(index) { + const { _cachedMeta: { vScale , _stacked , index: datasetIndex } , options: { base: baseValue , minBarLength } } = this; + const actualBase = baseValue || 0; + const parsed = this.getParsed(index); + const custom = parsed._custom; + const floating = isFloatBar(custom); + let value = parsed[vScale.axis]; + let start = 0; + let length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value; + let head, size; + if (length !== value) { + start = length - value; + length = value; + } + if (floating) { + value = custom.barStart; + length = custom.barEnd - custom.barStart; + if (value !== 0 && sign(value) !== sign(custom.barEnd)) { + start = 0; + } + start += value; + } + const startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start; + let base = vScale.getPixelForValue(startValue); + if (this.chart.getDataVisibility(index)) { + head = vScale.getPixelForValue(start + length); + } else { + head = base; + } + size = head - base; + if (Math.abs(size) < minBarLength) { + size = barSign(size, vScale, actualBase) * minBarLength; + if (value === actualBase) { + base -= size / 2; + } + const startPixel = vScale.getPixelForDecimal(0); + const endPixel = vScale.getPixelForDecimal(1); + const min = Math.min(startPixel, endPixel); + const max = Math.max(startPixel, endPixel); + base = Math.max(Math.min(base, max), min); + head = base + size; + if (_stacked && !floating) { + parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base); + } + } + if (base === vScale.getPixelForValue(actualBase)) { + const halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2; + base += halfGrid; + size -= halfGrid; + } + return { + size, + base, + head, + center: head + size / 2 + }; + } + _calculateBarIndexPixels(index, ruler) { + const scale = ruler.scale; + const options = this.options; + const skipNull = options.skipNull; + const maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity); + let center, size; + if (ruler.grouped) { + const stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount; + const range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options, stackCount) : computeFitCategoryTraits(index, ruler, options, stackCount); + const stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined); + center = range.start + range.chunk * stackIndex + range.chunk / 2; + size = Math.min(maxBarThickness, range.chunk * range.ratio); + } else { + center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index); + size = Math.min(maxBarThickness, ruler.min * ruler.ratio); + } + return { + base: center - size / 2, + head: center + size / 2, + center, + size + }; + } + draw() { + const meta = this._cachedMeta; + const vScale = meta.vScale; + const rects = meta.data; + const ilen = rects.length; + let i = 0; + for(; i < ilen; ++i){ + if (this.getParsed(i)[vScale.axis] !== null) { + rects[i].draw(this._ctx); + } + } + } +} + +class BubbleController extends DatasetController { + static id = 'bubble'; + static defaults = { + datasetElementType: false, + dataElementType: 'point', + animations: { + numbers: { + type: 'number', + properties: [ + 'x', + 'y', + 'borderWidth', + 'radius' + ] + } + } + }; + static overrides = { + scales: { + x: { + type: 'linear' + }, + y: { + type: 'linear' + } + } + }; + initialize() { + this.enableOptionSharing = true; + super.initialize(); + } + parsePrimitiveData(meta, data, start, count) { + const parsed = super.parsePrimitiveData(meta, data, start, count); + for(let i = 0; i < parsed.length; i++){ + parsed[i]._custom = this.resolveDataElementOptions(i + start).radius; + } + return parsed; + } + parseArrayData(meta, data, start, count) { + const parsed = super.parseArrayData(meta, data, start, count); + for(let i = 0; i < parsed.length; i++){ + const item = data[start + i]; + parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius); + } + return parsed; + } + parseObjectData(meta, data, start, count) { + const parsed = super.parseObjectData(meta, data, start, count); + for(let i = 0; i < parsed.length; i++){ + const item = data[start + i]; + parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius); + } + return parsed; + } + getMaxOverflow() { + const data = this._cachedMeta.data; + let max = 0; + for(let i = data.length - 1; i >= 0; --i){ + max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2); + } + return max > 0 && max; + } + getLabelAndValue(index) { + const meta = this._cachedMeta; + const labels = this.chart.data.labels || []; + const { xScale , yScale } = meta; + const parsed = this.getParsed(index); + const x = xScale.getLabelForValue(parsed.x); + const y = yScale.getLabelForValue(parsed.y); + const r = parsed._custom; + return { + label: labels[index] || '', + value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')' + }; + } + update(mode) { + const points = this._cachedMeta.data; + this.updateElements(points, 0, points.length, mode); + } + updateElements(points, start, count, mode) { + const reset = mode === 'reset'; + const { iScale , vScale } = this._cachedMeta; + const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode); + const iAxis = iScale.axis; + const vAxis = vScale.axis; + for(let i = start; i < start + count; i++){ + const point = points[i]; + const parsed = !reset && this.getParsed(i); + const properties = {}; + const iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]); + const vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]); + properties.skip = isNaN(iPixel) || isNaN(vPixel); + if (includeOptions) { + properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode); + if (reset) { + properties.options.radius = 0; + } + } + this.updateElement(point, i, properties, mode); + } + } + resolveDataElementOptions(index, mode) { + const parsed = this.getParsed(index); + let values = super.resolveDataElementOptions(index, mode); + if (values.$shared) { + values = Object.assign({}, values, { + $shared: false + }); + } + const radius = values.radius; + if (mode !== 'active') { + values.radius = 0; + } + values.radius += valueOrDefault(parsed && parsed._custom, radius); + return values; + } +} + +function getRatioAndOffset(rotation, circumference, cutout) { + let ratioX = 1; + let ratioY = 1; + let offsetX = 0; + let offsetY = 0; + if (circumference < TAU) { + const startAngle = rotation; + const endAngle = startAngle + circumference; + const startX = Math.cos(startAngle); + const startY = Math.sin(startAngle); + const endX = Math.cos(endAngle); + const endY = Math.sin(endAngle); + const calcMax = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout); + const calcMin = (angle, a, b)=>_angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout); + const maxX = calcMax(0, startX, endX); + const maxY = calcMax(HALF_PI, startY, endY); + const minX = calcMin(PI, startX, endX); + const minY = calcMin(PI + HALF_PI, startY, endY); + ratioX = (maxX - minX) / 2; + ratioY = (maxY - minY) / 2; + offsetX = -(maxX + minX) / 2; + offsetY = -(maxY + minY) / 2; + } + return { + ratioX, + ratioY, + offsetX, + offsetY + }; +} +class DoughnutController extends DatasetController { + static id = 'doughnut'; + static defaults = { + datasetElementType: false, + dataElementType: 'arc', + animation: { + animateRotate: true, + animateScale: false + }, + animations: { + numbers: { + type: 'number', + properties: [ + 'circumference', + 'endAngle', + 'innerRadius', + 'outerRadius', + 'startAngle', + 'x', + 'y', + 'offset', + 'borderWidth', + 'spacing' + ] + } + }, + cutout: '50%', + rotation: 0, + circumference: 360, + radius: '100%', + spacing: 0, + indexAxis: 'r' + }; + static descriptors = { + _scriptable: (name)=>name !== 'spacing', + _indexable: (name)=>name !== 'spacing' && !name.startsWith('borderDash') && !name.startsWith('hoverBorderDash') + }; + static overrides = { + aspectRatio: 1, + plugins: { + legend: { + labels: { + generateLabels (chart) { + const data = chart.data; + if (data.labels.length && data.datasets.length) { + const { labels: { pointStyle , color } } = chart.legend.options; + return data.labels.map((label, i)=>{ + const meta = chart.getDatasetMeta(0); + const style = meta.controller.getStyle(i); + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + fontColor: color, + lineWidth: style.borderWidth, + pointStyle: pointStyle, + hidden: !chart.getDataVisibility(i), + index: i + }; + }); + } + return []; + } + }, + onClick (e, legendItem, legend) { + legend.chart.toggleDataVisibility(legendItem.index); + legend.chart.update(); + } + } + } + }; + constructor(chart, datasetIndex){ + super(chart, datasetIndex); + this.enableOptionSharing = true; + this.innerRadius = undefined; + this.outerRadius = undefined; + this.offsetX = undefined; + this.offsetY = undefined; + } + linkScales() {} + parse(start, count) { + const data = this.getDataset().data; + const meta = this._cachedMeta; + if (this._parsing === false) { + meta._parsed = data; + } else { + let getter = (i)=>+data[i]; + if (isObject(data[start])) { + const { key ='value' } = this._parsing; + getter = (i)=>+resolveObjectKey(data[i], key); + } + let i, ilen; + for(i = start, ilen = start + count; i < ilen; ++i){ + meta._parsed[i] = getter(i); + } + } + } + _getRotation() { + return toRadians(this.options.rotation - 90); + } + _getCircumference() { + return toRadians(this.options.circumference); + } + _getRotationExtents() { + let min = TAU; + let max = -TAU; + for(let i = 0; i < this.chart.data.datasets.length; ++i){ + if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) { + const controller = this.chart.getDatasetMeta(i).controller; + const rotation = controller._getRotation(); + const circumference = controller._getCircumference(); + min = Math.min(min, rotation); + max = Math.max(max, rotation + circumference); + } + } + return { + rotation: min, + circumference: max - min + }; + } + update(mode) { + const chart = this.chart; + const { chartArea } = chart; + const meta = this._cachedMeta; + const arcs = meta.data; + const spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing; + const maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0); + const cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1); + const chartWeight = this._getRingWeight(this.index); + const { circumference , rotation } = this._getRotationExtents(); + const { ratioX , ratioY , offsetX , offsetY } = getRatioAndOffset(rotation, circumference, cutout); + const maxWidth = (chartArea.width - spacing) / ratioX; + const maxHeight = (chartArea.height - spacing) / ratioY; + const maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); + const outerRadius = toDimension(this.options.radius, maxRadius); + const innerRadius = Math.max(outerRadius * cutout, 0); + const radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal(); + this.offsetX = offsetX * outerRadius; + this.offsetY = offsetY * outerRadius; + meta.total = this.calculateTotal(); + this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index); + this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0); + this.updateElements(arcs, 0, arcs.length, mode); + } + _circumference(i, reset) { + const opts = this.options; + const meta = this._cachedMeta; + const circumference = this._getCircumference(); + if (reset && opts.animation.animateRotate || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) { + return 0; + } + return this.calculateCircumference(meta._parsed[i] * circumference / TAU); + } + updateElements(arcs, start, count, mode) { + const reset = mode === 'reset'; + const chart = this.chart; + const chartArea = chart.chartArea; + const opts = chart.options; + const animationOpts = opts.animation; + const centerX = (chartArea.left + chartArea.right) / 2; + const centerY = (chartArea.top + chartArea.bottom) / 2; + const animateScale = reset && animationOpts.animateScale; + const innerRadius = animateScale ? 0 : this.innerRadius; + const outerRadius = animateScale ? 0 : this.outerRadius; + const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode); + let startAngle = this._getRotation(); + let i; + for(i = 0; i < start; ++i){ + startAngle += this._circumference(i, reset); + } + for(i = start; i < start + count; ++i){ + const circumference = this._circumference(i, reset); + const arc = arcs[i]; + const properties = { + x: centerX + this.offsetX, + y: centerY + this.offsetY, + startAngle, + endAngle: startAngle + circumference, + circumference, + outerRadius, + innerRadius + }; + if (includeOptions) { + properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode); + } + startAngle += circumference; + this.updateElement(arc, i, properties, mode); + } + } + calculateTotal() { + const meta = this._cachedMeta; + const metaData = meta.data; + let total = 0; + let i; + for(i = 0; i < metaData.length; i++){ + const value = meta._parsed[i]; + if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) { + total += Math.abs(value); + } + } + return total; + } + calculateCircumference(value) { + const total = this._cachedMeta.total; + if (total > 0 && !isNaN(value)) { + return TAU * (Math.abs(value) / total); + } + return 0; + } + getLabelAndValue(index) { + const meta = this._cachedMeta; + const chart = this.chart; + const labels = chart.data.labels || []; + const value = formatNumber(meta._parsed[index], chart.options.locale); + return { + label: labels[index] || '', + value + }; + } + getMaxBorderWidth(arcs) { + let max = 0; + const chart = this.chart; + let i, ilen, meta, controller, options; + if (!arcs) { + for(i = 0, ilen = chart.data.datasets.length; i < ilen; ++i){ + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + arcs = meta.data; + controller = meta.controller; + break; + } + } + } + if (!arcs) { + return 0; + } + for(i = 0, ilen = arcs.length; i < ilen; ++i){ + options = controller.resolveDataElementOptions(i); + if (options.borderAlign !== 'inner') { + max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0); + } + } + return max; + } + getMaxOffset(arcs) { + let max = 0; + for(let i = 0, ilen = arcs.length; i < ilen; ++i){ + const options = this.resolveDataElementOptions(i); + max = Math.max(max, options.offset || 0, options.hoverOffset || 0); + } + return max; + } + _getRingWeightOffset(datasetIndex) { + let ringWeightOffset = 0; + for(let i = 0; i < datasetIndex; ++i){ + if (this.chart.isDatasetVisible(i)) { + ringWeightOffset += this._getRingWeight(i); + } + } + return ringWeightOffset; + } + _getRingWeight(datasetIndex) { + return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0); + } + _getVisibleDatasetWeightTotal() { + return this._getRingWeightOffset(this.chart.data.datasets.length) || 1; + } +} + +class LineController extends DatasetController { + static id = 'line'; + static defaults = { + datasetElementType: 'line', + dataElementType: 'point', + showLine: true, + spanGaps: false + }; + static overrides = { + scales: { + _index_: { + type: 'category' + }, + _value_: { + type: 'linear' + } + } + }; + initialize() { + this.enableOptionSharing = true; + this.supportsDecimation = true; + super.initialize(); + } + update(mode) { + const meta = this._cachedMeta; + const { dataset: line , data: points = [] , _dataset } = meta; + const animationsDisabled = this.chart._animationsDisabled; + let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled); + this._drawStart = start; + this._drawCount = count; + if (_scaleRangesChanged(meta)) { + start = 0; + count = points.length; + } + line._chart = this.chart; + line._datasetIndex = this.index; + line._decimated = !!_dataset._decimated; + line.points = points; + const options = this.resolveDatasetElementOptions(mode); + if (!this.options.showLine) { + options.borderWidth = 0; + } + options.segment = this.options.segment; + this.updateElement(line, undefined, { + animated: !animationsDisabled, + options + }, mode); + this.updateElements(points, start, count, mode); + } + updateElements(points, start, count, mode) { + const reset = mode === 'reset'; + const { iScale , vScale , _stacked , _dataset } = this._cachedMeta; + const { sharedOptions , includeOptions } = this._getSharedOptions(start, mode); + const iAxis = iScale.axis; + const vAxis = vScale.axis; + const { spanGaps , segment } = this.options; + const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY; + const directUpdate = this.chart._animationsDisabled || reset || mode === 'none'; + const end = start + count; + const pointsCount = points.length; + let prevParsed = start > 0 && this.getParsed(start - 1); + for(let i = 0; i < pointsCount; ++i){ + const point = points[i]; + const properties = directUpdate ? point : {}; + if (i < start || i >= end) { + properties.skip = true; + continue; + } + const parsed = this.getParsed(i); + const nullData = isNullOrUndef(parsed[vAxis]); + const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i); + const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i); + properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData; + properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength; + if (segment) { + properties.parsed = parsed; + properties.raw = _dataset.data[i]; + } + if (includeOptions) { + properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode); + } + if (!directUpdate) { + this.updateElement(point, i, properties, mode); + } + prevParsed = parsed; + } + } + getMaxOverflow() { + const meta = this._cachedMeta; + const dataset = meta.dataset; + const border = dataset.options && dataset.options.borderWidth || 0; + const data = meta.data || []; + if (!data.length) { + return border; + } + const firstPoint = data[0].size(this.resolveDataElementOptions(0)); + const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1)); + return Math.max(border, firstPoint, lastPoint) / 2; + } + draw() { + const meta = this._cachedMeta; + meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis); + super.draw(); + } +} + +class PolarAreaController extends DatasetController { + static id = 'polarArea'; + static defaults = { + dataElementType: 'arc', + animation: { + animateRotate: true, + animateScale: true + }, + animations: { + numbers: { + type: 'number', + properties: [ + 'x', + 'y', + 'startAngle', + 'endAngle', + 'innerRadius', + 'outerRadius' + ] + } + }, + indexAxis: 'r', + startAngle: 0 + }; + static overrides = { + aspectRatio: 1, + plugins: { + legend: { + labels: { + generateLabels (chart) { + const data = chart.data; + if (data.labels.length && data.datasets.length) { + const { labels: { pointStyle , color } } = chart.legend.options; + return data.labels.map((label, i)=>{ + const meta = chart.getDatasetMeta(0); + const style = meta.controller.getStyle(i); + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + fontColor: color, + lineWidth: style.borderWidth, + pointStyle: pointStyle, + hidden: !chart.getDataVisibility(i), + index: i + }; + }); + } + return []; + } + }, + onClick (e, legendItem, legend) { + legend.chart.toggleDataVisibility(legendItem.index); + legend.chart.update(); + } + } + }, + scales: { + r: { + type: 'radialLinear', + angleLines: { + display: false + }, + beginAtZero: true, + grid: { + circular: true + }, + pointLabels: { + display: false + }, + startAngle: 0 + } + } + }; + constructor(chart, datasetIndex){ + super(chart, datasetIndex); + this.innerRadius = undefined; + this.outerRadius = undefined; + } + getLabelAndValue(index) { + const meta = this._cachedMeta; + const chart = this.chart; + const labels = chart.data.labels || []; + const value = formatNumber(meta._parsed[index].r, chart.options.locale); + return { + label: labels[index] || '', + value + }; + } + parseObjectData(meta, data, start, count) { + return _parseObjectDataRadialScale.bind(this)(meta, data, start, count); + } + update(mode) { + const arcs = this._cachedMeta.data; + this._updateRadius(); + this.updateElements(arcs, 0, arcs.length, mode); + } + getMinMax() { + const meta = this._cachedMeta; + const range = { + min: Number.POSITIVE_INFINITY, + max: Number.NEGATIVE_INFINITY + }; + meta.data.forEach((element, index)=>{ + const parsed = this.getParsed(index).r; + if (!isNaN(parsed) && this.chart.getDataVisibility(index)) { + if (parsed < range.min) { + range.min = parsed; + } + if (parsed > range.max) { + range.max = parsed; + } + } + }); + return range; + } + _updateRadius() { + const chart = this.chart; + const chartArea = chart.chartArea; + const opts = chart.options; + const minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + const outerRadius = Math.max(minSize / 2, 0); + const innerRadius = Math.max(opts.cutoutPercentage ? outerRadius / 100 * opts.cutoutPercentage : 1, 0); + const radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount(); + this.outerRadius = outerRadius - radiusLength * this.index; + this.innerRadius = this.outerRadius - radiusLength; + } + updateElements(arcs, start, count, mode) { + const reset = mode === 'reset'; + const chart = this.chart; + const opts = chart.options; + const animationOpts = opts.animation; + const scale = this._cachedMeta.rScale; + const centerX = scale.xCenter; + const centerY = scale.yCenter; + const datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI; + let angle = datasetStartAngle; + let i; + const defaultAngle = 360 / this.countVisibleElements(); + for(i = 0; i < start; ++i){ + angle += this._computeAngle(i, mode, defaultAngle); + } + for(i = start; i < start + count; i++){ + const arc = arcs[i]; + let startAngle = angle; + let endAngle = angle + this._computeAngle(i, mode, defaultAngle); + let outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0; + angle = endAngle; + if (reset) { + if (animationOpts.animateScale) { + outerRadius = 0; + } + if (animationOpts.animateRotate) { + startAngle = endAngle = datasetStartAngle; + } + } + const properties = { + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius, + startAngle, + endAngle, + options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode) + }; + this.updateElement(arc, i, properties, mode); + } + } + countVisibleElements() { + const meta = this._cachedMeta; + let count = 0; + meta.data.forEach((element, index)=>{ + if (!isNaN(this.getParsed(index).r) && this.chart.getDataVisibility(index)) { + count++; + } + }); + return count; + } + _computeAngle(index, mode, defaultAngle) { + return this.chart.getDataVisibility(index) ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0; + } +} + +class PieController extends DoughnutController { + static id = 'pie'; + static defaults = { + cutout: 0, + rotation: 0, + circumference: 360, + radius: '100%' + }; +} + +class RadarController extends DatasetController { + static id = 'radar'; + static defaults = { + datasetElementType: 'line', + dataElementType: 'point', + indexAxis: 'r', + showLine: true, + elements: { + line: { + fill: 'start' + } + } + }; + static overrides = { + aspectRatio: 1, + scales: { + r: { + type: 'radialLinear' + } + } + }; + getLabelAndValue(index) { + const vScale = this._cachedMeta.vScale; + const parsed = this.getParsed(index); + return { + label: vScale.getLabels()[index], + value: '' + vScale.getLabelForValue(parsed[vScale.axis]) + }; + } + parseObjectData(meta, data, start, count) { + return _parseObjectDataRadialScale.bind(this)(meta, data, start, count); + } + update(mode) { + const meta = this._cachedMeta; + const line = meta.dataset; + const points = meta.data || []; + const labels = meta.iScale.getLabels(); + line.points = points; + if (mode !== 'resize') { + const options = this.resolveDatasetElementOptions(mode); + if (!this.options.showLine) { + options.borderWidth = 0; + } + const properties = { + _loop: true, + _fullLoop: labels.length === points.length, + options + }; + this.updateElement(line, undefined, properties, mode); + } + this.updateElements(points, 0, points.length, mode); + } + updateElements(points, start, count, mode) { + const scale = this._cachedMeta.rScale; + const reset = mode === 'reset'; + for(let i = start; i < start + count; i++){ + const point = points[i]; + const options = this.resolveDataElementOptions(i, point.active ? 'active' : mode); + const pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r); + const x = reset ? scale.xCenter : pointPosition.x; + const y = reset ? scale.yCenter : pointPosition.y; + const properties = { + x, + y, + angle: pointPosition.angle, + skip: isNaN(x) || isNaN(y), + options + }; + this.updateElement(point, i, properties, mode); + } + } +} + +class ScatterController extends DatasetController { + static id = 'scatter'; + static defaults = { + datasetElementType: false, + dataElementType: 'point', + showLine: false, + fill: false + }; + static overrides = { + interaction: { + mode: 'point' + }, + scales: { + x: { + type: 'linear' + }, + y: { + type: 'linear' + } + } + }; + getLabelAndValue(index) { + const meta = this._cachedMeta; + const labels = this.chart.data.labels || []; + const { xScale , yScale } = meta; + const parsed = this.getParsed(index); + const x = xScale.getLabelForValue(parsed.x); + const y = yScale.getLabelForValue(parsed.y); + return { + label: labels[index] || '', + value: '(' + x + ', ' + y + ')' + }; + } + update(mode) { + const meta = this._cachedMeta; + const { data: points = [] } = meta; + const animationsDisabled = this.chart._animationsDisabled; + let { start , count } = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled); + this._drawStart = start; + this._drawCount = count; + if (_scaleRangesChanged(meta)) { + start = 0; + count = points.length; + } + if (this.options.showLine) { + if (!this.datasetElementType) { + this.addElements(); + } + const { dataset: line , _dataset } = meta; + line._chart = this.chart; + line._datasetIndex = this.index; + line._decimated = !!_dataset._decimated; + line.points = points; + const options = this.resolveDatasetElementOptions(mode); + options.segment = this.options.segment; + this.updateElement(line, undefined, { + animated: !animationsDisabled, + options + }, mode); + } else if (this.datasetElementType) { + delete meta.dataset; + this.datasetElementType = false; + } + this.updateElements(points, start, count, mode); + } + addElements() { + const { showLine } = this.options; + if (!this.datasetElementType && showLine) { + this.datasetElementType = this.chart.registry.getElement('line'); + } + super.addElements(); + } + updateElements(points, start, count, mode) { + const reset = mode === 'reset'; + const { iScale , vScale , _stacked , _dataset } = this._cachedMeta; + const firstOpts = this.resolveDataElementOptions(start, mode); + const sharedOptions = this.getSharedOptions(firstOpts); + const includeOptions = this.includeOptions(mode, sharedOptions); + const iAxis = iScale.axis; + const vAxis = vScale.axis; + const { spanGaps , segment } = this.options; + const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY; + const directUpdate = this.chart._animationsDisabled || reset || mode === 'none'; + let prevParsed = start > 0 && this.getParsed(start - 1); + for(let i = start; i < start + count; ++i){ + const point = points[i]; + const parsed = this.getParsed(i); + const properties = directUpdate ? point : {}; + const nullData = isNullOrUndef(parsed[vAxis]); + const iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i); + const vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i); + properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData; + properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength; + if (segment) { + properties.parsed = parsed; + properties.raw = _dataset.data[i]; + } + if (includeOptions) { + properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode); + } + if (!directUpdate) { + this.updateElement(point, i, properties, mode); + } + prevParsed = parsed; + } + this.updateSharedOptions(sharedOptions, mode, firstOpts); + } + getMaxOverflow() { + const meta = this._cachedMeta; + const data = meta.data || []; + if (!this.options.showLine) { + let max = 0; + for(let i = data.length - 1; i >= 0; --i){ + max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2); + } + return max > 0 && max; + } + const dataset = meta.dataset; + const border = dataset.options && dataset.options.borderWidth || 0; + if (!data.length) { + return border; + } + const firstPoint = data[0].size(this.resolveDataElementOptions(0)); + const lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1)); + return Math.max(border, firstPoint, lastPoint) / 2; + } +} + +var controllers = /*#__PURE__*/Object.freeze({ +__proto__: null, +BarController: BarController, +BubbleController: BubbleController, +DoughnutController: DoughnutController, +LineController: LineController, +PieController: PieController, +PolarAreaController: PolarAreaController, +RadarController: RadarController, +ScatterController: ScatterController +}); + +/** + * @namespace Chart._adapters + * @since 2.8.0 + * @private + */ function abstract() { + throw new Error('This method is not implemented: Check that a complete date adapter is provided.'); +} +/** + * Date adapter (current used by the time scale) + * @namespace Chart._adapters._date + * @memberof Chart._adapters + * @private + */ class DateAdapterBase { + /** + * Override default date adapter methods. + * Accepts type parameter to define options type. + * @example + * Chart._adapters._date.override<{myAdapterOption: string}>({ + * init() { + * console.log(this.options.myAdapterOption); + * } + * }) + */ static override(members) { + Object.assign(DateAdapterBase.prototype, members); + } + options; + constructor(options){ + this.options = options || {}; + } + // eslint-disable-next-line @typescript-eslint/no-empty-function + init() {} + formats() { + return abstract(); + } + parse() { + return abstract(); + } + format() { + return abstract(); + } + add() { + return abstract(); + } + diff() { + return abstract(); + } + startOf() { + return abstract(); + } + endOf() { + return abstract(); + } +} +var adapters = { + _date: DateAdapterBase +}; + +function binarySearch(metaset, axis, value, intersect) { + const { controller , data , _sorted } = metaset; + const iScale = controller._cachedMeta.iScale; + if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) { + const lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey; + if (!intersect) { + return lookupMethod(data, axis, value); + } else if (controller._sharedOptions) { + const el = data[0]; + const range = typeof el.getRange === 'function' && el.getRange(axis); + if (range) { + const start = lookupMethod(data, axis, value - range); + const end = lookupMethod(data, axis, value + range); + return { + lo: start.lo, + hi: end.hi + }; + } + } + } + return { + lo: 0, + hi: data.length - 1 + }; +} + function evaluateInteractionItems(chart, axis, position, handler, intersect) { + const metasets = chart.getSortedVisibleDatasetMetas(); + const value = position[axis]; + for(let i = 0, ilen = metasets.length; i < ilen; ++i){ + const { index , data } = metasets[i]; + const { lo , hi } = binarySearch(metasets[i], axis, value, intersect); + for(let j = lo; j <= hi; ++j){ + const element = data[j]; + if (!element.skip) { + handler(element, index, j); + } + } + } +} + function getDistanceMetricForAxis(axis) { + const useX = axis.indexOf('x') !== -1; + const useY = axis.indexOf('y') !== -1; + return function(pt1, pt2) { + const deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + const deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; +} + function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) { + const items = []; + if (!includeInvisible && !chart.isPointInArea(position)) { + return items; + } + const evaluationFunc = function(element, datasetIndex, index) { + if (!includeInvisible && !_isPointInArea(element, chart.chartArea, 0)) { + return; + } + if (element.inRange(position.x, position.y, useFinalPosition)) { + items.push({ + element, + datasetIndex, + index + }); + } + }; + evaluateInteractionItems(chart, axis, position, evaluationFunc, true); + return items; +} + function getNearestRadialItems(chart, position, axis, useFinalPosition) { + let items = []; + function evaluationFunc(element, datasetIndex, index) { + const { startAngle , endAngle } = element.getProps([ + 'startAngle', + 'endAngle' + ], useFinalPosition); + const { angle } = getAngleFromPoint(element, { + x: position.x, + y: position.y + }); + if (_angleBetween(angle, startAngle, endAngle)) { + items.push({ + element, + datasetIndex, + index + }); + } + } + evaluateInteractionItems(chart, axis, position, evaluationFunc); + return items; +} + function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) { + let items = []; + const distanceMetric = getDistanceMetricForAxis(axis); + let minDistance = Number.POSITIVE_INFINITY; + function evaluationFunc(element, datasetIndex, index) { + const inRange = element.inRange(position.x, position.y, useFinalPosition); + if (intersect && !inRange) { + return; + } + const center = element.getCenterPoint(useFinalPosition); + const pointInArea = !!includeInvisible || chart.isPointInArea(center); + if (!pointInArea && !inRange) { + return; + } + const distance = distanceMetric(position, center); + if (distance < minDistance) { + items = [ + { + element, + datasetIndex, + index + } + ]; + minDistance = distance; + } else if (distance === minDistance) { + items.push({ + element, + datasetIndex, + index + }); + } + } + evaluateInteractionItems(chart, axis, position, evaluationFunc); + return items; +} + function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) { + if (!includeInvisible && !chart.isPointInArea(position)) { + return []; + } + return axis === 'r' && !intersect ? getNearestRadialItems(chart, position, axis, useFinalPosition) : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible); +} + function getAxisItems(chart, position, axis, intersect, useFinalPosition) { + const items = []; + const rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange'; + let intersectsItem = false; + evaluateInteractionItems(chart, axis, position, (element, datasetIndex, index)=>{ + if (element[rangeMethod](position[axis], useFinalPosition)) { + items.push({ + element, + datasetIndex, + index + }); + intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition); + } + }); + if (intersect && !intersectsItem) { + return []; + } + return items; +} + var Interaction = { + evaluateInteractionItems, + modes: { + index (chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + const axis = options.axis || 'x'; + const includeInvisible = options.includeInvisible || false; + const items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible); + const elements = []; + if (!items.length) { + return []; + } + chart.getSortedVisibleDatasetMetas().forEach((meta)=>{ + const index = items[0].index; + const element = meta.data[index]; + if (element && !element.skip) { + elements.push({ + element, + datasetIndex: meta.index, + index + }); + } + }); + return elements; + }, + dataset (chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + const axis = options.axis || 'xy'; + const includeInvisible = options.includeInvisible || false; + let items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible); + if (items.length > 0) { + const datasetIndex = items[0].datasetIndex; + const data = chart.getDatasetMeta(datasetIndex).data; + items = []; + for(let i = 0; i < data.length; ++i){ + items.push({ + element: data[i], + datasetIndex, + index: i + }); + } + } + return items; + }, + point (chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + const axis = options.axis || 'xy'; + const includeInvisible = options.includeInvisible || false; + return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible); + }, + nearest (chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + const axis = options.axis || 'xy'; + const includeInvisible = options.includeInvisible || false; + return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible); + }, + x (chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition); + }, + y (chart, e, options, useFinalPosition) { + const position = getRelativePosition(e, chart); + return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition); + } + } +}; + +const STATIC_POSITIONS = [ + 'left', + 'top', + 'right', + 'bottom' +]; +function filterByPosition(array, position) { + return array.filter((v)=>v.pos === position); +} +function filterDynamicPositionByAxis(array, axis) { + return array.filter((v)=>STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis); +} +function sortByWeight(array, reverse) { + return array.sort((a, b)=>{ + const v0 = reverse ? b : a; + const v1 = reverse ? a : b; + return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight; + }); +} +function wrapBoxes(boxes) { + const layoutBoxes = []; + let i, ilen, box, pos, stack, stackWeight; + for(i = 0, ilen = (boxes || []).length; i < ilen; ++i){ + box = boxes[i]; + ({ position: pos , options: { stack , stackWeight =1 } } = box); + layoutBoxes.push({ + index: i, + box, + pos, + horizontal: box.isHorizontal(), + weight: box.weight, + stack: stack && pos + stack, + stackWeight + }); + } + return layoutBoxes; +} +function buildStacks(layouts) { + const stacks = {}; + for (const wrap of layouts){ + const { stack , pos , stackWeight } = wrap; + if (!stack || !STATIC_POSITIONS.includes(pos)) { + continue; + } + const _stack = stacks[stack] || (stacks[stack] = { + count: 0, + placed: 0, + weight: 0, + size: 0 + }); + _stack.count++; + _stack.weight += stackWeight; + } + return stacks; +} + function setLayoutDims(layouts, params) { + const stacks = buildStacks(layouts); + const { vBoxMaxWidth , hBoxMaxHeight } = params; + let i, ilen, layout; + for(i = 0, ilen = layouts.length; i < ilen; ++i){ + layout = layouts[i]; + const { fullSize } = layout.box; + const stack = stacks[layout.stack]; + const factor = stack && layout.stackWeight / stack.weight; + if (layout.horizontal) { + layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth; + layout.height = hBoxMaxHeight; + } else { + layout.width = vBoxMaxWidth; + layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight; + } + } + return stacks; +} +function buildLayoutBoxes(boxes) { + const layoutBoxes = wrapBoxes(boxes); + const fullSize = sortByWeight(layoutBoxes.filter((wrap)=>wrap.box.fullSize), true); + const left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); + const right = sortByWeight(filterByPosition(layoutBoxes, 'right')); + const top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); + const bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + const centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x'); + const centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y'); + return { + fullSize, + leftAndTop: left.concat(top), + rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal), + chartArea: filterByPosition(layoutBoxes, 'chartArea'), + vertical: left.concat(right).concat(centerVertical), + horizontal: top.concat(bottom).concat(centerHorizontal) + }; +} +function getCombinedMax(maxPadding, chartArea, a, b) { + return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); +} +function updateMaxPadding(maxPadding, boxPadding) { + maxPadding.top = Math.max(maxPadding.top, boxPadding.top); + maxPadding.left = Math.max(maxPadding.left, boxPadding.left); + maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); + maxPadding.right = Math.max(maxPadding.right, boxPadding.right); +} +function updateDims(chartArea, params, layout, stacks) { + const { pos , box } = layout; + const maxPadding = chartArea.maxPadding; + if (!isObject(pos)) { + if (layout.size) { + chartArea[pos] -= layout.size; + } + const stack = stacks[layout.stack] || { + size: 0, + count: 1 + }; + stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width); + layout.size = stack.size / stack.count; + chartArea[pos] += layout.size; + } + if (box.getPadding) { + updateMaxPadding(maxPadding, box.getPadding()); + } + const newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right')); + const newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom')); + const widthChanged = newWidth !== chartArea.w; + const heightChanged = newHeight !== chartArea.h; + chartArea.w = newWidth; + chartArea.h = newHeight; + return layout.horizontal ? { + same: widthChanged, + other: heightChanged + } : { + same: heightChanged, + other: widthChanged + }; +} +function handleMaxPadding(chartArea) { + const maxPadding = chartArea.maxPadding; + function updatePos(pos) { + const change = Math.max(maxPadding[pos] - chartArea[pos], 0); + chartArea[pos] += change; + return change; + } + chartArea.y += updatePos('top'); + chartArea.x += updatePos('left'); + updatePos('right'); + updatePos('bottom'); +} +function getMargins(horizontal, chartArea) { + const maxPadding = chartArea.maxPadding; + function marginForPositions(positions) { + const margin = { + left: 0, + top: 0, + right: 0, + bottom: 0 + }; + positions.forEach((pos)=>{ + margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); + }); + return margin; + } + return horizontal ? marginForPositions([ + 'left', + 'right' + ]) : marginForPositions([ + 'top', + 'bottom' + ]); +} +function fitBoxes(boxes, chartArea, params, stacks) { + const refitBoxes = []; + let i, ilen, layout, box, refit, changed; + for(i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i){ + layout = boxes[i]; + box = layout.box; + box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea)); + const { same , other } = updateDims(chartArea, params, layout, stacks); + refit |= same && refitBoxes.length; + changed = changed || other; + if (!box.fullSize) { + refitBoxes.push(layout); + } + } + return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed; +} +function setBoxDims(box, left, top, width, height) { + box.top = top; + box.left = left; + box.right = left + width; + box.bottom = top + height; + box.width = width; + box.height = height; +} +function placeBoxes(boxes, chartArea, params, stacks) { + const userPadding = params.padding; + let { x , y } = chartArea; + for (const layout of boxes){ + const box = layout.box; + const stack = stacks[layout.stack] || { + count: 1, + placed: 0, + weight: 1 + }; + const weight = layout.stackWeight / stack.weight || 1; + if (layout.horizontal) { + const width = chartArea.w * weight; + const height = stack.size || box.height; + if (defined(stack.start)) { + y = stack.start; + } + if (box.fullSize) { + setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height); + } else { + setBoxDims(box, chartArea.left + stack.placed, y, width, height); + } + stack.start = y; + stack.placed += width; + y = box.bottom; + } else { + const height = chartArea.h * weight; + const width = stack.size || box.width; + if (defined(stack.start)) { + x = stack.start; + } + if (box.fullSize) { + setBoxDims(box, x, userPadding.top, width, params.outerHeight - userPadding.bottom - userPadding.top); + } else { + setBoxDims(box, x, chartArea.top + stack.placed, width, height); + } + stack.start = x; + stack.placed += height; + x = box.right; + } + } + chartArea.x = x; + chartArea.y = y; +} +var layouts = { + addBox (chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + item.fullSize = item.fullSize || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + item._layers = item._layers || function() { + return [ + { + z: 0, + draw (chartArea) { + item.draw(chartArea); + } + } + ]; + }; + chart.boxes.push(item); + }, + removeBox (chart, layoutItem) { + const index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + configure (chart, item, options) { + item.fullSize = options.fullSize; + item.position = options.position; + item.weight = options.weight; + }, + update (chart, width, height, minPadding) { + if (!chart) { + return; + } + const padding = toPadding(chart.options.layout.padding); + const availableWidth = Math.max(width - padding.width, 0); + const availableHeight = Math.max(height - padding.height, 0); + const boxes = buildLayoutBoxes(chart.boxes); + const verticalBoxes = boxes.vertical; + const horizontalBoxes = boxes.horizontal; + each(chart.boxes, (box)=>{ + if (typeof box.beforeLayout === 'function') { + box.beforeLayout(); + } + }); + const visibleVerticalBoxCount = verticalBoxes.reduce((total, wrap)=>wrap.box.options && wrap.box.options.display === false ? total : total + 1, 0) || 1; + const params = Object.freeze({ + outerWidth: width, + outerHeight: height, + padding, + availableWidth, + availableHeight, + vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount, + hBoxMaxHeight: availableHeight / 2 + }); + const maxPadding = Object.assign({}, padding); + updateMaxPadding(maxPadding, toPadding(minPadding)); + const chartArea = Object.assign({ + maxPadding, + w: availableWidth, + h: availableHeight, + x: padding.left, + y: padding.top + }, padding); + const stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + fitBoxes(boxes.fullSize, chartArea, params, stacks); + fitBoxes(verticalBoxes, chartArea, params, stacks); + if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) { + fitBoxes(verticalBoxes, chartArea, params, stacks); + } + handleMaxPadding(chartArea); + placeBoxes(boxes.leftAndTop, chartArea, params, stacks); + chartArea.x += chartArea.w; + chartArea.y += chartArea.h; + placeBoxes(boxes.rightAndBottom, chartArea, params, stacks); + chart.chartArea = { + left: chartArea.left, + top: chartArea.top, + right: chartArea.left + chartArea.w, + bottom: chartArea.top + chartArea.h, + height: chartArea.h, + width: chartArea.w + }; + each(boxes.chartArea, (layout)=>{ + const box = layout.box; + Object.assign(box, chart.chartArea); + box.update(chartArea.w, chartArea.h, { + left: 0, + top: 0, + right: 0, + bottom: 0 + }); + }); + } +}; + +class BasePlatform { + acquireContext(canvas, aspectRatio) {} + releaseContext(context) { + return false; + } + addEventListener(chart, type, listener) {} + removeEventListener(chart, type, listener) {} + getDevicePixelRatio() { + return 1; + } + getMaximumSize(element, width, height, aspectRatio) { + width = Math.max(0, width || element.width); + height = height || element.height; + return { + width, + height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height) + }; + } + isAttached(canvas) { + return true; + } + updateConfig(config) { + } +} + +class BasicPlatform extends BasePlatform { + acquireContext(item) { + return item && item.getContext && item.getContext('2d') || null; + } + updateConfig(config) { + config.options.animation = false; + } +} + +const EXPANDO_KEY = '$chartjs'; + const EVENT_TYPES = { + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; +const isNullOrEmpty = (value)=>value === null || value === ''; + function initCanvas(canvas, aspectRatio) { + const style = canvas.style; + const renderHeight = canvas.getAttribute('height'); + const renderWidth = canvas.getAttribute('width'); + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + style.display = style.display || 'block'; + style.boxSizing = style.boxSizing || 'border-box'; + if (isNullOrEmpty(renderWidth)) { + const displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + if (isNullOrEmpty(renderHeight)) { + if (canvas.style.height === '') { + canvas.height = canvas.width / (aspectRatio || 2); + } else { + const displayHeight = readUsedSize(canvas, 'height'); + if (displayHeight !== undefined) { + canvas.height = displayHeight; + } + } + } + return canvas; +} +const eventListenerOptions = supportsEventListenerOptions ? { + passive: true +} : false; +function addListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} +function removeListener(chart, type, listener) { + chart.canvas.removeEventListener(type, listener, eventListenerOptions); +} +function fromNativeEvent(event, chart) { + const type = EVENT_TYPES[event.type] || event.type; + const { x , y } = getRelativePosition(event, chart); + return { + type, + chart, + native: event, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null + }; +} +function nodeListContains(nodeList, canvas) { + for (const node of nodeList){ + if (node === canvas || node.contains(canvas)) { + return true; + } + } +} +function createAttachObserver(chart, type, listener) { + const canvas = chart.canvas; + const observer = new MutationObserver((entries)=>{ + let trigger = false; + for (const entry of entries){ + trigger = trigger || nodeListContains(entry.addedNodes, canvas); + trigger = trigger && !nodeListContains(entry.removedNodes, canvas); + } + if (trigger) { + listener(); + } + }); + observer.observe(document, { + childList: true, + subtree: true + }); + return observer; +} +function createDetachObserver(chart, type, listener) { + const canvas = chart.canvas; + const observer = new MutationObserver((entries)=>{ + let trigger = false; + for (const entry of entries){ + trigger = trigger || nodeListContains(entry.removedNodes, canvas); + trigger = trigger && !nodeListContains(entry.addedNodes, canvas); + } + if (trigger) { + listener(); + } + }); + observer.observe(document, { + childList: true, + subtree: true + }); + return observer; +} +const drpListeningCharts = new Map(); +let oldDevicePixelRatio = 0; +function onWindowResize() { + const dpr = window.devicePixelRatio; + if (dpr === oldDevicePixelRatio) { + return; + } + oldDevicePixelRatio = dpr; + drpListeningCharts.forEach((resize, chart)=>{ + if (chart.currentDevicePixelRatio !== dpr) { + resize(); + } + }); +} +function listenDevicePixelRatioChanges(chart, resize) { + if (!drpListeningCharts.size) { + window.addEventListener('resize', onWindowResize); + } + drpListeningCharts.set(chart, resize); +} +function unlistenDevicePixelRatioChanges(chart) { + drpListeningCharts.delete(chart); + if (!drpListeningCharts.size) { + window.removeEventListener('resize', onWindowResize); + } +} +function createResizeObserver(chart, type, listener) { + const canvas = chart.canvas; + const container = canvas && _getParentNode(canvas); + if (!container) { + return; + } + const resize = throttled((width, height)=>{ + const w = container.clientWidth; + listener(width, height); + if (w < container.clientWidth) { + listener(); + } + }, window); + const observer = new ResizeObserver((entries)=>{ + const entry = entries[0]; + const width = entry.contentRect.width; + const height = entry.contentRect.height; + if (width === 0 && height === 0) { + return; + } + resize(width, height); + }); + observer.observe(container); + listenDevicePixelRatioChanges(chart, resize); + return observer; +} +function releaseObserver(chart, type, observer) { + if (observer) { + observer.disconnect(); + } + if (type === 'resize') { + unlistenDevicePixelRatioChanges(chart); + } +} +function createProxyAndListen(chart, type, listener) { + const canvas = chart.canvas; + const proxy = throttled((event)=>{ + if (chart.ctx !== null) { + listener(fromNativeEvent(event, chart)); + } + }, chart); + addListener(canvas, type, proxy); + return proxy; +} + class DomPlatform extends BasePlatform { + acquireContext(canvas, aspectRatio) { + const context = canvas && canvas.getContext && canvas.getContext('2d'); + if (context && context.canvas === canvas) { + initCanvas(canvas, aspectRatio); + return context; + } + return null; + } + releaseContext(context) { + const canvas = context.canvas; + if (!canvas[EXPANDO_KEY]) { + return false; + } + const initial = canvas[EXPANDO_KEY].initial; + [ + 'height', + 'width' + ].forEach((prop)=>{ + const value = initial[prop]; + if (isNullOrUndef(value)) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + const style = initial.style || {}; + Object.keys(style).forEach((key)=>{ + canvas.style[key] = style[key]; + }); + canvas.width = canvas.width; + delete canvas[EXPANDO_KEY]; + return true; + } + addEventListener(chart, type, listener) { + this.removeEventListener(chart, type); + const proxies = chart.$proxies || (chart.$proxies = {}); + const handlers = { + attach: createAttachObserver, + detach: createDetachObserver, + resize: createResizeObserver + }; + const handler = handlers[type] || createProxyAndListen; + proxies[type] = handler(chart, type, listener); + } + removeEventListener(chart, type) { + const proxies = chart.$proxies || (chart.$proxies = {}); + const proxy = proxies[type]; + if (!proxy) { + return; + } + const handlers = { + attach: releaseObserver, + detach: releaseObserver, + resize: releaseObserver + }; + const handler = handlers[type] || removeListener; + handler(chart, type, proxy); + proxies[type] = undefined; + } + getDevicePixelRatio() { + return window.devicePixelRatio; + } + getMaximumSize(canvas, width, height, aspectRatio) { + return getMaximumSize(canvas, width, height, aspectRatio); + } + isAttached(canvas) { + const container = _getParentNode(canvas); + return !!(container && container.isConnected); + } +} + +function _detectPlatform(canvas) { + if (!_isDomSupported() || typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) { + return BasicPlatform; + } + return DomPlatform; +} + +class Element { + static defaults = {}; + static defaultRoutes = undefined; + x; + y; + active = false; + options; + $animations; + tooltipPosition(useFinalPosition) { + const { x , y } = this.getProps([ + 'x', + 'y' + ], useFinalPosition); + return { + x, + y + }; + } + hasValue() { + return isNumber(this.x) && isNumber(this.y); + } + getProps(props, final) { + const anims = this.$animations; + if (!final || !anims) { + // let's not create an object, if not needed + return this; + } + const ret = {}; + props.forEach((prop)=>{ + ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : this[prop]; + }); + return ret; + } +} + +function autoSkip(scale, ticks) { + const tickOpts = scale.options.ticks; + const determinedMaxTicks = determineMaxTicks(scale); + const ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks); + const majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; + const numMajorIndices = majorIndices.length; + const first = majorIndices[0]; + const last = majorIndices[numMajorIndices - 1]; + const newTicks = []; + if (numMajorIndices > ticksLimit) { + skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit); + return newTicks; + } + const spacing = calculateSpacing(majorIndices, ticks, ticksLimit); + if (numMajorIndices > 0) { + let i, ilen; + const avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null; + skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); + for(i = 0, ilen = numMajorIndices - 1; i < ilen; i++){ + skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]); + } + skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); + return newTicks; + } + skip(ticks, newTicks, spacing); + return newTicks; +} +function determineMaxTicks(scale) { + const offset = scale.options.offset; + const tickLength = scale._tickSize(); + const maxScale = scale._length / tickLength + (offset ? 0 : 1); + const maxChart = scale._maxLength / tickLength; + return Math.floor(Math.min(maxScale, maxChart)); +} + function calculateSpacing(majorIndices, ticks, ticksLimit) { + const evenMajorSpacing = getEvenSpacing(majorIndices); + const spacing = ticks.length / ticksLimit; + if (!evenMajorSpacing) { + return Math.max(spacing, 1); + } + const factors = _factorize(evenMajorSpacing); + for(let i = 0, ilen = factors.length - 1; i < ilen; i++){ + const factor = factors[i]; + if (factor > spacing) { + return factor; + } + } + return Math.max(spacing, 1); +} + function getMajorIndices(ticks) { + const result = []; + let i, ilen; + for(i = 0, ilen = ticks.length; i < ilen; i++){ + if (ticks[i].major) { + result.push(i); + } + } + return result; +} + function skipMajors(ticks, newTicks, majorIndices, spacing) { + let count = 0; + let next = majorIndices[0]; + let i; + spacing = Math.ceil(spacing); + for(i = 0; i < ticks.length; i++){ + if (i === next) { + newTicks.push(ticks[i]); + count++; + next = majorIndices[count * spacing]; + } + } +} + function skip(ticks, newTicks, spacing, majorStart, majorEnd) { + const start = valueOrDefault(majorStart, 0); + const end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length); + let count = 0; + let length, i, next; + spacing = Math.ceil(spacing); + if (majorEnd) { + length = majorEnd - majorStart; + spacing = length / Math.floor(length / spacing); + } + next = start; + while(next < 0){ + count++; + next = Math.round(start + count * spacing); + } + for(i = Math.max(start, 0); i < end; i++){ + if (i === next) { + newTicks.push(ticks[i]); + count++; + next = Math.round(start + count * spacing); + } + } +} + function getEvenSpacing(arr) { + const len = arr.length; + let i, diff; + if (len < 2) { + return false; + } + for(diff = arr[0], i = 1; i < len; ++i){ + if (arr[i] - arr[i - 1] !== diff) { + return false; + } + } + return diff; +} + +const reverseAlign = (align)=>align === 'left' ? 'right' : align === 'right' ? 'left' : align; +const offsetFromEdge = (scale, edge, offset)=>edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset; +const getTicksLimit = (ticksLength, maxTicksLimit)=>Math.min(maxTicksLimit || ticksLength, ticksLength); + function sample(arr, numItems) { + const result = []; + const increment = arr.length / numItems; + const len = arr.length; + let i = 0; + for(; i < len; i += increment){ + result.push(arr[Math.floor(i)]); + } + return result; +} + function getPixelForGridLine(scale, index, offsetGridLines) { + const length = scale.ticks.length; + const validIndex = Math.min(index, length - 1); + const start = scale._startPixel; + const end = scale._endPixel; + const epsilon = 1e-6; + let lineValue = scale.getPixelForTick(validIndex); + let offset; + if (offsetGridLines) { + if (length === 1) { + offset = Math.max(lineValue - start, end - lineValue); + } else if (index === 0) { + offset = (scale.getPixelForTick(1) - lineValue) / 2; + } else { + offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; + } + lineValue += validIndex < index ? offset : -offset; + if (lineValue < start - epsilon || lineValue > end + epsilon) { + return; + } + } + return lineValue; +} + function garbageCollect(caches, length) { + each(caches, (cache)=>{ + const gc = cache.gc; + const gcLen = gc.length / 2; + let i; + if (gcLen > length) { + for(i = 0; i < gcLen; ++i){ + delete cache.data[gc[i]]; + } + gc.splice(0, gcLen); + } + }); +} + function getTickMarkLength(options) { + return options.drawTicks ? options.tickLength : 0; +} + function getTitleHeight(options, fallback) { + if (!options.display) { + return 0; + } + const font = toFont(options.font, fallback); + const padding = toPadding(options.padding); + const lines = isArray(options.text) ? options.text.length : 1; + return lines * font.lineHeight + padding.height; +} +function createScaleContext(parent, scale) { + return createContext(parent, { + scale, + type: 'scale' + }); +} +function createTickContext(parent, index, tick) { + return createContext(parent, { + tick, + index, + type: 'tick' + }); +} +function titleAlign(align, position, reverse) { + let ret = _toLeftRightCenter(align); + if (reverse && position !== 'right' || !reverse && position === 'right') { + ret = reverseAlign(ret); + } + return ret; +} +function titleArgs(scale, offset, position, align) { + const { top , left , bottom , right , chart } = scale; + const { chartArea , scales } = chart; + let rotation = 0; + let maxWidth, titleX, titleY; + const height = bottom - top; + const width = right - left; + if (scale.isHorizontal()) { + titleX = _alignStartEnd(align, left, right); + if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + titleY = scales[positionAxisID].getPixelForValue(value) + height - offset; + } else if (position === 'center') { + titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset; + } else { + titleY = offsetFromEdge(scale, position, offset); + } + maxWidth = right - left; + } else { + if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + titleX = scales[positionAxisID].getPixelForValue(value) - width + offset; + } else if (position === 'center') { + titleX = (chartArea.left + chartArea.right) / 2 - width + offset; + } else { + titleX = offsetFromEdge(scale, position, offset); + } + titleY = _alignStartEnd(align, bottom, top); + rotation = position === 'left' ? -HALF_PI : HALF_PI; + } + return { + titleX, + titleY, + maxWidth, + rotation + }; +} +class Scale extends Element { + constructor(cfg){ + super(); + this.id = cfg.id; + this.type = cfg.type; + this.options = undefined; + this.ctx = cfg.ctx; + this.chart = cfg.chart; + this.top = undefined; + this.bottom = undefined; + this.left = undefined; + this.right = undefined; + this.width = undefined; + this.height = undefined; + this._margins = { + left: 0, + right: 0, + top: 0, + bottom: 0 + }; + this.maxWidth = undefined; + this.maxHeight = undefined; + this.paddingTop = undefined; + this.paddingBottom = undefined; + this.paddingLeft = undefined; + this.paddingRight = undefined; + this.axis = undefined; + this.labelRotation = undefined; + this.min = undefined; + this.max = undefined; + this._range = undefined; + this.ticks = []; + this._gridLineItems = null; + this._labelItems = null; + this._labelSizes = null; + this._length = 0; + this._maxLength = 0; + this._longestTextCache = {}; + this._startPixel = undefined; + this._endPixel = undefined; + this._reversePixels = false; + this._userMax = undefined; + this._userMin = undefined; + this._suggestedMax = undefined; + this._suggestedMin = undefined; + this._ticksLength = 0; + this._borderValue = 0; + this._cache = {}; + this._dataLimitsCached = false; + this.$context = undefined; + } + init(options) { + this.options = options.setContext(this.getContext()); + this.axis = options.axis; + this._userMin = this.parse(options.min); + this._userMax = this.parse(options.max); + this._suggestedMin = this.parse(options.suggestedMin); + this._suggestedMax = this.parse(options.suggestedMax); + } + parse(raw, index) { + return raw; + } + getUserBounds() { + let { _userMin , _userMax , _suggestedMin , _suggestedMax } = this; + _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY); + _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY); + _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY); + _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY); + return { + min: finiteOrDefault(_userMin, _suggestedMin), + max: finiteOrDefault(_userMax, _suggestedMax), + minDefined: isNumberFinite(_userMin), + maxDefined: isNumberFinite(_userMax) + }; + } + getMinMax(canStack) { + let { min , max , minDefined , maxDefined } = this.getUserBounds(); + let range; + if (minDefined && maxDefined) { + return { + min, + max + }; + } + const metas = this.getMatchingVisibleMetas(); + for(let i = 0, ilen = metas.length; i < ilen; ++i){ + range = metas[i].controller.getMinMax(this, canStack); + if (!minDefined) { + min = Math.min(min, range.min); + } + if (!maxDefined) { + max = Math.max(max, range.max); + } + } + min = maxDefined && min > max ? max : min; + max = minDefined && min > max ? min : max; + return { + min: finiteOrDefault(min, finiteOrDefault(max, min)), + max: finiteOrDefault(max, finiteOrDefault(min, max)) + }; + } + getPadding() { + return { + left: this.paddingLeft || 0, + top: this.paddingTop || 0, + right: this.paddingRight || 0, + bottom: this.paddingBottom || 0 + }; + } + getTicks() { + return this.ticks; + } + getLabels() { + const data = this.chart.data; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; + } + getLabelItems(chartArea = this.chart.chartArea) { + const items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea)); + return items; + } + beforeLayout() { + this._cache = {}; + this._dataLimitsCached = false; + } + beforeUpdate() { + callback(this.options.beforeUpdate, [ + this + ]); + } + update(maxWidth, maxHeight, margins) { + const { beginAtZero , grace , ticks: tickOpts } = this.options; + const sampleSize = tickOpts.sampleSize; + this.beforeUpdate(); + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this._margins = margins = Object.assign({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); + this.ticks = null; + this._labelSizes = null; + this._gridLineItems = null; + this._labelItems = null; + this.beforeSetDimensions(); + this.setDimensions(); + this.afterSetDimensions(); + this._maxLength = this.isHorizontal() ? this.width + margins.left + margins.right : this.height + margins.top + margins.bottom; + if (!this._dataLimitsCached) { + this.beforeDataLimits(); + this.determineDataLimits(); + this.afterDataLimits(); + this._range = _addGrace(this, grace, beginAtZero); + this._dataLimitsCached = true; + } + this.beforeBuildTicks(); + this.ticks = this.buildTicks() || []; + this.afterBuildTicks(); + const samplingEnabled = sampleSize < this.ticks.length; + this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks); + this.configure(); + this.beforeCalculateLabelRotation(); + this.calculateLabelRotation(); + this.afterCalculateLabelRotation(); + if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) { + this.ticks = autoSkip(this, this.ticks); + this._labelSizes = null; + this.afterAutoSkip(); + } + if (samplingEnabled) { + this._convertTicksToLabels(this.ticks); + } + this.beforeFit(); + this.fit(); + this.afterFit(); + this.afterUpdate(); + } + configure() { + let reversePixels = this.options.reverse; + let startPixel, endPixel; + if (this.isHorizontal()) { + startPixel = this.left; + endPixel = this.right; + } else { + startPixel = this.top; + endPixel = this.bottom; + reversePixels = !reversePixels; + } + this._startPixel = startPixel; + this._endPixel = endPixel; + this._reversePixels = reversePixels; + this._length = endPixel - startPixel; + this._alignToPixels = this.options.alignToPixels; + } + afterUpdate() { + callback(this.options.afterUpdate, [ + this + ]); + } + beforeSetDimensions() { + callback(this.options.beforeSetDimensions, [ + this + ]); + } + setDimensions() { + if (this.isHorizontal()) { + this.width = this.maxWidth; + this.left = 0; + this.right = this.width; + } else { + this.height = this.maxHeight; + this.top = 0; + this.bottom = this.height; + } + this.paddingLeft = 0; + this.paddingTop = 0; + this.paddingRight = 0; + this.paddingBottom = 0; + } + afterSetDimensions() { + callback(this.options.afterSetDimensions, [ + this + ]); + } + _callHooks(name) { + this.chart.notifyPlugins(name, this.getContext()); + callback(this.options[name], [ + this + ]); + } + beforeDataLimits() { + this._callHooks('beforeDataLimits'); + } + determineDataLimits() {} + afterDataLimits() { + this._callHooks('afterDataLimits'); + } + beforeBuildTicks() { + this._callHooks('beforeBuildTicks'); + } + buildTicks() { + return []; + } + afterBuildTicks() { + this._callHooks('afterBuildTicks'); + } + beforeTickToLabelConversion() { + callback(this.options.beforeTickToLabelConversion, [ + this + ]); + } + generateTickLabels(ticks) { + const tickOpts = this.options.ticks; + let i, ilen, tick; + for(i = 0, ilen = ticks.length; i < ilen; i++){ + tick = ticks[i]; + tick.label = callback(tickOpts.callback, [ + tick.value, + i, + ticks + ], this); + } + } + afterTickToLabelConversion() { + callback(this.options.afterTickToLabelConversion, [ + this + ]); + } + beforeCalculateLabelRotation() { + callback(this.options.beforeCalculateLabelRotation, [ + this + ]); + } + calculateLabelRotation() { + const options = this.options; + const tickOpts = options.ticks; + const numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit); + const minRotation = tickOpts.minRotation || 0; + const maxRotation = tickOpts.maxRotation; + let labelRotation = minRotation; + let tickWidth, maxHeight, maxLabelDiagonal; + if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) { + this.labelRotation = minRotation; + return; + } + const labelSizes = this._getLabelSizes(); + const maxLabelWidth = labelSizes.widest.width; + const maxLabelHeight = labelSizes.highest.height; + const maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth); + tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1); + if (maxLabelWidth + 6 > tickWidth) { + tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); + maxHeight = this.maxHeight - getTickMarkLength(options.grid) - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font); + maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); + labelRotation = toDegrees(Math.min(Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1)))); + labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); + } + this.labelRotation = labelRotation; + } + afterCalculateLabelRotation() { + callback(this.options.afterCalculateLabelRotation, [ + this + ]); + } + afterAutoSkip() {} + beforeFit() { + callback(this.options.beforeFit, [ + this + ]); + } + fit() { + const minSize = { + width: 0, + height: 0 + }; + const { chart , options: { ticks: tickOpts , title: titleOpts , grid: gridOpts } } = this; + const display = this._isVisible(); + const isHorizontal = this.isHorizontal(); + if (display) { + const titleHeight = getTitleHeight(titleOpts, chart.options.font); + if (isHorizontal) { + minSize.width = this.maxWidth; + minSize.height = getTickMarkLength(gridOpts) + titleHeight; + } else { + minSize.height = this.maxHeight; + minSize.width = getTickMarkLength(gridOpts) + titleHeight; + } + if (tickOpts.display && this.ticks.length) { + const { first , last , widest , highest } = this._getLabelSizes(); + const tickPadding = tickOpts.padding * 2; + const angleRadians = toRadians(this.labelRotation); + const cos = Math.cos(angleRadians); + const sin = Math.sin(angleRadians); + if (isHorizontal) { + const labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height; + minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding); + } else { + const labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height; + minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding); + } + this._calculatePadding(first, last, sin, cos); + } + } + this._handleMargins(); + if (isHorizontal) { + this.width = this._length = chart.width - this._margins.left - this._margins.right; + this.height = minSize.height; + } else { + this.width = minSize.width; + this.height = this._length = chart.height - this._margins.top - this._margins.bottom; + } + } + _calculatePadding(first, last, sin, cos) { + const { ticks: { align , padding } , position } = this.options; + const isRotated = this.labelRotation !== 0; + const labelsBelowTicks = position !== 'top' && this.axis === 'x'; + if (this.isHorizontal()) { + const offsetLeft = this.getPixelForTick(0) - this.left; + const offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1); + let paddingLeft = 0; + let paddingRight = 0; + if (isRotated) { + if (labelsBelowTicks) { + paddingLeft = cos * first.width; + paddingRight = sin * last.height; + } else { + paddingLeft = sin * first.height; + paddingRight = cos * last.width; + } + } else if (align === 'start') { + paddingRight = last.width; + } else if (align === 'end') { + paddingLeft = first.width; + } else if (align !== 'inner') { + paddingLeft = first.width / 2; + paddingRight = last.width / 2; + } + this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0); + this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0); + } else { + let paddingTop = last.height / 2; + let paddingBottom = first.height / 2; + if (align === 'start') { + paddingTop = 0; + paddingBottom = first.height; + } else if (align === 'end') { + paddingTop = last.height; + paddingBottom = 0; + } + this.paddingTop = paddingTop + padding; + this.paddingBottom = paddingBottom + padding; + } + } + _handleMargins() { + if (this._margins) { + this._margins.left = Math.max(this.paddingLeft, this._margins.left); + this._margins.top = Math.max(this.paddingTop, this._margins.top); + this._margins.right = Math.max(this.paddingRight, this._margins.right); + this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom); + } + } + afterFit() { + callback(this.options.afterFit, [ + this + ]); + } + isHorizontal() { + const { axis , position } = this.options; + return position === 'top' || position === 'bottom' || axis === 'x'; + } + isFullSize() { + return this.options.fullSize; + } + _convertTicksToLabels(ticks) { + this.beforeTickToLabelConversion(); + this.generateTickLabels(ticks); + let i, ilen; + for(i = 0, ilen = ticks.length; i < ilen; i++){ + if (isNullOrUndef(ticks[i].label)) { + ticks.splice(i, 1); + ilen--; + i--; + } + } + this.afterTickToLabelConversion(); + } + _getLabelSizes() { + let labelSizes = this._labelSizes; + if (!labelSizes) { + const sampleSize = this.options.ticks.sampleSize; + let ticks = this.ticks; + if (sampleSize < ticks.length) { + ticks = sample(ticks, sampleSize); + } + this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit); + } + return labelSizes; + } + _computeLabelSizes(ticks, length, maxTicksLimit) { + const { ctx , _longestTextCache: caches } = this; + const widths = []; + const heights = []; + const increment = Math.floor(length / getTicksLimit(length, maxTicksLimit)); + let widestLabelSize = 0; + let highestLabelSize = 0; + let i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel; + for(i = 0; i < length; i += increment){ + label = ticks[i].label; + tickFont = this._resolveTickFontOptions(i); + ctx.font = fontString = tickFont.string; + cache = caches[fontString] = caches[fontString] || { + data: {}, + gc: [] + }; + lineHeight = tickFont.lineHeight; + width = height = 0; + if (!isNullOrUndef(label) && !isArray(label)) { + width = _measureText(ctx, cache.data, cache.gc, width, label); + height = lineHeight; + } else if (isArray(label)) { + for(j = 0, jlen = label.length; j < jlen; ++j){ + nestedLabel = label[j]; + if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { + width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel); + height += lineHeight; + } + } + } + widths.push(width); + heights.push(height); + widestLabelSize = Math.max(width, widestLabelSize); + highestLabelSize = Math.max(height, highestLabelSize); + } + garbageCollect(caches, length); + const widest = widths.indexOf(widestLabelSize); + const highest = heights.indexOf(highestLabelSize); + const valueAt = (idx)=>({ + width: widths[idx] || 0, + height: heights[idx] || 0 + }); + return { + first: valueAt(0), + last: valueAt(length - 1), + widest: valueAt(widest), + highest: valueAt(highest), + widths, + heights + }; + } + getLabelForValue(value) { + return value; + } + getPixelForValue(value, index) { + return NaN; + } + getValueForPixel(pixel) {} + getPixelForTick(index) { + const ticks = this.ticks; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index].value); + } + getPixelForDecimal(decimal) { + if (this._reversePixels) { + decimal = 1 - decimal; + } + const pixel = this._startPixel + decimal * this._length; + return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel); + } + getDecimalForPixel(pixel) { + const decimal = (pixel - this._startPixel) / this._length; + return this._reversePixels ? 1 - decimal : decimal; + } + getBasePixel() { + return this.getPixelForValue(this.getBaseValue()); + } + getBaseValue() { + const { min , max } = this; + return min < 0 && max < 0 ? max : min > 0 && max > 0 ? min : 0; + } + getContext(index) { + const ticks = this.ticks || []; + if (index >= 0 && index < ticks.length) { + const tick = ticks[index]; + return tick.$context || (tick.$context = createTickContext(this.getContext(), index, tick)); + } + return this.$context || (this.$context = createScaleContext(this.chart.getContext(), this)); + } + _tickSize() { + const optionTicks = this.options.ticks; + const rot = toRadians(this.labelRotation); + const cos = Math.abs(Math.cos(rot)); + const sin = Math.abs(Math.sin(rot)); + const labelSizes = this._getLabelSizes(); + const padding = optionTicks.autoSkipPadding || 0; + const w = labelSizes ? labelSizes.widest.width + padding : 0; + const h = labelSizes ? labelSizes.highest.height + padding : 0; + return this.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin; + } + _isVisible() { + const display = this.options.display; + if (display !== 'auto') { + return !!display; + } + return this.getMatchingVisibleMetas().length > 0; + } + _computeGridLineItems(chartArea) { + const axis = this.axis; + const chart = this.chart; + const options = this.options; + const { grid , position , border } = options; + const offset = grid.offset; + const isHorizontal = this.isHorizontal(); + const ticks = this.ticks; + const ticksLength = ticks.length + (offset ? 1 : 0); + const tl = getTickMarkLength(grid); + const items = []; + const borderOpts = border.setContext(this.getContext()); + const axisWidth = borderOpts.display ? borderOpts.width : 0; + const axisHalfWidth = axisWidth / 2; + const alignBorderValue = function(pixel) { + return _alignPixel(chart, pixel, axisWidth); + }; + let borderValue, i, lineValue, alignedLineValue; + let tx1, ty1, tx2, ty2, x1, y1, x2, y2; + if (position === 'top') { + borderValue = alignBorderValue(this.bottom); + ty1 = this.bottom - tl; + ty2 = borderValue - axisHalfWidth; + y1 = alignBorderValue(chartArea.top) + axisHalfWidth; + y2 = chartArea.bottom; + } else if (position === 'bottom') { + borderValue = alignBorderValue(this.top); + y1 = chartArea.top; + y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; + ty1 = borderValue + axisHalfWidth; + ty2 = this.top + tl; + } else if (position === 'left') { + borderValue = alignBorderValue(this.right); + tx1 = this.right - tl; + tx2 = borderValue - axisHalfWidth; + x1 = alignBorderValue(chartArea.left) + axisHalfWidth; + x2 = chartArea.right; + } else if (position === 'right') { + borderValue = alignBorderValue(this.left); + x1 = chartArea.left; + x2 = alignBorderValue(chartArea.right) - axisHalfWidth; + tx1 = borderValue + axisHalfWidth; + tx2 = this.left + tl; + } else if (axis === 'x') { + if (position === 'center') { + borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5); + } else if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value)); + } + y1 = chartArea.top; + y2 = chartArea.bottom; + ty1 = borderValue + axisHalfWidth; + ty2 = ty1 + tl; + } else if (axis === 'y') { + if (position === 'center') { + borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2); + } else if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value)); + } + tx1 = borderValue - axisHalfWidth; + tx2 = tx1 - tl; + x1 = chartArea.left; + x2 = chartArea.right; + } + const limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength); + const step = Math.max(1, Math.ceil(ticksLength / limit)); + for(i = 0; i < ticksLength; i += step){ + const context = this.getContext(i); + const optsAtIndex = grid.setContext(context); + const optsAtIndexBorder = border.setContext(context); + const lineWidth = optsAtIndex.lineWidth; + const lineColor = optsAtIndex.color; + const borderDash = optsAtIndexBorder.dash || []; + const borderDashOffset = optsAtIndexBorder.dashOffset; + const tickWidth = optsAtIndex.tickWidth; + const tickColor = optsAtIndex.tickColor; + const tickBorderDash = optsAtIndex.tickBorderDash || []; + const tickBorderDashOffset = optsAtIndex.tickBorderDashOffset; + lineValue = getPixelForGridLine(this, i, offset); + if (lineValue === undefined) { + continue; + } + alignedLineValue = _alignPixel(chart, lineValue, lineWidth); + if (isHorizontal) { + tx1 = tx2 = x1 = x2 = alignedLineValue; + } else { + ty1 = ty2 = y1 = y2 = alignedLineValue; + } + items.push({ + tx1, + ty1, + tx2, + ty2, + x1, + y1, + x2, + y2, + width: lineWidth, + color: lineColor, + borderDash, + borderDashOffset, + tickWidth, + tickColor, + tickBorderDash, + tickBorderDashOffset + }); + } + this._ticksLength = ticksLength; + this._borderValue = borderValue; + return items; + } + _computeLabelItems(chartArea) { + const axis = this.axis; + const options = this.options; + const { position , ticks: optionTicks } = options; + const isHorizontal = this.isHorizontal(); + const ticks = this.ticks; + const { align , crossAlign , padding , mirror } = optionTicks; + const tl = getTickMarkLength(options.grid); + const tickAndPadding = tl + padding; + const hTickAndPadding = mirror ? -padding : tickAndPadding; + const rotation = -toRadians(this.labelRotation); + const items = []; + let i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; + let textBaseline = 'middle'; + if (position === 'top') { + y = this.bottom - hTickAndPadding; + textAlign = this._getXAxisLabelAlignment(); + } else if (position === 'bottom') { + y = this.top + hTickAndPadding; + textAlign = this._getXAxisLabelAlignment(); + } else if (position === 'left') { + const ret = this._getYAxisLabelAlignment(tl); + textAlign = ret.textAlign; + x = ret.x; + } else if (position === 'right') { + const ret = this._getYAxisLabelAlignment(tl); + textAlign = ret.textAlign; + x = ret.x; + } else if (axis === 'x') { + if (position === 'center') { + y = (chartArea.top + chartArea.bottom) / 2 + tickAndPadding; + } else if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding; + } + textAlign = this._getXAxisLabelAlignment(); + } else if (axis === 'y') { + if (position === 'center') { + x = (chartArea.left + chartArea.right) / 2 - tickAndPadding; + } else if (isObject(position)) { + const positionAxisID = Object.keys(position)[0]; + const value = position[positionAxisID]; + x = this.chart.scales[positionAxisID].getPixelForValue(value); + } + textAlign = this._getYAxisLabelAlignment(tl).textAlign; + } + if (axis === 'y') { + if (align === 'start') { + textBaseline = 'top'; + } else if (align === 'end') { + textBaseline = 'bottom'; + } + } + const labelSizes = this._getLabelSizes(); + for(i = 0, ilen = ticks.length; i < ilen; ++i){ + tick = ticks[i]; + label = tick.label; + const optsAtIndex = optionTicks.setContext(this.getContext(i)); + pixel = this.getPixelForTick(i) + optionTicks.labelOffset; + font = this._resolveTickFontOptions(i); + lineHeight = font.lineHeight; + lineCount = isArray(label) ? label.length : 1; + const halfCount = lineCount / 2; + const color = optsAtIndex.color; + const strokeColor = optsAtIndex.textStrokeColor; + const strokeWidth = optsAtIndex.textStrokeWidth; + let tickTextAlign = textAlign; + if (isHorizontal) { + x = pixel; + if (textAlign === 'inner') { + if (i === ilen - 1) { + tickTextAlign = !this.options.reverse ? 'right' : 'left'; + } else if (i === 0) { + tickTextAlign = !this.options.reverse ? 'left' : 'right'; + } else { + tickTextAlign = 'center'; + } + } + if (position === 'top') { + if (crossAlign === 'near' || rotation !== 0) { + textOffset = -lineCount * lineHeight + lineHeight / 2; + } else if (crossAlign === 'center') { + textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight; + } else { + textOffset = -labelSizes.highest.height + lineHeight / 2; + } + } else { + if (crossAlign === 'near' || rotation !== 0) { + textOffset = lineHeight / 2; + } else if (crossAlign === 'center') { + textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight; + } else { + textOffset = labelSizes.highest.height - lineCount * lineHeight; + } + } + if (mirror) { + textOffset *= -1; + } + if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) { + x += lineHeight / 2 * Math.sin(rotation); + } + } else { + y = pixel; + textOffset = (1 - lineCount) * lineHeight / 2; + } + let backdrop; + if (optsAtIndex.showLabelBackdrop) { + const labelPadding = toPadding(optsAtIndex.backdropPadding); + const height = labelSizes.heights[i]; + const width = labelSizes.widths[i]; + let top = textOffset - labelPadding.top; + let left = 0 - labelPadding.left; + switch(textBaseline){ + case 'middle': + top -= height / 2; + break; + case 'bottom': + top -= height; + break; + } + switch(textAlign){ + case 'center': + left -= width / 2; + break; + case 'right': + left -= width; + break; + case 'inner': + if (i === ilen - 1) { + left -= width; + } else if (i > 0) { + left -= width / 2; + } + break; + } + backdrop = { + left, + top, + width: width + labelPadding.width, + height: height + labelPadding.height, + color: optsAtIndex.backdropColor + }; + } + items.push({ + label, + font, + textOffset, + options: { + rotation, + color, + strokeColor, + strokeWidth, + textAlign: tickTextAlign, + textBaseline, + translation: [ + x, + y + ], + backdrop + } + }); + } + return items; + } + _getXAxisLabelAlignment() { + const { position , ticks } = this.options; + const rotation = -toRadians(this.labelRotation); + if (rotation) { + return position === 'top' ? 'left' : 'right'; + } + let align = 'center'; + if (ticks.align === 'start') { + align = 'left'; + } else if (ticks.align === 'end') { + align = 'right'; + } else if (ticks.align === 'inner') { + align = 'inner'; + } + return align; + } + _getYAxisLabelAlignment(tl) { + const { position , ticks: { crossAlign , mirror , padding } } = this.options; + const labelSizes = this._getLabelSizes(); + const tickAndPadding = tl + padding; + const widest = labelSizes.widest.width; + let textAlign; + let x; + if (position === 'left') { + if (mirror) { + x = this.right + padding; + if (crossAlign === 'near') { + textAlign = 'left'; + } else if (crossAlign === 'center') { + textAlign = 'center'; + x += widest / 2; + } else { + textAlign = 'right'; + x += widest; + } + } else { + x = this.right - tickAndPadding; + if (crossAlign === 'near') { + textAlign = 'right'; + } else if (crossAlign === 'center') { + textAlign = 'center'; + x -= widest / 2; + } else { + textAlign = 'left'; + x = this.left; + } + } + } else if (position === 'right') { + if (mirror) { + x = this.left + padding; + if (crossAlign === 'near') { + textAlign = 'right'; + } else if (crossAlign === 'center') { + textAlign = 'center'; + x -= widest / 2; + } else { + textAlign = 'left'; + x -= widest; + } + } else { + x = this.left + tickAndPadding; + if (crossAlign === 'near') { + textAlign = 'left'; + } else if (crossAlign === 'center') { + textAlign = 'center'; + x += widest / 2; + } else { + textAlign = 'right'; + x = this.right; + } + } + } else { + textAlign = 'right'; + } + return { + textAlign, + x + }; + } + _computeLabelArea() { + if (this.options.ticks.mirror) { + return; + } + const chart = this.chart; + const position = this.options.position; + if (position === 'left' || position === 'right') { + return { + top: 0, + left: this.left, + bottom: chart.height, + right: this.right + }; + } + if (position === 'top' || position === 'bottom') { + return { + top: this.top, + left: 0, + bottom: this.bottom, + right: chart.width + }; + } + } + drawBackground() { + const { ctx , options: { backgroundColor } , left , top , width , height } = this; + if (backgroundColor) { + ctx.save(); + ctx.fillStyle = backgroundColor; + ctx.fillRect(left, top, width, height); + ctx.restore(); + } + } + getLineWidthForValue(value) { + const grid = this.options.grid; + if (!this._isVisible() || !grid.display) { + return 0; + } + const ticks = this.ticks; + const index = ticks.findIndex((t)=>t.value === value); + if (index >= 0) { + const opts = grid.setContext(this.getContext(index)); + return opts.lineWidth; + } + return 0; + } + drawGrid(chartArea) { + const grid = this.options.grid; + const ctx = this.ctx; + const items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea)); + let i, ilen; + const drawLine = (p1, p2, style)=>{ + if (!style.width || !style.color) { + return; + } + ctx.save(); + ctx.lineWidth = style.width; + ctx.strokeStyle = style.color; + ctx.setLineDash(style.borderDash || []); + ctx.lineDashOffset = style.borderDashOffset; + ctx.beginPath(); + ctx.moveTo(p1.x, p1.y); + ctx.lineTo(p2.x, p2.y); + ctx.stroke(); + ctx.restore(); + }; + if (grid.display) { + for(i = 0, ilen = items.length; i < ilen; ++i){ + const item = items[i]; + if (grid.drawOnChartArea) { + drawLine({ + x: item.x1, + y: item.y1 + }, { + x: item.x2, + y: item.y2 + }, item); + } + if (grid.drawTicks) { + drawLine({ + x: item.tx1, + y: item.ty1 + }, { + x: item.tx2, + y: item.ty2 + }, { + color: item.tickColor, + width: item.tickWidth, + borderDash: item.tickBorderDash, + borderDashOffset: item.tickBorderDashOffset + }); + } + } + } + } + drawBorder() { + const { chart , ctx , options: { border , grid } } = this; + const borderOpts = border.setContext(this.getContext()); + const axisWidth = border.display ? borderOpts.width : 0; + if (!axisWidth) { + return; + } + const lastLineWidth = grid.setContext(this.getContext(0)).lineWidth; + const borderValue = this._borderValue; + let x1, x2, y1, y2; + if (this.isHorizontal()) { + x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2; + x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2; + y1 = y2 = borderValue; + } else { + y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2; + y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2; + x1 = x2 = borderValue; + } + ctx.save(); + ctx.lineWidth = borderOpts.width; + ctx.strokeStyle = borderOpts.color; + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + ctx.restore(); + } + drawLabels(chartArea) { + const optionTicks = this.options.ticks; + if (!optionTicks.display) { + return; + } + const ctx = this.ctx; + const area = this._computeLabelArea(); + if (area) { + clipArea(ctx, area); + } + const items = this.getLabelItems(chartArea); + for (const item of items){ + const renderTextOptions = item.options; + const tickFont = item.font; + const label = item.label; + const y = item.textOffset; + renderText(ctx, label, 0, y, tickFont, renderTextOptions); + } + if (area) { + unclipArea(ctx); + } + } + drawTitle() { + const { ctx , options: { position , title , reverse } } = this; + if (!title.display) { + return; + } + const font = toFont(title.font); + const padding = toPadding(title.padding); + const align = title.align; + let offset = font.lineHeight / 2; + if (position === 'bottom' || position === 'center' || isObject(position)) { + offset += padding.bottom; + if (isArray(title.text)) { + offset += font.lineHeight * (title.text.length - 1); + } + } else { + offset += padding.top; + } + const { titleX , titleY , maxWidth , rotation } = titleArgs(this, offset, position, align); + renderText(ctx, title.text, 0, 0, font, { + color: title.color, + maxWidth, + rotation, + textAlign: titleAlign(align, position, reverse), + textBaseline: 'middle', + translation: [ + titleX, + titleY + ] + }); + } + draw(chartArea) { + if (!this._isVisible()) { + return; + } + this.drawBackground(); + this.drawGrid(chartArea); + this.drawBorder(); + this.drawTitle(); + this.drawLabels(chartArea); + } + _layers() { + const opts = this.options; + const tz = opts.ticks && opts.ticks.z || 0; + const gz = valueOrDefault(opts.grid && opts.grid.z, -1); + const bz = valueOrDefault(opts.border && opts.border.z, 0); + if (!this._isVisible() || this.draw !== Scale.prototype.draw) { + return [ + { + z: tz, + draw: (chartArea)=>{ + this.draw(chartArea); + } + } + ]; + } + return [ + { + z: gz, + draw: (chartArea)=>{ + this.drawBackground(); + this.drawGrid(chartArea); + this.drawTitle(); + } + }, + { + z: bz, + draw: ()=>{ + this.drawBorder(); + } + }, + { + z: tz, + draw: (chartArea)=>{ + this.drawLabels(chartArea); + } + } + ]; + } + getMatchingVisibleMetas(type) { + const metas = this.chart.getSortedVisibleDatasetMetas(); + const axisID = this.axis + 'AxisID'; + const result = []; + let i, ilen; + for(i = 0, ilen = metas.length; i < ilen; ++i){ + const meta = metas[i]; + if (meta[axisID] === this.id && (!type || meta.type === type)) { + result.push(meta); + } + } + return result; + } + _resolveTickFontOptions(index) { + const opts = this.options.ticks.setContext(this.getContext(index)); + return toFont(opts.font); + } + _maxDigits() { + const fontSize = this._resolveTickFontOptions(0).lineHeight; + return (this.isHorizontal() ? this.width : this.height) / fontSize; + } +} + +class TypedRegistry { + constructor(type, scope, override){ + this.type = type; + this.scope = scope; + this.override = override; + this.items = Object.create(null); + } + isForType(type) { + return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype); + } + register(item) { + const proto = Object.getPrototypeOf(item); + let parentScope; + if (isIChartComponent(proto)) { + parentScope = this.register(proto); + } + const items = this.items; + const id = item.id; + const scope = this.scope + '.' + id; + if (!id) { + throw new Error('class does not have id: ' + item); + } + if (id in items) { + return scope; + } + items[id] = item; + registerDefaults(item, scope, parentScope); + if (this.override) { + defaults.override(item.id, item.overrides); + } + return scope; + } + get(id) { + return this.items[id]; + } + unregister(item) { + const items = this.items; + const id = item.id; + const scope = this.scope; + if (id in items) { + delete items[id]; + } + if (scope && id in defaults[scope]) { + delete defaults[scope][id]; + if (this.override) { + delete overrides[id]; + } + } + } +} +function registerDefaults(item, scope, parentScope) { + const itemDefaults = merge(Object.create(null), [ + parentScope ? defaults.get(parentScope) : {}, + defaults.get(scope), + item.defaults + ]); + defaults.set(scope, itemDefaults); + if (item.defaultRoutes) { + routeDefaults(scope, item.defaultRoutes); + } + if (item.descriptors) { + defaults.describe(scope, item.descriptors); + } +} +function routeDefaults(scope, routes) { + Object.keys(routes).forEach((property)=>{ + const propertyParts = property.split('.'); + const sourceName = propertyParts.pop(); + const sourceScope = [ + scope + ].concat(propertyParts).join('.'); + const parts = routes[property].split('.'); + const targetName = parts.pop(); + const targetScope = parts.join('.'); + defaults.route(sourceScope, sourceName, targetScope, targetName); + }); +} +function isIChartComponent(proto) { + return 'id' in proto && 'defaults' in proto; +} + +class Registry { + constructor(){ + this.controllers = new TypedRegistry(DatasetController, 'datasets', true); + this.elements = new TypedRegistry(Element, 'elements'); + this.plugins = new TypedRegistry(Object, 'plugins'); + this.scales = new TypedRegistry(Scale, 'scales'); + this._typedRegistries = [ + this.controllers, + this.scales, + this.elements + ]; + } + add(...args) { + this._each('register', args); + } + remove(...args) { + this._each('unregister', args); + } + addControllers(...args) { + this._each('register', args, this.controllers); + } + addElements(...args) { + this._each('register', args, this.elements); + } + addPlugins(...args) { + this._each('register', args, this.plugins); + } + addScales(...args) { + this._each('register', args, this.scales); + } + getController(id) { + return this._get(id, this.controllers, 'controller'); + } + getElement(id) { + return this._get(id, this.elements, 'element'); + } + getPlugin(id) { + return this._get(id, this.plugins, 'plugin'); + } + getScale(id) { + return this._get(id, this.scales, 'scale'); + } + removeControllers(...args) { + this._each('unregister', args, this.controllers); + } + removeElements(...args) { + this._each('unregister', args, this.elements); + } + removePlugins(...args) { + this._each('unregister', args, this.plugins); + } + removeScales(...args) { + this._each('unregister', args, this.scales); + } + _each(method, args, typedRegistry) { + [ + ...args + ].forEach((arg)=>{ + const reg = typedRegistry || this._getRegistryForType(arg); + if (typedRegistry || reg.isForType(arg) || reg === this.plugins && arg.id) { + this._exec(method, reg, arg); + } else { + each(arg, (item)=>{ + const itemReg = typedRegistry || this._getRegistryForType(item); + this._exec(method, itemReg, item); + }); + } + }); + } + _exec(method, registry, component) { + const camelMethod = _capitalize(method); + callback(component['before' + camelMethod], [], component); + registry[method](component); + callback(component['after' + camelMethod], [], component); + } + _getRegistryForType(type) { + for(let i = 0; i < this._typedRegistries.length; i++){ + const reg = this._typedRegistries[i]; + if (reg.isForType(type)) { + return reg; + } + } + return this.plugins; + } + _get(id, typedRegistry, type) { + const item = typedRegistry.get(id); + if (item === undefined) { + throw new Error('"' + id + '" is not a registered ' + type + '.'); + } + return item; + } +} +var registry = /* #__PURE__ */ new Registry(); + +class PluginService { + constructor(){ + this._init = []; + } + notify(chart, hook, args, filter) { + if (hook === 'beforeInit') { + this._init = this._createDescriptors(chart, true); + this._notify(this._init, chart, 'install'); + } + const descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart); + const result = this._notify(descriptors, chart, hook, args); + if (hook === 'afterDestroy') { + this._notify(descriptors, chart, 'stop'); + this._notify(this._init, chart, 'uninstall'); + } + return result; + } + _notify(descriptors, chart, hook, args) { + args = args || {}; + for (const descriptor of descriptors){ + const plugin = descriptor.plugin; + const method = plugin[hook]; + const params = [ + chart, + args, + descriptor.options + ]; + if (callback(method, params, plugin) === false && args.cancelable) { + return false; + } + } + return true; + } + invalidate() { + if (!isNullOrUndef(this._cache)) { + this._oldCache = this._cache; + this._cache = undefined; + } + } + _descriptors(chart) { + if (this._cache) { + return this._cache; + } + const descriptors = this._cache = this._createDescriptors(chart); + this._notifyStateChanges(chart); + return descriptors; + } + _createDescriptors(chart, all) { + const config = chart && chart.config; + const options = valueOrDefault(config.options && config.options.plugins, {}); + const plugins = allPlugins(config); + return options === false && !all ? [] : createDescriptors(chart, plugins, options, all); + } + _notifyStateChanges(chart) { + const previousDescriptors = this._oldCache || []; + const descriptors = this._cache; + const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.plugin.id === y.plugin.id)); + this._notify(diff(previousDescriptors, descriptors), chart, 'stop'); + this._notify(diff(descriptors, previousDescriptors), chart, 'start'); + } +} + function allPlugins(config) { + const localIds = {}; + const plugins = []; + const keys = Object.keys(registry.plugins.items); + for(let i = 0; i < keys.length; i++){ + plugins.push(registry.getPlugin(keys[i])); + } + const local = config.plugins || []; + for(let i = 0; i < local.length; i++){ + const plugin = local[i]; + if (plugins.indexOf(plugin) === -1) { + plugins.push(plugin); + localIds[plugin.id] = true; + } + } + return { + plugins, + localIds + }; +} +function getOpts(options, all) { + if (!all && options === false) { + return null; + } + if (options === true) { + return {}; + } + return options; +} +function createDescriptors(chart, { plugins , localIds }, options, all) { + const result = []; + const context = chart.getContext(); + for (const plugin of plugins){ + const id = plugin.id; + const opts = getOpts(options[id], all); + if (opts === null) { + continue; + } + result.push({ + plugin, + options: pluginOpts(chart.config, { + plugin, + local: localIds[id] + }, opts, context) + }); + } + return result; +} +function pluginOpts(config, { plugin , local }, opts, context) { + const keys = config.pluginScopeKeys(plugin); + const scopes = config.getOptionScopes(opts, keys); + if (local && plugin.defaults) { + scopes.push(plugin.defaults); + } + return config.createResolver(scopes, context, [ + '' + ], { + scriptable: false, + indexable: false, + allKeys: true + }); +} + +function getIndexAxis(type, options) { + const datasetDefaults = defaults.datasets[type] || {}; + const datasetOptions = (options.datasets || {})[type] || {}; + return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x'; +} +function getAxisFromDefaultScaleID(id, indexAxis) { + let axis = id; + if (id === '_index_') { + axis = indexAxis; + } else if (id === '_value_') { + axis = indexAxis === 'x' ? 'y' : 'x'; + } + return axis; +} +function getDefaultScaleIDFromAxis(axis, indexAxis) { + return axis === indexAxis ? '_index_' : '_value_'; +} +function idMatchesAxis(id) { + if (id === 'x' || id === 'y' || id === 'r') { + return id; + } +} +function axisFromPosition(position) { + if (position === 'top' || position === 'bottom') { + return 'x'; + } + if (position === 'left' || position === 'right') { + return 'y'; + } +} +function determineAxis(id, ...scaleOptions) { + if (idMatchesAxis(id)) { + return id; + } + for (const opts of scaleOptions){ + const axis = opts.axis || axisFromPosition(opts.position) || id.length > 1 && idMatchesAxis(id[0].toLowerCase()); + if (axis) { + return axis; + } + } + throw new Error(`Cannot determine type of '${id}' axis. Please provide 'axis' or 'position' option.`); +} +function getAxisFromDataset(id, axis, dataset) { + if (dataset[axis + 'AxisID'] === id) { + return { + axis + }; + } +} +function retrieveAxisFromDatasets(id, config) { + if (config.data && config.data.datasets) { + const boundDs = config.data.datasets.filter((d)=>d.xAxisID === id || d.yAxisID === id); + if (boundDs.length) { + return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]); + } + } + return {}; +} +function mergeScaleConfig(config, options) { + const chartDefaults = overrides[config.type] || { + scales: {} + }; + const configScales = options.scales || {}; + const chartIndexAxis = getIndexAxis(config.type, options); + const scales = Object.create(null); + Object.keys(configScales).forEach((id)=>{ + const scaleConf = configScales[id]; + if (!isObject(scaleConf)) { + return console.error(`Invalid scale configuration for scale: ${id}`); + } + if (scaleConf._proxy) { + return console.warn(`Ignoring resolver passed as options for scale: ${id}`); + } + const axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), defaults.scales[scaleConf.type]); + const defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis); + const defaultScaleOptions = chartDefaults.scales || {}; + scales[id] = mergeIf(Object.create(null), [ + { + axis + }, + scaleConf, + defaultScaleOptions[axis], + defaultScaleOptions[defaultId] + ]); + }); + config.data.datasets.forEach((dataset)=>{ + const type = dataset.type || config.type; + const indexAxis = dataset.indexAxis || getIndexAxis(type, options); + const datasetDefaults = overrides[type] || {}; + const defaultScaleOptions = datasetDefaults.scales || {}; + Object.keys(defaultScaleOptions).forEach((defaultID)=>{ + const axis = getAxisFromDefaultScaleID(defaultID, indexAxis); + const id = dataset[axis + 'AxisID'] || axis; + scales[id] = scales[id] || Object.create(null); + mergeIf(scales[id], [ + { + axis + }, + configScales[id], + defaultScaleOptions[defaultID] + ]); + }); + }); + Object.keys(scales).forEach((key)=>{ + const scale = scales[key]; + mergeIf(scale, [ + defaults.scales[scale.type], + defaults.scale + ]); + }); + return scales; +} +function initOptions(config) { + const options = config.options || (config.options = {}); + options.plugins = valueOrDefault(options.plugins, {}); + options.scales = mergeScaleConfig(config, options); +} +function initData(data) { + data = data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + return data; +} +function initConfig(config) { + config = config || {}; + config.data = initData(config.data); + initOptions(config); + return config; +} +const keyCache = new Map(); +const keysCached = new Set(); +function cachedKeys(cacheKey, generate) { + let keys = keyCache.get(cacheKey); + if (!keys) { + keys = generate(); + keyCache.set(cacheKey, keys); + keysCached.add(keys); + } + return keys; +} +const addIfFound = (set, obj, key)=>{ + const opts = resolveObjectKey(obj, key); + if (opts !== undefined) { + set.add(opts); + } +}; +class Config { + constructor(config){ + this._config = initConfig(config); + this._scopeCache = new Map(); + this._resolverCache = new Map(); + } + get platform() { + return this._config.platform; + } + get type() { + return this._config.type; + } + set type(type) { + this._config.type = type; + } + get data() { + return this._config.data; + } + set data(data) { + this._config.data = initData(data); + } + get options() { + return this._config.options; + } + set options(options) { + this._config.options = options; + } + get plugins() { + return this._config.plugins; + } + update() { + const config = this._config; + this.clearCache(); + initOptions(config); + } + clearCache() { + this._scopeCache.clear(); + this._resolverCache.clear(); + } + datasetScopeKeys(datasetType) { + return cachedKeys(datasetType, ()=>[ + [ + `datasets.${datasetType}`, + '' + ] + ]); + } + datasetAnimationScopeKeys(datasetType, transition) { + return cachedKeys(`${datasetType}.transition.${transition}`, ()=>[ + [ + `datasets.${datasetType}.transitions.${transition}`, + `transitions.${transition}` + ], + [ + `datasets.${datasetType}`, + '' + ] + ]); + } + datasetElementScopeKeys(datasetType, elementType) { + return cachedKeys(`${datasetType}-${elementType}`, ()=>[ + [ + `datasets.${datasetType}.elements.${elementType}`, + `datasets.${datasetType}`, + `elements.${elementType}`, + '' + ] + ]); + } + pluginScopeKeys(plugin) { + const id = plugin.id; + const type = this.type; + return cachedKeys(`${type}-plugin-${id}`, ()=>[ + [ + `plugins.${id}`, + ...plugin.additionalOptionScopes || [] + ] + ]); + } + _cachedScopes(mainScope, resetCache) { + const _scopeCache = this._scopeCache; + let cache = _scopeCache.get(mainScope); + if (!cache || resetCache) { + cache = new Map(); + _scopeCache.set(mainScope, cache); + } + return cache; + } + getOptionScopes(mainScope, keyLists, resetCache) { + const { options , type } = this; + const cache = this._cachedScopes(mainScope, resetCache); + const cached = cache.get(keyLists); + if (cached) { + return cached; + } + const scopes = new Set(); + keyLists.forEach((keys)=>{ + if (mainScope) { + scopes.add(mainScope); + keys.forEach((key)=>addIfFound(scopes, mainScope, key)); + } + keys.forEach((key)=>addIfFound(scopes, options, key)); + keys.forEach((key)=>addIfFound(scopes, overrides[type] || {}, key)); + keys.forEach((key)=>addIfFound(scopes, defaults, key)); + keys.forEach((key)=>addIfFound(scopes, descriptors, key)); + }); + const array = Array.from(scopes); + if (array.length === 0) { + array.push(Object.create(null)); + } + if (keysCached.has(keyLists)) { + cache.set(keyLists, array); + } + return array; + } + chartOptionScopes() { + const { options , type } = this; + return [ + options, + overrides[type] || {}, + defaults.datasets[type] || {}, + { + type + }, + defaults, + descriptors + ]; + } + resolveNamedOptions(scopes, names, context, prefixes = [ + '' + ]) { + const result = { + $shared: true + }; + const { resolver , subPrefixes } = getResolver(this._resolverCache, scopes, prefixes); + let options = resolver; + if (needContext(resolver, names)) { + result.$shared = false; + context = isFunction(context) ? context() : context; + const subResolver = this.createResolver(scopes, context, subPrefixes); + options = _attachContext(resolver, context, subResolver); + } + for (const prop of names){ + result[prop] = options[prop]; + } + return result; + } + createResolver(scopes, context, prefixes = [ + '' + ], descriptorDefaults) { + const { resolver } = getResolver(this._resolverCache, scopes, prefixes); + return isObject(context) ? _attachContext(resolver, context, undefined, descriptorDefaults) : resolver; + } +} +function getResolver(resolverCache, scopes, prefixes) { + let cache = resolverCache.get(scopes); + if (!cache) { + cache = new Map(); + resolverCache.set(scopes, cache); + } + const cacheKey = prefixes.join(); + let cached = cache.get(cacheKey); + if (!cached) { + const resolver = _createResolver(scopes, prefixes); + cached = { + resolver, + subPrefixes: prefixes.filter((p)=>!p.toLowerCase().includes('hover')) + }; + cache.set(cacheKey, cached); + } + return cached; +} +const hasFunction = (value)=>isObject(value) && Object.getOwnPropertyNames(value).some((key)=>isFunction(value[key])); +function needContext(proxy, names) { + const { isScriptable , isIndexable } = _descriptors(proxy); + for (const prop of names){ + const scriptable = isScriptable(prop); + const indexable = isIndexable(prop); + const value = (indexable || scriptable) && proxy[prop]; + if (scriptable && (isFunction(value) || hasFunction(value)) || indexable && isArray(value)) { + return true; + } + } + return false; +} + +var version = "4.4.1"; + +const KNOWN_POSITIONS = [ + 'top', + 'bottom', + 'left', + 'right', + 'chartArea' +]; +function positionIsHorizontal(position, axis) { + return position === 'top' || position === 'bottom' || KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x'; +} +function compare2Level(l1, l2) { + return function(a, b) { + return a[l1] === b[l1] ? a[l2] - b[l2] : a[l1] - b[l1]; + }; +} +function onAnimationsComplete(context) { + const chart = context.chart; + const animationOptions = chart.options.animation; + chart.notifyPlugins('afterRender'); + callback(animationOptions && animationOptions.onComplete, [ + context + ], chart); +} +function onAnimationProgress(context) { + const chart = context.chart; + const animationOptions = chart.options.animation; + callback(animationOptions && animationOptions.onProgress, [ + context + ], chart); +} + function getCanvas(item) { + if (_isDomSupported() && typeof item === 'string') { + item = document.getElementById(item); + } else if (item && item.length) { + item = item[0]; + } + if (item && item.canvas) { + item = item.canvas; + } + return item; +} +const instances = {}; +const getChart = (key)=>{ + const canvas = getCanvas(key); + return Object.values(instances).filter((c)=>c.canvas === canvas).pop(); +}; +function moveNumericKeys(obj, start, move) { + const keys = Object.keys(obj); + for (const key of keys){ + const intKey = +key; + if (intKey >= start) { + const value = obj[key]; + delete obj[key]; + if (move > 0 || intKey > start) { + obj[intKey + move] = value; + } + } + } +} + function determineLastEvent(e, lastEvent, inChartArea, isClick) { + if (!inChartArea || e.type === 'mouseout') { + return null; + } + if (isClick) { + return lastEvent; + } + return e; +} +function getSizeForArea(scale, chartArea, field) { + return scale.options.clip ? scale[field] : chartArea[field]; +} +function getDatasetArea(meta, chartArea) { + const { xScale , yScale } = meta; + if (xScale && yScale) { + return { + left: getSizeForArea(xScale, chartArea, 'left'), + right: getSizeForArea(xScale, chartArea, 'right'), + top: getSizeForArea(yScale, chartArea, 'top'), + bottom: getSizeForArea(yScale, chartArea, 'bottom') + }; + } + return chartArea; +} +class Chart { + static defaults = defaults; + static instances = instances; + static overrides = overrides; + static registry = registry; + static version = version; + static getChart = getChart; + static register(...items) { + registry.add(...items); + invalidatePlugins(); + } + static unregister(...items) { + registry.remove(...items); + invalidatePlugins(); + } + constructor(item, userConfig){ + const config = this.config = new Config(userConfig); + const initialCanvas = getCanvas(item); + const existingChart = getChart(initialCanvas); + if (existingChart) { + throw new Error('Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + ' must be destroyed before the canvas with ID \'' + existingChart.canvas.id + '\' can be reused.'); + } + const options = config.createResolver(config.chartOptionScopes(), this.getContext()); + this.platform = new (config.platform || _detectPlatform(initialCanvas))(); + this.platform.updateConfig(config); + const context = this.platform.acquireContext(initialCanvas, options.aspectRatio); + const canvas = context && context.canvas; + const height = canvas && canvas.height; + const width = canvas && canvas.width; + this.id = uid(); + this.ctx = context; + this.canvas = canvas; + this.width = width; + this.height = height; + this._options = options; + this._aspectRatio = this.aspectRatio; + this._layers = []; + this._metasets = []; + this._stacks = undefined; + this.boxes = []; + this.currentDevicePixelRatio = undefined; + this.chartArea = undefined; + this._active = []; + this._lastEvent = undefined; + this._listeners = {}; + this._responsiveListeners = undefined; + this._sortedMetasets = []; + this.scales = {}; + this._plugins = new PluginService(); + this.$proxies = {}; + this._hiddenIndices = {}; + this.attached = false; + this._animationsDisabled = undefined; + this.$context = undefined; + this._doResize = debounce((mode)=>this.update(mode), options.resizeDelay || 0); + this._dataChanges = []; + instances[this.id] = this; + if (!context || !canvas) { + console.error("Failed to create chart: can't acquire context from the given item"); + return; + } + animator.listen(this, 'complete', onAnimationsComplete); + animator.listen(this, 'progress', onAnimationProgress); + this._initialize(); + if (this.attached) { + this.update(); + } + } + get aspectRatio() { + const { options: { aspectRatio , maintainAspectRatio } , width , height , _aspectRatio } = this; + if (!isNullOrUndef(aspectRatio)) { + return aspectRatio; + } + if (maintainAspectRatio && _aspectRatio) { + return _aspectRatio; + } + return height ? width / height : null; + } + get data() { + return this.config.data; + } + set data(data) { + this.config.data = data; + } + get options() { + return this._options; + } + set options(options) { + this.config.options = options; + } + get registry() { + return registry; + } + _initialize() { + this.notifyPlugins('beforeInit'); + if (this.options.responsive) { + this.resize(); + } else { + retinaScale(this, this.options.devicePixelRatio); + } + this.bindEvents(); + this.notifyPlugins('afterInit'); + return this; + } + clear() { + clearCanvas(this.canvas, this.ctx); + return this; + } + stop() { + animator.stop(this); + return this; + } + resize(width, height) { + if (!animator.running(this)) { + this._resize(width, height); + } else { + this._resizeBeforeDraw = { + width, + height + }; + } + } + _resize(width, height) { + const options = this.options; + const canvas = this.canvas; + const aspectRatio = options.maintainAspectRatio && this.aspectRatio; + const newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio); + const newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio(); + const mode = this.width ? 'resize' : 'attach'; + this.width = newSize.width; + this.height = newSize.height; + this._aspectRatio = this.aspectRatio; + if (!retinaScale(this, newRatio, true)) { + return; + } + this.notifyPlugins('resize', { + size: newSize + }); + callback(options.onResize, [ + this, + newSize + ], this); + if (this.attached) { + if (this._doResize(mode)) { + this.render(); + } + } + } + ensureScalesHaveIDs() { + const options = this.options; + const scalesOptions = options.scales || {}; + each(scalesOptions, (axisOptions, axisID)=>{ + axisOptions.id = axisID; + }); + } + buildOrUpdateScales() { + const options = this.options; + const scaleOpts = options.scales; + const scales = this.scales; + const updated = Object.keys(scales).reduce((obj, id)=>{ + obj[id] = false; + return obj; + }, {}); + let items = []; + if (scaleOpts) { + items = items.concat(Object.keys(scaleOpts).map((id)=>{ + const scaleOptions = scaleOpts[id]; + const axis = determineAxis(id, scaleOptions); + const isRadial = axis === 'r'; + const isHorizontal = axis === 'x'; + return { + options: scaleOptions, + dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left', + dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear' + }; + })); + } + each(items, (item)=>{ + const scaleOptions = item.options; + const id = scaleOptions.id; + const axis = determineAxis(id, scaleOptions); + const scaleType = valueOrDefault(scaleOptions.type, item.dtype); + if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) { + scaleOptions.position = item.dposition; + } + updated[id] = true; + let scale = null; + if (id in scales && scales[id].type === scaleType) { + scale = scales[id]; + } else { + const scaleClass = registry.getScale(scaleType); + scale = new scaleClass({ + id, + type: scaleType, + ctx: this.ctx, + chart: this + }); + scales[scale.id] = scale; + } + scale.init(scaleOptions, options); + }); + each(updated, (hasUpdated, id)=>{ + if (!hasUpdated) { + delete scales[id]; + } + }); + each(scales, (scale)=>{ + layouts.configure(this, scale, scale.options); + layouts.addBox(this, scale); + }); + } + _updateMetasets() { + const metasets = this._metasets; + const numData = this.data.datasets.length; + const numMeta = metasets.length; + metasets.sort((a, b)=>a.index - b.index); + if (numMeta > numData) { + for(let i = numData; i < numMeta; ++i){ + this._destroyDatasetMeta(i); + } + metasets.splice(numData, numMeta - numData); + } + this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index')); + } + _removeUnreferencedMetasets() { + const { _metasets: metasets , data: { datasets } } = this; + if (metasets.length > datasets.length) { + delete this._stacks; + } + metasets.forEach((meta, index)=>{ + if (datasets.filter((x)=>x === meta._dataset).length === 0) { + this._destroyDatasetMeta(index); + } + }); + } + buildOrUpdateControllers() { + const newControllers = []; + const datasets = this.data.datasets; + let i, ilen; + this._removeUnreferencedMetasets(); + for(i = 0, ilen = datasets.length; i < ilen; i++){ + const dataset = datasets[i]; + let meta = this.getDatasetMeta(i); + const type = dataset.type || this.config.type; + if (meta.type && meta.type !== type) { + this._destroyDatasetMeta(i); + meta = this.getDatasetMeta(i); + } + meta.type = type; + meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options); + meta.order = dataset.order || 0; + meta.index = i; + meta.label = '' + dataset.label; + meta.visible = this.isDatasetVisible(i); + if (meta.controller) { + meta.controller.updateIndex(i); + meta.controller.linkScales(); + } else { + const ControllerClass = registry.getController(type); + const { datasetElementType , dataElementType } = defaults.datasets[type]; + Object.assign(ControllerClass, { + dataElementType: registry.getElement(dataElementType), + datasetElementType: datasetElementType && registry.getElement(datasetElementType) + }); + meta.controller = new ControllerClass(this, i); + newControllers.push(meta.controller); + } + } + this._updateMetasets(); + return newControllers; + } + _resetElements() { + each(this.data.datasets, (dataset, datasetIndex)=>{ + this.getDatasetMeta(datasetIndex).controller.reset(); + }, this); + } + reset() { + this._resetElements(); + this.notifyPlugins('reset'); + } + update(mode) { + const config = this.config; + config.update(); + const options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext()); + const animsDisabled = this._animationsDisabled = !options.animation; + this._updateScales(); + this._checkEventBindings(); + this._updateHiddenIndices(); + this._plugins.invalidate(); + if (this.notifyPlugins('beforeUpdate', { + mode, + cancelable: true + }) === false) { + return; + } + const newControllers = this.buildOrUpdateControllers(); + this.notifyPlugins('beforeElementsUpdate'); + let minPadding = 0; + for(let i = 0, ilen = this.data.datasets.length; i < ilen; i++){ + const { controller } = this.getDatasetMeta(i); + const reset = !animsDisabled && newControllers.indexOf(controller) === -1; + controller.buildOrUpdateElements(reset); + minPadding = Math.max(+controller.getMaxOverflow(), minPadding); + } + minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0; + this._updateLayout(minPadding); + if (!animsDisabled) { + each(newControllers, (controller)=>{ + controller.reset(); + }); + } + this._updateDatasets(mode); + this.notifyPlugins('afterUpdate', { + mode + }); + this._layers.sort(compare2Level('z', '_idx')); + const { _active , _lastEvent } = this; + if (_lastEvent) { + this._eventHandler(_lastEvent, true); + } else if (_active.length) { + this._updateHoverStyles(_active, _active, true); + } + this.render(); + } + _updateScales() { + each(this.scales, (scale)=>{ + layouts.removeBox(this, scale); + }); + this.ensureScalesHaveIDs(); + this.buildOrUpdateScales(); + } + _checkEventBindings() { + const options = this.options; + const existingEvents = new Set(Object.keys(this._listeners)); + const newEvents = new Set(options.events); + if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) { + this.unbindEvents(); + this.bindEvents(); + } + } + _updateHiddenIndices() { + const { _hiddenIndices } = this; + const changes = this._getUniformDataChanges() || []; + for (const { method , start , count } of changes){ + const move = method === '_removeElements' ? -count : count; + moveNumericKeys(_hiddenIndices, start, move); + } + } + _getUniformDataChanges() { + const _dataChanges = this._dataChanges; + if (!_dataChanges || !_dataChanges.length) { + return; + } + this._dataChanges = []; + const datasetCount = this.data.datasets.length; + const makeSet = (idx)=>new Set(_dataChanges.filter((c)=>c[0] === idx).map((c, i)=>i + ',' + c.splice(1).join(','))); + const changeSet = makeSet(0); + for(let i = 1; i < datasetCount; i++){ + if (!setsEqual(changeSet, makeSet(i))) { + return; + } + } + return Array.from(changeSet).map((c)=>c.split(',')).map((a)=>({ + method: a[1], + start: +a[2], + count: +a[3] + })); + } + _updateLayout(minPadding) { + if (this.notifyPlugins('beforeLayout', { + cancelable: true + }) === false) { + return; + } + layouts.update(this, this.width, this.height, minPadding); + const area = this.chartArea; + const noArea = area.width <= 0 || area.height <= 0; + this._layers = []; + each(this.boxes, (box)=>{ + if (noArea && box.position === 'chartArea') { + return; + } + if (box.configure) { + box.configure(); + } + this._layers.push(...box._layers()); + }, this); + this._layers.forEach((item, index)=>{ + item._idx = index; + }); + this.notifyPlugins('afterLayout'); + } + _updateDatasets(mode) { + if (this.notifyPlugins('beforeDatasetsUpdate', { + mode, + cancelable: true + }) === false) { + return; + } + for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){ + this.getDatasetMeta(i).controller.configure(); + } + for(let i = 0, ilen = this.data.datasets.length; i < ilen; ++i){ + this._updateDataset(i, isFunction(mode) ? mode({ + datasetIndex: i + }) : mode); + } + this.notifyPlugins('afterDatasetsUpdate', { + mode + }); + } + _updateDataset(index, mode) { + const meta = this.getDatasetMeta(index); + const args = { + meta, + index, + mode, + cancelable: true + }; + if (this.notifyPlugins('beforeDatasetUpdate', args) === false) { + return; + } + meta.controller._update(mode); + args.cancelable = false; + this.notifyPlugins('afterDatasetUpdate', args); + } + render() { + if (this.notifyPlugins('beforeRender', { + cancelable: true + }) === false) { + return; + } + if (animator.has(this)) { + if (this.attached && !animator.running(this)) { + animator.start(this); + } + } else { + this.draw(); + onAnimationsComplete({ + chart: this + }); + } + } + draw() { + let i; + if (this._resizeBeforeDraw) { + const { width , height } = this._resizeBeforeDraw; + this._resize(width, height); + this._resizeBeforeDraw = null; + } + this.clear(); + if (this.width <= 0 || this.height <= 0) { + return; + } + if (this.notifyPlugins('beforeDraw', { + cancelable: true + }) === false) { + return; + } + const layers = this._layers; + for(i = 0; i < layers.length && layers[i].z <= 0; ++i){ + layers[i].draw(this.chartArea); + } + this._drawDatasets(); + for(; i < layers.length; ++i){ + layers[i].draw(this.chartArea); + } + this.notifyPlugins('afterDraw'); + } + _getSortedDatasetMetas(filterVisible) { + const metasets = this._sortedMetasets; + const result = []; + let i, ilen; + for(i = 0, ilen = metasets.length; i < ilen; ++i){ + const meta = metasets[i]; + if (!filterVisible || meta.visible) { + result.push(meta); + } + } + return result; + } + getSortedVisibleDatasetMetas() { + return this._getSortedDatasetMetas(true); + } + _drawDatasets() { + if (this.notifyPlugins('beforeDatasetsDraw', { + cancelable: true + }) === false) { + return; + } + const metasets = this.getSortedVisibleDatasetMetas(); + for(let i = metasets.length - 1; i >= 0; --i){ + this._drawDataset(metasets[i]); + } + this.notifyPlugins('afterDatasetsDraw'); + } + _drawDataset(meta) { + const ctx = this.ctx; + const clip = meta._clip; + const useClip = !clip.disabled; + const area = getDatasetArea(meta, this.chartArea); + const args = { + meta, + index: meta.index, + cancelable: true + }; + if (this.notifyPlugins('beforeDatasetDraw', args) === false) { + return; + } + if (useClip) { + clipArea(ctx, { + left: clip.left === false ? 0 : area.left - clip.left, + right: clip.right === false ? this.width : area.right + clip.right, + top: clip.top === false ? 0 : area.top - clip.top, + bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom + }); + } + meta.controller.draw(); + if (useClip) { + unclipArea(ctx); + } + args.cancelable = false; + this.notifyPlugins('afterDatasetDraw', args); + } + isPointInArea(point) { + return _isPointInArea(point, this.chartArea, this._minPadding); + } + getElementsAtEventForMode(e, mode, options, useFinalPosition) { + const method = Interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options, useFinalPosition); + } + return []; + } + getDatasetMeta(datasetIndex) { + const dataset = this.data.datasets[datasetIndex]; + const metasets = this._metasets; + let meta = metasets.filter((x)=>x && x._dataset === dataset).pop(); + if (!meta) { + meta = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, + xAxisID: null, + yAxisID: null, + order: dataset && dataset.order || 0, + index: datasetIndex, + _dataset: dataset, + _parsed: [], + _sorted: false + }; + metasets.push(meta); + } + return meta; + } + getContext() { + return this.$context || (this.$context = createContext(null, { + chart: this, + type: 'chart' + })); + } + getVisibleDatasetCount() { + return this.getSortedVisibleDatasetMetas().length; + } + isDatasetVisible(datasetIndex) { + const dataset = this.data.datasets[datasetIndex]; + if (!dataset) { + return false; + } + const meta = this.getDatasetMeta(datasetIndex); + return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden; + } + setDatasetVisibility(datasetIndex, visible) { + const meta = this.getDatasetMeta(datasetIndex); + meta.hidden = !visible; + } + toggleDataVisibility(index) { + this._hiddenIndices[index] = !this._hiddenIndices[index]; + } + getDataVisibility(index) { + return !this._hiddenIndices[index]; + } + _updateVisibility(datasetIndex, dataIndex, visible) { + const mode = visible ? 'show' : 'hide'; + const meta = this.getDatasetMeta(datasetIndex); + const anims = meta.controller._resolveAnimations(undefined, mode); + if (defined(dataIndex)) { + meta.data[dataIndex].hidden = !visible; + this.update(); + } else { + this.setDatasetVisibility(datasetIndex, visible); + anims.update(meta, { + visible + }); + this.update((ctx)=>ctx.datasetIndex === datasetIndex ? mode : undefined); + } + } + hide(datasetIndex, dataIndex) { + this._updateVisibility(datasetIndex, dataIndex, false); + } + show(datasetIndex, dataIndex) { + this._updateVisibility(datasetIndex, dataIndex, true); + } + _destroyDatasetMeta(datasetIndex) { + const meta = this._metasets[datasetIndex]; + if (meta && meta.controller) { + meta.controller._destroy(); + } + delete this._metasets[datasetIndex]; + } + _stop() { + let i, ilen; + this.stop(); + animator.remove(this); + for(i = 0, ilen = this.data.datasets.length; i < ilen; ++i){ + this._destroyDatasetMeta(i); + } + } + destroy() { + this.notifyPlugins('beforeDestroy'); + const { canvas , ctx } = this; + this._stop(); + this.config.clearCache(); + if (canvas) { + this.unbindEvents(); + clearCanvas(canvas, ctx); + this.platform.releaseContext(ctx); + this.canvas = null; + this.ctx = null; + } + delete instances[this.id]; + this.notifyPlugins('afterDestroy'); + } + toBase64Image(...args) { + return this.canvas.toDataURL(...args); + } + bindEvents() { + this.bindUserEvents(); + if (this.options.responsive) { + this.bindResponsiveEvents(); + } else { + this.attached = true; + } + } + bindUserEvents() { + const listeners = this._listeners; + const platform = this.platform; + const _add = (type, listener)=>{ + platform.addEventListener(this, type, listener); + listeners[type] = listener; + }; + const listener = (e, x, y)=>{ + e.offsetX = x; + e.offsetY = y; + this._eventHandler(e); + }; + each(this.options.events, (type)=>_add(type, listener)); + } + bindResponsiveEvents() { + if (!this._responsiveListeners) { + this._responsiveListeners = {}; + } + const listeners = this._responsiveListeners; + const platform = this.platform; + const _add = (type, listener)=>{ + platform.addEventListener(this, type, listener); + listeners[type] = listener; + }; + const _remove = (type, listener)=>{ + if (listeners[type]) { + platform.removeEventListener(this, type, listener); + delete listeners[type]; + } + }; + const listener = (width, height)=>{ + if (this.canvas) { + this.resize(width, height); + } + }; + let detached; + const attached = ()=>{ + _remove('attach', attached); + this.attached = true; + this.resize(); + _add('resize', listener); + _add('detach', detached); + }; + detached = ()=>{ + this.attached = false; + _remove('resize', listener); + this._stop(); + this._resize(0, 0); + _add('attach', attached); + }; + if (platform.isAttached(this.canvas)) { + attached(); + } else { + detached(); + } + } + unbindEvents() { + each(this._listeners, (listener, type)=>{ + this.platform.removeEventListener(this, type, listener); + }); + this._listeners = {}; + each(this._responsiveListeners, (listener, type)=>{ + this.platform.removeEventListener(this, type, listener); + }); + this._responsiveListeners = undefined; + } + updateHoverStyle(items, mode, enabled) { + const prefix = enabled ? 'set' : 'remove'; + let meta, item, i, ilen; + if (mode === 'dataset') { + meta = this.getDatasetMeta(items[0].datasetIndex); + meta.controller['_' + prefix + 'DatasetHoverStyle'](); + } + for(i = 0, ilen = items.length; i < ilen; ++i){ + item = items[i]; + const controller = item && this.getDatasetMeta(item.datasetIndex).controller; + if (controller) { + controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index); + } + } + } + getActiveElements() { + return this._active || []; + } + setActiveElements(activeElements) { + const lastActive = this._active || []; + const active = activeElements.map(({ datasetIndex , index })=>{ + const meta = this.getDatasetMeta(datasetIndex); + if (!meta) { + throw new Error('No dataset found at index ' + datasetIndex); + } + return { + datasetIndex, + element: meta.data[index], + index + }; + }); + const changed = !_elementsEqual(active, lastActive); + if (changed) { + this._active = active; + this._lastEvent = null; + this._updateHoverStyles(active, lastActive); + } + } + notifyPlugins(hook, args, filter) { + return this._plugins.notify(this, hook, args, filter); + } + isPluginEnabled(pluginId) { + return this._plugins._cache.filter((p)=>p.plugin.id === pluginId).length === 1; + } + _updateHoverStyles(active, lastActive, replay) { + const hoverOptions = this.options.hover; + const diff = (a, b)=>a.filter((x)=>!b.some((y)=>x.datasetIndex === y.datasetIndex && x.index === y.index)); + const deactivated = diff(lastActive, active); + const activated = replay ? active : diff(active, lastActive); + if (deactivated.length) { + this.updateHoverStyle(deactivated, hoverOptions.mode, false); + } + if (activated.length && hoverOptions.mode) { + this.updateHoverStyle(activated, hoverOptions.mode, true); + } + } + _eventHandler(e, replay) { + const args = { + event: e, + replay, + cancelable: true, + inChartArea: this.isPointInArea(e) + }; + const eventFilter = (plugin)=>(plugin.options.events || this.options.events).includes(e.native.type); + if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) { + return; + } + const changed = this._handleEvent(e, replay, args.inChartArea); + args.cancelable = false; + this.notifyPlugins('afterEvent', args, eventFilter); + if (changed || args.changed) { + this.render(); + } + return this; + } + _handleEvent(e, replay, inChartArea) { + const { _active: lastActive = [] , options } = this; + const useFinalPosition = replay; + const active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition); + const isClick = _isClickEvent(e); + const lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick); + if (inChartArea) { + this._lastEvent = null; + callback(options.onHover, [ + e, + active, + this + ], this); + if (isClick) { + callback(options.onClick, [ + e, + active, + this + ], this); + } + } + const changed = !_elementsEqual(active, lastActive); + if (changed || replay) { + this._active = active; + this._updateHoverStyles(active, lastActive, replay); + } + this._lastEvent = lastEvent; + return changed; + } + _getActiveElements(e, lastActive, inChartArea, useFinalPosition) { + if (e.type === 'mouseout') { + return []; + } + if (!inChartArea) { + return lastActive; + } + const hoverOptions = this.options.hover; + return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition); + } +} +function invalidatePlugins() { + return each(Chart.instances, (chart)=>chart._plugins.invalidate()); +} + +function clipArc(ctx, element, endAngle) { + const { startAngle , pixelMargin , x , y , outerRadius , innerRadius } = element; + let angleMargin = pixelMargin / outerRadius; + // Draw an inner border by clipping the arc and drawing a double-width border + // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders + ctx.beginPath(); + ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin); + if (innerRadius > pixelMargin) { + angleMargin = pixelMargin / innerRadius; + ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true); + } else { + ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI); + } + ctx.closePath(); + ctx.clip(); +} +function toRadiusCorners(value) { + return _readValueToProps(value, [ + 'outerStart', + 'outerEnd', + 'innerStart', + 'innerEnd' + ]); +} +/** + * Parse border radius from the provided options + */ function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) { + const o = toRadiusCorners(arc.options.borderRadius); + const halfThickness = (outerRadius - innerRadius) / 2; + const innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2); + // Outer limits are complicated. We want to compute the available angular distance at + // a radius of outerRadius - borderRadius because for small angular distances, this term limits. + // We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners. + // + // If the borderRadius is large, that value can become negative. + // This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius + // we know that the thickness term will dominate and compute the limits at that point + const computeOuterLimit = (val)=>{ + const outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2; + return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit)); + }; + return { + outerStart: computeOuterLimit(o.outerStart), + outerEnd: computeOuterLimit(o.outerEnd), + innerStart: _limitValue(o.innerStart, 0, innerLimit), + innerEnd: _limitValue(o.innerEnd, 0, innerLimit) + }; +} +/** + * Convert (r, 𝜃) to (x, y) + */ function rThetaToXY(r, theta, x, y) { + return { + x: x + r * Math.cos(theta), + y: y + r * Math.sin(theta) + }; +} +/** + * Path the arc, respecting border radius by separating into left and right halves. + * + * Start End + * + * 1--->a--->2 Outer + * / \ + * 8 3 + * | | + * | | + * 7 4 + * \ / + * 6<---b<---5 Inner + */ function pathArc(ctx, element, offset, spacing, end, circular) { + const { x , y , startAngle: start , pixelMargin , innerRadius: innerR } = element; + const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0); + const innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0; + let spacingOffset = 0; + const alpha = end - start; + if (spacing) { + // When spacing is present, it is the same for all items + // So we adjust the start and end angle of the arc such that + // the distance is the same as it would be without the spacing + const noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0; + const noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0; + const avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2; + const adjustedAngle = avNogSpacingRadius !== 0 ? alpha * avNogSpacingRadius / (avNogSpacingRadius + spacing) : alpha; + spacingOffset = (alpha - adjustedAngle) / 2; + } + const beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius; + const angleOffset = (alpha - beta) / 2; + const startAngle = start + angleOffset + spacingOffset; + const endAngle = end - angleOffset - spacingOffset; + const { outerStart , outerEnd , innerStart , innerEnd } = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle); + const outerStartAdjustedRadius = outerRadius - outerStart; + const outerEndAdjustedRadius = outerRadius - outerEnd; + const outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius; + const outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius; + const innerStartAdjustedRadius = innerRadius + innerStart; + const innerEndAdjustedRadius = innerRadius + innerEnd; + const innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius; + const innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius; + ctx.beginPath(); + if (circular) { + // The first arc segments from point 1 to point a to point 2 + const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2; + ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle); + ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle); + // The corner segment from point 2 to point 3 + if (outerEnd > 0) { + const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y); + ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI); + } + // The line from point 3 to point 4 + const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y); + ctx.lineTo(p4.x, p4.y); + // The corner segment from point 4 to point 5 + if (innerEnd > 0) { + const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y); + ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI); + } + // The inner arc from point 5 to point b to point 6 + const innerMidAdjustedAngle = (endAngle - innerEnd / innerRadius + (startAngle + innerStart / innerRadius)) / 2; + ctx.arc(x, y, innerRadius, endAngle - innerEnd / innerRadius, innerMidAdjustedAngle, true); + ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + innerStart / innerRadius, true); + // The corner segment from point 6 to point 7 + if (innerStart > 0) { + const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y); + ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI); + } + // The line from point 7 to point 8 + const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y); + ctx.lineTo(p8.x, p8.y); + // The corner segment from point 8 to point 1 + if (outerStart > 0) { + const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y); + ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle); + } + } else { + ctx.moveTo(x, y); + const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x; + const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y; + ctx.lineTo(outerStartX, outerStartY); + const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x; + const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y; + ctx.lineTo(outerEndX, outerEndY); + } + ctx.closePath(); +} +function drawArc(ctx, element, offset, spacing, circular) { + const { fullCircles , startAngle , circumference } = element; + let endAngle = element.endAngle; + if (fullCircles) { + pathArc(ctx, element, offset, spacing, endAngle, circular); + for(let i = 0; i < fullCircles; ++i){ + ctx.fill(); + } + if (!isNaN(circumference)) { + endAngle = startAngle + (circumference % TAU || TAU); + } + } + pathArc(ctx, element, offset, spacing, endAngle, circular); + ctx.fill(); + return endAngle; +} +function drawBorder(ctx, element, offset, spacing, circular) { + const { fullCircles , startAngle , circumference , options } = element; + const { borderWidth , borderJoinStyle , borderDash , borderDashOffset } = options; + const inner = options.borderAlign === 'inner'; + if (!borderWidth) { + return; + } + ctx.setLineDash(borderDash || []); + ctx.lineDashOffset = borderDashOffset; + if (inner) { + ctx.lineWidth = borderWidth * 2; + ctx.lineJoin = borderJoinStyle || 'round'; + } else { + ctx.lineWidth = borderWidth; + ctx.lineJoin = borderJoinStyle || 'bevel'; + } + let endAngle = element.endAngle; + if (fullCircles) { + pathArc(ctx, element, offset, spacing, endAngle, circular); + for(let i = 0; i < fullCircles; ++i){ + ctx.stroke(); + } + if (!isNaN(circumference)) { + endAngle = startAngle + (circumference % TAU || TAU); + } + } + if (inner) { + clipArc(ctx, element, endAngle); + } + if (!fullCircles) { + pathArc(ctx, element, offset, spacing, endAngle, circular); + ctx.stroke(); + } +} +class ArcElement extends Element { + static id = 'arc'; + static defaults = { + borderAlign: 'center', + borderColor: '#fff', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: undefined, + borderRadius: 0, + borderWidth: 2, + offset: 0, + spacing: 0, + angle: undefined, + circular: true + }; + static defaultRoutes = { + backgroundColor: 'backgroundColor' + }; + static descriptors = { + _scriptable: true, + _indexable: (name)=>name !== 'borderDash' + }; + circumference; + endAngle; + fullCircles; + innerRadius; + outerRadius; + pixelMargin; + startAngle; + constructor(cfg){ + super(); + this.options = undefined; + this.circumference = undefined; + this.startAngle = undefined; + this.endAngle = undefined; + this.innerRadius = undefined; + this.outerRadius = undefined; + this.pixelMargin = 0; + this.fullCircles = 0; + if (cfg) { + Object.assign(this, cfg); + } + } + inRange(chartX, chartY, useFinalPosition) { + const point = this.getProps([ + 'x', + 'y' + ], useFinalPosition); + const { angle , distance } = getAngleFromPoint(point, { + x: chartX, + y: chartY + }); + const { startAngle , endAngle , innerRadius , outerRadius , circumference } = this.getProps([ + 'startAngle', + 'endAngle', + 'innerRadius', + 'outerRadius', + 'circumference' + ], useFinalPosition); + const rAdjust = (this.options.spacing + this.options.borderWidth) / 2; + const _circumference = valueOrDefault(circumference, endAngle - startAngle); + const betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle); + const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust); + return betweenAngles && withinRadius; + } + getCenterPoint(useFinalPosition) { + const { x , y , startAngle , endAngle , innerRadius , outerRadius } = this.getProps([ + 'x', + 'y', + 'startAngle', + 'endAngle', + 'innerRadius', + 'outerRadius' + ], useFinalPosition); + const { offset , spacing } = this.options; + const halfAngle = (startAngle + endAngle) / 2; + const halfRadius = (innerRadius + outerRadius + spacing + offset) / 2; + return { + x: x + Math.cos(halfAngle) * halfRadius, + y: y + Math.sin(halfAngle) * halfRadius + }; + } + tooltipPosition(useFinalPosition) { + return this.getCenterPoint(useFinalPosition); + } + draw(ctx) { + const { options , circumference } = this; + const offset = (options.offset || 0) / 4; + const spacing = (options.spacing || 0) / 2; + const circular = options.circular; + this.pixelMargin = options.borderAlign === 'inner' ? 0.33 : 0; + this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0; + if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) { + return; + } + ctx.save(); + const halfAngle = (this.startAngle + this.endAngle) / 2; + ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset); + const fix = 1 - Math.sin(Math.min(PI, circumference || 0)); + const radiusOffset = offset * fix; + ctx.fillStyle = options.backgroundColor; + ctx.strokeStyle = options.borderColor; + drawArc(ctx, this, radiusOffset, spacing, circular); + drawBorder(ctx, this, radiusOffset, spacing, circular); + ctx.restore(); + } +} + +function setStyle(ctx, options, style = options) { + ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle); + ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash)); + ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset); + ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle); + ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth); + ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor); +} +function lineTo(ctx, previous, target) { + ctx.lineTo(target.x, target.y); +} + function getLineMethod(options) { + if (options.stepped) { + return _steppedLineTo; + } + if (options.tension || options.cubicInterpolationMode === 'monotone') { + return _bezierCurveTo; + } + return lineTo; +} +function pathVars(points, segment, params = {}) { + const count = points.length; + const { start: paramsStart = 0 , end: paramsEnd = count - 1 } = params; + const { start: segmentStart , end: segmentEnd } = segment; + const start = Math.max(paramsStart, segmentStart); + const end = Math.min(paramsEnd, segmentEnd); + const outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd; + return { + count, + start, + loop: segment.loop, + ilen: end < start && !outside ? count + end - start : end - start + }; +} + function pathSegment(ctx, line, segment, params) { + const { points , options } = line; + const { count , start , loop , ilen } = pathVars(points, segment, params); + const lineMethod = getLineMethod(options); + let { move =true , reverse } = params || {}; + let i, point, prev; + for(i = 0; i <= ilen; ++i){ + point = points[(start + (reverse ? ilen - i : i)) % count]; + if (point.skip) { + continue; + } else if (move) { + ctx.moveTo(point.x, point.y); + move = false; + } else { + lineMethod(ctx, prev, point, reverse, options.stepped); + } + prev = point; + } + if (loop) { + point = points[(start + (reverse ? ilen : 0)) % count]; + lineMethod(ctx, prev, point, reverse, options.stepped); + } + return !!loop; +} + function fastPathSegment(ctx, line, segment, params) { + const points = line.points; + const { count , start , ilen } = pathVars(points, segment, params); + const { move =true , reverse } = params || {}; + let avgX = 0; + let countX = 0; + let i, point, prevX, minY, maxY, lastY; + const pointIndex = (index)=>(start + (reverse ? ilen - index : index)) % count; + const drawX = ()=>{ + if (minY !== maxY) { + ctx.lineTo(avgX, maxY); + ctx.lineTo(avgX, minY); + ctx.lineTo(avgX, lastY); + } + }; + if (move) { + point = points[pointIndex(0)]; + ctx.moveTo(point.x, point.y); + } + for(i = 0; i <= ilen; ++i){ + point = points[pointIndex(i)]; + if (point.skip) { + continue; + } + const x = point.x; + const y = point.y; + const truncX = x | 0; + if (truncX === prevX) { + if (y < minY) { + minY = y; + } else if (y > maxY) { + maxY = y; + } + avgX = (countX * avgX + x) / ++countX; + } else { + drawX(); + ctx.lineTo(x, y); + prevX = truncX; + countX = 0; + minY = maxY = y; + } + lastY = y; + } + drawX(); +} + function _getSegmentMethod(line) { + const opts = line.options; + const borderDash = opts.borderDash && opts.borderDash.length; + const useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash; + return useFastPath ? fastPathSegment : pathSegment; +} + function _getInterpolationMethod(options) { + if (options.stepped) { + return _steppedInterpolation; + } + if (options.tension || options.cubicInterpolationMode === 'monotone') { + return _bezierInterpolation; + } + return _pointInLine; +} +function strokePathWithCache(ctx, line, start, count) { + let path = line._path; + if (!path) { + path = line._path = new Path2D(); + if (line.path(path, start, count)) { + path.closePath(); + } + } + setStyle(ctx, line.options); + ctx.stroke(path); +} +function strokePathDirect(ctx, line, start, count) { + const { segments , options } = line; + const segmentMethod = _getSegmentMethod(line); + for (const segment of segments){ + setStyle(ctx, options, segment.style); + ctx.beginPath(); + if (segmentMethod(ctx, line, segment, { + start, + end: start + count - 1 + })) { + ctx.closePath(); + } + ctx.stroke(); + } +} +const usePath2D = typeof Path2D === 'function'; +function draw(ctx, line, start, count) { + if (usePath2D && !line.options.segment) { + strokePathWithCache(ctx, line, start, count); + } else { + strokePathDirect(ctx, line, start, count); + } +} +class LineElement extends Element { + static id = 'line'; + static defaults = { + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: 'miter', + borderWidth: 3, + capBezierPoints: true, + cubicInterpolationMode: 'default', + fill: false, + spanGaps: false, + stepped: false, + tension: 0 + }; + static defaultRoutes = { + backgroundColor: 'backgroundColor', + borderColor: 'borderColor' + }; + static descriptors = { + _scriptable: true, + _indexable: (name)=>name !== 'borderDash' && name !== 'fill' + }; + constructor(cfg){ + super(); + this.animated = true; + this.options = undefined; + this._chart = undefined; + this._loop = undefined; + this._fullLoop = undefined; + this._path = undefined; + this._points = undefined; + this._segments = undefined; + this._decimated = false; + this._pointsUpdated = false; + this._datasetIndex = undefined; + if (cfg) { + Object.assign(this, cfg); + } + } + updateControlPoints(chartArea, indexAxis) { + const options = this.options; + if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) { + const loop = options.spanGaps ? this._loop : this._fullLoop; + _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis); + this._pointsUpdated = true; + } + } + set points(points) { + this._points = points; + delete this._segments; + delete this._path; + this._pointsUpdated = false; + } + get points() { + return this._points; + } + get segments() { + return this._segments || (this._segments = _computeSegments(this, this.options.segment)); + } + first() { + const segments = this.segments; + const points = this.points; + return segments.length && points[segments[0].start]; + } + last() { + const segments = this.segments; + const points = this.points; + const count = segments.length; + return count && points[segments[count - 1].end]; + } + interpolate(point, property) { + const options = this.options; + const value = point[property]; + const points = this.points; + const segments = _boundSegments(this, { + property, + start: value, + end: value + }); + if (!segments.length) { + return; + } + const result = []; + const _interpolate = _getInterpolationMethod(options); + let i, ilen; + for(i = 0, ilen = segments.length; i < ilen; ++i){ + const { start , end } = segments[i]; + const p1 = points[start]; + const p2 = points[end]; + if (p1 === p2) { + result.push(p1); + continue; + } + const t = Math.abs((value - p1[property]) / (p2[property] - p1[property])); + const interpolated = _interpolate(p1, p2, t, options.stepped); + interpolated[property] = point[property]; + result.push(interpolated); + } + return result.length === 1 ? result[0] : result; + } + pathSegment(ctx, segment, params) { + const segmentMethod = _getSegmentMethod(this); + return segmentMethod(ctx, this, segment, params); + } + path(ctx, start, count) { + const segments = this.segments; + const segmentMethod = _getSegmentMethod(this); + let loop = this._loop; + start = start || 0; + count = count || this.points.length - start; + for (const segment of segments){ + loop &= segmentMethod(ctx, this, segment, { + start, + end: start + count - 1 + }); + } + return !!loop; + } + draw(ctx, chartArea, start, count) { + const options = this.options || {}; + const points = this.points || []; + if (points.length && options.borderWidth) { + ctx.save(); + draw(ctx, this, start, count); + ctx.restore(); + } + if (this.animated) { + this._pointsUpdated = false; + this._path = undefined; + } + } +} + +function inRange$1(el, pos, axis, useFinalPosition) { + const options = el.options; + const { [axis]: value } = el.getProps([ + axis + ], useFinalPosition); + return Math.abs(pos - value) < options.radius + options.hitRadius; +} +class PointElement extends Element { + static id = 'point'; + parsed; + skip; + stop; + /** + * @type {any} + */ static defaults = { + borderWidth: 1, + hitRadius: 1, + hoverBorderWidth: 1, + hoverRadius: 4, + pointStyle: 'circle', + radius: 3, + rotation: 0 + }; + /** + * @type {any} + */ static defaultRoutes = { + backgroundColor: 'backgroundColor', + borderColor: 'borderColor' + }; + constructor(cfg){ + super(); + this.options = undefined; + this.parsed = undefined; + this.skip = undefined; + this.stop = undefined; + if (cfg) { + Object.assign(this, cfg); + } + } + inRange(mouseX, mouseY, useFinalPosition) { + const options = this.options; + const { x , y } = this.getProps([ + 'x', + 'y' + ], useFinalPosition); + return Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2) < Math.pow(options.hitRadius + options.radius, 2); + } + inXRange(mouseX, useFinalPosition) { + return inRange$1(this, mouseX, 'x', useFinalPosition); + } + inYRange(mouseY, useFinalPosition) { + return inRange$1(this, mouseY, 'y', useFinalPosition); + } + getCenterPoint(useFinalPosition) { + const { x , y } = this.getProps([ + 'x', + 'y' + ], useFinalPosition); + return { + x, + y + }; + } + size(options) { + options = options || this.options || {}; + let radius = options.radius || 0; + radius = Math.max(radius, radius && options.hoverRadius || 0); + const borderWidth = radius && options.borderWidth || 0; + return (radius + borderWidth) * 2; + } + draw(ctx, area) { + const options = this.options; + if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) { + return; + } + ctx.strokeStyle = options.borderColor; + ctx.lineWidth = options.borderWidth; + ctx.fillStyle = options.backgroundColor; + drawPoint(ctx, options, this.x, this.y); + } + getRange() { + const options = this.options || {}; + // @ts-expect-error Fallbacks should never be hit in practice + return options.radius + options.hitRadius; + } +} + +function getBarBounds(bar, useFinalPosition) { + const { x , y , base , width , height } = bar.getProps([ + 'x', + 'y', + 'base', + 'width', + 'height' + ], useFinalPosition); + let left, right, top, bottom, half; + if (bar.horizontal) { + half = height / 2; + left = Math.min(x, base); + right = Math.max(x, base); + top = y - half; + bottom = y + half; + } else { + half = width / 2; + left = x - half; + right = x + half; + top = Math.min(y, base); + bottom = Math.max(y, base); + } + return { + left, + top, + right, + bottom + }; +} +function skipOrLimit(skip, value, min, max) { + return skip ? 0 : _limitValue(value, min, max); +} +function parseBorderWidth(bar, maxW, maxH) { + const value = bar.options.borderWidth; + const skip = bar.borderSkipped; + const o = toTRBL(value); + return { + t: skipOrLimit(skip.top, o.top, 0, maxH), + r: skipOrLimit(skip.right, o.right, 0, maxW), + b: skipOrLimit(skip.bottom, o.bottom, 0, maxH), + l: skipOrLimit(skip.left, o.left, 0, maxW) + }; +} +function parseBorderRadius(bar, maxW, maxH) { + const { enableBorderRadius } = bar.getProps([ + 'enableBorderRadius' + ]); + const value = bar.options.borderRadius; + const o = toTRBLCorners(value); + const maxR = Math.min(maxW, maxH); + const skip = bar.borderSkipped; + const enableBorder = enableBorderRadius || isObject(value); + return { + topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR), + topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR), + bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR), + bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR) + }; +} +function boundingRects(bar) { + const bounds = getBarBounds(bar); + const width = bounds.right - bounds.left; + const height = bounds.bottom - bounds.top; + const border = parseBorderWidth(bar, width / 2, height / 2); + const radius = parseBorderRadius(bar, width / 2, height / 2); + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height, + radius + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b, + radius: { + topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)), + topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)), + bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)), + bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r)) + } + } + }; +} +function inRange(bar, x, y, useFinalPosition) { + const skipX = x === null; + const skipY = y === null; + const skipBoth = skipX && skipY; + const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition); + return bounds && (skipX || _isBetween(x, bounds.left, bounds.right)) && (skipY || _isBetween(y, bounds.top, bounds.bottom)); +} +function hasRadius(radius) { + return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight; +} + function addNormalRectPath(ctx, rect) { + ctx.rect(rect.x, rect.y, rect.w, rect.h); +} +function inflateRect(rect, amount, refRect = {}) { + const x = rect.x !== refRect.x ? -amount : 0; + const y = rect.y !== refRect.y ? -amount : 0; + const w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x; + const h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y; + return { + x: rect.x + x, + y: rect.y + y, + w: rect.w + w, + h: rect.h + h, + radius: rect.radius + }; +} +class BarElement extends Element { + static id = 'bar'; + static defaults = { + borderSkipped: 'start', + borderWidth: 0, + borderRadius: 0, + inflateAmount: 'auto', + pointStyle: undefined + }; + static defaultRoutes = { + backgroundColor: 'backgroundColor', + borderColor: 'borderColor' + }; + constructor(cfg){ + super(); + this.options = undefined; + this.horizontal = undefined; + this.base = undefined; + this.width = undefined; + this.height = undefined; + this.inflateAmount = undefined; + if (cfg) { + Object.assign(this, cfg); + } + } + draw(ctx) { + const { inflateAmount , options: { borderColor , backgroundColor } } = this; + const { inner , outer } = boundingRects(this); + const addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath; + ctx.save(); + if (outer.w !== inner.w || outer.h !== inner.h) { + ctx.beginPath(); + addRectPath(ctx, inflateRect(outer, inflateAmount, inner)); + ctx.clip(); + addRectPath(ctx, inflateRect(inner, -inflateAmount, outer)); + ctx.fillStyle = borderColor; + ctx.fill('evenodd'); + } + ctx.beginPath(); + addRectPath(ctx, inflateRect(inner, inflateAmount)); + ctx.fillStyle = backgroundColor; + ctx.fill(); + ctx.restore(); + } + inRange(mouseX, mouseY, useFinalPosition) { + return inRange(this, mouseX, mouseY, useFinalPosition); + } + inXRange(mouseX, useFinalPosition) { + return inRange(this, mouseX, null, useFinalPosition); + } + inYRange(mouseY, useFinalPosition) { + return inRange(this, null, mouseY, useFinalPosition); + } + getCenterPoint(useFinalPosition) { + const { x , y , base , horizontal } = this.getProps([ + 'x', + 'y', + 'base', + 'horizontal' + ], useFinalPosition); + return { + x: horizontal ? (x + base) / 2 : x, + y: horizontal ? y : (y + base) / 2 + }; + } + getRange(axis) { + return axis === 'x' ? this.width / 2 : this.height / 2; + } +} + +var elements = /*#__PURE__*/Object.freeze({ +__proto__: null, +ArcElement: ArcElement, +BarElement: BarElement, +LineElement: LineElement, +PointElement: PointElement +}); + +const BORDER_COLORS = [ + 'rgb(54, 162, 235)', + 'rgb(255, 99, 132)', + 'rgb(255, 159, 64)', + 'rgb(255, 205, 86)', + 'rgb(75, 192, 192)', + 'rgb(153, 102, 255)', + 'rgb(201, 203, 207)' // grey +]; +// Border colors with 50% transparency +const BACKGROUND_COLORS = /* #__PURE__ */ BORDER_COLORS.map((color)=>color.replace('rgb(', 'rgba(').replace(')', ', 0.5)')); +function getBorderColor(i) { + return BORDER_COLORS[i % BORDER_COLORS.length]; +} +function getBackgroundColor(i) { + return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length]; +} +function colorizeDefaultDataset(dataset, i) { + dataset.borderColor = getBorderColor(i); + dataset.backgroundColor = getBackgroundColor(i); + return ++i; +} +function colorizeDoughnutDataset(dataset, i) { + dataset.backgroundColor = dataset.data.map(()=>getBorderColor(i++)); + return i; +} +function colorizePolarAreaDataset(dataset, i) { + dataset.backgroundColor = dataset.data.map(()=>getBackgroundColor(i++)); + return i; +} +function getColorizer(chart) { + let i = 0; + return (dataset, datasetIndex)=>{ + const controller = chart.getDatasetMeta(datasetIndex).controller; + if (controller instanceof DoughnutController) { + i = colorizeDoughnutDataset(dataset, i); + } else if (controller instanceof PolarAreaController) { + i = colorizePolarAreaDataset(dataset, i); + } else if (controller) { + i = colorizeDefaultDataset(dataset, i); + } + }; +} +function containsColorsDefinitions(descriptors) { + let k; + for(k in descriptors){ + if (descriptors[k].borderColor || descriptors[k].backgroundColor) { + return true; + } + } + return false; +} +function containsColorsDefinition(descriptor) { + return descriptor && (descriptor.borderColor || descriptor.backgroundColor); +} +var plugin_colors = { + id: 'colors', + defaults: { + enabled: true, + forceOverride: false + }, + beforeLayout (chart, _args, options) { + if (!options.enabled) { + return; + } + const { data: { datasets } , options: chartOptions } = chart.config; + const { elements } = chartOptions; + if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || elements && containsColorsDefinitions(elements))) { + return; + } + const colorizer = getColorizer(chart); + datasets.forEach(colorizer); + } +}; + +function lttbDecimation(data, start, count, availableWidth, options) { + const samples = options.samples || availableWidth; + if (samples >= count) { + return data.slice(start, start + count); + } + const decimated = []; + const bucketWidth = (count - 2) / (samples - 2); + let sampledIndex = 0; + const endIndex = start + count - 1; + let a = start; + let i, maxAreaPoint, maxArea, area, nextA; + decimated[sampledIndex++] = data[a]; + for(i = 0; i < samples - 2; i++){ + let avgX = 0; + let avgY = 0; + let j; + const avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start; + const avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start; + const avgRangeLength = avgRangeEnd - avgRangeStart; + for(j = avgRangeStart; j < avgRangeEnd; j++){ + avgX += data[j].x; + avgY += data[j].y; + } + avgX /= avgRangeLength; + avgY /= avgRangeLength; + const rangeOffs = Math.floor(i * bucketWidth) + 1 + start; + const rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start; + const { x: pointAx , y: pointAy } = data[a]; + maxArea = area = -1; + for(j = rangeOffs; j < rangeTo; j++){ + area = 0.5 * Math.abs((pointAx - avgX) * (data[j].y - pointAy) - (pointAx - data[j].x) * (avgY - pointAy)); + if (area > maxArea) { + maxArea = area; + maxAreaPoint = data[j]; + nextA = j; + } + } + decimated[sampledIndex++] = maxAreaPoint; + a = nextA; + } + decimated[sampledIndex++] = data[endIndex]; + return decimated; +} +function minMaxDecimation(data, start, count, availableWidth) { + let avgX = 0; + let countX = 0; + let i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY; + const decimated = []; + const endIndex = start + count - 1; + const xMin = data[start].x; + const xMax = data[endIndex].x; + const dx = xMax - xMin; + for(i = start; i < start + count; ++i){ + point = data[i]; + x = (point.x - xMin) / dx * availableWidth; + y = point.y; + const truncX = x | 0; + if (truncX === prevX) { + if (y < minY) { + minY = y; + minIndex = i; + } else if (y > maxY) { + maxY = y; + maxIndex = i; + } + avgX = (countX * avgX + point.x) / ++countX; + } else { + const lastIndex = i - 1; + if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) { + const intermediateIndex1 = Math.min(minIndex, maxIndex); + const intermediateIndex2 = Math.max(minIndex, maxIndex); + if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) { + decimated.push({ + ...data[intermediateIndex1], + x: avgX + }); + } + if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) { + decimated.push({ + ...data[intermediateIndex2], + x: avgX + }); + } + } + if (i > 0 && lastIndex !== startIndex) { + decimated.push(data[lastIndex]); + } + decimated.push(point); + prevX = truncX; + countX = 0; + minY = maxY = y; + minIndex = maxIndex = startIndex = i; + } + } + return decimated; +} +function cleanDecimatedDataset(dataset) { + if (dataset._decimated) { + const data = dataset._data; + delete dataset._decimated; + delete dataset._data; + Object.defineProperty(dataset, 'data', { + configurable: true, + enumerable: true, + writable: true, + value: data + }); + } +} +function cleanDecimatedData(chart) { + chart.data.datasets.forEach((dataset)=>{ + cleanDecimatedDataset(dataset); + }); +} +function getStartAndCountOfVisiblePointsSimplified(meta, points) { + const pointCount = points.length; + let start = 0; + let count; + const { iScale } = meta; + const { min , max , minDefined , maxDefined } = iScale.getUserBounds(); + if (minDefined) { + start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1); + } + if (maxDefined) { + count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start; + } else { + count = pointCount - start; + } + return { + start, + count + }; +} +var plugin_decimation = { + id: 'decimation', + defaults: { + algorithm: 'min-max', + enabled: false + }, + beforeElementsUpdate: (chart, args, options)=>{ + if (!options.enabled) { + cleanDecimatedData(chart); + return; + } + const availableWidth = chart.width; + chart.data.datasets.forEach((dataset, datasetIndex)=>{ + const { _data , indexAxis } = dataset; + const meta = chart.getDatasetMeta(datasetIndex); + const data = _data || dataset.data; + if (resolve([ + indexAxis, + chart.options.indexAxis + ]) === 'y') { + return; + } + if (!meta.controller.supportsDecimation) { + return; + } + const xAxis = chart.scales[meta.xAxisID]; + if (xAxis.type !== 'linear' && xAxis.type !== 'time') { + return; + } + if (chart.options.parsing) { + return; + } + let { start , count } = getStartAndCountOfVisiblePointsSimplified(meta, data); + const threshold = options.threshold || 4 * availableWidth; + if (count <= threshold) { + cleanDecimatedDataset(dataset); + return; + } + if (isNullOrUndef(_data)) { + dataset._data = data; + delete dataset.data; + Object.defineProperty(dataset, 'data', { + configurable: true, + enumerable: true, + get: function() { + return this._decimated; + }, + set: function(d) { + this._data = d; + } + }); + } + let decimated; + switch(options.algorithm){ + case 'lttb': + decimated = lttbDecimation(data, start, count, availableWidth, options); + break; + case 'min-max': + decimated = minMaxDecimation(data, start, count, availableWidth); + break; + default: + throw new Error(`Unsupported decimation algorithm '${options.algorithm}'`); + } + dataset._decimated = decimated; + }); + }, + destroy (chart) { + cleanDecimatedData(chart); + } +}; + +function _segments(line, target, property) { + const segments = line.segments; + const points = line.points; + const tpoints = target.points; + const parts = []; + for (const segment of segments){ + let { start , end } = segment; + end = _findSegmentEnd(start, end, points); + const bounds = _getBounds(property, points[start], points[end], segment.loop); + if (!target.segments) { + parts.push({ + source: segment, + target: bounds, + start: points[start], + end: points[end] + }); + continue; + } + const targetSegments = _boundSegments(target, bounds); + for (const tgt of targetSegments){ + const subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop); + const fillSources = _boundSegment(segment, points, subBounds); + for (const fillSource of fillSources){ + parts.push({ + source: fillSource, + target: tgt, + start: { + [property]: _getEdge(bounds, subBounds, 'start', Math.max) + }, + end: { + [property]: _getEdge(bounds, subBounds, 'end', Math.min) + } + }); + } + } + } + return parts; +} +function _getBounds(property, first, last, loop) { + if (loop) { + return; + } + let start = first[property]; + let end = last[property]; + if (property === 'angle') { + start = _normalizeAngle(start); + end = _normalizeAngle(end); + } + return { + property, + start, + end + }; +} +function _pointsFromSegments(boundary, line) { + const { x =null , y =null } = boundary || {}; + const linePoints = line.points; + const points = []; + line.segments.forEach(({ start , end })=>{ + end = _findSegmentEnd(start, end, linePoints); + const first = linePoints[start]; + const last = linePoints[end]; + if (y !== null) { + points.push({ + x: first.x, + y + }); + points.push({ + x: last.x, + y + }); + } else if (x !== null) { + points.push({ + x, + y: first.y + }); + points.push({ + x, + y: last.y + }); + } + }); + return points; +} +function _findSegmentEnd(start, end, points) { + for(; end > start; end--){ + const point = points[end]; + if (!isNaN(point.x) && !isNaN(point.y)) { + break; + } + } + return end; +} +function _getEdge(a, b, prop, fn) { + if (a && b) { + return fn(a[prop], b[prop]); + } + return a ? a[prop] : b ? b[prop] : 0; +} + +function _createBoundaryLine(boundary, line) { + let points = []; + let _loop = false; + if (isArray(boundary)) { + _loop = true; + points = boundary; + } else { + points = _pointsFromSegments(boundary, line); + } + return points.length ? new LineElement({ + points, + options: { + tension: 0 + }, + _loop, + _fullLoop: _loop + }) : null; +} +function _shouldApplyFill(source) { + return source && source.fill !== false; +} + +function _resolveTarget(sources, index, propagate) { + const source = sources[index]; + let fill = source.fill; + const visited = [ + index + ]; + let target; + if (!propagate) { + return fill; + } + while(fill !== false && visited.indexOf(fill) === -1){ + if (!isNumberFinite(fill)) { + return fill; + } + target = sources[fill]; + if (!target) { + return false; + } + if (target.visible) { + return fill; + } + visited.push(fill); + fill = target.fill; + } + return false; +} + function _decodeFill(line, index, count) { + const fill = parseFillOption(line); + if (isObject(fill)) { + return isNaN(fill.value) ? false : fill; + } + let target = parseFloat(fill); + if (isNumberFinite(target) && Math.floor(target) === target) { + return decodeTargetIndex(fill[0], index, target, count); + } + return [ + 'origin', + 'start', + 'end', + 'stack', + 'shape' + ].indexOf(fill) >= 0 && fill; +} +function decodeTargetIndex(firstCh, index, target, count) { + if (firstCh === '-' || firstCh === '+') { + target = index + target; + } + if (target === index || target < 0 || target >= count) { + return false; + } + return target; +} + function _getTargetPixel(fill, scale) { + let pixel = null; + if (fill === 'start') { + pixel = scale.bottom; + } else if (fill === 'end') { + pixel = scale.top; + } else if (isObject(fill)) { + pixel = scale.getPixelForValue(fill.value); + } else if (scale.getBasePixel) { + pixel = scale.getBasePixel(); + } + return pixel; +} + function _getTargetValue(fill, scale, startValue) { + let value; + if (fill === 'start') { + value = startValue; + } else if (fill === 'end') { + value = scale.options.reverse ? scale.min : scale.max; + } else if (isObject(fill)) { + value = fill.value; + } else { + value = scale.getBaseValue(); + } + return value; +} + function parseFillOption(line) { + const options = line.options; + const fillOption = options.fill; + let fill = valueOrDefault(fillOption && fillOption.target, fillOption); + if (fill === undefined) { + fill = !!options.backgroundColor; + } + if (fill === false || fill === null) { + return false; + } + if (fill === true) { + return 'origin'; + } + return fill; +} + +function _buildStackLine(source) { + const { scale , index , line } = source; + const points = []; + const segments = line.segments; + const sourcePoints = line.points; + const linesBelow = getLinesBelow(scale, index); + linesBelow.push(_createBoundaryLine({ + x: null, + y: scale.bottom + }, line)); + for(let i = 0; i < segments.length; i++){ + const segment = segments[i]; + for(let j = segment.start; j <= segment.end; j++){ + addPointsBelow(points, sourcePoints[j], linesBelow); + } + } + return new LineElement({ + points, + options: {} + }); +} + function getLinesBelow(scale, index) { + const below = []; + const metas = scale.getMatchingVisibleMetas('line'); + for(let i = 0; i < metas.length; i++){ + const meta = metas[i]; + if (meta.index === index) { + break; + } + if (!meta.hidden) { + below.unshift(meta.dataset); + } + } + return below; +} + function addPointsBelow(points, sourcePoint, linesBelow) { + const postponed = []; + for(let j = 0; j < linesBelow.length; j++){ + const line = linesBelow[j]; + const { first , last , point } = findPoint(line, sourcePoint, 'x'); + if (!point || first && last) { + continue; + } + if (first) { + postponed.unshift(point); + } else { + points.push(point); + if (!last) { + break; + } + } + } + points.push(...postponed); +} + function findPoint(line, sourcePoint, property) { + const point = line.interpolate(sourcePoint, property); + if (!point) { + return {}; + } + const pointValue = point[property]; + const segments = line.segments; + const linePoints = line.points; + let first = false; + let last = false; + for(let i = 0; i < segments.length; i++){ + const segment = segments[i]; + const firstValue = linePoints[segment.start][property]; + const lastValue = linePoints[segment.end][property]; + if (_isBetween(pointValue, firstValue, lastValue)) { + first = pointValue === firstValue; + last = pointValue === lastValue; + break; + } + } + return { + first, + last, + point + }; +} + +class simpleArc { + constructor(opts){ + this.x = opts.x; + this.y = opts.y; + this.radius = opts.radius; + } + pathSegment(ctx, bounds, opts) { + const { x , y , radius } = this; + bounds = bounds || { + start: 0, + end: TAU + }; + ctx.arc(x, y, radius, bounds.end, bounds.start, true); + return !opts.bounds; + } + interpolate(point) { + const { x , y , radius } = this; + const angle = point.angle; + return { + x: x + Math.cos(angle) * radius, + y: y + Math.sin(angle) * radius, + angle + }; + } +} + +function _getTarget(source) { + const { chart , fill , line } = source; + if (isNumberFinite(fill)) { + return getLineByIndex(chart, fill); + } + if (fill === 'stack') { + return _buildStackLine(source); + } + if (fill === 'shape') { + return true; + } + const boundary = computeBoundary(source); + if (boundary instanceof simpleArc) { + return boundary; + } + return _createBoundaryLine(boundary, line); +} + function getLineByIndex(chart, index) { + const meta = chart.getDatasetMeta(index); + const visible = meta && chart.isDatasetVisible(index); + return visible ? meta.dataset : null; +} +function computeBoundary(source) { + const scale = source.scale || {}; + if (scale.getPointPositionForValue) { + return computeCircularBoundary(source); + } + return computeLinearBoundary(source); +} +function computeLinearBoundary(source) { + const { scale ={} , fill } = source; + const pixel = _getTargetPixel(fill, scale); + if (isNumberFinite(pixel)) { + const horizontal = scale.isHorizontal(); + return { + x: horizontal ? pixel : null, + y: horizontal ? null : pixel + }; + } + return null; +} +function computeCircularBoundary(source) { + const { scale , fill } = source; + const options = scale.options; + const length = scale.getLabels().length; + const start = options.reverse ? scale.max : scale.min; + const value = _getTargetValue(fill, scale, start); + const target = []; + if (options.grid.circular) { + const center = scale.getPointPositionForValue(0, start); + return new simpleArc({ + x: center.x, + y: center.y, + radius: scale.getDistanceFromCenterForValue(value) + }); + } + for(let i = 0; i < length; ++i){ + target.push(scale.getPointPositionForValue(i, value)); + } + return target; +} + +function _drawfill(ctx, source, area) { + const target = _getTarget(source); + const { line , scale , axis } = source; + const lineOpts = line.options; + const fillOption = lineOpts.fill; + const color = lineOpts.backgroundColor; + const { above =color , below =color } = fillOption || {}; + if (target && line.points.length) { + clipArea(ctx, area); + doFill(ctx, { + line, + target, + above, + below, + area, + scale, + axis + }); + unclipArea(ctx); + } +} +function doFill(ctx, cfg) { + const { line , target , above , below , area , scale } = cfg; + const property = line._loop ? 'angle' : cfg.axis; + ctx.save(); + if (property === 'x' && below !== above) { + clipVertical(ctx, target, area.top); + fill(ctx, { + line, + target, + color: above, + scale, + property + }); + ctx.restore(); + ctx.save(); + clipVertical(ctx, target, area.bottom); + } + fill(ctx, { + line, + target, + color: below, + scale, + property + }); + ctx.restore(); +} +function clipVertical(ctx, target, clipY) { + const { segments , points } = target; + let first = true; + let lineLoop = false; + ctx.beginPath(); + for (const segment of segments){ + const { start , end } = segment; + const firstPoint = points[start]; + const lastPoint = points[_findSegmentEnd(start, end, points)]; + if (first) { + ctx.moveTo(firstPoint.x, firstPoint.y); + first = false; + } else { + ctx.lineTo(firstPoint.x, clipY); + ctx.lineTo(firstPoint.x, firstPoint.y); + } + lineLoop = !!target.pathSegment(ctx, segment, { + move: lineLoop + }); + if (lineLoop) { + ctx.closePath(); + } else { + ctx.lineTo(lastPoint.x, clipY); + } + } + ctx.lineTo(target.first().x, clipY); + ctx.closePath(); + ctx.clip(); +} +function fill(ctx, cfg) { + const { line , target , property , color , scale } = cfg; + const segments = _segments(line, target, property); + for (const { source: src , target: tgt , start , end } of segments){ + const { style: { backgroundColor =color } = {} } = src; + const notShape = target !== true; + ctx.save(); + ctx.fillStyle = backgroundColor; + clipBounds(ctx, scale, notShape && _getBounds(property, start, end)); + ctx.beginPath(); + const lineLoop = !!line.pathSegment(ctx, src); + let loop; + if (notShape) { + if (lineLoop) { + ctx.closePath(); + } else { + interpolatedLineTo(ctx, target, end, property); + } + const targetLoop = !!target.pathSegment(ctx, tgt, { + move: lineLoop, + reverse: true + }); + loop = lineLoop && targetLoop; + if (!loop) { + interpolatedLineTo(ctx, target, start, property); + } + } + ctx.closePath(); + ctx.fill(loop ? 'evenodd' : 'nonzero'); + ctx.restore(); + } +} +function clipBounds(ctx, scale, bounds) { + const { top , bottom } = scale.chart.chartArea; + const { property , start , end } = bounds || {}; + if (property === 'x') { + ctx.beginPath(); + ctx.rect(start, top, end - start, bottom - top); + ctx.clip(); + } +} +function interpolatedLineTo(ctx, target, point, property) { + const interpolatedPoint = target.interpolate(point, property); + if (interpolatedPoint) { + ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y); + } +} + +var index = { + id: 'filler', + afterDatasetsUpdate (chart, _args, options) { + const count = (chart.data.datasets || []).length; + const sources = []; + let meta, i, line, source; + for(i = 0; i < count; ++i){ + meta = chart.getDatasetMeta(i); + line = meta.dataset; + source = null; + if (line && line.options && line instanceof LineElement) { + source = { + visible: chart.isDatasetVisible(i), + index: i, + fill: _decodeFill(line, i, count), + chart, + axis: meta.controller.options.indexAxis, + scale: meta.vScale, + line + }; + } + meta.$filler = source; + sources.push(source); + } + for(i = 0; i < count; ++i){ + source = sources[i]; + if (!source || source.fill === false) { + continue; + } + source.fill = _resolveTarget(sources, i, options.propagate); + } + }, + beforeDraw (chart, _args, options) { + const draw = options.drawTime === 'beforeDraw'; + const metasets = chart.getSortedVisibleDatasetMetas(); + const area = chart.chartArea; + for(let i = metasets.length - 1; i >= 0; --i){ + const source = metasets[i].$filler; + if (!source) { + continue; + } + source.line.updateControlPoints(area, source.axis); + if (draw && source.fill) { + _drawfill(chart.ctx, source, area); + } + } + }, + beforeDatasetsDraw (chart, _args, options) { + if (options.drawTime !== 'beforeDatasetsDraw') { + return; + } + const metasets = chart.getSortedVisibleDatasetMetas(); + for(let i = metasets.length - 1; i >= 0; --i){ + const source = metasets[i].$filler; + if (_shouldApplyFill(source)) { + _drawfill(chart.ctx, source, chart.chartArea); + } + } + }, + beforeDatasetDraw (chart, args, options) { + const source = args.meta.$filler; + if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') { + return; + } + _drawfill(chart.ctx, source, chart.chartArea); + }, + defaults: { + propagate: true, + drawTime: 'beforeDatasetDraw' + } +}; + +const getBoxSize = (labelOpts, fontSize)=>{ + let { boxHeight =fontSize , boxWidth =fontSize } = labelOpts; + if (labelOpts.usePointStyle) { + boxHeight = Math.min(boxHeight, fontSize); + boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize); + } + return { + boxWidth, + boxHeight, + itemHeight: Math.max(fontSize, boxHeight) + }; +}; +const itemsEqual = (a, b)=>a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index; +class Legend extends Element { + constructor(config){ + super(); + this._added = false; + this.legendHitBoxes = []; + this._hoveredItem = null; + this.doughnutMode = false; + this.chart = config.chart; + this.options = config.options; + this.ctx = config.ctx; + this.legendItems = undefined; + this.columnSizes = undefined; + this.lineWidths = undefined; + this.maxHeight = undefined; + this.maxWidth = undefined; + this.top = undefined; + this.bottom = undefined; + this.left = undefined; + this.right = undefined; + this.height = undefined; + this.width = undefined; + this._margins = undefined; + this.position = undefined; + this.weight = undefined; + this.fullSize = undefined; + } + update(maxWidth, maxHeight, margins) { + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this._margins = margins; + this.setDimensions(); + this.buildLabels(); + this.fit(); + } + setDimensions() { + if (this.isHorizontal()) { + this.width = this.maxWidth; + this.left = this._margins.left; + this.right = this.width; + } else { + this.height = this.maxHeight; + this.top = this._margins.top; + this.bottom = this.height; + } + } + buildLabels() { + const labelOpts = this.options.labels || {}; + let legendItems = callback(labelOpts.generateLabels, [ + this.chart + ], this) || []; + if (labelOpts.filter) { + legendItems = legendItems.filter((item)=>labelOpts.filter(item, this.chart.data)); + } + if (labelOpts.sort) { + legendItems = legendItems.sort((a, b)=>labelOpts.sort(a, b, this.chart.data)); + } + if (this.options.reverse) { + legendItems.reverse(); + } + this.legendItems = legendItems; + } + fit() { + const { options , ctx } = this; + if (!options.display) { + this.width = this.height = 0; + return; + } + const labelOpts = options.labels; + const labelFont = toFont(labelOpts.font); + const fontSize = labelFont.size; + const titleHeight = this._computeTitleHeight(); + const { boxWidth , itemHeight } = getBoxSize(labelOpts, fontSize); + let width, height; + ctx.font = labelFont.string; + if (this.isHorizontal()) { + width = this.maxWidth; + height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10; + } else { + height = this.maxHeight; + width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10; + } + this.width = Math.min(width, options.maxWidth || this.maxWidth); + this.height = Math.min(height, options.maxHeight || this.maxHeight); + } + _fitRows(titleHeight, fontSize, boxWidth, itemHeight) { + const { ctx , maxWidth , options: { labels: { padding } } } = this; + const hitboxes = this.legendHitBoxes = []; + const lineWidths = this.lineWidths = [ + 0 + ]; + const lineHeight = itemHeight + padding; + let totalHeight = titleHeight; + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + let row = -1; + let top = -lineHeight; + this.legendItems.forEach((legendItem, i)=>{ + const itemWidth = boxWidth + fontSize / 2 + ctx.measureText(legendItem.text).width; + if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) { + totalHeight += lineHeight; + lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; + top += lineHeight; + row++; + } + hitboxes[i] = { + left: 0, + top, + row, + width: itemWidth, + height: itemHeight + }; + lineWidths[lineWidths.length - 1] += itemWidth + padding; + }); + return totalHeight; + } + _fitCols(titleHeight, labelFont, boxWidth, _itemHeight) { + const { ctx , maxHeight , options: { labels: { padding } } } = this; + const hitboxes = this.legendHitBoxes = []; + const columnSizes = this.columnSizes = []; + const heightLimit = maxHeight - titleHeight; + let totalWidth = padding; + let currentColWidth = 0; + let currentColHeight = 0; + let left = 0; + let col = 0; + this.legendItems.forEach((legendItem, i)=>{ + const { itemWidth , itemHeight } = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight); + if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) { + totalWidth += currentColWidth + padding; + columnSizes.push({ + width: currentColWidth, + height: currentColHeight + }); + left += currentColWidth + padding; + col++; + currentColWidth = currentColHeight = 0; + } + hitboxes[i] = { + left, + top: currentColHeight, + col, + width: itemWidth, + height: itemHeight + }; + currentColWidth = Math.max(currentColWidth, itemWidth); + currentColHeight += itemHeight + padding; + }); + totalWidth += currentColWidth; + columnSizes.push({ + width: currentColWidth, + height: currentColHeight + }); + return totalWidth; + } + adjustHitBoxes() { + if (!this.options.display) { + return; + } + const titleHeight = this._computeTitleHeight(); + const { legendHitBoxes: hitboxes , options: { align , labels: { padding } , rtl } } = this; + const rtlHelper = getRtlAdapter(rtl, this.left, this.width); + if (this.isHorizontal()) { + let row = 0; + let left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]); + for (const hitbox of hitboxes){ + if (row !== hitbox.row) { + row = hitbox.row; + left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]); + } + hitbox.top += this.top + titleHeight + padding; + hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width); + left += hitbox.width + padding; + } + } else { + let col = 0; + let top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height); + for (const hitbox of hitboxes){ + if (hitbox.col !== col) { + col = hitbox.col; + top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height); + } + hitbox.top = top; + hitbox.left += this.left + padding; + hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(hitbox.left), hitbox.width); + top += hitbox.height + padding; + } + } + } + isHorizontal() { + return this.options.position === 'top' || this.options.position === 'bottom'; + } + draw() { + if (this.options.display) { + const ctx = this.ctx; + clipArea(ctx, this); + this._draw(); + unclipArea(ctx); + } + } + _draw() { + const { options: opts , columnSizes , lineWidths , ctx } = this; + const { align , labels: labelOpts } = opts; + const defaultColor = defaults.color; + const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width); + const labelFont = toFont(labelOpts.font); + const { padding } = labelOpts; + const fontSize = labelFont.size; + const halfFontSize = fontSize / 2; + let cursor; + this.drawTitle(); + ctx.textAlign = rtlHelper.textAlign('left'); + ctx.textBaseline = 'middle'; + ctx.lineWidth = 0.5; + ctx.font = labelFont.string; + const { boxWidth , boxHeight , itemHeight } = getBoxSize(labelOpts, fontSize); + const drawLegendBox = function(x, y, legendItem) { + if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) { + return; + } + ctx.save(); + const lineWidth = valueOrDefault(legendItem.lineWidth, 1); + ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor); + ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt'); + ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0); + ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter'); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor); + ctx.setLineDash(valueOrDefault(legendItem.lineDash, [])); + if (labelOpts.usePointStyle) { + const drawOptions = { + radius: boxHeight * Math.SQRT2 / 2, + pointStyle: legendItem.pointStyle, + rotation: legendItem.rotation, + borderWidth: lineWidth + }; + const centerX = rtlHelper.xPlus(x, boxWidth / 2); + const centerY = y + halfFontSize; + drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth); + } else { + const yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0); + const xBoxLeft = rtlHelper.leftForLtr(x, boxWidth); + const borderRadius = toTRBLCorners(legendItem.borderRadius); + ctx.beginPath(); + if (Object.values(borderRadius).some((v)=>v !== 0)) { + addRoundedRectPath(ctx, { + x: xBoxLeft, + y: yBoxTop, + w: boxWidth, + h: boxHeight, + radius: borderRadius + }); + } else { + ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight); + } + ctx.fill(); + if (lineWidth !== 0) { + ctx.stroke(); + } + } + ctx.restore(); + }; + const fillText = function(x, y, legendItem) { + renderText(ctx, legendItem.text, x, y + itemHeight / 2, labelFont, { + strikethrough: legendItem.hidden, + textAlign: rtlHelper.textAlign(legendItem.textAlign) + }); + }; + const isHorizontal = this.isHorizontal(); + const titleHeight = this._computeTitleHeight(); + if (isHorizontal) { + cursor = { + x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]), + y: this.top + padding + titleHeight, + line: 0 + }; + } else { + cursor = { + x: this.left + padding, + y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height), + line: 0 + }; + } + overrideTextDirection(this.ctx, opts.textDirection); + const lineHeight = itemHeight + padding; + this.legendItems.forEach((legendItem, i)=>{ + ctx.strokeStyle = legendItem.fontColor; + ctx.fillStyle = legendItem.fontColor; + const textWidth = ctx.measureText(legendItem.text).width; + const textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign)); + const width = boxWidth + halfFontSize + textWidth; + let x = cursor.x; + let y = cursor.y; + rtlHelper.setWidth(this.width); + if (isHorizontal) { + if (i > 0 && x + width + padding > this.right) { + y = cursor.y += lineHeight; + cursor.line++; + x = cursor.x = _alignStartEnd(align, this.left + padding, this.right - lineWidths[cursor.line]); + } + } else if (i > 0 && y + lineHeight > this.bottom) { + x = cursor.x = x + columnSizes[cursor.line].width + padding; + cursor.line++; + y = cursor.y = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[cursor.line].height); + } + const realX = rtlHelper.x(x); + drawLegendBox(realX, y, legendItem); + x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : this.right, opts.rtl); + fillText(rtlHelper.x(x), y, legendItem); + if (isHorizontal) { + cursor.x += width + padding; + } else if (typeof legendItem.text !== 'string') { + const fontLineHeight = labelFont.lineHeight; + cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight) + padding; + } else { + cursor.y += lineHeight; + } + }); + restoreTextDirection(this.ctx, opts.textDirection); + } + drawTitle() { + const opts = this.options; + const titleOpts = opts.title; + const titleFont = toFont(titleOpts.font); + const titlePadding = toPadding(titleOpts.padding); + if (!titleOpts.display) { + return; + } + const rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width); + const ctx = this.ctx; + const position = titleOpts.position; + const halfFontSize = titleFont.size / 2; + const topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize; + let y; + let left = this.left; + let maxWidth = this.width; + if (this.isHorizontal()) { + maxWidth = Math.max(...this.lineWidths); + y = this.top + topPaddingPlusHalfFontSize; + left = _alignStartEnd(opts.align, left, this.right - maxWidth); + } else { + const maxHeight = this.columnSizes.reduce((acc, size)=>Math.max(acc, size.height), 0); + y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight()); + } + const x = _alignStartEnd(position, left, left + maxWidth); + ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position)); + ctx.textBaseline = 'middle'; + ctx.strokeStyle = titleOpts.color; + ctx.fillStyle = titleOpts.color; + ctx.font = titleFont.string; + renderText(ctx, titleOpts.text, x, y, titleFont); + } + _computeTitleHeight() { + const titleOpts = this.options.title; + const titleFont = toFont(titleOpts.font); + const titlePadding = toPadding(titleOpts.padding); + return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0; + } + _getLegendItemAt(x, y) { + let i, hitBox, lh; + if (_isBetween(x, this.left, this.right) && _isBetween(y, this.top, this.bottom)) { + lh = this.legendHitBoxes; + for(i = 0; i < lh.length; ++i){ + hitBox = lh[i]; + if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width) && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) { + return this.legendItems[i]; + } + } + } + return null; + } + handleEvent(e) { + const opts = this.options; + if (!isListened(e.type, opts)) { + return; + } + const hoveredItem = this._getLegendItemAt(e.x, e.y); + if (e.type === 'mousemove' || e.type === 'mouseout') { + const previous = this._hoveredItem; + const sameItem = itemsEqual(previous, hoveredItem); + if (previous && !sameItem) { + callback(opts.onLeave, [ + e, + previous, + this + ], this); + } + this._hoveredItem = hoveredItem; + if (hoveredItem && !sameItem) { + callback(opts.onHover, [ + e, + hoveredItem, + this + ], this); + } + } else if (hoveredItem) { + callback(opts.onClick, [ + e, + hoveredItem, + this + ], this); + } + } +} +function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) { + const itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx); + const itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight); + return { + itemWidth, + itemHeight + }; +} +function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) { + let legendItemText = legendItem.text; + if (legendItemText && typeof legendItemText !== 'string') { + legendItemText = legendItemText.reduce((a, b)=>a.length > b.length ? a : b); + } + return boxWidth + labelFont.size / 2 + ctx.measureText(legendItemText).width; +} +function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) { + let itemHeight = _itemHeight; + if (typeof legendItem.text !== 'string') { + itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight); + } + return itemHeight; +} +function calculateLegendItemHeight(legendItem, fontLineHeight) { + const labelHeight = legendItem.text ? legendItem.text.length : 0; + return fontLineHeight * labelHeight; +} +function isListened(type, opts) { + if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) { + return true; + } + if (opts.onClick && (type === 'click' || type === 'mouseup')) { + return true; + } + return false; +} +var plugin_legend = { + id: 'legend', + _element: Legend, + start (chart, _args, options) { + const legend = chart.legend = new Legend({ + ctx: chart.ctx, + options, + chart + }); + layouts.configure(chart, legend, options); + layouts.addBox(chart, legend); + }, + stop (chart) { + layouts.removeBox(chart, chart.legend); + delete chart.legend; + }, + beforeUpdate (chart, _args, options) { + const legend = chart.legend; + layouts.configure(chart, legend, options); + legend.options = options; + }, + afterUpdate (chart) { + const legend = chart.legend; + legend.buildLabels(); + legend.adjustHitBoxes(); + }, + afterEvent (chart, args) { + if (!args.replay) { + chart.legend.handleEvent(args.event); + } + }, + defaults: { + display: true, + position: 'top', + align: 'center', + fullSize: true, + reverse: false, + weight: 1000, + onClick (e, legendItem, legend) { + const index = legendItem.datasetIndex; + const ci = legend.chart; + if (ci.isDatasetVisible(index)) { + ci.hide(index); + legendItem.hidden = true; + } else { + ci.show(index); + legendItem.hidden = false; + } + }, + onHover: null, + onLeave: null, + labels: { + color: (ctx)=>ctx.chart.options.color, + boxWidth: 40, + padding: 10, + generateLabels (chart) { + const datasets = chart.data.datasets; + const { labels: { usePointStyle , pointStyle , textAlign , color , useBorderRadius , borderRadius } } = chart.legend.options; + return chart._getSortedDatasetMetas().map((meta)=>{ + const style = meta.controller.getStyle(usePointStyle ? 0 : undefined); + const borderWidth = toPadding(style.borderWidth); + return { + text: datasets[meta.index].label, + fillStyle: style.backgroundColor, + fontColor: color, + hidden: !meta.visible, + lineCap: style.borderCapStyle, + lineDash: style.borderDash, + lineDashOffset: style.borderDashOffset, + lineJoin: style.borderJoinStyle, + lineWidth: (borderWidth.width + borderWidth.height) / 4, + strokeStyle: style.borderColor, + pointStyle: pointStyle || style.pointStyle, + rotation: style.rotation, + textAlign: textAlign || style.textAlign, + borderRadius: useBorderRadius && (borderRadius || style.borderRadius), + datasetIndex: meta.index + }; + }, this); + } + }, + title: { + color: (ctx)=>ctx.chart.options.color, + display: false, + position: 'center', + text: '' + } + }, + descriptors: { + _scriptable: (name)=>!name.startsWith('on'), + labels: { + _scriptable: (name)=>![ + 'generateLabels', + 'filter', + 'sort' + ].includes(name) + } + } +}; + +class Title extends Element { + constructor(config){ + super(); + this.chart = config.chart; + this.options = config.options; + this.ctx = config.ctx; + this._padding = undefined; + this.top = undefined; + this.bottom = undefined; + this.left = undefined; + this.right = undefined; + this.width = undefined; + this.height = undefined; + this.position = undefined; + this.weight = undefined; + this.fullSize = undefined; + } + update(maxWidth, maxHeight) { + const opts = this.options; + this.left = 0; + this.top = 0; + if (!opts.display) { + this.width = this.height = this.right = this.bottom = 0; + return; + } + this.width = this.right = maxWidth; + this.height = this.bottom = maxHeight; + const lineCount = isArray(opts.text) ? opts.text.length : 1; + this._padding = toPadding(opts.padding); + const textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height; + if (this.isHorizontal()) { + this.height = textSize; + } else { + this.width = textSize; + } + } + isHorizontal() { + const pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + } + _drawArgs(offset) { + const { top , left , bottom , right , options } = this; + const align = options.align; + let rotation = 0; + let maxWidth, titleX, titleY; + if (this.isHorizontal()) { + titleX = _alignStartEnd(align, left, right); + titleY = top + offset; + maxWidth = right - left; + } else { + if (options.position === 'left') { + titleX = left + offset; + titleY = _alignStartEnd(align, bottom, top); + rotation = PI * -0.5; + } else { + titleX = right - offset; + titleY = _alignStartEnd(align, top, bottom); + rotation = PI * 0.5; + } + maxWidth = bottom - top; + } + return { + titleX, + titleY, + maxWidth, + rotation + }; + } + draw() { + const ctx = this.ctx; + const opts = this.options; + if (!opts.display) { + return; + } + const fontOpts = toFont(opts.font); + const lineHeight = fontOpts.lineHeight; + const offset = lineHeight / 2 + this._padding.top; + const { titleX , titleY , maxWidth , rotation } = this._drawArgs(offset); + renderText(ctx, opts.text, 0, 0, fontOpts, { + color: opts.color, + maxWidth, + rotation, + textAlign: _toLeftRightCenter(opts.align), + textBaseline: 'middle', + translation: [ + titleX, + titleY + ] + }); + } +} +function createTitle(chart, titleOpts) { + const title = new Title({ + ctx: chart.ctx, + options: titleOpts, + chart + }); + layouts.configure(chart, title, titleOpts); + layouts.addBox(chart, title); + chart.titleBlock = title; +} +var plugin_title = { + id: 'title', + _element: Title, + start (chart, _args, options) { + createTitle(chart, options); + }, + stop (chart) { + const titleBlock = chart.titleBlock; + layouts.removeBox(chart, titleBlock); + delete chart.titleBlock; + }, + beforeUpdate (chart, _args, options) { + const title = chart.titleBlock; + layouts.configure(chart, title, options); + title.options = options; + }, + defaults: { + align: 'center', + display: false, + font: { + weight: 'bold' + }, + fullSize: true, + padding: 10, + position: 'top', + text: '', + weight: 2000 + }, + defaultRoutes: { + color: 'color' + }, + descriptors: { + _scriptable: true, + _indexable: false + } +}; + +const map = new WeakMap(); +var plugin_subtitle = { + id: 'subtitle', + start (chart, _args, options) { + const title = new Title({ + ctx: chart.ctx, + options, + chart + }); + layouts.configure(chart, title, options); + layouts.addBox(chart, title); + map.set(chart, title); + }, + stop (chart) { + layouts.removeBox(chart, map.get(chart)); + map.delete(chart); + }, + beforeUpdate (chart, _args, options) { + const title = map.get(chart); + layouts.configure(chart, title, options); + title.options = options; + }, + defaults: { + align: 'center', + display: false, + font: { + weight: 'normal' + }, + fullSize: true, + padding: 0, + position: 'top', + text: '', + weight: 1500 + }, + defaultRoutes: { + color: 'color' + }, + descriptors: { + _scriptable: true, + _indexable: false + } +}; + +const positioners = { + average (items) { + if (!items.length) { + return false; + } + let i, len; + let x = 0; + let y = 0; + let count = 0; + for(i = 0, len = items.length; i < len; ++i){ + const el = items[i].element; + if (el && el.hasValue()) { + const pos = el.tooltipPosition(); + x += pos.x; + y += pos.y; + ++count; + } + } + return { + x: x / count, + y: y / count + }; + }, + nearest (items, eventPosition) { + if (!items.length) { + return false; + } + let x = eventPosition.x; + let y = eventPosition.y; + let minDistance = Number.POSITIVE_INFINITY; + let i, len, nearestElement; + for(i = 0, len = items.length; i < len; ++i){ + const el = items[i].element; + if (el && el.hasValue()) { + const center = el.getCenterPoint(); + const d = distanceBetweenPoints(eventPosition, center); + if (d < minDistance) { + minDistance = d; + nearestElement = el; + } + } + } + if (nearestElement) { + const tp = nearestElement.tooltipPosition(); + x = tp.x; + y = tp.y; + } + return { + x, + y + }; + } +}; +function pushOrConcat(base, toPush) { + if (toPush) { + if (isArray(toPush)) { + Array.prototype.push.apply(base, toPush); + } else { + base.push(toPush); + } + } + return base; +} + function splitNewlines(str) { + if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { + return str.split('\n'); + } + return str; +} + function createTooltipItem(chart, item) { + const { element , datasetIndex , index } = item; + const controller = chart.getDatasetMeta(datasetIndex).controller; + const { label , value } = controller.getLabelAndValue(index); + return { + chart, + label, + parsed: controller.getParsed(index), + raw: chart.data.datasets[datasetIndex].data[index], + formattedValue: value, + dataset: controller.getDataset(), + dataIndex: index, + datasetIndex, + element + }; +} + function getTooltipSize(tooltip, options) { + const ctx = tooltip.chart.ctx; + const { body , footer , title } = tooltip; + const { boxWidth , boxHeight } = options; + const bodyFont = toFont(options.bodyFont); + const titleFont = toFont(options.titleFont); + const footerFont = toFont(options.footerFont); + const titleLineCount = title.length; + const footerLineCount = footer.length; + const bodyLineItemCount = body.length; + const padding = toPadding(options.padding); + let height = padding.height; + let width = 0; + let combinedBodyLength = body.reduce((count, bodyItem)=>count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length, 0); + combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length; + if (titleLineCount) { + height += titleLineCount * titleFont.lineHeight + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom; + } + if (combinedBodyLength) { + const bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight; + height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight + (combinedBodyLength - 1) * options.bodySpacing; + } + if (footerLineCount) { + height += options.footerMarginTop + footerLineCount * footerFont.lineHeight + (footerLineCount - 1) * options.footerSpacing; + } + let widthPadding = 0; + const maxLineWidth = function(line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; + ctx.save(); + ctx.font = titleFont.string; + each(tooltip.title, maxLineWidth); + ctx.font = bodyFont.string; + each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth); + widthPadding = options.displayColors ? boxWidth + 2 + options.boxPadding : 0; + each(body, (bodyItem)=>{ + each(bodyItem.before, maxLineWidth); + each(bodyItem.lines, maxLineWidth); + each(bodyItem.after, maxLineWidth); + }); + widthPadding = 0; + ctx.font = footerFont.string; + each(tooltip.footer, maxLineWidth); + ctx.restore(); + width += padding.width; + return { + width, + height + }; +} +function determineYAlign(chart, size) { + const { y , height } = size; + if (y < height / 2) { + return 'top'; + } else if (y > chart.height - height / 2) { + return 'bottom'; + } + return 'center'; +} +function doesNotFitWithAlign(xAlign, chart, options, size) { + const { x , width } = size; + const caret = options.caretSize + options.caretPadding; + if (xAlign === 'left' && x + width + caret > chart.width) { + return true; + } + if (xAlign === 'right' && x - width - caret < 0) { + return true; + } +} +function determineXAlign(chart, options, size, yAlign) { + const { x , width } = size; + const { width: chartWidth , chartArea: { left , right } } = chart; + let xAlign = 'center'; + if (yAlign === 'center') { + xAlign = x <= (left + right) / 2 ? 'left' : 'right'; + } else if (x <= width / 2) { + xAlign = 'left'; + } else if (x >= chartWidth - width / 2) { + xAlign = 'right'; + } + if (doesNotFitWithAlign(xAlign, chart, options, size)) { + xAlign = 'center'; + } + return xAlign; +} + function determineAlignment(chart, options, size) { + const yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size); + return { + xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign), + yAlign + }; +} +function alignX(size, xAlign) { + let { x , width } = size; + if (xAlign === 'right') { + x -= width; + } else if (xAlign === 'center') { + x -= width / 2; + } + return x; +} +function alignY(size, yAlign, paddingAndSize) { + let { y , height } = size; + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= height + paddingAndSize; + } else { + y -= height / 2; + } + return y; +} + function getBackgroundPoint(options, size, alignment, chart) { + const { caretSize , caretPadding , cornerRadius } = options; + const { xAlign , yAlign } = alignment; + const paddingAndSize = caretSize + caretPadding; + const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius); + let x = alignX(size, xAlign); + const y = alignY(size, yAlign, paddingAndSize); + if (yAlign === 'center') { + if (xAlign === 'left') { + x += paddingAndSize; + } else if (xAlign === 'right') { + x -= paddingAndSize; + } + } else if (xAlign === 'left') { + x -= Math.max(topLeft, bottomLeft) + caretSize; + } else if (xAlign === 'right') { + x += Math.max(topRight, bottomRight) + caretSize; + } + return { + x: _limitValue(x, 0, chart.width - size.width), + y: _limitValue(y, 0, chart.height - size.height) + }; +} +function getAlignedX(tooltip, align, options) { + const padding = toPadding(options.padding); + return align === 'center' ? tooltip.x + tooltip.width / 2 : align === 'right' ? tooltip.x + tooltip.width - padding.right : tooltip.x + padding.left; +} + function getBeforeAfterBodyLines(callback) { + return pushOrConcat([], splitNewlines(callback)); +} +function createTooltipContext(parent, tooltip, tooltipItems) { + return createContext(parent, { + tooltip, + tooltipItems, + type: 'tooltip' + }); +} +function overrideCallbacks(callbacks, context) { + const override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks; + return override ? callbacks.override(override) : callbacks; +} +const defaultCallbacks = { + beforeTitle: noop, + title (tooltipItems) { + if (tooltipItems.length > 0) { + const item = tooltipItems[0]; + const labels = item.chart.data.labels; + const labelCount = labels ? labels.length : 0; + if (this && this.options && this.options.mode === 'dataset') { + return item.dataset.label || ''; + } else if (item.label) { + return item.label; + } else if (labelCount > 0 && item.dataIndex < labelCount) { + return labels[item.dataIndex]; + } + } + return ''; + }, + afterTitle: noop, + beforeBody: noop, + beforeLabel: noop, + label (tooltipItem) { + if (this && this.options && this.options.mode === 'dataset') { + return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue; + } + let label = tooltipItem.dataset.label || ''; + if (label) { + label += ': '; + } + const value = tooltipItem.formattedValue; + if (!isNullOrUndef(value)) { + label += value; + } + return label; + }, + labelColor (tooltipItem) { + const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); + const options = meta.controller.getStyle(tooltipItem.dataIndex); + return { + borderColor: options.borderColor, + backgroundColor: options.backgroundColor, + borderWidth: options.borderWidth, + borderDash: options.borderDash, + borderDashOffset: options.borderDashOffset, + borderRadius: 0 + }; + }, + labelTextColor () { + return this.options.bodyColor; + }, + labelPointStyle (tooltipItem) { + const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); + const options = meta.controller.getStyle(tooltipItem.dataIndex); + return { + pointStyle: options.pointStyle, + rotation: options.rotation + }; + }, + afterLabel: noop, + afterBody: noop, + beforeFooter: noop, + footer: noop, + afterFooter: noop +}; + function invokeCallbackWithFallback(callbacks, name, ctx, arg) { + const result = callbacks[name].call(ctx, arg); + if (typeof result === 'undefined') { + return defaultCallbacks[name].call(ctx, arg); + } + return result; +} +class Tooltip extends Element { + static positioners = positioners; + constructor(config){ + super(); + this.opacity = 0; + this._active = []; + this._eventPosition = undefined; + this._size = undefined; + this._cachedAnimations = undefined; + this._tooltipItems = []; + this.$animations = undefined; + this.$context = undefined; + this.chart = config.chart; + this.options = config.options; + this.dataPoints = undefined; + this.title = undefined; + this.beforeBody = undefined; + this.body = undefined; + this.afterBody = undefined; + this.footer = undefined; + this.xAlign = undefined; + this.yAlign = undefined; + this.x = undefined; + this.y = undefined; + this.height = undefined; + this.width = undefined; + this.caretX = undefined; + this.caretY = undefined; + this.labelColors = undefined; + this.labelPointStyles = undefined; + this.labelTextColors = undefined; + } + initialize(options) { + this.options = options; + this._cachedAnimations = undefined; + this.$context = undefined; + } + _resolveAnimations() { + const cached = this._cachedAnimations; + if (cached) { + return cached; + } + const chart = this.chart; + const options = this.options.setContext(this.getContext()); + const opts = options.enabled && chart.options.animation && options.animations; + const animations = new Animations(this.chart, opts); + if (opts._cacheable) { + this._cachedAnimations = Object.freeze(animations); + } + return animations; + } + getContext() { + return this.$context || (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems)); + } + getTitle(context, options) { + const { callbacks } = options; + const beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context); + const title = invokeCallbackWithFallback(callbacks, 'title', this, context); + const afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context); + let lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeTitle)); + lines = pushOrConcat(lines, splitNewlines(title)); + lines = pushOrConcat(lines, splitNewlines(afterTitle)); + return lines; + } + getBeforeBody(tooltipItems, options) { + return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems)); + } + getBody(tooltipItems, options) { + const { callbacks } = options; + const bodyItems = []; + each(tooltipItems, (context)=>{ + const bodyItem = { + before: [], + lines: [], + after: [] + }; + const scoped = overrideCallbacks(callbacks, context); + pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', this, context))); + pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', this, context)); + pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', this, context))); + bodyItems.push(bodyItem); + }); + return bodyItems; + } + getAfterBody(tooltipItems, options) { + return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems)); + } + getFooter(tooltipItems, options) { + const { callbacks } = options; + const beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems); + const footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems); + const afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems); + let lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeFooter)); + lines = pushOrConcat(lines, splitNewlines(footer)); + lines = pushOrConcat(lines, splitNewlines(afterFooter)); + return lines; + } + _createItems(options) { + const active = this._active; + const data = this.chart.data; + const labelColors = []; + const labelPointStyles = []; + const labelTextColors = []; + let tooltipItems = []; + let i, len; + for(i = 0, len = active.length; i < len; ++i){ + tooltipItems.push(createTooltipItem(this.chart, active[i])); + } + if (options.filter) { + tooltipItems = tooltipItems.filter((element, index, array)=>options.filter(element, index, array, data)); + } + if (options.itemSort) { + tooltipItems = tooltipItems.sort((a, b)=>options.itemSort(a, b, data)); + } + each(tooltipItems, (context)=>{ + const scoped = overrideCallbacks(options.callbacks, context); + labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', this, context)); + labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', this, context)); + labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', this, context)); + }); + this.labelColors = labelColors; + this.labelPointStyles = labelPointStyles; + this.labelTextColors = labelTextColors; + this.dataPoints = tooltipItems; + return tooltipItems; + } + update(changed, replay) { + const options = this.options.setContext(this.getContext()); + const active = this._active; + let properties; + let tooltipItems = []; + if (!active.length) { + if (this.opacity !== 0) { + properties = { + opacity: 0 + }; + } + } else { + const position = positioners[options.position].call(this, active, this._eventPosition); + tooltipItems = this._createItems(options); + this.title = this.getTitle(tooltipItems, options); + this.beforeBody = this.getBeforeBody(tooltipItems, options); + this.body = this.getBody(tooltipItems, options); + this.afterBody = this.getAfterBody(tooltipItems, options); + this.footer = this.getFooter(tooltipItems, options); + const size = this._size = getTooltipSize(this, options); + const positionAndSize = Object.assign({}, position, size); + const alignment = determineAlignment(this.chart, options, positionAndSize); + const backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart); + this.xAlign = alignment.xAlign; + this.yAlign = alignment.yAlign; + properties = { + opacity: 1, + x: backgroundPoint.x, + y: backgroundPoint.y, + width: size.width, + height: size.height, + caretX: position.x, + caretY: position.y + }; + } + this._tooltipItems = tooltipItems; + this.$context = undefined; + if (properties) { + this._resolveAnimations().update(this, properties); + } + if (changed && options.external) { + options.external.call(this, { + chart: this.chart, + tooltip: this, + replay + }); + } + } + drawCaret(tooltipPoint, ctx, size, options) { + const caretPosition = this.getCaretPosition(tooltipPoint, size, options); + ctx.lineTo(caretPosition.x1, caretPosition.y1); + ctx.lineTo(caretPosition.x2, caretPosition.y2); + ctx.lineTo(caretPosition.x3, caretPosition.y3); + } + getCaretPosition(tooltipPoint, size, options) { + const { xAlign , yAlign } = this; + const { caretSize , cornerRadius } = options; + const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(cornerRadius); + const { x: ptX , y: ptY } = tooltipPoint; + const { width , height } = size; + let x1, x2, x3, y1, y2, y3; + if (yAlign === 'center') { + y2 = ptY + height / 2; + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; + y1 = y2 + caretSize; + y3 = y2 - caretSize; + } else { + x1 = ptX + width; + x2 = x1 + caretSize; + y1 = y2 - caretSize; + y3 = y2 + caretSize; + } + x3 = x1; + } else { + if (xAlign === 'left') { + x2 = ptX + Math.max(topLeft, bottomLeft) + caretSize; + } else if (xAlign === 'right') { + x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize; + } else { + x2 = this.caretX; + } + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else { + y1 = ptY + height; + y2 = y1 + caretSize; + x1 = x2 + caretSize; + x3 = x2 - caretSize; + } + y3 = y1; + } + return { + x1, + x2, + x3, + y1, + y2, + y3 + }; + } + drawTitle(pt, ctx, options) { + const title = this.title; + const length = title.length; + let titleFont, titleSpacing, i; + if (length) { + const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width); + pt.x = getAlignedX(this, options.titleAlign, options); + ctx.textAlign = rtlHelper.textAlign(options.titleAlign); + ctx.textBaseline = 'middle'; + titleFont = toFont(options.titleFont); + titleSpacing = options.titleSpacing; + ctx.fillStyle = options.titleColor; + ctx.font = titleFont.string; + for(i = 0; i < length; ++i){ + ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2); + pt.y += titleFont.lineHeight + titleSpacing; + if (i + 1 === length) { + pt.y += options.titleMarginBottom - titleSpacing; + } + } + } + } + _drawColorBox(ctx, pt, i, rtlHelper, options) { + const labelColor = this.labelColors[i]; + const labelPointStyle = this.labelPointStyles[i]; + const { boxHeight , boxWidth } = options; + const bodyFont = toFont(options.bodyFont); + const colorX = getAlignedX(this, 'left', options); + const rtlColorX = rtlHelper.x(colorX); + const yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0; + const colorY = pt.y + yOffSet; + if (options.usePointStyle) { + const drawOptions = { + radius: Math.min(boxWidth, boxHeight) / 2, + pointStyle: labelPointStyle.pointStyle, + rotation: labelPointStyle.rotation, + borderWidth: 1 + }; + const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2; + const centerY = colorY + boxHeight / 2; + ctx.strokeStyle = options.multiKeyBackground; + ctx.fillStyle = options.multiKeyBackground; + drawPoint(ctx, drawOptions, centerX, centerY); + ctx.strokeStyle = labelColor.borderColor; + ctx.fillStyle = labelColor.backgroundColor; + drawPoint(ctx, drawOptions, centerX, centerY); + } else { + ctx.lineWidth = isObject(labelColor.borderWidth) ? Math.max(...Object.values(labelColor.borderWidth)) : labelColor.borderWidth || 1; + ctx.strokeStyle = labelColor.borderColor; + ctx.setLineDash(labelColor.borderDash || []); + ctx.lineDashOffset = labelColor.borderDashOffset || 0; + const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth); + const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2); + const borderRadius = toTRBLCorners(labelColor.borderRadius); + if (Object.values(borderRadius).some((v)=>v !== 0)) { + ctx.beginPath(); + ctx.fillStyle = options.multiKeyBackground; + addRoundedRectPath(ctx, { + x: outerX, + y: colorY, + w: boxWidth, + h: boxHeight, + radius: borderRadius + }); + ctx.fill(); + ctx.stroke(); + ctx.fillStyle = labelColor.backgroundColor; + ctx.beginPath(); + addRoundedRectPath(ctx, { + x: innerX, + y: colorY + 1, + w: boxWidth - 2, + h: boxHeight - 2, + radius: borderRadius + }); + ctx.fill(); + } else { + ctx.fillStyle = options.multiKeyBackground; + ctx.fillRect(outerX, colorY, boxWidth, boxHeight); + ctx.strokeRect(outerX, colorY, boxWidth, boxHeight); + ctx.fillStyle = labelColor.backgroundColor; + ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2); + } + } + ctx.fillStyle = this.labelTextColors[i]; + } + drawBody(pt, ctx, options) { + const { body } = this; + const { bodySpacing , bodyAlign , displayColors , boxHeight , boxWidth , boxPadding } = options; + const bodyFont = toFont(options.bodyFont); + let bodyLineHeight = bodyFont.lineHeight; + let xLinePadding = 0; + const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width); + const fillLineOfText = function(line) { + ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2); + pt.y += bodyLineHeight + bodySpacing; + }; + const bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); + let bodyItem, textColor, lines, i, j, ilen, jlen; + ctx.textAlign = bodyAlign; + ctx.textBaseline = 'middle'; + ctx.font = bodyFont.string; + pt.x = getAlignedX(this, bodyAlignForCalculation, options); + ctx.fillStyle = options.bodyColor; + each(this.beforeBody, fillLineOfText); + xLinePadding = displayColors && bodyAlignForCalculation !== 'right' ? bodyAlign === 'center' ? boxWidth / 2 + boxPadding : boxWidth + 2 + boxPadding : 0; + for(i = 0, ilen = body.length; i < ilen; ++i){ + bodyItem = body[i]; + textColor = this.labelTextColors[i]; + ctx.fillStyle = textColor; + each(bodyItem.before, fillLineOfText); + lines = bodyItem.lines; + if (displayColors && lines.length) { + this._drawColorBox(ctx, pt, i, rtlHelper, options); + bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight); + } + for(j = 0, jlen = lines.length; j < jlen; ++j){ + fillLineOfText(lines[j]); + bodyLineHeight = bodyFont.lineHeight; + } + each(bodyItem.after, fillLineOfText); + } + xLinePadding = 0; + bodyLineHeight = bodyFont.lineHeight; + each(this.afterBody, fillLineOfText); + pt.y -= bodySpacing; + } + drawFooter(pt, ctx, options) { + const footer = this.footer; + const length = footer.length; + let footerFont, i; + if (length) { + const rtlHelper = getRtlAdapter(options.rtl, this.x, this.width); + pt.x = getAlignedX(this, options.footerAlign, options); + pt.y += options.footerMarginTop; + ctx.textAlign = rtlHelper.textAlign(options.footerAlign); + ctx.textBaseline = 'middle'; + footerFont = toFont(options.footerFont); + ctx.fillStyle = options.footerColor; + ctx.font = footerFont.string; + for(i = 0; i < length; ++i){ + ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2); + pt.y += footerFont.lineHeight + options.footerSpacing; + } + } + } + drawBackground(pt, ctx, tooltipSize, options) { + const { xAlign , yAlign } = this; + const { x , y } = pt; + const { width , height } = tooltipSize; + const { topLeft , topRight , bottomLeft , bottomRight } = toTRBLCorners(options.cornerRadius); + ctx.fillStyle = options.backgroundColor; + ctx.strokeStyle = options.borderColor; + ctx.lineWidth = options.borderWidth; + ctx.beginPath(); + ctx.moveTo(x + topLeft, y); + if (yAlign === 'top') { + this.drawCaret(pt, ctx, tooltipSize, options); + } + ctx.lineTo(x + width - topRight, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + topRight); + if (yAlign === 'center' && xAlign === 'right') { + this.drawCaret(pt, ctx, tooltipSize, options); + } + ctx.lineTo(x + width, y + height - bottomRight); + ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height); + if (yAlign === 'bottom') { + this.drawCaret(pt, ctx, tooltipSize, options); + } + ctx.lineTo(x + bottomLeft, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft); + if (yAlign === 'center' && xAlign === 'left') { + this.drawCaret(pt, ctx, tooltipSize, options); + } + ctx.lineTo(x, y + topLeft); + ctx.quadraticCurveTo(x, y, x + topLeft, y); + ctx.closePath(); + ctx.fill(); + if (options.borderWidth > 0) { + ctx.stroke(); + } + } + _updateAnimationTarget(options) { + const chart = this.chart; + const anims = this.$animations; + const animX = anims && anims.x; + const animY = anims && anims.y; + if (animX || animY) { + const position = positioners[options.position].call(this, this._active, this._eventPosition); + if (!position) { + return; + } + const size = this._size = getTooltipSize(this, options); + const positionAndSize = Object.assign({}, position, this._size); + const alignment = determineAlignment(chart, options, positionAndSize); + const point = getBackgroundPoint(options, positionAndSize, alignment, chart); + if (animX._to !== point.x || animY._to !== point.y) { + this.xAlign = alignment.xAlign; + this.yAlign = alignment.yAlign; + this.width = size.width; + this.height = size.height; + this.caretX = position.x; + this.caretY = position.y; + this._resolveAnimations().update(this, point); + } + } + } + _willRender() { + return !!this.opacity; + } + draw(ctx) { + const options = this.options.setContext(this.getContext()); + let opacity = this.opacity; + if (!opacity) { + return; + } + this._updateAnimationTarget(options); + const tooltipSize = { + width: this.width, + height: this.height + }; + const pt = { + x: this.x, + y: this.y + }; + opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity; + const padding = toPadding(options.padding); + const hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length; + if (options.enabled && hasTooltipContent) { + ctx.save(); + ctx.globalAlpha = opacity; + this.drawBackground(pt, ctx, tooltipSize, options); + overrideTextDirection(ctx, options.textDirection); + pt.y += padding.top; + this.drawTitle(pt, ctx, options); + this.drawBody(pt, ctx, options); + this.drawFooter(pt, ctx, options); + restoreTextDirection(ctx, options.textDirection); + ctx.restore(); + } + } + getActiveElements() { + return this._active || []; + } + setActiveElements(activeElements, eventPosition) { + const lastActive = this._active; + const active = activeElements.map(({ datasetIndex , index })=>{ + const meta = this.chart.getDatasetMeta(datasetIndex); + if (!meta) { + throw new Error('Cannot find a dataset at index ' + datasetIndex); + } + return { + datasetIndex, + element: meta.data[index], + index + }; + }); + const changed = !_elementsEqual(lastActive, active); + const positionChanged = this._positionChanged(active, eventPosition); + if (changed || positionChanged) { + this._active = active; + this._eventPosition = eventPosition; + this._ignoreReplayEvents = true; + this.update(true); + } + } + handleEvent(e, replay, inChartArea = true) { + if (replay && this._ignoreReplayEvents) { + return false; + } + this._ignoreReplayEvents = false; + const options = this.options; + const lastActive = this._active || []; + const active = this._getActiveElements(e, lastActive, replay, inChartArea); + const positionChanged = this._positionChanged(active, e); + const changed = replay || !_elementsEqual(active, lastActive) || positionChanged; + if (changed) { + this._active = active; + if (options.enabled || options.external) { + this._eventPosition = { + x: e.x, + y: e.y + }; + this.update(true, replay); + } + } + return changed; + } + _getActiveElements(e, lastActive, replay, inChartArea) { + const options = this.options; + if (e.type === 'mouseout') { + return []; + } + if (!inChartArea) { + return lastActive.filter((i)=>this.chart.data.datasets[i.datasetIndex] && this.chart.getDatasetMeta(i.datasetIndex).controller.getParsed(i.index) !== undefined); + } + const active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay); + if (options.reverse) { + active.reverse(); + } + return active; + } + _positionChanged(active, e) { + const { caretX , caretY , options } = this; + const position = positioners[options.position].call(this, active, e); + return position !== false && (caretX !== position.x || caretY !== position.y); + } +} +var plugin_tooltip = { + id: 'tooltip', + _element: Tooltip, + positioners, + afterInit (chart, _args, options) { + if (options) { + chart.tooltip = new Tooltip({ + chart, + options + }); + } + }, + beforeUpdate (chart, _args, options) { + if (chart.tooltip) { + chart.tooltip.initialize(options); + } + }, + reset (chart, _args, options) { + if (chart.tooltip) { + chart.tooltip.initialize(options); + } + }, + afterDraw (chart) { + const tooltip = chart.tooltip; + if (tooltip && tooltip._willRender()) { + const args = { + tooltip + }; + if (chart.notifyPlugins('beforeTooltipDraw', { + ...args, + cancelable: true + }) === false) { + return; + } + tooltip.draw(chart.ctx); + chart.notifyPlugins('afterTooltipDraw', args); + } + }, + afterEvent (chart, args) { + if (chart.tooltip) { + const useFinalPosition = args.replay; + if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) { + args.changed = true; + } + } + }, + defaults: { + enabled: true, + external: null, + position: 'average', + backgroundColor: 'rgba(0,0,0,0.8)', + titleColor: '#fff', + titleFont: { + weight: 'bold' + }, + titleSpacing: 2, + titleMarginBottom: 6, + titleAlign: 'left', + bodyColor: '#fff', + bodySpacing: 2, + bodyFont: {}, + bodyAlign: 'left', + footerColor: '#fff', + footerSpacing: 2, + footerMarginTop: 6, + footerFont: { + weight: 'bold' + }, + footerAlign: 'left', + padding: 6, + caretPadding: 2, + caretSize: 5, + cornerRadius: 6, + boxHeight: (ctx, opts)=>opts.bodyFont.size, + boxWidth: (ctx, opts)=>opts.bodyFont.size, + multiKeyBackground: '#fff', + displayColors: true, + boxPadding: 0, + borderColor: 'rgba(0,0,0,0)', + borderWidth: 0, + animation: { + duration: 400, + easing: 'easeOutQuart' + }, + animations: { + numbers: { + type: 'number', + properties: [ + 'x', + 'y', + 'width', + 'height', + 'caretX', + 'caretY' + ] + }, + opacity: { + easing: 'linear', + duration: 200 + } + }, + callbacks: defaultCallbacks + }, + defaultRoutes: { + bodyFont: 'font', + footerFont: 'font', + titleFont: 'font' + }, + descriptors: { + _scriptable: (name)=>name !== 'filter' && name !== 'itemSort' && name !== 'external', + _indexable: false, + callbacks: { + _scriptable: false, + _indexable: false + }, + animation: { + _fallback: false + }, + animations: { + _fallback: 'animation' + } + }, + additionalOptionScopes: [ + 'interaction' + ] +}; + +var plugins = /*#__PURE__*/Object.freeze({ +__proto__: null, +Colors: plugin_colors, +Decimation: plugin_decimation, +Filler: index, +Legend: plugin_legend, +SubTitle: plugin_subtitle, +Title: plugin_title, +Tooltip: plugin_tooltip +}); + +const addIfString = (labels, raw, index, addedLabels)=>{ + if (typeof raw === 'string') { + index = labels.push(raw) - 1; + addedLabels.unshift({ + index, + label: raw + }); + } else if (isNaN(raw)) { + index = null; + } + return index; +}; +function findOrAddLabel(labels, raw, index, addedLabels) { + const first = labels.indexOf(raw); + if (first === -1) { + return addIfString(labels, raw, index, addedLabels); + } + const last = labels.lastIndexOf(raw); + return first !== last ? index : first; +} +const validIndex = (index, max)=>index === null ? null : _limitValue(Math.round(index), 0, max); +function _getLabelForValue(value) { + const labels = this.getLabels(); + if (value >= 0 && value < labels.length) { + return labels[value]; + } + return value; +} +class CategoryScale extends Scale { + static id = 'category'; + static defaults = { + ticks: { + callback: _getLabelForValue + } + }; + constructor(cfg){ + super(cfg); + this._startValue = undefined; + this._valueRange = 0; + this._addedLabels = []; + } + init(scaleOptions) { + const added = this._addedLabels; + if (added.length) { + const labels = this.getLabels(); + for (const { index , label } of added){ + if (labels[index] === label) { + labels.splice(index, 1); + } + } + this._addedLabels = []; + } + super.init(scaleOptions); + } + parse(raw, index) { + if (isNullOrUndef(raw)) { + return null; + } + const labels = this.getLabels(); + index = isFinite(index) && labels[index] === raw ? index : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels); + return validIndex(index, labels.length - 1); + } + determineDataLimits() { + const { minDefined , maxDefined } = this.getUserBounds(); + let { min , max } = this.getMinMax(true); + if (this.options.bounds === 'ticks') { + if (!minDefined) { + min = 0; + } + if (!maxDefined) { + max = this.getLabels().length - 1; + } + } + this.min = min; + this.max = max; + } + buildTicks() { + const min = this.min; + const max = this.max; + const offset = this.options.offset; + const ticks = []; + let labels = this.getLabels(); + labels = min === 0 && max === labels.length - 1 ? labels : labels.slice(min, max + 1); + this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1); + this._startValue = this.min - (offset ? 0.5 : 0); + for(let value = min; value <= max; value++){ + ticks.push({ + value + }); + } + return ticks; + } + getLabelForValue(value) { + return _getLabelForValue.call(this, value); + } + configure() { + super.configure(); + if (!this.isHorizontal()) { + this._reversePixels = !this._reversePixels; + } + } + getPixelForValue(value) { + if (typeof value !== 'number') { + value = this.parse(value); + } + return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange); + } + getPixelForTick(index) { + const ticks = this.ticks; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index].value); + } + getValueForPixel(pixel) { + return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange); + } + getBasePixel() { + return this.bottom; + } +} + +function generateTicks$1(generationOptions, dataRange) { + const ticks = []; + const MIN_SPACING = 1e-14; + const { bounds , step , min , max , precision , count , maxTicks , maxDigits , includeBounds } = generationOptions; + const unit = step || 1; + const maxSpaces = maxTicks - 1; + const { min: rmin , max: rmax } = dataRange; + const minDefined = !isNullOrUndef(min); + const maxDefined = !isNullOrUndef(max); + const countDefined = !isNullOrUndef(count); + const minSpacing = (rmax - rmin) / (maxDigits + 1); + let spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit; + let factor, niceMin, niceMax, numSpaces; + if (spacing < MIN_SPACING && !minDefined && !maxDefined) { + return [ + { + value: rmin + }, + { + value: rmax + } + ]; + } + numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); + if (numSpaces > maxSpaces) { + spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit; + } + if (!isNullOrUndef(precision)) { + factor = Math.pow(10, precision); + spacing = Math.ceil(spacing * factor) / factor; + } + if (bounds === 'ticks') { + niceMin = Math.floor(rmin / spacing) * spacing; + niceMax = Math.ceil(rmax / spacing) * spacing; + } else { + niceMin = rmin; + niceMax = rmax; + } + if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) { + numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks)); + spacing = (max - min) / numSpaces; + niceMin = min; + niceMax = max; + } else if (countDefined) { + niceMin = minDefined ? min : niceMin; + niceMax = maxDefined ? max : niceMax; + numSpaces = count - 1; + spacing = (niceMax - niceMin) / numSpaces; + } else { + numSpaces = (niceMax - niceMin) / spacing; + if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } + } + const decimalPlaces = Math.max(_decimalPlaces(spacing), _decimalPlaces(niceMin)); + factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision); + niceMin = Math.round(niceMin * factor) / factor; + niceMax = Math.round(niceMax * factor) / factor; + let j = 0; + if (minDefined) { + if (includeBounds && niceMin !== min) { + ticks.push({ + value: min + }); + if (niceMin < min) { + j++; + } + if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) { + j++; + } + } else if (niceMin < min) { + j++; + } + } + for(; j < numSpaces; ++j){ + const tickValue = Math.round((niceMin + j * spacing) * factor) / factor; + if (maxDefined && tickValue > max) { + break; + } + ticks.push({ + value: tickValue + }); + } + if (maxDefined && includeBounds && niceMax !== max) { + if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) { + ticks[ticks.length - 1].value = max; + } else { + ticks.push({ + value: max + }); + } + } else if (!maxDefined || niceMax === max) { + ticks.push({ + value: niceMax + }); + } + return ticks; +} +function relativeLabelSize(value, minSpacing, { horizontal , minRotation }) { + const rad = toRadians(minRotation); + const ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001; + const length = 0.75 * minSpacing * ('' + value).length; + return Math.min(minSpacing / ratio, length); +} +class LinearScaleBase extends Scale { + constructor(cfg){ + super(cfg); + this.start = undefined; + this.end = undefined; + this._startValue = undefined; + this._endValue = undefined; + this._valueRange = 0; + } + parse(raw, index) { + if (isNullOrUndef(raw)) { + return null; + } + if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) { + return null; + } + return +raw; + } + handleTickRangeOptions() { + const { beginAtZero } = this.options; + const { minDefined , maxDefined } = this.getUserBounds(); + let { min , max } = this; + const setMin = (v)=>min = minDefined ? min : v; + const setMax = (v)=>max = maxDefined ? max : v; + if (beginAtZero) { + const minSign = sign(min); + const maxSign = sign(max); + if (minSign < 0 && maxSign < 0) { + setMax(0); + } else if (minSign > 0 && maxSign > 0) { + setMin(0); + } + } + if (min === max) { + let offset = max === 0 ? 1 : Math.abs(max * 0.05); + setMax(max + offset); + if (!beginAtZero) { + setMin(min - offset); + } + } + this.min = min; + this.max = max; + } + getTickLimit() { + const tickOpts = this.options.ticks; + let { maxTicksLimit , stepSize } = tickOpts; + let maxTicks; + if (stepSize) { + maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1; + if (maxTicks > 1000) { + console.warn(`scales.${this.id}.ticks.stepSize: ${stepSize} would result generating up to ${maxTicks} ticks. Limiting to 1000.`); + maxTicks = 1000; + } + } else { + maxTicks = this.computeTickLimit(); + maxTicksLimit = maxTicksLimit || 11; + } + if (maxTicksLimit) { + maxTicks = Math.min(maxTicksLimit, maxTicks); + } + return maxTicks; + } + computeTickLimit() { + return Number.POSITIVE_INFINITY; + } + buildTicks() { + const opts = this.options; + const tickOpts = opts.ticks; + let maxTicks = this.getTickLimit(); + maxTicks = Math.max(2, maxTicks); + const numericGeneratorOptions = { + maxTicks, + bounds: opts.bounds, + min: opts.min, + max: opts.max, + precision: tickOpts.precision, + step: tickOpts.stepSize, + count: tickOpts.count, + maxDigits: this._maxDigits(), + horizontal: this.isHorizontal(), + minRotation: tickOpts.minRotation || 0, + includeBounds: tickOpts.includeBounds !== false + }; + const dataRange = this._range || this; + const ticks = generateTicks$1(numericGeneratorOptions, dataRange); + if (opts.bounds === 'ticks') { + _setMinAndMaxByKey(ticks, this, 'value'); + } + if (opts.reverse) { + ticks.reverse(); + this.start = this.max; + this.end = this.min; + } else { + this.start = this.min; + this.end = this.max; + } + return ticks; + } + configure() { + const ticks = this.ticks; + let start = this.min; + let end = this.max; + super.configure(); + if (this.options.offset && ticks.length) { + const offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; + start -= offset; + end += offset; + } + this._startValue = start; + this._endValue = end; + this._valueRange = end - start; + } + getLabelForValue(value) { + return formatNumber(value, this.chart.options.locale, this.options.ticks.format); + } +} + +class LinearScale extends LinearScaleBase { + static id = 'linear'; + static defaults = { + ticks: { + callback: Ticks.formatters.numeric + } + }; + determineDataLimits() { + const { min , max } = this.getMinMax(true); + this.min = isNumberFinite(min) ? min : 0; + this.max = isNumberFinite(max) ? max : 1; + this.handleTickRangeOptions(); + } + computeTickLimit() { + const horizontal = this.isHorizontal(); + const length = horizontal ? this.width : this.height; + const minRotation = toRadians(this.options.ticks.minRotation); + const ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001; + const tickFont = this._resolveTickFontOptions(0); + return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio)); + } + getPixelForValue(value) { + return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange); + } + getValueForPixel(pixel) { + return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; + } +} + +const log10Floor = (v)=>Math.floor(log10(v)); +const changeExponent = (v, m)=>Math.pow(10, log10Floor(v) + m); +function isMajor(tickVal) { + const remain = tickVal / Math.pow(10, log10Floor(tickVal)); + return remain === 1; +} +function steps(min, max, rangeExp) { + const rangeStep = Math.pow(10, rangeExp); + const start = Math.floor(min / rangeStep); + const end = Math.ceil(max / rangeStep); + return end - start; +} +function startExp(min, max) { + const range = max - min; + let rangeExp = log10Floor(range); + while(steps(min, max, rangeExp) > 10){ + rangeExp++; + } + while(steps(min, max, rangeExp) < 10){ + rangeExp--; + } + return Math.min(rangeExp, log10Floor(min)); +} + function generateTicks(generationOptions, { min , max }) { + min = finiteOrDefault(generationOptions.min, min); + const ticks = []; + const minExp = log10Floor(min); + let exp = startExp(min, max); + let precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; + const stepSize = Math.pow(10, exp); + const base = minExp > exp ? Math.pow(10, minExp) : 0; + const start = Math.round((min - base) * precision) / precision; + const offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10; + let significand = Math.floor((start - offset) / Math.pow(10, exp)); + let value = finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision); + while(value < max){ + ticks.push({ + value, + major: isMajor(value), + significand + }); + if (significand >= 10) { + significand = significand < 15 ? 15 : 20; + } else { + significand++; + } + if (significand >= 20) { + exp++; + significand = 2; + precision = exp >= 0 ? 1 : precision; + } + value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision; + } + const lastTick = finiteOrDefault(generationOptions.max, value); + ticks.push({ + value: lastTick, + major: isMajor(lastTick), + significand + }); + return ticks; +} +class LogarithmicScale extends Scale { + static id = 'logarithmic'; + static defaults = { + ticks: { + callback: Ticks.formatters.logarithmic, + major: { + enabled: true + } + } + }; + constructor(cfg){ + super(cfg); + this.start = undefined; + this.end = undefined; + this._startValue = undefined; + this._valueRange = 0; + } + parse(raw, index) { + const value = LinearScaleBase.prototype.parse.apply(this, [ + raw, + index + ]); + if (value === 0) { + this._zero = true; + return undefined; + } + return isNumberFinite(value) && value > 0 ? value : null; + } + determineDataLimits() { + const { min , max } = this.getMinMax(true); + this.min = isNumberFinite(min) ? Math.max(0, min) : null; + this.max = isNumberFinite(max) ? Math.max(0, max) : null; + if (this.options.beginAtZero) { + this._zero = true; + } + if (this._zero && this.min !== this._suggestedMin && !isNumberFinite(this._userMin)) { + this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0); + } + this.handleTickRangeOptions(); + } + handleTickRangeOptions() { + const { minDefined , maxDefined } = this.getUserBounds(); + let min = this.min; + let max = this.max; + const setMin = (v)=>min = minDefined ? min : v; + const setMax = (v)=>max = maxDefined ? max : v; + if (min === max) { + if (min <= 0) { + setMin(1); + setMax(10); + } else { + setMin(changeExponent(min, -1)); + setMax(changeExponent(max, +1)); + } + } + if (min <= 0) { + setMin(changeExponent(max, -1)); + } + if (max <= 0) { + setMax(changeExponent(min, +1)); + } + this.min = min; + this.max = max; + } + buildTicks() { + const opts = this.options; + const generationOptions = { + min: this._userMin, + max: this._userMax + }; + const ticks = generateTicks(generationOptions, this); + if (opts.bounds === 'ticks') { + _setMinAndMaxByKey(ticks, this, 'value'); + } + if (opts.reverse) { + ticks.reverse(); + this.start = this.max; + this.end = this.min; + } else { + this.start = this.min; + this.end = this.max; + } + return ticks; + } + getLabelForValue(value) { + return value === undefined ? '0' : formatNumber(value, this.chart.options.locale, this.options.ticks.format); + } + configure() { + const start = this.min; + super.configure(); + this._startValue = log10(start); + this._valueRange = log10(this.max) - log10(start); + } + getPixelForValue(value) { + if (value === undefined || value === 0) { + value = this.min; + } + if (value === null || isNaN(value)) { + return NaN; + } + return this.getPixelForDecimal(value === this.min ? 0 : (log10(value) - this._startValue) / this._valueRange); + } + getValueForPixel(pixel) { + const decimal = this.getDecimalForPixel(pixel); + return Math.pow(10, this._startValue + decimal * this._valueRange); + } +} + +function getTickBackdropHeight(opts) { + const tickOpts = opts.ticks; + if (tickOpts.display && opts.display) { + const padding = toPadding(tickOpts.backdropPadding); + return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height; + } + return 0; +} +function measureLabelSize(ctx, font, label) { + label = isArray(label) ? label : [ + label + ]; + return { + w: _longestText(ctx, font.string, label), + h: label.length * font.lineHeight + }; +} +function determineLimits(angle, pos, size, min, max) { + if (angle === min || angle === max) { + return { + start: pos - size / 2, + end: pos + size / 2 + }; + } else if (angle < min || angle > max) { + return { + start: pos - size, + end: pos + }; + } + return { + start: pos, + end: pos + size + }; +} + function fitWithPointLabels(scale) { + const orig = { + l: scale.left + scale._padding.left, + r: scale.right - scale._padding.right, + t: scale.top + scale._padding.top, + b: scale.bottom - scale._padding.bottom + }; + const limits = Object.assign({}, orig); + const labelSizes = []; + const padding = []; + const valueCount = scale._pointLabels.length; + const pointLabelOpts = scale.options.pointLabels; + const additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0; + for(let i = 0; i < valueCount; i++){ + const opts = pointLabelOpts.setContext(scale.getPointLabelContext(i)); + padding[i] = opts.padding; + const pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle); + const plFont = toFont(opts.font); + const textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]); + labelSizes[i] = textSize; + const angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle); + const angle = Math.round(toDegrees(angleRadians)); + const hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + const vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + updateLimits(limits, orig, angleRadians, hLimits, vLimits); + } + scale.setCenterPoint(orig.l - limits.l, limits.r - orig.r, orig.t - limits.t, limits.b - orig.b); + scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding); +} +function updateLimits(limits, orig, angle, hLimits, vLimits) { + const sin = Math.abs(Math.sin(angle)); + const cos = Math.abs(Math.cos(angle)); + let x = 0; + let y = 0; + if (hLimits.start < orig.l) { + x = (orig.l - hLimits.start) / sin; + limits.l = Math.min(limits.l, orig.l - x); + } else if (hLimits.end > orig.r) { + x = (hLimits.end - orig.r) / sin; + limits.r = Math.max(limits.r, orig.r + x); + } + if (vLimits.start < orig.t) { + y = (orig.t - vLimits.start) / cos; + limits.t = Math.min(limits.t, orig.t - y); + } else if (vLimits.end > orig.b) { + y = (vLimits.end - orig.b) / cos; + limits.b = Math.max(limits.b, orig.b + y); + } +} +function createPointLabelItem(scale, index, itemOpts) { + const outerDistance = scale.drawingArea; + const { extra , additionalAngle , padding , size } = itemOpts; + const pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle); + const angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI))); + const y = yForAngle(pointLabelPosition.y, size.h, angle); + const textAlign = getTextAlignForAngle(angle); + const left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign); + return { + visible: true, + x: pointLabelPosition.x, + y, + textAlign, + left, + top: y, + right: left + size.w, + bottom: y + size.h + }; +} +function isNotOverlapped(item, area) { + if (!area) { + return true; + } + const { left , top , right , bottom } = item; + const apexesInArea = _isPointInArea({ + x: left, + y: top + }, area) || _isPointInArea({ + x: left, + y: bottom + }, area) || _isPointInArea({ + x: right, + y: top + }, area) || _isPointInArea({ + x: right, + y: bottom + }, area); + return !apexesInArea; +} +function buildPointLabelItems(scale, labelSizes, padding) { + const items = []; + const valueCount = scale._pointLabels.length; + const opts = scale.options; + const { centerPointLabels , display } = opts.pointLabels; + const itemOpts = { + extra: getTickBackdropHeight(opts) / 2, + additionalAngle: centerPointLabels ? PI / valueCount : 0 + }; + let area; + for(let i = 0; i < valueCount; i++){ + itemOpts.padding = padding[i]; + itemOpts.size = labelSizes[i]; + const item = createPointLabelItem(scale, i, itemOpts); + items.push(item); + if (display === 'auto') { + item.visible = isNotOverlapped(item, area); + if (item.visible) { + area = item; + } + } + } + return items; +} +function getTextAlignForAngle(angle) { + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } + return 'right'; +} +function leftForTextAlign(x, w, align) { + if (align === 'right') { + x -= w; + } else if (align === 'center') { + x -= w / 2; + } + return x; +} +function yForAngle(y, h, angle) { + if (angle === 90 || angle === 270) { + y -= h / 2; + } else if (angle > 270 || angle < 90) { + y -= h; + } + return y; +} +function drawPointLabelBox(ctx, opts, item) { + const { left , top , right , bottom } = item; + const { backdropColor } = opts; + if (!isNullOrUndef(backdropColor)) { + const borderRadius = toTRBLCorners(opts.borderRadius); + const padding = toPadding(opts.backdropPadding); + ctx.fillStyle = backdropColor; + const backdropLeft = left - padding.left; + const backdropTop = top - padding.top; + const backdropWidth = right - left + padding.width; + const backdropHeight = bottom - top + padding.height; + if (Object.values(borderRadius).some((v)=>v !== 0)) { + ctx.beginPath(); + addRoundedRectPath(ctx, { + x: backdropLeft, + y: backdropTop, + w: backdropWidth, + h: backdropHeight, + radius: borderRadius + }); + ctx.fill(); + } else { + ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight); + } + } +} +function drawPointLabels(scale, labelCount) { + const { ctx , options: { pointLabels } } = scale; + for(let i = labelCount - 1; i >= 0; i--){ + const item = scale._pointLabelItems[i]; + if (!item.visible) { + continue; + } + const optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i)); + drawPointLabelBox(ctx, optsAtIndex, item); + const plFont = toFont(optsAtIndex.font); + const { x , y , textAlign } = item; + renderText(ctx, scale._pointLabels[i], x, y + plFont.lineHeight / 2, plFont, { + color: optsAtIndex.color, + textAlign: textAlign, + textBaseline: 'middle' + }); + } +} +function pathRadiusLine(scale, radius, circular, labelCount) { + const { ctx } = scale; + if (circular) { + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU); + } else { + let pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + for(let i = 1; i < labelCount; i++){ + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); + } + } +} +function drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) { + const ctx = scale.ctx; + const circular = gridLineOpts.circular; + const { color , lineWidth } = gridLineOpts; + if (!circular && !labelCount || !color || !lineWidth || radius < 0) { + return; + } + ctx.save(); + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; + ctx.setLineDash(borderOpts.dash); + ctx.lineDashOffset = borderOpts.dashOffset; + ctx.beginPath(); + pathRadiusLine(scale, radius, circular, labelCount); + ctx.closePath(); + ctx.stroke(); + ctx.restore(); +} +function createPointLabelContext(parent, index, label) { + return createContext(parent, { + label, + index, + type: 'pointLabel' + }); +} +class RadialLinearScale extends LinearScaleBase { + static id = 'radialLinear'; + static defaults = { + display: true, + animate: true, + position: 'chartArea', + angleLines: { + display: true, + lineWidth: 1, + borderDash: [], + borderDashOffset: 0.0 + }, + grid: { + circular: false + }, + startAngle: 0, + ticks: { + showLabelBackdrop: true, + callback: Ticks.formatters.numeric + }, + pointLabels: { + backdropColor: undefined, + backdropPadding: 2, + display: true, + font: { + size: 10 + }, + callback (label) { + return label; + }, + padding: 5, + centerPointLabels: false + } + }; + static defaultRoutes = { + 'angleLines.color': 'borderColor', + 'pointLabels.color': 'color', + 'ticks.color': 'color' + }; + static descriptors = { + angleLines: { + _fallback: 'grid' + } + }; + constructor(cfg){ + super(cfg); + this.xCenter = undefined; + this.yCenter = undefined; + this.drawingArea = undefined; + this._pointLabels = []; + this._pointLabelItems = []; + } + setDimensions() { + const padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2); + const w = this.width = this.maxWidth - padding.width; + const h = this.height = this.maxHeight - padding.height; + this.xCenter = Math.floor(this.left + w / 2 + padding.left); + this.yCenter = Math.floor(this.top + h / 2 + padding.top); + this.drawingArea = Math.floor(Math.min(w, h) / 2); + } + determineDataLimits() { + const { min , max } = this.getMinMax(false); + this.min = isNumberFinite(min) && !isNaN(min) ? min : 0; + this.max = isNumberFinite(max) && !isNaN(max) ? max : 0; + this.handleTickRangeOptions(); + } + computeTickLimit() { + return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); + } + generateTickLabels(ticks) { + LinearScaleBase.prototype.generateTickLabels.call(this, ticks); + this._pointLabels = this.getLabels().map((value, index)=>{ + const label = callback(this.options.pointLabels.callback, [ + value, + index + ], this); + return label || label === 0 ? label : ''; + }).filter((v, i)=>this.chart.getDataVisibility(i)); + } + fit() { + const opts = this.options; + if (opts.display && opts.pointLabels.display) { + fitWithPointLabels(this); + } else { + this.setCenterPoint(0, 0, 0, 0); + } + } + setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) { + this.xCenter += Math.floor((leftMovement - rightMovement) / 2); + this.yCenter += Math.floor((topMovement - bottomMovement) / 2); + this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement)); + } + getIndexAngle(index) { + const angleMultiplier = TAU / (this._pointLabels.length || 1); + const startAngle = this.options.startAngle || 0; + return _normalizeAngle(index * angleMultiplier + toRadians(startAngle)); + } + getDistanceFromCenterForValue(value) { + if (isNullOrUndef(value)) { + return NaN; + } + const scalingFactor = this.drawingArea / (this.max - this.min); + if (this.options.reverse) { + return (this.max - value) * scalingFactor; + } + return (value - this.min) * scalingFactor; + } + getValueForDistanceFromCenter(distance) { + if (isNullOrUndef(distance)) { + return NaN; + } + const scaledDistance = distance / (this.drawingArea / (this.max - this.min)); + return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance; + } + getPointLabelContext(index) { + const pointLabels = this._pointLabels || []; + if (index >= 0 && index < pointLabels.length) { + const pointLabel = pointLabels[index]; + return createPointLabelContext(this.getContext(), index, pointLabel); + } + } + getPointPosition(index, distanceFromCenter, additionalAngle = 0) { + const angle = this.getIndexAngle(index) - HALF_PI + additionalAngle; + return { + x: Math.cos(angle) * distanceFromCenter + this.xCenter, + y: Math.sin(angle) * distanceFromCenter + this.yCenter, + angle + }; + } + getPointPositionForValue(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + } + getBasePosition(index) { + return this.getPointPositionForValue(index || 0, this.getBaseValue()); + } + getPointLabelPosition(index) { + const { left , top , right , bottom } = this._pointLabelItems[index]; + return { + left, + top, + right, + bottom + }; + } + drawBackground() { + const { backgroundColor , grid: { circular } } = this.options; + if (backgroundColor) { + const ctx = this.ctx; + ctx.save(); + ctx.beginPath(); + pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length); + ctx.closePath(); + ctx.fillStyle = backgroundColor; + ctx.fill(); + ctx.restore(); + } + } + drawGrid() { + const ctx = this.ctx; + const opts = this.options; + const { angleLines , grid , border } = opts; + const labelCount = this._pointLabels.length; + let i, offset, position; + if (opts.pointLabels.display) { + drawPointLabels(this, labelCount); + } + if (grid.display) { + this.ticks.forEach((tick, index)=>{ + if (index !== 0) { + offset = this.getDistanceFromCenterForValue(tick.value); + const context = this.getContext(index); + const optsAtIndex = grid.setContext(context); + const optsAtIndexBorder = border.setContext(context); + drawRadiusLine(this, optsAtIndex, offset, labelCount, optsAtIndexBorder); + } + }); + } + if (angleLines.display) { + ctx.save(); + for(i = labelCount - 1; i >= 0; i--){ + const optsAtIndex = angleLines.setContext(this.getPointLabelContext(i)); + const { color , lineWidth } = optsAtIndex; + if (!lineWidth || !color) { + continue; + } + ctx.lineWidth = lineWidth; + ctx.strokeStyle = color; + ctx.setLineDash(optsAtIndex.borderDash); + ctx.lineDashOffset = optsAtIndex.borderDashOffset; + offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max); + position = this.getPointPosition(i, offset); + ctx.beginPath(); + ctx.moveTo(this.xCenter, this.yCenter); + ctx.lineTo(position.x, position.y); + ctx.stroke(); + } + ctx.restore(); + } + } + drawBorder() {} + drawLabels() { + const ctx = this.ctx; + const opts = this.options; + const tickOpts = opts.ticks; + if (!tickOpts.display) { + return; + } + const startAngle = this.getIndexAngle(0); + let offset, width; + ctx.save(); + ctx.translate(this.xCenter, this.yCenter); + ctx.rotate(startAngle); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + this.ticks.forEach((tick, index)=>{ + if (index === 0 && !opts.reverse) { + return; + } + const optsAtIndex = tickOpts.setContext(this.getContext(index)); + const tickFont = toFont(optsAtIndex.font); + offset = this.getDistanceFromCenterForValue(this.ticks[index].value); + if (optsAtIndex.showLabelBackdrop) { + ctx.font = tickFont.string; + width = ctx.measureText(tick.label).width; + ctx.fillStyle = optsAtIndex.backdropColor; + const padding = toPadding(optsAtIndex.backdropPadding); + ctx.fillRect(-width / 2 - padding.left, -offset - tickFont.size / 2 - padding.top, width + padding.width, tickFont.size + padding.height); + } + renderText(ctx, tick.label, 0, -offset, tickFont, { + color: optsAtIndex.color, + strokeColor: optsAtIndex.textStrokeColor, + strokeWidth: optsAtIndex.textStrokeWidth + }); + }); + ctx.restore(); + } + drawTitle() {} +} + +const INTERVALS = { + millisecond: { + common: true, + size: 1, + steps: 1000 + }, + second: { + common: true, + size: 1000, + steps: 60 + }, + minute: { + common: true, + size: 60000, + steps: 60 + }, + hour: { + common: true, + size: 3600000, + steps: 24 + }, + day: { + common: true, + size: 86400000, + steps: 30 + }, + week: { + common: false, + size: 604800000, + steps: 4 + }, + month: { + common: true, + size: 2.628e9, + steps: 12 + }, + quarter: { + common: false, + size: 7.884e9, + steps: 4 + }, + year: { + common: true, + size: 3.154e10 + } +}; + const UNITS = /* #__PURE__ */ Object.keys(INTERVALS); + function sorter(a, b) { + return a - b; +} + function parse(scale, input) { + if (isNullOrUndef(input)) { + return null; + } + const adapter = scale._adapter; + const { parser , round , isoWeekday } = scale._parseOpts; + let value = input; + if (typeof parser === 'function') { + value = parser(value); + } + if (!isNumberFinite(value)) { + value = typeof parser === 'string' ? adapter.parse(value, parser) : adapter.parse(value); + } + if (value === null) { + return null; + } + if (round) { + value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) ? adapter.startOf(value, 'isoWeek', isoWeekday) : adapter.startOf(value, round); + } + return +value; +} + function determineUnitForAutoTicks(minUnit, min, max, capacity) { + const ilen = UNITS.length; + for(let i = UNITS.indexOf(minUnit); i < ilen - 1; ++i){ + const interval = INTERVALS[UNITS[i]]; + const factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER; + if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { + return UNITS[i]; + } + } + return UNITS[ilen - 1]; +} + function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { + for(let i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--){ + const unit = UNITS[i]; + if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { + return unit; + } + } + return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; +} + function determineMajorUnit(unit) { + for(let i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i){ + if (INTERVALS[UNITS[i]].common) { + return UNITS[i]; + } + } +} + function addTick(ticks, time, timestamps) { + if (!timestamps) { + ticks[time] = true; + } else if (timestamps.length) { + const { lo , hi } = _lookup(timestamps, time); + const timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi]; + ticks[timestamp] = true; + } +} + function setMajorTicks(scale, ticks, map, majorUnit) { + const adapter = scale._adapter; + const first = +adapter.startOf(ticks[0].value, majorUnit); + const last = ticks[ticks.length - 1].value; + let major, index; + for(major = first; major <= last; major = +adapter.add(major, 1, majorUnit)){ + index = map[major]; + if (index >= 0) { + ticks[index].major = true; + } + } + return ticks; +} + function ticksFromTimestamps(scale, values, majorUnit) { + const ticks = []; + const map = {}; + const ilen = values.length; + let i, value; + for(i = 0; i < ilen; ++i){ + value = values[i]; + map[value] = i; + ticks.push({ + value, + major: false + }); + } + return ilen === 0 || !majorUnit ? ticks : setMajorTicks(scale, ticks, map, majorUnit); +} +class TimeScale extends Scale { + static id = 'time'; + static defaults = { + bounds: 'data', + adapters: {}, + time: { + parser: false, + unit: false, + round: false, + isoWeekday: false, + minUnit: 'millisecond', + displayFormats: {} + }, + ticks: { + source: 'auto', + callback: false, + major: { + enabled: false + } + } + }; + constructor(props){ + super(props); + this._cache = { + data: [], + labels: [], + all: [] + }; + this._unit = 'day'; + this._majorUnit = undefined; + this._offsets = {}; + this._normalized = false; + this._parseOpts = undefined; + } + init(scaleOpts, opts = {}) { + const time = scaleOpts.time || (scaleOpts.time = {}); + const adapter = this._adapter = new adapters._date(scaleOpts.adapters.date); + adapter.init(opts); + mergeIf(time.displayFormats, adapter.formats()); + this._parseOpts = { + parser: time.parser, + round: time.round, + isoWeekday: time.isoWeekday + }; + super.init(scaleOpts); + this._normalized = opts.normalized; + } + parse(raw, index) { + if (raw === undefined) { + return null; + } + return parse(this, raw); + } + beforeLayout() { + super.beforeLayout(); + this._cache = { + data: [], + labels: [], + all: [] + }; + } + determineDataLimits() { + const options = this.options; + const adapter = this._adapter; + const unit = options.time.unit || 'day'; + let { min , max , minDefined , maxDefined } = this.getUserBounds(); + function _applyBounds(bounds) { + if (!minDefined && !isNaN(bounds.min)) { + min = Math.min(min, bounds.min); + } + if (!maxDefined && !isNaN(bounds.max)) { + max = Math.max(max, bounds.max); + } + } + if (!minDefined || !maxDefined) { + _applyBounds(this._getLabelBounds()); + if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') { + _applyBounds(this.getMinMax(false)); + } + } + min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit); + max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1; + this.min = Math.min(min, max - 1); + this.max = Math.max(min + 1, max); + } + _getLabelBounds() { + const arr = this.getLabelTimestamps(); + let min = Number.POSITIVE_INFINITY; + let max = Number.NEGATIVE_INFINITY; + if (arr.length) { + min = arr[0]; + max = arr[arr.length - 1]; + } + return { + min, + max + }; + } + buildTicks() { + const options = this.options; + const timeOpts = options.time; + const tickOpts = options.ticks; + const timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate(); + if (options.bounds === 'ticks' && timestamps.length) { + this.min = this._userMin || timestamps[0]; + this.max = this._userMax || timestamps[timestamps.length - 1]; + } + const min = this.min; + const max = this.max; + const ticks = _filterBetween(timestamps, min, max); + this._unit = timeOpts.unit || (tickOpts.autoSkip ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min)) : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max)); + this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined : determineMajorUnit(this._unit); + this.initOffsets(timestamps); + if (options.reverse) { + ticks.reverse(); + } + return ticksFromTimestamps(this, ticks, this._majorUnit); + } + afterAutoSkip() { + if (this.options.offsetAfterAutoskip) { + this.initOffsets(this.ticks.map((tick)=>+tick.value)); + } + } + initOffsets(timestamps = []) { + let start = 0; + let end = 0; + let first, last; + if (this.options.offset && timestamps.length) { + first = this.getDecimalForValue(timestamps[0]); + if (timestamps.length === 1) { + start = 1 - first; + } else { + start = (this.getDecimalForValue(timestamps[1]) - first) / 2; + } + last = this.getDecimalForValue(timestamps[timestamps.length - 1]); + if (timestamps.length === 1) { + end = last; + } else { + end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2; + } + } + const limit = timestamps.length < 3 ? 0.5 : 0.25; + start = _limitValue(start, 0, limit); + end = _limitValue(end, 0, limit); + this._offsets = { + start, + end, + factor: 1 / (start + 1 + end) + }; + } + _generate() { + const adapter = this._adapter; + const min = this.min; + const max = this.max; + const options = this.options; + const timeOpts = options.time; + const minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min)); + const stepSize = valueOrDefault(options.ticks.stepSize, 1); + const weekday = minor === 'week' ? timeOpts.isoWeekday : false; + const hasWeekday = isNumber(weekday) || weekday === true; + const ticks = {}; + let first = min; + let time, count; + if (hasWeekday) { + first = +adapter.startOf(first, 'isoWeek', weekday); + } + first = +adapter.startOf(first, hasWeekday ? 'day' : minor); + if (adapter.diff(max, min, minor) > 100000 * stepSize) { + throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor); + } + const timestamps = options.ticks.source === 'data' && this.getDataTimestamps(); + for(time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++){ + addTick(ticks, time, timestamps); + } + if (time === max || options.bounds === 'ticks' || count === 1) { + addTick(ticks, time, timestamps); + } + return Object.keys(ticks).sort(sorter).map((x)=>+x); + } + getLabelForValue(value) { + const adapter = this._adapter; + const timeOpts = this.options.time; + if (timeOpts.tooltipFormat) { + return adapter.format(value, timeOpts.tooltipFormat); + } + return adapter.format(value, timeOpts.displayFormats.datetime); + } + format(value, format) { + const options = this.options; + const formats = options.time.displayFormats; + const unit = this._unit; + const fmt = format || formats[unit]; + return this._adapter.format(value, fmt); + } + _tickFormatFunction(time, index, ticks, format) { + const options = this.options; + const formatter = options.ticks.callback; + if (formatter) { + return callback(formatter, [ + time, + index, + ticks + ], this); + } + const formats = options.time.displayFormats; + const unit = this._unit; + const majorUnit = this._majorUnit; + const minorFormat = unit && formats[unit]; + const majorFormat = majorUnit && formats[majorUnit]; + const tick = ticks[index]; + const major = majorUnit && majorFormat && tick && tick.major; + return this._adapter.format(time, format || (major ? majorFormat : minorFormat)); + } + generateTickLabels(ticks) { + let i, ilen, tick; + for(i = 0, ilen = ticks.length; i < ilen; ++i){ + tick = ticks[i]; + tick.label = this._tickFormatFunction(tick.value, i, ticks); + } + } + getDecimalForValue(value) { + return value === null ? NaN : (value - this.min) / (this.max - this.min); + } + getPixelForValue(value) { + const offsets = this._offsets; + const pos = this.getDecimalForValue(value); + return this.getPixelForDecimal((offsets.start + pos) * offsets.factor); + } + getValueForPixel(pixel) { + const offsets = this._offsets; + const pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end; + return this.min + pos * (this.max - this.min); + } + _getLabelSize(label) { + const ticksOpts = this.options.ticks; + const tickLabelWidth = this.ctx.measureText(label).width; + const angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); + const cosRotation = Math.cos(angle); + const sinRotation = Math.sin(angle); + const tickFontSize = this._resolveTickFontOptions(0).size; + return { + w: tickLabelWidth * cosRotation + tickFontSize * sinRotation, + h: tickLabelWidth * sinRotation + tickFontSize * cosRotation + }; + } + _getLabelCapacity(exampleTime) { + const timeOpts = this.options.time; + const displayFormats = timeOpts.displayFormats; + const format = displayFormats[timeOpts.unit] || displayFormats.millisecond; + const exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [ + exampleTime + ], this._majorUnit), format); + const size = this._getLabelSize(exampleLabel); + const capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1; + return capacity > 0 ? capacity : 1; + } + getDataTimestamps() { + let timestamps = this._cache.data || []; + let i, ilen; + if (timestamps.length) { + return timestamps; + } + const metas = this.getMatchingVisibleMetas(); + if (this._normalized && metas.length) { + return this._cache.data = metas[0].controller.getAllParsedValues(this); + } + for(i = 0, ilen = metas.length; i < ilen; ++i){ + timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this)); + } + return this._cache.data = this.normalize(timestamps); + } + getLabelTimestamps() { + const timestamps = this._cache.labels || []; + let i, ilen; + if (timestamps.length) { + return timestamps; + } + const labels = this.getLabels(); + for(i = 0, ilen = labels.length; i < ilen; ++i){ + timestamps.push(parse(this, labels[i])); + } + return this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps); + } + normalize(values) { + return _arrayUnique(values.sort(sorter)); + } +} + +function interpolate(table, val, reverse) { + let lo = 0; + let hi = table.length - 1; + let prevSource, nextSource, prevTarget, nextTarget; + if (reverse) { + if (val >= table[lo].pos && val <= table[hi].pos) { + ({ lo , hi } = _lookupByKey(table, 'pos', val)); + } + ({ pos: prevSource , time: prevTarget } = table[lo]); + ({ pos: nextSource , time: nextTarget } = table[hi]); + } else { + if (val >= table[lo].time && val <= table[hi].time) { + ({ lo , hi } = _lookupByKey(table, 'time', val)); + } + ({ time: prevSource , pos: prevTarget } = table[lo]); + ({ time: nextSource , pos: nextTarget } = table[hi]); + } + const span = nextSource - prevSource; + return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget; +} +class TimeSeriesScale extends TimeScale { + static id = 'timeseries'; + static defaults = TimeScale.defaults; + constructor(props){ + super(props); + this._table = []; + this._minPos = undefined; + this._tableRange = undefined; + } + initOffsets() { + const timestamps = this._getTimestampsForTable(); + const table = this._table = this.buildLookupTable(timestamps); + this._minPos = interpolate(table, this.min); + this._tableRange = interpolate(table, this.max) - this._minPos; + super.initOffsets(timestamps); + } + buildLookupTable(timestamps) { + const { min , max } = this; + const items = []; + const table = []; + let i, ilen, prev, curr, next; + for(i = 0, ilen = timestamps.length; i < ilen; ++i){ + curr = timestamps[i]; + if (curr >= min && curr <= max) { + items.push(curr); + } + } + if (items.length < 2) { + return [ + { + time: min, + pos: 0 + }, + { + time: max, + pos: 1 + } + ]; + } + for(i = 0, ilen = items.length; i < ilen; ++i){ + next = items[i + 1]; + prev = items[i - 1]; + curr = items[i]; + if (Math.round((next + prev) / 2) !== curr) { + table.push({ + time: curr, + pos: i / (ilen - 1) + }); + } + } + return table; + } + _generate() { + const min = this.min; + const max = this.max; + let timestamps = super.getDataTimestamps(); + if (!timestamps.includes(min) || !timestamps.length) { + timestamps.splice(0, 0, min); + } + if (!timestamps.includes(max) || timestamps.length === 1) { + timestamps.push(max); + } + return timestamps.sort((a, b)=>a - b); + } + _getTimestampsForTable() { + let timestamps = this._cache.all || []; + if (timestamps.length) { + return timestamps; + } + const data = this.getDataTimestamps(); + const label = this.getLabelTimestamps(); + if (data.length && label.length) { + timestamps = this.normalize(data.concat(label)); + } else { + timestamps = data.length ? data : label; + } + timestamps = this._cache.all = timestamps; + return timestamps; + } + getDecimalForValue(value) { + return (interpolate(this._table, value) - this._minPos) / this._tableRange; + } + getValueForPixel(pixel) { + const offsets = this._offsets; + const decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end; + return interpolate(this._table, decimal * this._tableRange + this._minPos, true); + } +} + +var scales = /*#__PURE__*/Object.freeze({ +__proto__: null, +CategoryScale: CategoryScale, +LinearScale: LinearScale, +LogarithmicScale: LogarithmicScale, +RadialLinearScale: RadialLinearScale, +TimeScale: TimeScale, +TimeSeriesScale: TimeSeriesScale +}); + +const registerables = [ + controllers, + elements, + plugins, + scales +]; + +export { Animation, Animations, ArcElement, BarController, BarElement, BasePlatform, BasicPlatform, BubbleController, CategoryScale, Chart, plugin_colors as Colors, DatasetController, plugin_decimation as Decimation, DomPlatform, DoughnutController, Element, index as Filler, Interaction, plugin_legend as Legend, LineController, LineElement, LinearScale, LogarithmicScale, PieController, PointElement, PolarAreaController, RadarController, RadialLinearScale, Scale, ScatterController, plugin_subtitle as SubTitle, Ticks, TimeScale, TimeSeriesScale, plugin_title as Title, plugin_tooltip as Tooltip, adapters as _adapters, _detectPlatform, animator, controllers, defaults, elements, layouts, plugins, registerables, registry, scales }; +//# sourceMappingURL=chart.js.map + diff --git a/assets/js/libraries/chart-4.4.1.min.js b/assets/js/libraries/chart-4.4.1.min.js new file mode 100644 index 00000000..053dbf04 --- /dev/null +++ b/assets/js/libraries/chart-4.4.1.min.js @@ -0,0 +1,15 @@ +/*! + * Chart.js v4.4.1 + * https://www.chartjs.org + * (c) 2023 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Go},get Decimation(){return Qo},get Filler(){return ma},get Legend(){return ya},get SubTitle(){return ka},get Title(){return Ma},get Tooltip(){return Ba}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;at,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)t-e)).pop(),e}function N(t){return!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return nt[s][e]et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function rt(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ot.forEach((e=>{delete t[e]})),delete t._chartjs)}function lt(t){const e=new Set(t);return e.size===t.length?t:Array.from(e)}const ht="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function ct(t,e){let i=[],s=!1;return function(...n){i=n,s||(s=!0,ht.call(window,(()=>{s=!1,t.apply(e,i)})))}}function dt(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const ut=t=>"start"===t?"left":"end"===t?"right":"center",ft=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,gt=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;function pt(t,e,i){const s=e.length;let n=0,o=s;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:h,max:c,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(n=J(Math.min(it(r,l,h).lo,i?s:it(e,l,a.getPixelForValue(h)).lo),0,s-1)),o=u?J(Math.max(it(r,a.axis,c,!0).hi+1,i?0:it(e,l,a.getPixelForValue(c),!0).hi+1),n,s)-n:s-n}return{start:n,count:o}}function mt(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}class bt{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=ht.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}var xt=new bt; +/*! + * @kurkle/color v0.3.2 + * https://github.com/kurkle/color#readme + * (c) 2023 Jukka Kurkela + * Released under the MIT License + */function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=Ft(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function qt(t,e){return t?Object.assign(e||{},t):t}function Kt(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=Mt(t[3]))):(e=qt(t,{r:0,g:0,b:0,a:1})).a=Mt(e.a),e}function Gt(t){return"r"===t.charAt(0)?function(t){const e=$t.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=e[8]?vt(t):yt(255*t,0,255)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?vt(i):yt(i,0,255)),s=255&(e[4]?vt(s):yt(s,0,255)),n=255&(e[6]?vt(n):yt(n,0,255)),{r:i,g:s,b:n,a:o}}}(t):Bt(t)}class Zt{constructor(t){if(t instanceof Zt)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=Kt(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*St[s[1]],g:255&17*St[s[2]],b:255&17*St[s[3]],a:5===o?17*St[s[4]]:255}:7!==o&&9!==o||(n={r:St[s[1]]<<4|St[s[2]],g:St[s[3]]<<4|St[s[4]],b:St[s[5]]<<4|St[s[6]],a:9===o?St[s[7]]<<4|St[s[8]]:255})),i=n||jt(t)||Gt(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=qt(this._rgb);return t&&(t.a=wt(t.a)),t}set rgb(t){this._rgb=Kt(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${wt(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):void 0;var t}hexString(){return this._valid?At(this._rgb):void 0}hslString(){return this._valid?function(t){if(!t)return;const e=It(t),i=e[0],s=kt(e[1]),n=kt(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${wt(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):void 0}mix(t,e){if(t){const i=this.rgb,s=t.rgb;let n;const o=e===n?.5:e,a=2*o-1,r=i.a-s.a,l=((a*r==-1?a:(a+r)/(1+a*r))+1)/2;n=1-l,i.r=255&l*i.r+n*s.r+.5,i.g=255&l*i.g+n*s.g+.5,i.b=255&l*i.b+n*s.b+.5,i.a=o*i.a+(1-o)*s.a,this.rgb=i}return this}interpolate(t,e){return t&&(this._rgb=function(t,e,i){const s=Ut(wt(t.r)),n=Ut(wt(t.g)),o=Ut(wt(t.b));return{r:Mt(Yt(s+i*(Ut(wt(e.r))-s))),g:Mt(Yt(n+i*(Ut(wt(e.g))-n))),b:Mt(Yt(o+i*(Ut(wt(e.b))-o))),a:t.a+i*(e.a-t.a)}}(this._rgb,t._rgb,e)),this}clone(){return new Zt(this.rgb)}alpha(t){return this._rgb.a=Mt(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=_t(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return Xt(this._rgb,2,t),this}darken(t){return Xt(this._rgb,2,-t),this}saturate(t){return Xt(this._rgb,1,t),this}desaturate(t){return Xt(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=It(t);i[0]=Vt(i[0]+e),i=Ft(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function Jt(t){if(t&&"object"==typeof t){const e=t.toString();return"[object CanvasPattern]"===e||"[object CanvasGradient]"===e}return!1}function Qt(t){return Jt(t)?t:new Zt(t)}function te(t){return Jt(t)?t:new Zt(t).saturate(.5).darken(.1).hexString()}const ee=["x","y","borderWidth","radius","tension"],ie=["color","borderColor","backgroundColor"];const se=new Map;function ne(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=se.get(i);return s||(s=new Intl.NumberFormat(t,e),se.set(i,s)),s}(e,i).format(t)}const oe={values:t=>n(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=z(Math.abs(o)),r=isNaN(a)?1:Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),ne(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=i[e].significand||t/Math.pow(10,Math.floor(z(t)));return[1,2,3,5,10,15].includes(s)||e>.8*i.length?oe.numeric.call(this,t,e,i):""}};var ae={formatters:oe};const re=Object.create(null),le=Object.create(null);function he(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>te(e.backgroundColor),this.hoverBorderColor=(t,e)=>te(e.borderColor),this.hoverColor=(t,e)=>te(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0,includeInvisible:!1},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t),this.apply(e)}set(t,e){return ce(this,t,e)}get(t){return he(this,t)}describe(t,e){return ce(le,t,e)}override(t,e){return ce(re,t,e)}route(t,e,i,s){const n=he(this,t),a=he(this,i),r="_"+e;Object.defineProperties(n,{[r]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[r],e=a[s];return o(t)?Object.assign({},e,t):l(t,e)},set(t){this[r]=t}}})}apply(t){t.forEach((t=>t(this)))}}var ue=new de({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}},[function(t){t.set("animation",{delay:void 0,duration:1e3,easing:"easeOutQuart",fn:void 0,from:void 0,loop:void 0,to:void 0,type:void 0}),t.describe("animation",{_fallback:!1,_indexable:!1,_scriptable:t=>"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),t.set("animations",{colors:{type:"color",properties:ie},numbers:{type:"number",properties:ee}}),t.describe("animations",{_fallback:"animation"}),t.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}})},function(t){t.set("layout",{autoPadding:!0,padding:{top:0,right:0,bottom:0,left:0}})},function(t){t.set("scale",{display:!0,offset:!1,reverse:!1,beginAtZero:!1,bounds:"ticks",clip:!0,grace:0,grid:{display:!0,lineWidth:1,drawOnChartArea:!0,drawTicks:!0,tickLength:8,tickWidth:(t,e)=>e.lineWidth,tickColor:(t,e)=>e.color,offset:!1},border:{display:!0,dash:[],dashOffset:0,width:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:ae.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),t.route("scale.ticks","color","","color"),t.route("scale.grid","color","","borderColor"),t.route("scale.border","color","","borderColor"),t.route("scale.title","color","","color"),t.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t&&"dash"!==t}),t.describe("scales",{_fallback:"scale"}),t.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t})}]);function fe(){return"undefined"!=typeof window&&"undefined"!=typeof document}function ge(t){let e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e}function pe(t,e,i){let s;return"string"==typeof t?(s=parseInt(t,10),-1!==t.indexOf("%")&&(s=s/100*e.parentNode[i])):s=t,s}const me=t=>t.ownerDocument.defaultView.getComputedStyle(t,null);function be(t,e){return me(t).getPropertyValue(e)}const xe=["top","right","bottom","left"];function _e(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=xe[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}const ye=(t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot);function ve(t,e){if("native"in t)return t;const{canvas:i,currentDevicePixelRatio:s}=e,n=me(i),o="border-box"===n.boxSizing,a=_e(n,"padding"),r=_e(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.touches,s=i&&i.length?i[0]:t,{offsetX:n,offsetY:o}=s;let a,r,l=!1;if(ye(n,o,t.target))a=n,r=o;else{const t=e.getBoundingClientRect();a=s.clientX-t.left,r=s.clientY-t.top,l=!0}return{x:a,y:r,box:l}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const Me=t=>Math.round(10*t)/10;function we(t,e,i,s){const n=me(t),o=_e(n,"margin"),a=pe(n.maxWidth,t,"clientWidth")||T,r=pe(n.maxHeight,t,"clientHeight")||T,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=ge(t);if(o){const t=o.getBoundingClientRect(),a=me(o),r=_e(a,"border","width"),l=_e(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=pe(a.maxWidth,o,"clientWidth"),n=pe(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||T,maxHeight:n||T}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=_e(n,"border","width"),e=_e(n,"padding");h-=e.width+t.width,c-=e.height+t.height}h=Math.max(0,h-o.width),c=Math.max(0,s?h/s:c-o.height),h=Me(Math.min(h,a,l.maxWidth)),c=Me(Math.min(c,r,l.maxHeight)),h&&!c&&(c=Me(h/2));return(void 0!==e||void 0!==i)&&s&&l.height&&c>l.height&&(c=l.height,h=Me(Math.floor(c*s))),{width:h,height:c}}function ke(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t.width*s);t.height=Math.floor(t.height),t.width=Math.floor(t.width);const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const Se=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};fe()&&(window.addEventListener("test",null,e),window.removeEventListener("test",null,e))}catch(t){}return t}();function Pe(t,e){const i=be(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function De(t){return!t||s(t.size)||s(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Ce(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function Oe(t,e,i,s){let o=(s=s||{}).data=s.data||{},a=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(o=s.data={},a=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let r=0;const l=i.length;let h,c,d,u,f;for(h=0;hi.length){for(h=0;h0&&t.stroke()}}function Re(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==r.strokeColor;let c,d;for(t.save(),t.font=a.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]),s(e.rotation)||t.rotate(e.rotation),e.color&&(t.fillStyle=e.color),e.textAlign&&(t.textAlign=e.textAlign),e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,r),c=0;ct[0])){const o=i||t;void 0===s&&(s=ti("_fallback",t));const a={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:o,_fallback:s,_getTarget:n,override:i=>je([i,...t],e,o,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>qe(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=ti(Ue(o,t),i),void 0!==n)return Xe(t,n)?Je(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>ei(t).includes(e),ownKeys:t=>ei(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function $e(t,e,i,s){const a={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:Ye(t,s),setContext:e=>$e(t,e,i,s),override:n=>$e(t.override(n),e,i,s)};return new Proxy(a,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>qe(t,e,(()=>function(t,e,i){const{_proxy:s,_context:a,_subProxy:r,_descriptors:l}=t;let h=s[e];S(h)&&l.isScriptable(e)&&(h=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t);let l=e(o,a||s);r.delete(t),Xe(t,l)&&(l=Je(n._scopes,n,t,l));return l}(e,h,t,i));n(h)&&h.length&&(h=function(t,e,i,s){const{_proxy:n,_context:a,_subProxy:r,_descriptors:l}=i;if(void 0!==a.index&&s(t))return e[a.index%e.length];if(o(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const o of i){const i=Je(s,n,t,o);e.push($e(i,a,r&&r[t],l))}}return e}(e,h,t,l.isIndexable));Xe(e,h)&&(h=$e(h,a,r&&r[e],l));return h}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function Ye(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:S(i)?i:()=>i,isIndexable:S(s)?s:()=>s}}const Ue=(t,e)=>t?t+w(e):e,Xe=(t,e)=>o(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function qe(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e))return t[e];const s=i();return t[e]=s,s}function Ke(t,e,i){return S(t)?t(e,i):t}const Ge=(t,e)=>!0===t?e:"string"==typeof t?M(e,t):void 0;function Ze(t,e,i,s,n){for(const o of e){const e=Ge(i,o);if(e){t.add(e);const o=Ke(e._fallback,i,n);if(void 0!==o&&o!==i&&o!==s)return o}else if(!1===e&&void 0!==s&&i!==s)return null}return!1}function Je(t,e,i,s){const a=e._rootScopes,r=Ke(e._fallback,i,s),l=[...t,...a],h=new Set;h.add(s);let c=Qe(h,l,i,r||i,s);return null!==c&&((void 0===r||r===i||(c=Qe(h,l,r,c,s),null!==c))&&je(Array.from(h),[""],a,r,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const a=s[e];if(n(a)&&o(i))return i;return a||{}}(e,i,s))))}function Qe(t,e,i,s,n){for(;i;)i=Ze(t,e,i,s,n);return i}function ti(t,e){for(const i of e){if(!i)continue;const e=i[t];if(void 0!==e)return e}}function ei(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}function ii(t,e,i,s){const{iScale:n}=t,{key:o="r"}=this._parsing,a=new Array(s);let r,l,h,c;for(r=0,l=s;re"x"===t?"y":"x";function ai(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=q(o,n),l=q(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function ri(t,e="x"){const i=oi(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=ni(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)ri(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,di=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*O/i),ui=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*O/i)+1,fi={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*E),easeOutSine:t=>Math.sin(t*E),easeInOutSine:t=>-.5*(Math.cos(C*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>ci(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>ci(t)?t:di(t,.075,.3),easeOutElastic:t=>ci(t)?t:ui(t,.075,.3),easeInOutElastic(t){const e=.1125;return ci(t)?t:t<.5?.5*di(2*t,e,.45):.5+.5*ui(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-fi.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*fi.easeInBounce(2*t):.5*fi.easeOutBounce(2*t-1)+.5};function gi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function pi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function mi(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=gi(t,n,i),r=gi(n,o,i),l=gi(o,e,i),h=gi(a,r,i),c=gi(r,l,i);return gi(h,c,i)}const bi=/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/,xi=/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;function _i(t,e){const i=(""+t).match(bi);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}const yi=t=>+t||0;function vi(t,e){const i={},s=o(e),n=s?Object.keys(e):e,a=o(t)?s?i=>l(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=yi(a(t));return i}function Mi(t){return vi(t,{top:"y",right:"x",bottom:"y",left:"x"})}function wi(t){return vi(t,["topLeft","topRight","bottomLeft","bottomRight"])}function ki(t){const e=Mi(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function Si(t,e){t=t||{},e=e||ue.font;let i=l(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=l(t.style,e.style);s&&!(""+s).match(xi)&&(console.warn('Invalid font style specified: "'+s+'"'),s=void 0);const n={family:l(t.family,e.family),lineHeight:_i(l(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:l(t.weight,e.weight),string:""};return n.string=De(n),n}function Pi(t,e,i,s){let o,a,r,l=!0;for(o=0,a=t.length;oi&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Ci(t,e){return Object.assign(Object.create(t),e)}function Oi(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ai(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function Ti(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Li(t){return"angle"===t?{between:Z,compare:K,normalize:G}:{between:tt,compare:(t,e)=>t-e,normalize:t=>t}}function Ei({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Ri(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Li(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Li(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hx||l(n,b,p)&&0!==r(n,b),v=()=>!x||0===r(o,p)||l(o,b,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==b&&(x=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Ei({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,b=p));return null!==_&&g.push(Ei({start:_,end:d,loop:u,count:a,style:f})),g}function Ii(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Fi(t,[{start:a,end:r,loop:o}],i,e);return Fi(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,r{t[a](e[i],n)&&(o.push({element:t,datasetIndex:s,index:l}),r=r||t.inRange(e.x,e.y,n))})),s&&!r?[]:o}var Xi={evaluateInteractionItems:Hi,modes:{index(t,e,i,s){const n=ve(e,t),o=i.axis||"x",a=i.includeInvisible||!1,r=i.intersect?ji(t,n,o,s,a):Yi(t,n,o,!1,s,a),l=[];return r.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=r[0].index,i=t.data[e];i&&!i.skip&&l.push({element:i,datasetIndex:t.index,index:e})})),l):[]},dataset(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;let r=i.intersect?ji(t,n,o,s,a):Yi(t,n,o,!1,s,a);if(r.length>0){const e=r[0].datasetIndex,i=t.getDatasetMeta(e).data;r=[];for(let t=0;tji(t,ve(e,t),i.axis||"xy",s,i.includeInvisible||!1),nearest(t,e,i,s){const n=ve(e,t),o=i.axis||"xy",a=i.includeInvisible||!1;return Yi(t,n,o,i.intersect,s,a)},x:(t,e,i,s)=>Ui(t,ve(e,t),"x",i.intersect,s),y:(t,e,i,s)=>Ui(t,ve(e,t),"y",i.intersect,s)}};const qi=["left","top","right","bottom"];function Ki(t,e){return t.filter((t=>t.pos===e))}function Gi(t,e){return t.filter((t=>-1===qi.indexOf(t.pos)&&t.box.axis===e))}function Zi(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function Ji(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!qi.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function ss(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Zi(Ki(e,"left"),!0),n=Zi(Ki(e,"right")),o=Zi(Ki(e,"top"),!0),a=Zi(Ki(e,"bottom")),r=Gi(e,"x"),l=Gi(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Ki(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;u(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),f=Object.assign({},n);ts(f,ki(s));const g=Object.assign({maxPadding:f,w:o,h:a,x:n.left,y:n.top},n),p=Ji(l.concat(h),d);ss(r.fullSize,g,d,p),ss(l,g,d,p),ss(h,g,d,p)&&ss(l,g,d,p),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(g),os(r.leftAndTop,g,d,p),g.x+=g.w,g.y+=g.h,os(r.rightAndBottom,g,d,p),t.chartArea={left:g.left,top:g.top,right:g.left+g.w,bottom:g.top+g.h,height:g.h,width:g.w},u(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(g.w,g.h,{left:0,top:0,right:0,bottom:0})}))}};class rs{acquireContext(t,e){}releaseContext(t){return!1}addEventListener(t,e,i){}removeEventListener(t,e,i){}getDevicePixelRatio(){return 1}getMaximumSize(t,e,i,s){return e=Math.max(0,e||t.width),i=i||t.height,{width:e,height:Math.max(0,s?Math.floor(e/s):i)}}isAttached(t){return!0}updateConfig(t){}}class ls extends rs{acquireContext(t){return t&&t.getContext&&t.getContext("2d")||null}updateConfig(t){t.options.animation=!1}}const hs="$chartjs",cs={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"},ds=t=>null===t||""===t;const us=!!Se&&{passive:!0};function fs(t,e,i){t.canvas.removeEventListener(e,i,us)}function gs(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function ps(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||gs(i.addedNodes,s),e=e&&!gs(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function ms(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||gs(i.removedNodes,s),e=e&&!gs(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const bs=new Map;let xs=0;function _s(){const t=window.devicePixelRatio;t!==xs&&(xs=t,bs.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function ys(t,e,i){const s=t.canvas,n=s&&ge(s);if(!n)return;const o=ct(((t,e)=>{const s=n.clientWidth;i(t,e),s{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||o(i,s)}));return a.observe(n),function(t,e){bs.size||window.addEventListener("resize",_s),bs.set(t,e)}(t,o),a}function vs(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){bs.delete(t),bs.size||window.removeEventListener("resize",_s)}(t)}function Ms(t,e,i){const s=t.canvas,n=ct((e=>{null!==t.ctx&&i(function(t,e){const i=cs[t.type]||t.type,{x:s,y:n}=ve(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t);return function(t,e,i){t.addEventListener(e,i,us)}(s,e,n),n}class ws extends rs{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t[hs]={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",ds(n)){const e=Pe(t,"width");void 0!==e&&(t.width=e)}if(ds(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Pe(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e[hs])return!1;const i=e[hs].initial;["height","width"].forEach((t=>{const n=i[t];s(n)?e.removeAttribute(t):e.setAttribute(t,n)}));const n=i.style||{};return Object.keys(n).forEach((t=>{e.style[t]=n[t]})),e.width=e.width,delete e[hs],!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:ps,detach:ms,resize:ys}[e]||Ms;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:vs,detach:vs,resize:vs}[e]||fs)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return we(t,e,i,s)}isAttached(t){const e=ge(t);return!(!e||!e.isConnected)}}function ks(t){return!fe()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?ls:ws}var Ss=Object.freeze({__proto__:null,BasePlatform:rs,BasicPlatform:ls,DomPlatform:ws,_detectPlatform:ks});const Ps="transparent",Ds={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=Qt(t||Ps),n=s.valid&&Qt(e||Ps);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class Cs{constructor(t,e,i,s){const n=e[i];s=Pi([t.to,s,n,t.from]);const o=Pi([t.from,n,s]);this._active=!0,this._fn=t.fn||Ds[t.type||typeof o],this._easing=fi[t.easing]||fi.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=Pi([t.to,e,s,t.from]),this._from=Pi([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t{const a=t[s];if(!o(a))return;const r={};for(const t of e)r[t]=a[t];(n(a.properties)&&a.properties||[s]).forEach((t=>{t!==s&&i.has(t)||i.set(t,r)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new Cs(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(xt.add(this._chart,i),!0):void 0}}function As(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function Ts(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function zs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Vs(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i],void 0!==e[s]._visualValues&&void 0!==e[s]._visualValues[i]&&delete e[s]._visualValues[i]}}}const Bs=t=>"reset"===t||"none"===t,Ws=(t,e)=>e?t:Object.assign({},t);class Ns{static defaults={};static datasetElementType=null;static dataElementType=null;constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.supportsDecimation=!1,this.$context=void 0,this._syncList=[],this.datasetElementType=new.target.datasetElementType,this.dataElementType=new.target.dataElementType,this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=Es(t.vScale,t),this.addElements(),this.options.fill&&!this.chart.isPluginEnabled("filler")&&console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options")}updateIndex(t){this.index!==t&&Vs(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=l(i.xAxisID,Fs(t,"x")),o=e.yAxisID=l(i.yAxisID,Fs(t,"y")),a=e.rAxisID=l(i.rAxisID,Fs(t,"r")),r=e.indexAxis,h=e.iAxisID=s(r,n,o,a),c=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(h),e.vScale=this.getScaleForId(c)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&rt(this._data,this),t._stacked&&Vs(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(o(e))this._data=function(t){const e=Object.keys(t),i=new Array(e.length);let s,n,o;for(s=0,n=e.length;s0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,d=s;else{d=n(s[t])?this.parseArrayData(i,s,t,e):o(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const a=()=>null===c[l]||f&&c[l]t&&!e.hidden&&e._stacked&&{keys:Ts(i,!0),values:null})(e,i,this.chart),h={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:c,max:d}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(r);let u,f;function g(){f=s[u];const e=f[r.axis];return!a(f[t.axis])||c>e||d=0;--u)if(!g()){this.updateRangeFromParsed(h,t,f,l);break}return h}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s,e)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Ws(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new Os(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||Bs(t)||this.chart._animationsDisabled}_getSharedOptions(t,e){const i=this.resolveDataElementOptions(t,e),s=this._sharedOptions,n=this.getSharedOptions(i),o=this.includeOptions(e,n)||n!==s;return this.updateSharedOptions(n,e,i),{sharedOptions:n,includeOptions:o}}updateElement(t,e,i,s){Bs(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!Bs(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}function js(t,e){const i=t.options.ticks,n=function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),o=Math.min(i.maxTicksLimit||n,n),a=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;io)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(a,e,o);if(r>0){let t,i;const n=r>1?Math.round((h-l)/(r-1)):null;for($s(e,c,d,s(n)?0:l-n,l),t=0,i=r-1;t"top"===e||"left"===e?t[e]+i:t[e]-i,Us=(t,e)=>Math.min(e||t,t);function Xs(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Ks(t){return t.drawTicks?t.tickLength:0}function Gs(t,e){if(!t.display)return 0;const i=Si(t.font,e),s=ki(t.padding);return(n(t.text)?t.text.length:1)*i.lineHeight+s.height}function Zs(t,e,i){let s=ut(t);return(i&&"right"!==e||!i&&"right"===e)&&(s=(t=>"left"===t?"right":"right"===t?"left":t)(s)),s}class Js extends Hs{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=r(t,Number.POSITIVE_INFINITY),e=r(e,Number.NEGATIVE_INFINITY),i=r(i,Number.POSITIVE_INFINITY),s=r(s,Number.NEGATIVE_INFINITY),{min:r(t,i),max:r(e,s),minDefined:a(t),maxDefined:a(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:r(i,r(s,i)),max:r(s,r(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}getLabelItems(t=this.chart.chartArea){return this._labelItems||(this._labelItems=this._computeLabelItems(t))}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){d(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=Di(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=J(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Ks(t.grid)-e.padding-Gs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=Y(Math.min(Math.asin(J((h.highest.height+6)/o,-1,1)),Math.asin(J(a/r,-1,1))-Math.asin(J(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){d(this.options.afterCalculateLabelRotation,[this])}afterAutoSkip(){}beforeFit(){d(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=Gs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Ks(n)+o):(t.height=this.maxHeight,t.width=Ks(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=$(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:"inner"!==n&&(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){d(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:r[t]||0,height:l[t]||0});return{first:P(0),last:P(e-1),widest:P(k),highest:P(S),widths:r,heights:l}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return Q(this._alignToPixels?Ae(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:a,border:r}=s,h=n.offset,c=this.isHorizontal(),d=this.ticks.length+(h?1:0),u=Ks(n),f=[],g=r.setContext(this.getContext()),p=g.display?g.width:0,m=p/2,b=function(t){return Ae(i,t,p)};let x,_,y,v,M,w,k,S,P,D,C,O;if("top"===a)x=b(this.bottom),w=this.bottom-u,S=x-m,D=b(t.top)+m,O=t.bottom;else if("bottom"===a)x=b(this.top),D=t.top,O=b(t.bottom)-m,w=x+m,S=this.top+u;else if("left"===a)x=b(this.right),M=this.right-u,k=x-m,P=b(t.left)+m,C=t.right;else if("right"===a)x=b(this.left),P=t.left,C=b(t.right)-m,M=x+m,k=this.left+u;else if("x"===e){if("center"===a)x=b((t.top+t.bottom)/2+.5);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}D=t.top,O=t.bottom,w=x+m,S=w+u}else if("y"===e){if("center"===a)x=b((t.left+t.right)/2);else if(o(a)){const t=Object.keys(a)[0],e=a[t];x=b(this.chart.scales[t].getPixelForValue(e))}M=x-m,k=M-u,P=t.left,C=t.right}const A=l(s.ticks.maxTicksLimit,d),T=Math.max(1,Math.ceil(d/A));for(_=0;_0&&(o-=s/2)}d={left:o,top:n,width:s+e.width,height:i+e.height,color:t.backdropColor}}b.push({label:v,font:P,textOffset:O,options:{rotation:m,color:i,strokeColor:o,strokeWidth:h,textAlign:f,textBaseline:A,translation:[M,w],backdrop:d}})}return b}_getXAxisLabelAlignment(){const{position:t,ticks:e}=this.options;if(-$(this.labelRotation))return"top"===t?"left":"right";let i="center";return"start"===e.align?i="left":"end"===e.align?i="right":"inner"===e.align&&(i="inner"),i}_getYAxisLabelAlignment(t){const{position:e,ticks:{crossAlign:i,mirror:s,padding:n}}=this.options,o=t+n,a=this._getLabelSizes().widest.width;let r,l;return"left"===e?s?(l=this.right+n,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l+=a)):(l=this.right-o,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l=this.left)):"right"===e?s?(l=this.left+n,"near"===i?r="right":"center"===i?(r="center",l-=a/2):(r="left",l-=a)):(l=this.left+o,"near"===i?r="left":"center"===i?(r="center",l+=a/2):(r="right",l=this.right)):r="right",{textAlign:r,x:l}}_computeLabelArea(){if(this.options.ticks.mirror)return;const t=this.chart,e=this.options.position;return"left"===e||"right"===e?{top:0,left:this.left,bottom:t.height,right:this.right}:"top"===e||"bottom"===e?{top:this.top,left:0,bottom:this.bottom,right:t.width}:void 0}drawBackground(){const{ctx:t,options:{backgroundColor:e},left:i,top:s,width:n,height:o}=this;e&&(t.save(),t.fillStyle=e,t.fillRect(i,s,n,o),t.restore())}getLineWidthForValue(t){const e=this.options.grid;if(!this._isVisible()||!e.display)return 0;const i=this.ticks.findIndex((e=>e.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:s,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");ue.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&ue.describe(e,t.descriptors)}(t,o,i),this.override&&ue.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in ue[s]&&(delete ue[s][i],this.override&&delete re[i])}}class tn{constructor(){this.controllers=new Qs(Ns,"datasets",!0),this.elements=new Qs(Hs,"elements"),this.plugins=new Qs(Object,"plugins"),this.scales=new Qs(Js,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):u(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=w(t);d(i["before"+s],[],i),e[t](i),d(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function nn(t,e){return e||!1!==t?!0===t?{}:t:null}function on(t,{plugin:e,local:i},s,n){const o=t.pluginScopeKeys(e),a=t.getOptionScopes(s,o);return i&&e.defaults&&a.push(e.defaults),t.createResolver(a,n,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function an(t,e){const i=ue.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function rn(t){if("x"===t||"y"===t||"r"===t)return t}function ln(t,...e){if(rn(t))return t;for(const s of e){const e=s.axis||("top"===(i=s.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.length>1&&rn(t[0].toLowerCase());if(e)return e}var i;throw new Error(`Cannot determine type of '${t}' axis. Please provide 'axis' or 'position' option.`)}function hn(t,e,i){if(i[e+"AxisID"]===t)return{axis:e}}function cn(t,e){const i=re[t.type]||{scales:{}},s=e.scales||{},n=an(t.type,e),a=Object.create(null);return Object.keys(s).forEach((e=>{const r=s[e];if(!o(r))return console.error(`Invalid scale configuration for scale: ${e}`);if(r._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${e}`);const l=ln(e,r,function(t,e){if(e.data&&e.data.datasets){const i=e.data.datasets.filter((e=>e.xAxisID===t||e.yAxisID===t));if(i.length)return hn(t,"x",i[0])||hn(t,"y",i[0])}return{}}(e,t),ue.scales[r.type]),h=function(t,e){return t===e?"_index_":"_value_"}(l,n),c=i.scales||{};a[e]=x(Object.create(null),[{axis:l},r,c[l],c[h]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,o=i.indexAxis||an(n,e),r=(re[n]||{}).scales||{};Object.keys(r).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,o),n=i[e+"AxisID"]||e;a[n]=a[n]||Object.create(null),x(a[n],[{axis:e},s[n],r[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];x(e,[ue.scales[e.type],ue.scale])})),a}function dn(t){const e=t.options||(t.options={});e.plugins=l(e.plugins,{}),e.scales=cn(t,e)}function un(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const fn=new Map,gn=new Set;function pn(t,e){let i=fn.get(t);return i||(i=e(),fn.set(t,i),gn.add(i)),i}const mn=(t,e,i)=>{const s=M(e,i);void 0!==s&&t.add(s)};class bn{constructor(t){this._config=function(t){return(t=t||{}).data=un(t.data),dn(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=un(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),dn(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return pn(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return pn(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return pn(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return pn(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>mn(r,t,e)))),e.forEach((t=>mn(r,s,t))),e.forEach((t=>mn(r,re[n]||{},t))),e.forEach((t=>mn(r,ue,t))),e.forEach((t=>mn(r,le,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),gn.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,re[e]||{},ue.datasets[e]||{},{type:e},ue,le]}resolveNamedOptions(t,e,i,s=[""]){const o={$shared:!0},{resolver:a,subPrefixes:r}=xn(this._resolverCache,t,s);let l=a;if(function(t,e){const{isScriptable:i,isIndexable:s}=Ye(t);for(const o of e){const e=i(o),a=s(o),r=(a||e)&&t[o];if(e&&(S(r)||_n(r))||a&&n(r))return!0}return!1}(a,e)){o.$shared=!1;l=$e(a,i=S(i)?i():i,this.createResolver(t,i,r))}for(const t of e)o[t]=l[t];return o}createResolver(t,e,i=[""],s){const{resolver:n}=xn(this._resolverCache,t,i);return o(e)?$e(n,e,void 0,s):n}}function xn(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:je(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const _n=t=>o(t)&&Object.getOwnPropertyNames(t).some((e=>S(t[e])));const yn=["top","bottom","left","right","chartArea"];function vn(t,e){return"top"===t||"bottom"===t||-1===yn.indexOf(t)&&"x"===e}function Mn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function wn(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),d(i&&i.onComplete,[t],e)}function kn(t){const e=t.chart,i=e.options.animation;d(i&&i.onProgress,[t],e)}function Sn(t){return fe()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const Pn={},Dn=t=>{const e=Sn(t);return Object.values(Pn).filter((t=>t.canvas===e)).pop()};function Cn(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}function On(t,e,i){return t.options.clip?t[i]:e[i]}class An{static defaults=ue;static instances=Pn;static overrides=re;static registry=en;static version="4.4.1";static getChart=Dn;static register(...t){en.add(...t),Tn()}static unregister(...t){en.remove(...t),Tn()}constructor(t,e){const s=this.config=new bn(e),n=Sn(t),o=Dn(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas with ID '"+o.canvas.id+"' can be reused.");const a=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||ks(n)),this.platform.updateConfig(s);const r=this.platform.acquireContext(n,a.aspectRatio),l=r&&r.canvas,h=l&&l.height,c=l&&l.width;this.id=i(),this.ctx=r,this.canvas=l,this.width=c,this.height=h,this._options=a,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new sn,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=dt((t=>this.update(t)),a.resizeDelay||0),this._dataChanges=[],Pn[this.id]=this,r&&l?(xt.listen(this,"complete",wn),xt.listen(this,"progress",kn),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:n,_aspectRatio:o}=this;return s(t)?e&&o?o:n?i/n:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}get registry(){return en}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():ke(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Te(this.canvas,this.ctx),this}stop(){return xt.stop(this),this}resize(t,e){xt.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,ke(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),d(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){u(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=ln(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),u(n,(e=>{const n=e.options,o=n.id,a=ln(o,n),r=l(n.type,e.dtype);void 0!==n.position&&vn(n.position,a)===vn(e.dposition)||(n.position=e.dposition),s[o]=!0;let h=null;if(o in i&&i[o].type===r)h=i[o];else{h=new(en.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[h.id]=h}h.init(n,t)})),u(s,((t,e)=>{t||delete i[e]})),u(i,(t=>{as.configure(this,t,t.options),as.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(Mn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){u(this.scales,(t=>{as.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);P(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){Cn(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;as.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],u(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i=t._clip,s=!i.disabled,n=function(t,e){const{xScale:i,yScale:s}=t;return i&&s?{left:On(i,e,"left"),right:On(i,e,"right"),top:On(s,e,"top"),bottom:On(s,e,"bottom")}:e}(t,this.chartArea),o={meta:t,index:t.index,cancelable:!0};!1!==this.notifyPlugins("beforeDatasetDraw",o)&&(s&&Ie(e,{left:!1===i.left?0:n.left-i.left,right:!1===i.right?this.width:n.right+i.right,top:!1===i.top?0:n.top-i.top,bottom:!1===i.bottom?this.height:n.bottom+i.bottom}),t.controller.draw(),s&&ze(e),o.cancelable=!1,this.notifyPlugins("afterDatasetDraw",o))}isPointInArea(t){return Re(t,this.chartArea,this._minPadding)}getElementsAtEventForMode(t,e,i,s){const n=Xi.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Ci(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);k(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),xt.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};u(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){u(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},u(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!f(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}isPluginEnabled(t){return 1===this._plugins._cache.filter((e=>e.plugin.id===t)).length}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:this.isPointInArea(t)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=D(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,d(n.onHover,[t,a,this],this),r&&d(n.onClick,[t,a,this],this));const h=!f(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}function Tn(){return u(An.instances,(t=>t._plugins.invalidate()))}function Ln(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}class En{static override(t){Object.assign(En.prototype,t)}options;constructor(t){this.options=t||{}}init(){}formats(){return Ln()}parse(){return Ln()}format(){return Ln()}add(){return Ln()}diff(){return Ln()}startOf(){return Ln()}endOf(){return Ln()}}var Rn={_date:En};function In(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(k(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function Fn(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base"spacing"!==t,_indexable:t=>"spacing"!==t&&!t.startsWith("borderDash")&&!t.startsWith("hoverBorderDash")};static overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i,color:s}}=t.legend.options;return e.labels.map(((e,n)=>{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}}};constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,a,r=t=>+i[t];if(o(i[t])){const{key:t="value"}=this._parsing;r=e=>+M(i[e],t)}for(n=t,a=t+e;nZ(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>Z(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(E,c,u),b=g(C,h,d),x=g(C+E,c,u);s=(p-b)/2,n=(m-x)/2,o=-(p+b)/2,a=-(m+x)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(u,d,r),b=(i.width-o)/f,x=(i.height-o)/g,_=Math.max(Math.min(b,x)/2,0),y=c(this.options.radius,_),v=(y-Math.max(y*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=p*y,this.offsetY=m*y,s.total=this.calculateTotal(),this.outerRadius=y-v*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-v*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/O)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,{sharedOptions:f,includeOptions:g}=this._getSharedOptions(e,s);let p,m=this._getRotation();for(p=0;p0&&!isNaN(t)?O*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s{const o=t.getDatasetMeta(0).controller.getStyle(n);return{text:e,fillStyle:o.backgroundColor,strokeStyle:o.borderColor,fontColor:s,lineWidth:o.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(n),index:n}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=ne(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}getMinMax(){const t=this._cachedMeta,e={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY};return t.data.forEach(((t,i)=>{const s=this.getParsed(i).r;!isNaN(s)&&this.chart.getDataVisibility(i)&&(se.max&&(e.max=s))})),e}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.options.animation,r=this._cachedMeta.rScale,l=r.xCenter,h=r.yCenter,c=r.getIndexAngle(0)-.5*C;let d,u=c;const f=360/this.countVisibleElements();for(d=0;d{!isNaN(this.getParsed(i).r)&&this.chart.getDataVisibility(i)&&e++})),e}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?$(this.resolveDataElementOptions(t,e).angle||i):0}}var Yn=Object.freeze({__proto__:null,BarController:class extends Ns{static id="bar";static defaults={datasetElementType:!1,dataElementType:"bar",categoryPercentage:.8,barPercentage:.9,grouped:!0,animations:{numbers:{type:"number",properties:["x","y","base","width","height"]}}};static overrides={scales:{_index_:{type:"category",offset:!0,grid:{offset:!0}},_value_:{type:"linear",beginAtZero:!0}}};parsePrimitiveData(t,e,i,s){return Fn(t,e,i,s)}parseArrayData(t,e,i,s){return Fn(t,e,i,s)}parseObjectData(t,e,i,s){const{iScale:n,vScale:o}=t,{xAxisKey:a="x",yAxisKey:r="y"}=this._parsing,l="x"===n.axis?a:r,h="x"===o.axis?a:r,c=[];let d,u,f,g;for(d=i,u=i+s;dt.controller.options.grouped)),o=i.options.stacked,a=[],r=t=>{const i=t.controller.getParsed(e),n=i&&i[t.vScale.axis];if(s(n)||isNaN(n))return!0};for(const i of n)if((void 0===e||!r(i))&&((!1===o||-1===a.indexOf(i.stack)||void 0===o&&void 0===i.stack)&&a.push(i.stack),i.index===t))break;return a.length||a.push(void 0),a}_getStackCount(t){return this._getStacks(void 0,t).length}_getStackIndex(t,e,i){const s=this._getStacks(t,i),n=void 0!==e?s.indexOf(e):-1;return-1===n?s.length-1:n}_getRuler(){const t=this.options,e=this._cachedMeta,i=e.iScale,s=[];let n,o;for(n=0,o=e.data.length;n=i?1:-1)}(u,e,r)*a,f===r&&(b-=u/2);const t=e.getPixelForDecimal(0),s=e.getPixelForDecimal(1),o=Math.min(t,s),h=Math.max(t,s);b=Math.max(Math.min(b,h),o),d=b+u,i&&!c&&(l._stacks[e.axis]._visualValues[n]=e.getValueForPixel(d)-e.getValueForPixel(b))}if(b===e.getPixelForValue(r)){const t=F(u)*e.getLineWidthForValue(r)/2;b+=t,u-=t}return{size:u,base:b,head:d,center:d+u/2}}_calculateBarIndexPixels(t,e){const i=e.scale,n=this.options,o=n.skipNull,a=l(n.maxBarThickness,1/0);let r,h;if(e.grouped){const i=o?this._getStackCount(t):e.stackCount,l="flex"===n.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart.data.labels||[],{xScale:s,yScale:n}=e,o=this.getParsed(t),a=s.getLabelForValue(o.x),r=n.getLabelForValue(o.y),l=o._custom;return{label:i[t]||"",value:"("+a+", "+r+(l?", "+l:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,{sharedOptions:r,includeOptions:l}=this._getSharedOptions(e,s),h=o.axis,c=a.axis;for(let d=e;d0&&this.getParsed(e-1);for(let i=0;i<_;++i){const g=t[i],_=b?g:{};if(i=x){_.skip=!0;continue}const v=this.getParsed(i),M=s(v[f]),w=_[u]=a.getPixelForValue(v[u],i),k=_[f]=o||M?r.getBasePixel():r.getPixelForValue(l?this.applyStack(r,v,l):v[f],i);_.skip=isNaN(w)||isNaN(k)||M,_.stop=i>0&&Math.abs(v[u]-y[u])>m,p&&(_.parsed=v,_.raw=h.data[i]),d&&(_.options=c||this.resolveDataElementOptions(i,g.active?"active":n)),b||this.updateElement(g,i,_,n),y=v}}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}},PieController:class extends jn{static id="pie";static defaults={cutout:0,rotation:0,circumference:360,radius:"100%"}},PolarAreaController:$n,RadarController:class extends Ns{static id="radar";static defaults={datasetElementType:"line",dataElementType:"point",indexAxis:"r",showLine:!0,elements:{line:{fill:"start"}}};static overrides={aspectRatio:1,scales:{r:{type:"radialLinear"}}};getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}parseObjectData(t,e,i,s){return ii.bind(this)(t,e,i,s)}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this._cachedMeta.rScale,o="reset"===s;for(let a=e;a0&&this.getParsed(e-1);for(let c=e;c0&&Math.abs(i[f]-_[f])>b,m&&(p.parsed=i,p.raw=h.data[c]),u&&(p.options=d||this.resolveDataElementOptions(c,e.active?"active":n)),x||this.updateElement(e,c,p,n),_=i}this.updateSharedOptions(d,n,c)}getMaxOverflow(){const t=this._cachedMeta,e=t.data||[];if(!this.options.showLine){let t=0;for(let i=e.length-1;i>=0;--i)t=Math.max(t,e[i].size(this.resolveDataElementOptions(i))/2);return t>0&&t}const i=t.dataset,s=i.options&&i.options.borderWidth||0;if(!e.length)return s;const n=e[0].size(this.resolveDataElementOptions(0)),o=e[e.length-1].size(this.resolveDataElementOptions(e.length-1));return Math.max(s,n,o)/2}}});function Un(t,e,i,s){const n=vi(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return J(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:J(n.innerStart,0,a),innerEnd:J(n.innerEnd,0,a)}}function Xn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function qn(t,e,i,s,n,o){const{x:a,y:r,startAngle:l,pixelMargin:h,innerRadius:c}=e,d=Math.max(e.outerRadius+s+i-h,0),u=c>0?c+s+i+h:0;let f=0;const g=n-l;if(s){const t=((c>0?c-s:0)+(d>0?d-s:0))/2;f=(g-(0!==t?g*t/(t+s):g))/2}const p=(g-Math.max(.001,g*d-i/C)/d)/2,m=l+p+f,b=n-p-f,{outerStart:x,outerEnd:_,innerStart:y,innerEnd:v}=Un(e,u,d,b-m),M=d-x,w=d-_,k=m+x/M,S=b-_/w,P=u+y,D=u+v,O=m+y/P,A=b-v/D;if(t.beginPath(),o){const e=(k+S)/2;if(t.arc(a,r,d,k,e),t.arc(a,r,d,e,S),_>0){const e=Xn(w,S,a,r);t.arc(e.x,e.y,_,S,b+E)}const i=Xn(D,b,a,r);if(t.lineTo(i.x,i.y),v>0){const e=Xn(D,A,a,r);t.arc(e.x,e.y,v,b+E,A+Math.PI)}const s=(b-v/u+(m+y/u))/2;if(t.arc(a,r,u,b-v/u,s,!0),t.arc(a,r,u,s,m+y/u,!0),y>0){const e=Xn(P,O,a,r);t.arc(e.x,e.y,y,O+Math.PI,m-E)}const n=Xn(M,m,a,r);if(t.lineTo(n.x,n.y),x>0){const e=Xn(M,k,a,r);t.arc(e.x,e.y,x,m-E,k)}}else{t.moveTo(a,r);const e=Math.cos(k)*d+a,i=Math.sin(k)*d+r;t.lineTo(e,i);const s=Math.cos(S)*d+a,n=Math.sin(S)*d+r;t.lineTo(s,n)}t.closePath()}function Kn(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r,options:l}=e,{borderWidth:h,borderJoinStyle:c,borderDash:d,borderDashOffset:u}=l,f="inner"===l.borderAlign;if(!h)return;t.setLineDash(d||[]),t.lineDashOffset=u,f?(t.lineWidth=2*h,t.lineJoin=c||"round"):(t.lineWidth=h,t.lineJoin=c||"bevel");let g=e.endAngle;if(o){qn(t,e,i,s,g,n);for(let e=0;en?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+E,s-E),t.closePath(),t.clip()}(t,e,g),o||(qn(t,e,i,s,g,n),t.stroke())}function Gn(t,e,i=e){t.lineCap=l(i.borderCapStyle,e.borderCapStyle),t.setLineDash(l(i.borderDash,e.borderDash)),t.lineDashOffset=l(i.borderDashOffset,e.borderDashOffset),t.lineJoin=l(i.borderJoinStyle,e.borderJoinStyle),t.lineWidth=l(i.borderWidth,e.borderWidth),t.strokeStyle=l(i.borderColor,e.borderColor)}function Zn(t,e,i){t.lineTo(i.x,i.y)}function Jn(t,e,i={}){const s=t.length,{start:n=0,end:o=s-1}=i,{start:a,end:r}=e,l=Math.max(n,a),h=Math.min(o,r),c=nr&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[x(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[x(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(b*m+e)/++b):(_(),t.lineTo(e,i),u=s,b=0,f=g=i),p=i}_()}function eo(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?to:Qn}const io="function"==typeof Path2D;function so(t,e,i,s){io&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),Gn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=eo(e);for(const r of n)Gn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class no extends Hs{static id="line";static defaults={borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",borderWidth:3,capBezierPoints:!0,cubicInterpolationMode:"default",fill:!1,spanGaps:!1,stepped:!1,tension:0};static defaultRoutes={backgroundColor:"backgroundColor",borderColor:"borderColor"};static descriptors={_scriptable:!0,_indexable:t=>"borderDash"!==t&&"fill"!==t};constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;hi(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=zi(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Ii(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?pi:t.tension||"monotone"===t.cubicInterpolationMode?mi:gi}(i);let l,h;for(l=0,h=o.length;l"borderDash"!==t};circumference;endAngle;fullCircles;innerRadius;outerRadius;pixelMargin;startAngle;constructor(t){super(),this.options=void 0,this.circumference=void 0,this.startAngle=void 0,this.endAngle=void 0,this.innerRadius=void 0,this.outerRadius=void 0,this.pixelMargin=0,this.fullCircles=0,t&&Object.assign(this,t)}inRange(t,e,i){const s=this.getProps(["x","y"],i),{angle:n,distance:o}=X(s,{x:t,y:e}),{startAngle:a,endAngle:r,innerRadius:h,outerRadius:c,circumference:d}=this.getProps(["startAngle","endAngle","innerRadius","outerRadius","circumference"],i),u=(this.options.spacing+this.options.borderWidth)/2,f=l(d,r-a)>=O||Z(n,a,r),g=tt(o,h+u,c+u);return f&&g}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/4,n=(e.spacing||0)/2,o=e.circular;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>O?Math.floor(i/O):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();const a=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(a)*s,Math.sin(a)*s);const r=s*(1-Math.sin(Math.min(C,i||0)));t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,function(t,e,i,s,n){const{fullCircles:o,startAngle:a,circumference:r}=e;let l=e.endAngle;if(o){qn(t,e,i,s,l,n);for(let e=0;e("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}function po(t){const e=this.getLabels();return t>=0&&ts=e?s:t,a=t=>n=i?n:t;if(t){const t=F(s),e=F(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=0===n?1:Math.abs(.05*n);a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const n=function(t,e){const i=[],{bounds:n,step:o,min:a,max:r,precision:l,count:h,maxTicks:c,maxDigits:d,includeBounds:u}=t,f=o||1,g=c-1,{min:p,max:m}=e,b=!s(a),x=!s(r),_=!s(h),y=(m-p)/(d+1);let v,M,w,k,S=B((m-p)/g/f)*f;if(S<1e-14&&!b&&!x)return[{value:p},{value:m}];k=Math.ceil(m/S)-Math.floor(p/S),k>g&&(S=B(k*S/g/f)*f),s(l)||(v=Math.pow(10,l),S=Math.ceil(S*v)/v),"ticks"===n?(M=Math.floor(p/S)*S,w=Math.ceil(m/S)*S):(M=p,w=m),b&&x&&o&&H((r-a)/o,S/1e3)?(k=Math.round(Math.min((r-a)/S,c)),S=(r-a)/k,M=a,w=r):_?(M=b?a:M,w=x?r:w,k=h-1,S=(w-M)/k):(k=(w-M)/S,k=V(k,Math.round(k),S/1e3)?Math.round(k):Math.ceil(k));const P=Math.max(U(S),U(M));v=Math.pow(10,s(l)?P:l),M=Math.round(M*v)/v,w=Math.round(w*v)/v;let D=0;for(b&&(u&&M!==a?(i.push({value:a}),Mr)break;i.push({value:t})}return x&&u&&w!==r?i.length&&V(i[i.length-1].value,r,mo(r,y,t))?i[i.length-1].value=r:i.push({value:r}):x&&w!==r||i.push({value:w}),i}({maxTicks:i,bounds:t.bounds,min:t.min,max:t.max,precision:e.precision,step:e.stepSize,count:e.count,maxDigits:this._maxDigits(),horizontal:this.isHorizontal(),minRotation:e.minRotation||0,includeBounds:!1!==e.includeBounds},this._range||this);return"ticks"===t.bounds&&j(n,this,"value"),t.reverse?(n.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),n}configure(){const t=this.ticks;let e=this.min,i=this.max;if(super.configure(),this.options.offset&&t.length){const s=(i-e)/Math.max(t.length-1,1)/2;e-=s,i+=s}this._startValue=e,this._endValue=i,this._valueRange=i-e}getLabelForValue(t){return ne(t,this.chart.options.locale,this.options.ticks.format)}}class xo extends bo{static id="linear";static defaults={ticks:{callback:ae.formatters.numeric}};determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?t:0,this.max=a(e)?e:1,this.handleTickRangeOptions()}computeTickLimit(){const t=this.isHorizontal(),e=t?this.width:this.height,i=$(this.options.ticks.minRotation),s=(t?Math.sin(i):Math.cos(i))||.001,n=this._resolveTickFontOptions(0);return Math.ceil(e/Math.min(40,n.lineHeight/s))}getPixelForValue(t){return null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getValueForPixel(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange}}const _o=t=>Math.floor(z(t)),yo=(t,e)=>Math.pow(10,_o(t)+e);function vo(t){return 1===t/Math.pow(10,_o(t))}function Mo(t,e,i){const s=Math.pow(10,i),n=Math.floor(t/s);return Math.ceil(e/s)-n}function wo(t,{min:e,max:i}){e=r(t.min,e);const s=[],n=_o(e);let o=function(t,e){let i=_o(e-t);for(;Mo(t,e,i)>10;)i++;for(;Mo(t,e,i)<10;)i--;return Math.min(i,_o(t))}(e,i),a=o<0?Math.pow(10,Math.abs(o)):1;const l=Math.pow(10,o),h=n>o?Math.pow(10,n):0,c=Math.round((e-h)*a)/a,d=Math.floor((e-h)/l/10)*l*10;let u=Math.floor((c-d)/Math.pow(10,o)),f=r(t.min,Math.round((h+d+u*Math.pow(10,o))*a)/a);for(;f=10?u=u<15?15:20:u++,u>=20&&(o++,u=2,a=o>=0?1:a),f=Math.round((h+d+u*Math.pow(10,o))*a)/a;const g=r(t.max,f);return s.push({value:g,major:vo(g),significand:u}),s}class ko extends Js{static id="logarithmic";static defaults={ticks:{callback:ae.formatters.logarithmic,major:{enabled:!0}}};constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._valueRange=0}parse(t,e){const i=bo.prototype.parse.apply(this,[t,e]);if(0!==i)return a(i)&&i>0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=a(t)?Math.max(0,t):null,this.max=a(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this._zero&&this.min!==this._suggestedMin&&!a(this._userMin)&&(this.min=t===yo(this.min,0)?yo(this.min,-1):yo(this.min,0)),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t;i===s&&(i<=0?(n(1),o(10)):(n(yo(i,-1)),o(yo(s,1)))),i<=0&&n(yo(s,-1)),s<=0&&o(yo(i,1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=wo({min:this._userMin,max:this._userMax},this);return"ticks"===t.bounds&&j(e,this,"value"),t.reverse?(e.reverse(),this.start=this.max,this.end=this.min):(this.start=this.min,this.end=this.max),e}getLabelForValue(t){return void 0===t?"0":ne(t,this.chart.options.locale,this.options.ticks.format)}configure(){const t=this.min;super.configure(),this._startValue=z(t),this._valueRange=z(this.max)-z(t)}getPixelForValue(t){return void 0!==t&&0!==t||(t=this.min),null===t||isNaN(t)?NaN:this.getPixelForDecimal(t===this.min?0:(z(t)-this._startValue)/this._valueRange)}getValueForPixel(t){const e=this.getDecimalForPixel(t);return Math.pow(10,this._startValue+e*this._valueRange)}}function So(t){const e=t.ticks;if(e.display&&t.display){const t=ki(e.backdropPadding);return l(e.font&&e.font.size,ue.font.size)+t.height}return 0}function Po(t,e,i,s,n){return t===s||t===n?{start:e-i/2,end:e+i/2}:tn?{start:e-i,end:e}:{start:e,end:e+i}}function Do(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],o=[],a=t._pointLabels.length,r=t.options.pointLabels,l=r.centerPointLabels?C/a:0;for(let u=0;ue.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function Oo(t,e,i){const s=t.drawingArea,{extra:n,additionalAngle:o,padding:a,size:r}=i,l=t.getPointPosition(e,s+n+a,o),h=Math.round(Y(G(l.angle+E))),c=function(t,e,i){90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e);return t}(l.y,r.h,h),d=function(t){if(0===t||180===t)return"center";if(t<180)return"left";return"right"}(h),u=function(t,e,i){"right"===i?t-=e:"center"===i&&(t-=e/2);return t}(l.x,r.w,d);return{visible:!0,x:l.x,y:c,textAlign:d,left:u,top:c,right:u+r.w,bottom:c+r.h}}function Ao(t,e){if(!e)return!0;const{left:i,top:s,right:n,bottom:o}=t;return!(Re({x:i,y:s},e)||Re({x:i,y:o},e)||Re({x:n,y:s},e)||Re({x:n,y:o},e))}function To(t,e,i){const{left:n,top:o,right:a,bottom:r}=i,{backdropColor:l}=e;if(!s(l)){const i=wi(e.borderRadius),s=ki(e.backdropPadding);t.fillStyle=l;const h=n-s.left,c=o-s.top,d=a-n+s.width,u=r-o+s.height;Object.values(i).some((t=>0!==t))?(t.beginPath(),He(t,{x:h,y:c,w:d,h:u,radius:i}),t.fill()):t.fillRect(h,c,d,u)}}function Lo(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,O);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;ot,padding:5,centerPointLabels:!1}};static defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"};static descriptors={angleLines:{_fallback:"grid"}};constructor(t){super(t),this.xCenter=void 0,this.yCenter=void 0,this.drawingArea=void 0,this._pointLabels=[],this._pointLabelItems=[]}setDimensions(){const t=this._padding=ki(So(this.options)/2),e=this.width=this.maxWidth-t.width,i=this.height=this.maxHeight-t.height;this.xCenter=Math.floor(this.left+e/2+t.left),this.yCenter=Math.floor(this.top+i/2+t.top),this.drawingArea=Math.floor(Math.min(e,i)/2)}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!1);this.min=a(t)&&!isNaN(t)?t:0,this.max=a(e)&&!isNaN(e)?e:0,this.handleTickRangeOptions()}computeTickLimit(){return Math.ceil(this.drawingArea/So(this.options))}generateTickLabels(t){bo.prototype.generateTickLabels.call(this,t),this._pointLabels=this.getLabels().map(((t,e)=>{const i=d(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?Do(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return G(t*(O/(this._pointLabels.length||1))+$(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if(s(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if(s(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;n--){const e=t._pointLabelItems[n];if(!e.visible)continue;const o=s.setContext(t.getPointLabelContext(n));To(i,o,e);const a=Si(o.font),{x:r,y:l,textAlign:h}=e;Ne(i,t._pointLabels[n],r,l+a.lineHeight/2,a,{color:o.color,textAlign:h,textBaseline:"middle"})}}(this,o),s.display&&this.ticks.forEach(((t,e)=>{if(0!==e){r=this.getDistanceFromCenterForValue(t.value);const i=this.getContext(e),a=s.setContext(i),l=n.setContext(i);!function(t,e,i,s,n){const o=t.ctx,a=e.circular,{color:r,lineWidth:l}=e;!a&&!s||!r||!l||i<0||(o.save(),o.strokeStyle=r,o.lineWidth=l,o.setLineDash(n.dash),o.lineDashOffset=n.dashOffset,o.beginPath(),Lo(t,i,a,s),o.closePath(),o.stroke(),o.restore())}(this,a,r,o,l)}})),i.display){for(t.save(),a=o-1;a>=0;a--){const s=i.setContext(this.getPointLabelContext(a)),{color:n,lineWidth:o}=s;o&&n&&(t.lineWidth=o,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,r=this.getDistanceFromCenterForValue(e.ticks.reverse?this.min:this.max),l=this.getPointPosition(a,r),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(l.x,l.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=Si(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=ki(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}Ne(t,s.label,0,-n,l,{color:r.color,strokeColor:r.textStrokeColor,strokeWidth:r.textStrokeWidth})})),t.restore()}drawTitle(){}}const Ro={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Io=Object.keys(Ro);function zo(t,e){return t-e}function Fo(t,e){if(s(e))return null;const i=t._adapter,{parser:n,round:o,isoWeekday:r}=t._parseOpts;let l=e;return"function"==typeof n&&(l=n(l)),a(l)||(l="string"==typeof n?i.parse(l,n):i.parse(l)),null===l?null:(o&&(l="week"!==o||!N(r)&&!0!==r?i.startOf(l,o):i.startOf(l,"isoWeek",r)),+l)}function Vo(t,e,i,s){const n=Io.length;for(let o=Io.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function Wo(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class No extends Js{static id="time";static defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",callback:!1,major:{enabled:!1}}};constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e={}){const i=t.time||(t.time={}),s=this._adapter=new Rn._date(t.adapters.date);s.init(e),x(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:Fo(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:r}=this.getUserBounds();function l(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),r||isNaN(t.max)||(n=Math.max(n,t.max))}o&&r||(l(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||l(this.getMinMax(!1))),s=a(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=a(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=nt(s,n,this.max);return this._unit=e.unit||(i.autoSkip?Vo(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=Io.length-1;o>=Io.indexOf(i);o--){const i=Io[o];if(Ro[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return Io[i?Io.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=Io.indexOf(t)+1,i=Io.length;e+t.value)))}initOffsets(t=[]){let e,i,s=0,n=0;this.options.offset&&t.length&&(e=this.getDecimalForValue(t[0]),s=1===t.length?1-e:(this.getDecimalForValue(t[1])-e)/2,i=this.getDecimalForValue(t[t.length-1]),n=1===t.length?i:(i-this.getDecimalForValue(t[t.length-2]))/2);const o=t.length<3?.5:.25;s=J(s,0,o),n=J(n,0,o),this._offsets={start:s,end:n,factor:1/(s+1+n)}}_generate(){const t=this._adapter,e=this.min,i=this.max,s=this.options,n=s.time,o=n.unit||Vo(n.minUnit,e,i,this._getLabelCapacity(e)),a=l(s.ticks.stepSize,1),r="week"===o&&n.isoWeekday,h=N(r)||!0===r,c={};let d,u,f=e;if(h&&(f=+t.startOf(f,"isoWeek",r)),f=+t.startOf(f,h?"day":o),t.diff(i,e,o)>1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const g="data"===s.ticks.source&&this.getDataTimestamps();for(d=f,u=0;d+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}format(t,e){const i=this.options.time.displayFormats,s=this._unit,n=e||i[s];return this._adapter.format(t,n)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.ticks.callback;if(o)return d(o,[t,e,i],this);const a=n.time.displayFormats,r=this._unit,l=this._majorUnit,h=r&&a[r],c=l&&a[l],u=i[e],f=l&&c&&u&&u.major;return this._adapter.format(t,s||(f?c:h))}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=it(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=it(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}var jo=Object.freeze({__proto__:null,CategoryScale:class extends Js{static id="category";static defaults={ticks:{callback:po}};constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if(s(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:J(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:go(i,t,l(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){return po.call(this,t)}configure(){super.configure(),this.isHorizontal()||(this._reversePixels=!this._reversePixels)}getPixelForValue(t){return"number"!=typeof t&&(t=this.parse(t)),null===t?NaN:this.getPixelForDecimal((t-this._startValue)/this._valueRange)}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}},LinearScale:xo,LogarithmicScale:ko,RadialLinearScale:Eo,TimeScale:No,TimeSeriesScale:class extends No{static id="timeseries";static defaults=No.defaults;constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=Ho(e,this.min),this._tableRange=Ho(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;ot-e))}_getTimestampsForTable(){let t=this._cache.all||[];if(t.length)return t;const e=this.getDataTimestamps(),i=this.getLabelTimestamps();return t=e.length&&i.length?this.normalize(e.concat(i)):e.length?e:i,t=this._cache.all=t,t}getDecimalForValue(t){return(Ho(this._table,t)-this._minPos)/this._tableRange}getValueForPixel(t){const e=this._offsets,i=this.getDecimalForPixel(t)/e.factor-e.end;return Ho(this._table,i*this._tableRange+this._minPos,!0)}}});const $o=["rgb(54, 162, 235)","rgb(255, 99, 132)","rgb(255, 159, 64)","rgb(255, 205, 86)","rgb(75, 192, 192)","rgb(153, 102, 255)","rgb(201, 203, 207)"],Yo=$o.map((t=>t.replace("rgb(","rgba(").replace(")",", 0.5)")));function Uo(t){return $o[t%$o.length]}function Xo(t){return Yo[t%Yo.length]}function qo(t){let e=0;return(i,s)=>{const n=t.getDatasetMeta(s).controller;n instanceof jn?e=function(t,e){return t.backgroundColor=t.data.map((()=>Uo(e++))),e}(i,e):n instanceof $n?e=function(t,e){return t.backgroundColor=t.data.map((()=>Xo(e++))),e}(i,e):n&&(e=function(t,e){return t.borderColor=Uo(e),t.backgroundColor=Xo(e),++e}(i,e))}}function Ko(t){let e;for(e in t)if(t[e].borderColor||t[e].backgroundColor)return!0;return!1}var Go={id:"colors",defaults:{enabled:!0,forceOverride:!1},beforeLayout(t,e,i){if(!i.enabled)return;const{data:{datasets:s},options:n}=t.config,{elements:o}=n;if(!i.forceOverride&&(Ko(s)||(a=n)&&(a.borderColor||a.backgroundColor)||o&&Ko(o)))return;var a;const r=qo(t);s.forEach(r)}};function Zo(t){if(t._decimated){const e=t._data;delete t._decimated,delete t._data,Object.defineProperty(t,"data",{configurable:!0,enumerable:!0,writable:!0,value:e})}}function Jo(t){t.data.datasets.forEach((t=>{Zo(t)}))}var Qo={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void Jo(t);const n=t.width;t.data.datasets.forEach(((e,o)=>{const{_data:a,indexAxis:r}=e,l=t.getDatasetMeta(o),h=a||e.data;if("y"===Pi([r,t.options.indexAxis]))return;if(!l.controller.supportsDecimation)return;const c=t.scales[l.xAxisID];if("linear"!==c.type&&"time"!==c.type)return;if(t.options.parsing)return;let{start:d,count:u}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=J(it(e,o.axis,a).lo,0,i-1)),s=h?J(it(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(l,h);if(u<=(i.threshold||4*n))return void Zo(e);let f;switch(s(a)&&(e._data=h,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":f=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(h,d,u,n,i);break;case"min-max":f=function(t,e,i,n){let o,a,r,l,h,c,d,u,f,g,p=0,m=0;const b=[],x=e+i-1,_=t[e].x,y=t[x].x-_;for(o=e;og&&(g=l,d=o),p=(m*p+a.x)/++m;else{const i=o-1;if(!s(c)&&!s(d)){const e=Math.min(c,d),s=Math.max(c,d);e!==u&&e!==i&&b.push({...t[e],x:p}),s!==u&&s!==i&&b.push({...t[s],x:p})}o>0&&i!==u&&b.push(t[i]),b.push(a),h=e,m=0,f=g=l,c=d=u=o}}return b}(h,d,u,n);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=f}))},destroy(t){Jo(t)}};function ta(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=G(n),o=G(o)),{property:t,start:n,end:o}}function ea(t,e,i){for(;e>t;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function ia(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function sa(t,e){let i=[],s=!1;return n(t)?(s=!0,i=t):i=function(t,e){const{x:i=null,y:s=null}=t||{},n=e.points,o=[];return e.segments.forEach((({start:t,end:e})=>{e=ea(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new no({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function na(t){return t&&!1!==t.fill}function oa(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!a(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function aa(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=l(i&&i.target,i);void 0===s&&(s=!!e.backgroundColor);if(!1===s||null===s)return!1;if(!0===s)return"origin";return s}(t);if(o(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return a(n)&&Math.floor(n)===n?function(t,e,i,s){"-"!==t&&"+"!==t||(i=e+i);if(i===e||i<0||i>=s)return!1;return i}(s[0],e,n,i):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}function ra(t,e,i){const s=[];for(let n=0;n=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&i.fill&&da(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;na(i)&&da(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;na(s)&&"beforeDatasetDraw"===i.drawTime&&da(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const ba=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=t.pointStyleWidth||Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class xa extends Hs{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=d(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=Si(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=ba(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,s,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const{itemWidth:p,itemHeight:m}=function(t,e,i,s,n){const o=function(t,e,i,s){let n=t.text;n&&"string"!=typeof n&&(n=n.reduce(((t,e)=>t.length>e.length?t:e)));return e+i.size/2+s.measureText(n).width}(s,t,e,i),a=function(t,e,i){let s=t;"string"!=typeof e.text&&(s=_a(e,i));return s}(n,s,e.lineHeight);return{itemWidth:o,itemHeight:a}}(i,e,n,t,s);o>0&&u+m+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:m},d=Math.max(d,p),u+=m+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:n}}=this,o=Oi(n,this.left,this.width);if(this.isHorizontal()){let n=0,a=ft(i,this.left+s,this.right-this.lineWidths[n]);for(const r of e)n!==r.row&&(n=r.row,a=ft(i,this.left+s,this.right-this.lineWidths[n])),r.top+=this.top+t+s,r.left=o.leftForLtr(o.x(a),r.width),a+=r.width+s}else{let n=0,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height);for(const r of e)r.col!==n&&(n=r.col,a=ft(i,this.top+t+s,this.bottom-this.columnSizes[n].height)),r.top=a,r.left+=this.left+s,r.left=o.leftForLtr(o.x(r.left),r.width),a+=r.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Ie(t,this),this._draw(),ze(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:n,labels:o}=t,a=ue.color,r=Oi(t.rtl,this.left,this.width),h=Si(o.font),{padding:c}=o,d=h.size,u=d/2;let f;this.drawTitle(),s.textAlign=r.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=h.string;const{boxWidth:g,boxHeight:p,itemHeight:m}=ba(o,d),b=this.isHorizontal(),x=this._computeTitleHeight();f=b?{x:ft(n,this.left+c,this.right-i[0]),y:this.top+c+x,line:0}:{x:this.left+c,y:ft(n,this.top+x+c,this.bottom-e[0].height),line:0},Ai(this.ctx,t.textDirection);const _=m+c;this.legendItems.forEach(((y,v)=>{s.strokeStyle=y.fontColor,s.fillStyle=y.fontColor;const M=s.measureText(y.text).width,w=r.textAlign(y.textAlign||(y.textAlign=o.textAlign)),k=g+u+M;let S=f.x,P=f.y;r.setWidth(this.width),b?v>0&&S+k+c>this.right&&(P=f.y+=_,f.line++,S=f.x=ft(n,this.left+c,this.right-i[f.line])):v>0&&P+_>this.bottom&&(S=f.x=S+e[f.line].width+c,f.line++,P=f.y=ft(n,this.top+x+c,this.bottom-e[f.line].height));if(function(t,e,i){if(isNaN(g)||g<=0||isNaN(p)||p<0)return;s.save();const n=l(i.lineWidth,1);if(s.fillStyle=l(i.fillStyle,a),s.lineCap=l(i.lineCap,"butt"),s.lineDashOffset=l(i.lineDashOffset,0),s.lineJoin=l(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=l(i.strokeStyle,a),s.setLineDash(l(i.lineDash,[])),o.usePointStyle){const a={radius:p*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},l=r.xPlus(t,g/2);Ee(s,a,l,e+u,o.pointStyleWidth&&g)}else{const o=e+Math.max((d-p)/2,0),a=r.leftForLtr(t,g),l=wi(i.borderRadius);s.beginPath(),Object.values(l).some((t=>0!==t))?He(s,{x:a,y:o,w:g,h:p,radius:l}):s.rect(a,o,g,p),s.fill(),0!==n&&s.stroke()}s.restore()}(r.x(S),P,y),S=gt(w,S+g+u,b?S+k:this.right,t.rtl),function(t,e,i){Ne(s,i.text,t,e+m/2,h,{strikethrough:i.hidden,textAlign:r.textAlign(i.textAlign)})}(r.x(S),P,y),b)f.x+=k+c;else if("string"!=typeof y.text){const t=h.lineHeight;f.y+=_a(y,t)+c}else f.y+=_})),Ti(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=Si(e.font),s=ki(e.padding);if(!e.display)return;const n=Oi(t.rtl,this.left,this.width),o=this.ctx,a=e.position,r=i.size/2,l=s.top+r;let h,c=this.left,d=this.width;if(this.isHorizontal())d=Math.max(...this.lineWidths),h=this.top+l,c=ft(t.align,c,this.right-d);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);h=l+ft(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const u=ft(a,c,c+d);o.textAlign=n.textAlign(ut(a)),o.textBaseline="middle",o.strokeStyle=e.color,o.fillStyle=e.color,o.font=i.string,Ne(o,e.text,u,h,i)}_computeTitleHeight(){const t=this.options.title,e=Si(t.font),i=ki(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(tt(t,this.left,this.right)&&tt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o,useBorderRadius:a,borderRadius:r}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const l=t.controller.getStyle(i?0:void 0),h=ki(l.borderWidth);return{text:e[t.index].label,fillStyle:l.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:l.borderCapStyle,lineDash:l.borderDash,lineDashOffset:l.borderDashOffset,lineJoin:l.borderJoinStyle,lineWidth:(h.width+h.height)/4,strokeStyle:l.borderColor,pointStyle:s||l.pointStyle,rotation:l.rotation,textAlign:n||l.textAlign,borderRadius:a&&(r||l.borderRadius),datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class va extends Hs{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=n(i.text)?i.text.length:1;this._padding=ki(i.padding);const o=s*Si(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=o:this.width=o}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:n,options:o}=this,a=o.align;let r,l,h,c=0;return this.isHorizontal()?(l=ft(a,i,n),h=e+t,r=n-i):("left"===o.position?(l=i+t,h=ft(a,s,e),c=-.5*C):(l=n-t,h=ft(a,e,s),c=.5*C),r=s-e),{titleX:l,titleY:h,maxWidth:r,rotation:c}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=Si(e.font),s=i.lineHeight/2+this._padding.top,{titleX:n,titleY:o,maxWidth:a,rotation:r}=this._drawArgs(s);Ne(t,e.text,0,0,i,{color:e.color,maxWidth:a,rotation:r,textAlign:ut(e.align),textBaseline:"middle",translation:[n,o]})}}var Ma={id:"title",_element:va,start(t,e,i){!function(t,e){const i=new va({ctx:t.ctx,options:e,chart:t});as.configure(t,i,e),as.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;as.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;as.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const wa=new WeakMap;var ka={id:"subtitle",start(t,e,i){const s=new va({ctx:t.ctx,options:i,chart:t});as.configure(t,s,i),as.addBox(t,s),wa.set(t,s)},stop(t){as.removeBox(t,wa.get(t)),wa.delete(t)},beforeUpdate(t,e,i){const s=wa.get(t);as.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Sa={average(t){if(!t.length)return!1;let e,i,s=0,n=0,o=0;for(e=0,i=t.length;e-1?t.split("\n"):t}function Ca(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Oa(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=Si(e.bodyFont),h=Si(e.titleFont),c=Si(e.footerFont),d=o.length,f=n.length,g=s.length,p=ki(e.padding);let m=p.height,b=0,x=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(m+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){m+=g*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-g)*l.lineHeight+(x-1)*e.bodySpacing}f&&(m+=e.footerMarginTop+f*c.lineHeight+(f-1)*e.footerSpacing);let _=0;const y=function(t){b=Math.max(b,i.measureText(t).width+_)};return i.save(),i.font=h.string,u(t.title,y),i.font=l.string,u(t.beforeBody.concat(t.afterBody),y),_=e.displayColors?a+2+e.boxPadding:0,u(s,(t=>{u(t.before,y),u(t.lines,y),u(t.after,y)})),_=0,i.font=c.string,u(t.footer,y),i.restore(),b+=p.width,{width:b,height:m}}function Aa(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function Ta(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||Aa(t,e,i,s),yAlign:s}}function La(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=wi(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:J(g,0,s.width-e.width),y:J(p,0,s.height-e.height)}}function Ea(t,e,i){const s=ki(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function Ra(t){return Pa([],Da(t))}function Ia(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}const za={beforeTitle:e,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex{const e={before:[],lines:[],after:[]},n=Ia(i,t);Pa(e.before,Da(Fa(n,"beforeLabel",this,t))),Pa(e.lines,Fa(n,"label",this,t)),Pa(e.after,Da(Fa(n,"afterLabel",this,t))),s.push(e)})),s}getAfterBody(t,e){return Ra(Fa(e.callbacks,"afterBody",this,t))}getFooter(t,e){const{callbacks:i}=e,s=Fa(i,"beforeFooter",this,t),n=Fa(i,"footer",this,t),o=Fa(i,"afterFooter",this,t);let a=[];return a=Pa(a,Da(s)),a=Pa(a,Da(n)),a=Pa(a,Da(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),u(l,(e=>{const i=Ia(t.callbacks,e);s.push(Fa(i,"labelColor",this,e)),n.push(Fa(i,"labelPointStyle",this,e)),o.push(Fa(i,"labelTextColor",this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=Sa[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Oa(this,i),a=Object.assign({},t,e),r=Ta(this.chart,i,a),l=La(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=wi(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,b,x,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,x=_+o,y=_-o):(p=d+f,m=p+o,x=_-o,y=_+o),b=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(x=u,_=x-o,p=m-o,b=m+o):(x=u+g,_=x+o,p=m+o,b=m-o),y=x),{x1:p,x2:m,x3:b,y1:x,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Oi(i.rtl,this.x,this.width);for(t.x=Ea(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=Si(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,He(t,{x:e,y:g,w:h,h:l,radius:r}),t.fill(),t.stroke(),t.fillStyle=a.backgroundColor,t.beginPath(),He(t,{x:i,y:g+1,w:h-2,h:l-2,radius:r}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,g,h,l),t.strokeRect(e,g,h,l),t.fillStyle=a.backgroundColor,t.fillRect(i,g+1,h-2,l-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=Si(i.bodyFont);let d=c.lineHeight,f=0;const g=Oi(i.rtl,this.x,this.width),p=function(i){e.fillText(i,g.x(t.x+f),t.y+d/2),t.y+=d+n},m=g.textAlign(o);let b,x,_,y,v,M,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=Ea(this,m,i),e.fillStyle=i.bodyColor,u(this.beforeBody,p),f=a&&"right"!==m?"center"===o?l/2+h:l+2+h:0,y=0,M=s.length;y0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=Sa[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Oa(this,t),a=Object.assign({},i,this._size),r=Ta(e,t,a),l=La(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}_willRender(){return!!this.opacity}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=ki(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ai(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),Ti(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!f(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!f(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e.filter((t=>this.chart.data.datasets[t.datasetIndex]&&void 0!==this.chart.getDatasetMeta(t.datasetIndex).controller.getParsed(t.index)));const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=Sa[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}var Ba={id:"tooltip",_element:Va,positioners:Sa,afterInit(t,e,i){i&&(t.tooltip=new Va({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip;if(e&&e._willRender()){const i={tooltip:e};if(!1===t.notifyPlugins("beforeTooltipDraw",{...i,cancelable:!0}))return;e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i)}},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:za},defaultRoutes:{bodyFont:"font",footerFont:"font",titleFont:"font"},descriptors:{_scriptable:t=>"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]};return An.register(Yn,jo,fo,t),An.helpers={...Wi},An._adapters=Rn,An.Animation=Cs,An.Animations=Os,An.animator=xt,An.controllers=en.controllers.items,An.DatasetController=Ns,An.Element=Hs,An.elements=fo,An.Interaction=Xi,An.layouts=as,An.platforms=Ss,An.Scale=Js,An.Ticks=ae,Object.assign(An,Yn,jo,fo,t,Ss),An.Chart=An,"undefined"!=typeof window&&(window.Chart=An),An})); +//# sourceMappingURL=chart.umd.js.map + diff --git a/assets/js/libraries/footable.js b/assets/js/libraries/footable.js new file mode 100644 index 00000000..7131ca8c --- /dev/null +++ b/assets/js/libraries/footable.js @@ -0,0 +1,7371 @@ +/* +* FooTable v3 - FooTable is a jQuery plugin that aims to make HTML tables on smaller devices look awesome. +* @version 3.1.6 +* @link http://fooplugins.com +* @copyright Steven Usher & Brad Vincent 2015 +* @license Released under the GPLv3 license. +*/ +(function($, F){ + // add in console we use in case it's missing + window.console = window.console || { log:function(){}, error:function(){} }; + + /** + * The jQuery plugin initializer. + * @function jQuery.fn.footable + * @param {(object|FooTable.Defaults)} [options] - The options to initialize the plugin with. + * @param {function} [ready] - A callback function to execute for each initialized plugin. + * @returns {jQuery} + */ + $.fn.footable = function (options, ready) { + options = options || {}; + // make sure we only work with tables + return this.filter('table').each(function (i, tbl) { + F.init(tbl, options, ready); + }); + }; + + var debug_defaults = { + events: [] + }; + F.__debug__ = JSON.parse(localStorage.getItem('footable_debug')) || false; + F.__debug_options__ = JSON.parse(localStorage.getItem('footable_debug_options')) || debug_defaults; + + /** + * Gets or sets the internal debug variable which enables some additional logging to the console. + * When enabled this value is stored in the localStorage so it can persist across page reloads. + * @param {boolean} value - Whether or not to enable additional logging. + * @param {object} [options] - Any debug specific options. + * @returns {(boolean|undefined)} + */ + F.debug = function(value, options){ + if (!F.is.boolean(value)) return F.__debug__; + F.__debug__ = value; + if (F.__debug__){ + localStorage.setItem('footable_debug', JSON.stringify(F.__debug__)); + F.__debug_options__ = $.extend(true, {}, debug_defaults, options || {}); + if (F.is.hash(options)){ + localStorage.setItem('footable_debug_options', JSON.stringify(F.__debug_options__)); + } + } else { + localStorage.removeItem('footable_debug'); + localStorage.removeItem('footable_debug_options'); + } + }; + + /** + * Gets the FooTable instance of the supplied table if one exists. + * @param {(jQuery|jQuery.selector|HTMLTableElement)} table - The jQuery table object, selector or the HTMLTableElement to retrieve FooTable from. + * @returns {(FooTable.Table|undefined)} + */ + F.get = function(table){ + return $(table).first().data('__FooTable__'); + }; + + /** + * Initializes a new instance of FooTable on the supplied table. + * @param {(jQuery|jQuery.selector|HTMLTableElement)} table - The jQuery table object, selector or the HTMLTableElement to initialize FooTable on. + * @param {object} options - The options to initialize FooTable with. + * @param {function} [ready] - A callback function to execute once the plugin is initialized. + * @returns {FooTable.Table} + */ + F.init = function(table, options, ready){ + var ft = F.get(table); + if (ft instanceof F.Table) ft.destroy(); + return new F.Table(table, options, ready); + }; + + /** + * Gets the FooTable.Row instance for the supplied element. + * @param {(jQuery|jQuery.selector|HTMLTableElement)} element - A jQuery object, selector or the HTMLElement of an element to retrieve the FooTable.Row for. + * @returns {FooTable.Row} + */ + F.getRow = function(element){ + // to get the FooTable.Row object simply walk up the DOM, find the TR and grab the __FooTableRow__ data value + var $row = $(element).closest('tr'); + // if this is a detail row get the previous row in the table to get the main TR element + if ($row.hasClass('footable-detail-row')){ + $row = $row.prev(); + } + // grab the row object + return $row.data('__FooTableRow__'); + }; + + // The below are external type definitions mainly used as pointers to jQuery docs for important information + /** + * jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API + * that works across a multitude of browsers. With a combination of versatility and extensibility, jQuery has changed the way that millions of people write JavaScript. + * @name jQuery + * @constructor + * @returns {jQuery} + * @see {@link http://api.jquery.com/} + */ + + /** + * This object provides a subset of the methods of the Deferred object (then, done, fail, always, pipe, and state) to prevent users from changing the state of the Deferred. + * @typedef {object} jQuery.Promise + * @see {@link http://api.jquery.com/Types/#Promise} + */ + + /** + * As of jQuery 1.5, the Deferred object provides a way to register multiple callbacks into self-managed callback queues, invoke callback queues as appropriate, + * and relay the success or failure state of any synchronous or asynchronous function. + * @typedef {object} jQuery.Deferred + * @see {@link http://api.jquery.com/Types/#Deferred} + */ + + /** + * jQuery's event system normalizes the event object according to W3C standards. The event object is guaranteed to be passed to the event handler. Most properties from + * the original event are copied over and normalized to the new event object. + * @typedef {object} jQuery.Event + * @see {@link http://api.jquery.com/category/events/event-object/} + */ + + /** + * Provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events. + * @memberof jQuery + * @function when + * @param {...jQuery.Deferred} deferreds - Any number of deferred objects to wait for. + * @returns {jQuery.Promise} + * @see {@link http://api.jquery.com/jQuery.when/} + */ + + /** + * The jQuery.fn namespace used to register plugins with jQuery. + * @memberof jQuery + * @namespace fn + * @see {@link http://learn.jquery.com/plugins/basic-plugin-creation/} + */ +})( + jQuery, + /** + * The core FooTable namespace containing all the plugin code. + * @namespace + */ + FooTable = window.FooTable || {} +); +(function(F){ + var returnTrue = function(){ return true; }; + + /** + * This namespace contains commonly used array utility methods. + * @namespace {object} FooTable.arr + */ + F.arr = {}; + + /** + * Iterates over each item in the supplied array and performs the supplied function passing in the current item as the first argument. + * @memberof FooTable.arr + * @function each + * @param {Array} array - The array to iterate + * @param {function} func - The function to execute for each item. The first argument supplied to this function is the current item and the second is the current index. + */ + F.arr.each = function (array, func) { + if (!F.is.array(array) || !F.is.fn(func)) return; + for (var i = 0, len = array.length; i < len; i++) { + if (func(array[i], i) === false) break; + } + }; + + /** + * Get all items in the supplied array that optionally matches the supplied where function. If no items are found an empty array is returned. + * @memberof FooTable.arr + * @function get + * @param {Array} array - The array to get items from. + * @param {function} where - This function must return a boolean value, true includes the item in the result array. + * @returns {Array} + */ + F.arr.get = function (array, where) { + var result = []; + if (!F.is.array(array)) return result; + if (!F.is.fn(where)) return array; + for (var i = 0, len = array.length; i < len; i++) { + if (where(array[i], i)) result.push(array[i]); + } + return result; + }; + + /** + * Get a boolean value indicating if any item exists in the supplied array that optionally matches the supplied where function. + * @memberof FooTable.arr + * @function any + * @param {Array} array - The array to check. + * @param {function} [where] - [Optional] This function must return a boolean value, true indicates that the current item is a valid match. + * @returns {boolean} + */ + F.arr.any = function (array, where) { + if (!F.is.array(array)) return false; + where = F.is.fn(where) ? where : returnTrue; + for (var i = 0, len = array.length; i < len; i++) { + if (where(array[i], i)) return true; + } + return false; + }; + + /** + * Checks if the supplied value exists in the array. + * @memberof FooTable.arr + * @function contains + * @param {Array} array - The array to check. + * @param {*} value - The value to check for. + * @returns {boolean} + */ + F.arr.contains = function(array, value){ + if (!F.is.array(array) || F.is.undef(value)) return false; + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] == value) return true; + } + return false; + }; + + /** + * Get the first item in the supplied array that optionally matches the supplied where function. If no item is found null is returned. + * @memberof FooTable.arr + * @function first + * @param {Array} array - The array to get the item from. + * @param {function} [where] - [Optional] This function must return a boolean value, true indicates that the current item can be returned. + * @returns {(*|null)} + */ + F.arr.first = function (array, where) { + if (!F.is.array(array)) return null; + where = F.is.fn(where) ? where : returnTrue; + for (var i = 0, len = array.length; i < len; i++) { + if (where(array[i], i)) return array[i]; + } + return null; + }; + + /** + * Creates a new array from the results of the supplied getter function. If no items are found an empty array is returned, to exclude an item from the results return null. + * @memberof FooTable.arr + * @function map + * @param {Array} array - The array to iterate. + * @param {function} getter - This function must return either a new value or null. + * The first argument is the result being returned at this point in the iteration. The second argument is the current item being iterated. + * @returns {(*|null)} + */ + F.arr.map = function (array, getter) { + var result = [], returned = null; + if (!F.is.array(array) || !F.is.fn(getter)) return result; + for (var i = 0, len = array.length; i < len; i++) { + if ((returned = getter(array[i], i)) != null) result.push(returned); + } + return result; + }; + + /** + * Removes items from the array matching the supplied where function. All removed items are returned in a new array. + * @memberof FooTable.arr + * @function remove + * @param {Array} array - The array to iterate and remove items from. + * @param {function} where - This function must return a boolean value, true includes the item in the result array. + * @returns {*} + */ + F.arr.remove = function (array, where) { + var remove = [], removed = []; + if (!F.is.array(array) || !F.is.fn(where)) return removed; + var i = 0, len = array.length; + for (; i < len; i++) { + if (where(array[i], i, removed)){ + remove.push(i); + removed.push(array[i]); + } + } + // sort the indexes to be removed from largest to smallest + remove.sort(function(a, b){ return b - a; }); + i = 0; len = remove.length; + for(; i < len; i++){ + var index = remove[i] - i; + array.splice(index, 1); + } + return removed; + }; + + /** + * Deletes a single item from the array. The item if removed is returned. + * @memberof FooTable.arr + * @function delete + * @param {Array} array - The array to iterate and delete the item from. + * @param {*} item - The item to find and delete. + * @returns {(*|null)} + */ + F.arr.delete = function(array, item){ + var remove = -1, removed = null; + if (!F.is.array(array) || F.is.undef(item)) return removed; + var i = 0, len = array.length; + for (; i < len; i++) { + if (array[i] == item){ + remove = i; + removed = array[i]; + break; + } + } + if (remove != -1) array.splice(remove, 1); + return removed; + }; + + /** + * Replaces a single item in the array with a new one. + * @memberof FooTable.arr + * @function replace + * @param {Array} array - The array to iterate and replace the item in. + * @param {*} oldItem - The item to be replaced. + * @param {*} newItem - The item to be inserted. + */ + F.arr.replace = function(array, oldItem, newItem){ + var index = array.indexOf(oldItem); + if (index !== -1) array[index] = newItem; + }; + +})(FooTable); +(function (F) { + + /** + * This namespace contains commonly used 'is' type methods that return boolean values. + * @namespace FooTable.is + */ + F.is = {}; + + /** + * Checks if the type of the value is the same as that supplied. + * @memberof FooTable.is + * @function type + * @param {*} value - The value to check the type of. + * @param {string} type - The type to check for. + * @returns {boolean} + */ + F.is.type = function (value, type) { + return typeof value === type; + }; + + /** + * Checks if the value is defined. + * @memberof FooTable.is + * @function defined + * @param {*} value - The value to check is defined. + * @returns {boolean} + */ + F.is.defined = function (value) { + return typeof value !== 'undefined'; + }; + + /** + * Checks if the value is undefined. + * @memberof FooTable.is + * @function undef + * @param {*} value - The value to check is undefined. + * @returns {boolean} + */ + F.is.undef = function (value) { + return typeof value === 'undefined'; + }; + + /** + * Checks if the value is an array. + * @memberof FooTable.is + * @function array + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.array = function (value) { + return '[object Array]' === Object.prototype.toString.call(value); + }; + + /** + * Checks if the value is a date. + * @memberof FooTable.is + * @function date + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.date = function (value) { + return '[object Date]' === Object.prototype.toString.call(value) && !isNaN(value.getTime()); + }; + + /** + * Checks if the value is a boolean. + * @memberof FooTable.is + * @function boolean + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.boolean = function (value) { + return '[object Boolean]' === Object.prototype.toString.call(value); + }; + + /** + * Checks if the value is a string. + * @memberof FooTable.is + * @function string + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.string = function (value) { + return '[object String]' === Object.prototype.toString.call(value); + }; + + /** + * Checks if the value is a number. + * @memberof FooTable.is + * @function number + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.number = function (value) { + return '[object Number]' === Object.prototype.toString.call(value) && !isNaN(value); + }; + + /** + * Checks if the value is a function. + * @memberof FooTable.is + * @function fn + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.fn = function (value) { + return (F.is.defined(window) && value === window.alert) || '[object Function]' === Object.prototype.toString.call(value); + }; + + /** + * Checks if the value is an error. + * @memberof FooTable.is + * @function error + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.error = function (value) { + return '[object Error]' === Object.prototype.toString.call(value); + }; + + /** + * Checks if the value is an object. + * @memberof FooTable.is + * @function object + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.object = function (value) { + return '[object Object]' === Object.prototype.toString.call(value); + }; + + /** + * Checks if the value is a hash. + * @memberof FooTable.is + * @function hash + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.hash = function (value) { + return F.is.object(value) && value.constructor === Object && !value.nodeType && !value.setInterval; + }; + + /** + * Checks if the supplied object is an HTMLElement + * @memberof FooTable.is + * @function element + * @param {object} obj - The object to check. + * @returns {boolean} + */ + F.is.element = function (obj) { + return typeof HTMLElement === 'object' + ? obj instanceof HTMLElement + : obj && typeof obj === 'object' && obj !== null && obj.nodeType === 1 && typeof obj.nodeName === 'string'; + }; + + /** + * This is a simple check to determine if an object is a jQuery promise object. It simply checks the object has a "then" and "promise" function defined. + * The promise object is created as an object literal inside of jQuery.Deferred. + * It has no prototype, nor any other truly unique properties that could be used to distinguish it. + * This method should be a little more accurate than the internal jQuery one that simply checks for a "promise" method. + * @memberof FooTable.is + * @function promise + * @param {object} obj - The object to check. + * @returns {boolean} + */ + F.is.promise = function(obj){ + return F.is.object(obj) && F.is.fn(obj.then) && F.is.fn(obj.promise); + }; + + /** + * Checks if the supplied object is an instance of a jQuery object. + * @memberof FooTable.is + * @function jq + * @param {object} obj - The object to check. + * @returns {boolean} + */ + F.is.jq = function(obj){ + return F.is.defined(window.jQuery) && obj instanceof jQuery && obj.length > 0; + }; + + /** + * Checks if the supplied object is a moment.js date object. + * @memberof FooTable.is + * @function moment + * @param {object} obj - The object to check. + * @returns {boolean} + */ + F.is.moment = function(obj){ + return F.is.defined(window.moment) && F.is.object(obj) && F.is.boolean(obj._isAMomentObject) + }; + + /** + * Checks if the supplied value is an object and if it is empty. + * @memberof FooTable.is + * @function emptyObject + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.emptyObject = function(value){ + if (!F.is.hash(value)) return false; + for(var prop in value) { + if(value.hasOwnProperty(prop)) + return false; + } + return true; + }; + + /** + * Checks if the supplied value is an array and if it is empty. + * @memberof FooTable.is + * @function emptyArray + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.emptyArray = function(value){ + return F.is.array(value) ? value.length === 0 : true; + }; + + /** + * Checks if the supplied value is a string and if it is empty. + * @memberof FooTable.is + * @function emptyString + * @param {*} value - The value to check. + * @returns {boolean} + */ + F.is.emptyString = function(value){ + return F.is.string(value) ? value.length === 0 : true; + }; + +})(FooTable); +(function (F) { + /** + * This namespace contains commonly used string utility methods. + * @namespace FooTable.str + */ + F.str = {}; + + /** + * Checks if the supplied string contains the given substring. + * @memberof FooTable.str + * @function contains + * @param {string} str - The string to check. + * @param {string} contains - The string to check for. + * @param {boolean} [ignoreCase=false] - Whether or not to ignore casing when performing the check. + * @returns {boolean} + */ + F.str.contains = function (str, contains, ignoreCase) { + if (F.is.emptyString(str) || F.is.emptyString(contains)) return false; + return contains.length <= str.length + && (ignoreCase ? str.toUpperCase().indexOf(contains.toUpperCase()) : str.indexOf(contains)) !== -1; + }; + + /** + * Checks if the supplied string contains the exact given substring. + * @memberof FooTable.str + * @function contains + * @param {string} str - The string to check. + * @param {string} contains - The string to check for. + * @param {boolean} [ignoreCase=false] - Whether or not to ignore casing when performing the check. + * @returns {boolean} + */ + F.str.containsExact = function (str, contains, ignoreCase) { + if (F.is.emptyString(str) || F.is.emptyString(contains) || contains.length > str.length) return false; + return new RegExp('\\b'+ F.str.escapeRegExp(contains)+'\\b', ignoreCase ? 'i' : '').test(str); + }; + + /** + * Checks if the supplied string contains the given word. + * @memberof FooTable.str + * @function containsWord + * @param {string} str - The string to check. + * @param {string} word - The word to check for. + * @param {boolean} [ignoreCase=false] - Whether or not to ignore casing when performing the check. + * @returns {boolean} + */ + F.str.containsWord = function(str, word, ignoreCase){ + if (F.is.emptyString(str) || F.is.emptyString(word) || str.length < word.length) + return false; + var parts = str.split(/\W/); + for (var i = 0, len = parts.length; i < len; i++){ + if (ignoreCase ? parts[i].toUpperCase() == word.toUpperCase() : parts[i] == word) return true; + } + return false; + }; + + /** + * Returns the remainder of a string split on the first index of the given substring. + * @memberof FooTable.str + * @function from + * @param {string} str - The string to split. + * @param {string} from - The substring to split on. + * @returns {string} + */ + F.str.from = function (str, from) { + if (F.is.emptyString(str)) return str; + return F.str.contains(str, from) ? str.substring(str.indexOf(from) + 1) : str; + }; + + /** + * Checks if a string starts with the supplied prefix. + * @memberof FooTable.str + * @function startsWith + * @param {string} str - The string to check. + * @param {string} prefix - The prefix to check for. + * @returns {boolean} + */ + F.str.startsWith = function (str, prefix) { + if (F.is.emptyString(str)) return str == prefix; + return str.slice(0, prefix.length) == prefix; + }; + + /** + * Takes the supplied string and converts it to camel case. + * @memberof FooTable.str + * @function toCamelCase + * @param {string} str - The string to camel case. + * @returns {string} + */ + F.str.toCamelCase = function (str) { + if (F.is.emptyString(str)) return str; + if (str.toUpperCase() === str) return str.toLowerCase(); + return str.replace(/^([A-Z])|[-\s_](\w)/g, function (match, p1, p2) { + if (F.is.string(p2)) return p2.toUpperCase(); + return p1.toLowerCase(); + }); + }; + + /** + * Generates a random string 9 characters long using the optional prefix if supplied. + * @memberof FooTable.str + * @function random + * @param {string} [prefix] - The prefix to append to the 9 random characters. + * @returns {string} + */ + F.str.random = function(prefix){ + prefix = F.is.emptyString(prefix) ? '' : prefix; + return prefix + Math.random().toString(36).substr(2, 9); + }; + + /** + * Escapes a string for use in a regular expression. + * @memberof FooTable.str + * @function escapeRegExp + * @param {string} str - The string to escape. + * @returns {string} + */ + F.str.escapeRegExp = function(str){ + if (F.is.emptyString(str)) return str; + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + }; + +})(FooTable); +(function (F) { + "use strict"; + + if (!Object.create) { + Object.create = (function () { + var Object = function () {}; + return function (prototype) { + if (arguments.length > 1) + throw Error('Second argument not supported'); + + if (!F.is.object(prototype)) + throw TypeError('Argument must be an object'); + + Object.prototype = prototype; + var result = new Object(); + Object.prototype = null; + return result; + }; + })(); + } + + /** + * This base implementation does nothing except provide access to the {@link FooTable.Class#extend} method. + * @constructs FooTable.Class + * @classdesc This class is based off of John Resig's [Simple JavaScript Inheritance]{@link http://ejohn.org/blog/simple-javascript-inheritance} but it has been updated to be ES 5.1 + * compatible by implementing an [Object.create polyfill]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill} + * for older browsers. + * @see {@link http://ejohn.org/blog/simple-javascript-inheritance} + * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill} + * @returns {FooTable.Class} + */ + function Class() {} + + var __extendable__ = /xyz/.test(function () {xyz;}) ? /\b_super\b/ : /.*/; + + // this._super() within the context of the new function is a pointer to the original function + // except if the hook param is specified then the this._super variable is the result of the original function + Class.__extend__ = function(proto, name, func, original){ + // to all who venture here, here be dragons! + proto[name] = F.is.fn(original) && __extendable__.test(func) ? + (function (name, fn) { + return function () { + var tmp, ret; + tmp = this._super; + this._super = original; + ret = fn.apply(this, arguments); + this._super = tmp; + return ret; + }; + })(name, func) : func; + }; + + /** + * Creates a new class that inherits from this class which in turn allows itself to be extended or if a name and function is supplied extends only that specific function on the class. + * @param {(object|string)} arg1 - An object containing any new methods/members to implement or the name of the method to extend. + * @param {function} arg2 - If the first argument is a method name then this is the new function to replace it with. + * @returns {FooTable.Class} A new class that inherits from the base class. + * @example The below shows an example of how to implement inheritance using this method. + * var Person = FooTable.Class.extend({ + * construct: function(isDancing){ + * this.dancing = isDancing; + * }, + * dance: function(){ + * return this.dancing; + * } + * }); + * + * var Ninja = Person.extend({ + * construct: function(){ + * this._super( false ); + * }, + * dance: function(){ + * // Call the inherited version of dance() + * return this._super(); + * }, + * swingSword: function(){ + * return true; + * } + * }); + * + * var p = new Person(true); + * p.dance(); // => true + * + * var n = new Ninja(); + * n.dance(); // => false + * n.swingSword(); // => true + * + * // Should all be true + * p instanceof Person && p instanceof FooTable.Class && + * n instanceof Ninja && n instanceof Person && n instanceof FooTable.Class + */ + Class.extend = function (arg1 , arg2) { + var args = Array.prototype.slice.call(arguments); + arg1 = args.shift(); + arg2 = args.shift(); + + function __extend__(proto, name, func, original){ + // to all who venture here, here be dragons! + proto[name] = F.is.fn(original) && __extendable__.test(func) ? + (function (name, fn, ofn) { + return function () { + var tmp, ret; + tmp = this._super; + this._super = ofn; + ret = fn.apply(this, arguments); + this._super = tmp; + return ret; + }; + })(name, func, original) : func; + } + + if (F.is.hash(arg1)){ + var proto = Object.create(this.prototype), + _super = this.prototype; + for (var name in arg1) { + if (name === '__ctor__') continue; + __extend__(proto, name, arg1[name], _super[name]); + } + var obj = F.is.fn(proto.__ctor__) ? proto.__ctor__ : function () { + if (!F.is.fn(this.construct)) + throw new SyntaxError('FooTable class objects must be constructed with the "new" keyword.'); + this.construct.apply(this, arguments); + }; + proto.construct = F.is.fn(proto.construct) ? proto.construct : function(){}; + obj.prototype = proto; + proto.constructor = obj; + obj.extend = Class.extend; + return obj; + } else if (F.is.string(arg1) && F.is.fn(arg2)) { + __extend__(this.prototype, arg1, arg2, this.prototype[arg1]); + } + }; + + F.Class = Class; + + F.ClassFactory = F.Class.extend(/** @lends FooTable.ClassFactory */{ + /** + * This is a simple factory for {@link FooTable.Class} objects allowing them to be registered using a friendly name + * and then new instances can be created using this friendly name. + * @constructs + * @extends FooTable.Class + * @returns {FooTable.ClassFactory} + * @this FooTable.ClassFactory + */ + construct: function(){ + /** + * An object containing all registered classes. + * @type {{}} + */ + this.registered = {}; + }, + /** + * Checks if the factory contains a class registered using the supplied name. + * @instance + * @param {string} name - The name of the class to check. + * @returns {boolean} + * @this FooTable.ClassFactory + */ + contains: function(name){ + return F.is.defined(this.registered[name]); + }, + /** + * Gets an array of all registered names. + * @instance + * @returns {Array.} + * @this FooTable.ClassFactory + */ + names: function(){ + var names = [], name; + for (name in this.registered){ + if (!this.registered.hasOwnProperty(name)) continue; + names.push(name); + } + return names; + }, + /** + * Registers a class object using the supplied friendly name and priority. The priority is only taken into account when loading all registered classes + * using the {@link FooTable.ClassFactory#load} method. + * @instance + * @param {string} name - The friendly name of the class. + * @param {function} klass - The class to register. + * @param {number} priority - This determines the order that the class is created when using the {@link FooTable.ClassFactory#load} method, higher values are loaded first. + * @this FooTable.ClassFactory + */ + register: function(name, klass, priority){ + if (!F.is.string(name) || !F.is.fn(klass)) return; + var current = this.registered[name]; + this.registered[name] = { + name: name, + klass: klass, + priority: F.is.number(priority) ? priority : (F.is.defined(current) ? current.priority : 0) + }; + }, + /** + * Creates new instances of all registered classes using there priority and the supplied arguments to return them in an array. + * @instance + * @param {object} subs - An object containing classes to substitute on load. + * @param {*} arg1 - The first argument to supply when creating new instances of all registered classes. + * @param {*} [argN...] - Any number of additional arguments to supply when creating new instances of all registered classes. + * @returns {Array.} + * @this FooTable.ClassFactory + */ + load: function(subs, arg1, argN){ + var self = this, args = Array.prototype.slice.call(arguments), reg = [], loaded = [], name, klass; + subs = args.shift() || {}; + for (name in self.registered){ + if (!self.registered.hasOwnProperty(name)) continue; + var component = self.registered[name]; + if (subs.hasOwnProperty(name)){ + klass = subs[name]; + if (F.is.string(klass)) klass = F.getFnPointer(subs[name]); + if (F.is.fn(klass)){ + component = {name: name, klass: klass, priority: self.registered[name].priority}; + } + } + reg.push(component); + } + for (name in subs){ + if (!subs.hasOwnProperty(name) || self.registered.hasOwnProperty(name)) continue; + klass = subs[name]; + if (F.is.string(klass)) klass = F.getFnPointer(subs[name]); + if (F.is.fn(klass)){ + reg.push({name: name, klass: klass, priority: 0}); + } + } + reg.sort(function(a, b){ return b.priority - a.priority; }); + F.arr.each(reg, function(r){ + if (F.is.fn(r.klass)){ + loaded.push(self._make(r.klass, args)); + } + }); + return loaded; + }, + /** + * Create a new instance of a single class using the supplied name and arguments. + * @instance + * @param {string} name - The name of the class to create. + * @param {*} arg1 - The first argument to supply to the new instance. + * @param {*} [argN...] - Any number of additional arguments to supply to the new instance. + * @returns {FooTable.Class} + * @this FooTable.ClassFactory + */ + make: function(name, arg1, argN){ + var self = this, args = Array.prototype.slice.call(arguments), reg; + name = args.shift(); + reg = self.registered[name]; + if (F.is.fn(reg.klass)){ + return self._make(reg.klass, args); + } + return null; + }, + /** + * This in effect lets us use the "apply" method on a function using the "new" keyword. + * @instance + * @private + * @param {function} klass + * @param args + * @returns {FooTable.Class} + * @this FooTable.ClassFactory + */ + _make: function(klass, args){ + function Class() { + return klass.apply(this, args); + } + Class.prototype = klass.prototype; + return new Class(); + } + }); + +})(FooTable); +(function($, F){ + + /** + * Converts the supplied cssText string into JSON object. + * @param {string} cssText - The cssText to convert to a JSON object. + * @returns {object} + */ + F.css2json = function(cssText){ + if (F.is.emptyString(cssText)) return {}; + var json = {}, props = cssText.split(';'), pair, key, value; + for (var i = 0, i_len = props.length; i < i_len; i++){ + if (F.is.emptyString(props[i])) continue; + pair = props[i].split(':'); + if (F.is.emptyString(pair[0]) || F.is.emptyString(pair[1])) continue; + key = F.str.toCamelCase($.trim(pair[0])); + value = $.trim(pair[1]); + json[key] = value; + } + return json; + }; + + /** + * Attempts to retrieve a function pointer using the given name. + * @param {string} functionName - The name of the function to fetch a pointer to. + * @returns {(function|object|null)} + */ + F.getFnPointer = function(functionName){ + if (F.is.emptyString(functionName)) return null; + var pointer = window, + parts = functionName.split('.'); + F.arr.each(parts, function(part){ + if (pointer[part]) pointer = pointer[part]; + }); + return F.is.fn(pointer) ? pointer : null; + }; + + /** + * Checks the value for function properties such as the {@link FooTable.Column#formatter} option which could also be specified using just the name + * and attempts to return the correct function pointer or null if none was found matching the value. + * @param {FooTable.Class} self - The class to use as the 'this' keyword within the context of the function. + * @param {(function|string)} value - The actual function or the name of the function for the property. + * @param {function} [def] - A default function to return if none is found. + * @returns {(function|null)} + */ + F.checkFnValue = function(self, value, def){ + def = F.is.fn(def) ? def : null; + function wrap(t, fn, d){ + if (!F.is.fn(fn)) return d; + return function(){ + return fn.apply(t, arguments); + }; + } + return F.is.fn(value) ? wrap(self, value, def) : (F.is.type(value, 'string') ? wrap(self, F.getFnPointer(value), def) : def); + }; + +})(jQuery, FooTable); +(function($, F){ + + F.Cell = F.Class.extend(/** @lends FooTable.Cell */{ + /** + * The cell class containing all the properties for cells. + * @constructs + * @extends FooTable.Class + * @param {FooTable.Table} table - The root {@link FooTable.Table} this cell belongs to. + * @param {FooTable.Row} row - The parent {@link FooTable.Row} this cell belongs to. + * @param {FooTable.Column} column - The {@link FooTable.Column} this cell falls under. + * @param {(*|HTMLElement|jQuery)} valueOrElement - Either the value or the element for the cell. + * @returns {FooTable.Cell} + * @this FooTable.Cell + */ + construct: function (table, row, column, valueOrElement) { + /** + * The root {@link FooTable.Table} for the cell. + * @instance + * @readonly + * @type {FooTable.Table} + */ + this.ft = table; + /** + * The parent {@link FooTable.Row} for the cell. + * @instance + * @readonly + * @type {FooTable.Row} + */ + this.row = row; + /** + * The {@link FooTable.Column} this cell falls under. + * @instance + * @readonly + * @type {FooTable.Column} + */ + this.column = column; + this.created = false; + this.define(valueOrElement); + }, + /** + * This is supplied either the value or the cell element/jQuery object if it exists. + * If supplied the element we need set the $el property and parse the value from it. + * @instance + * @protected + * @param {(*|jQuery)} valueOrElement - The value or element to define the cell. + * @this FooTable.Cell + */ + define: function(valueOrElement){ + /** + * The jQuery table cell object this instance wraps. + * @instance + * @type {jQuery} + */ + this.$el = F.is.element(valueOrElement) || F.is.jq(valueOrElement) ? $(valueOrElement) : null; + /** + * The jQuery row object that represents this cell in the details table. + * @type {jQuery} + */ + this.$detail = null; + + var hasOptions = F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options) && F.is.defined(valueOrElement.value); + + /** + * The value of the cell. + * @instance + * @type {*} + */ + this.value = this.column.parser.call(this.column, F.is.jq(this.$el) ? this.$el : (hasOptions ? valueOrElement.value : valueOrElement), this.ft.o); + + /** + * Contains any options for the cell. These are the options supplied through the plugin constructor as part of the row object itself. + * @type {object} + */ + this.o = $.extend(true, { + classes: null, + style: null + }, hasOptions ? valueOrElement.options : {}); + /** + * An array of CSS classes for the cell. + * @instance + * @protected + * @type {Array.} + */ + this.classes = F.is.jq(this.$el) && this.$el.attr('class') ? this.$el.attr('class').match(/\S+/g) : (F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : [])); + /** + * The inline styles for the cell. + * @instance + * @protected + * @type {object} + */ + this.style = F.is.jq(this.$el) && this.$el.attr('style') ? F.css2json(this.$el.attr('style')) : (F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {})); + }, + /** + * After the cell has been defined this ensures that the $el and #detail properties are jQuery objects by either creating or updating them. + * @instance + * @protected + * @this FooTable.Cell + */ + $create: function(){ + if (this.created) return; + (this.$el = F.is.jq(this.$el) ? this.$el : $('')) + .data('value', this.value) + .contents().detach().end() + .append(this.format(this.value)); + + this._setClasses(this.$el); + this._setStyle(this.$el); + + this.$detail = $('').addClass(this.row.classes.join(' ')) + .data('__FooTableCell__', this) + .append($('')) + .append($('')); + + this.created = true; + }, + /** + * Collapses this cell and displays it in the details row. + * @instance + * @protected + */ + collapse: function(){ + if (!this.created) return; + this.$detail.children('th').html(this.column.title); + this.$el.clone() + .attr('id', this.$el.attr('id') ? this.$el.attr('id') + '-detail' : undefined) + .css('display', 'table-cell') + .html('') + .append(this.$el.contents().detach()) + .replaceAll(this.$detail.children('td').first()); + + if (!F.is.jq(this.$detail.parent())) + this.$detail.appendTo(this.row.$details.find('.footable-details > tbody')); + }, + /** + * Restores this cell from a detail row back into the normal row. + * @instance + * @protected + */ + restore: function(){ + if (!this.created) return; + if (F.is.jq(this.$detail.parent())){ + var $cell = this.$detail.children('td').first(); + this.$el + .attr('class', $cell.attr('class')) + .attr('style', $cell.attr('style')) + .css('display', (this.column.hidden || !this.column.visible) ? 'none' : 'table-cell') + .append($cell.contents().detach()); + } + this.$detail.detach(); + }, + /** + * Helper method to call this cell's column parser function supplying the required parameters. + * @instance + * @protected + * @returns {*} + * @see FooTable.Column#parser + * @this FooTable.Cell + */ + parse: function(){ + return this.column.parser.call(this.column, this.$el, this.ft.o); + }, + /** + * Helper method to call this cell's column formatter function using the supplied value and any additional required parameters. + * @instance + * @protected + * @param {*} value - The value to format. + * @returns {(string|HTMLElement|jQuery)} + * @see FooTable.Column#formatter + * @this FooTable.Cell + */ + format: function(value){ + return this.column.formatter.call(this.column, value, this.ft.o, this.row.value); + }, + /** + * Allows easy access to getting or setting the cell's value. If the value is set all associated properties are also updated along with the actual element. + * Using this method also allows us to supply an object containing options and the value for the cell. + * @instance + * @param {*} [value] - The value to set for the cell. If not supplied the current value of the cell is returned. + * @param {boolean} [redraw=true] - Whether or not to redraw the row once the value has been set. + * @param {boolean} [redrawSelf=true] - Whether or not to redraw the cell itself once the value has been set, if `false` this will override the supplied `redraw` value and prevent the row from redrawing as well. + * @returns {(*|undefined)} + * @this FooTable.Cell + */ + val: function(value, redraw, redrawSelf){ + if (F.is.undef(value)){ + // get + return this.value; + } + // set + var self = this, hasOptions = F.is.hash(value) && F.is.hash(value.options) && F.is.defined(value.value); + this.o = $.extend(true, { + classes: self.classes, + style: self.style + }, hasOptions ? value.options : {}); + + this.value = hasOptions ? value.value : value; + this.classes = F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : []); + this.style = F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {}); + + redrawSelf = F.is.boolean(redrawSelf) ? redrawSelf : true; + if (this.created && redrawSelf){ + this.$el.data('value', this.value).empty(); + + var $detail = this.$detail.children('td').first().empty(), + $target = F.is.jq(this.$detail.parent()) ? $detail : this.$el; + + $target.append(this.format(this.value)); + + this._setClasses($target); + this._setStyle($target); + + if (F.is.boolean(redraw) ? redraw : true) this.row.draw(); + } + }, + _setClasses: function($el){ + var hasColClasses = !F.is.emptyArray(this.column.classes), + hasClasses = !F.is.emptyArray(this.classes), + classes = null; + $el.removeAttr('class'); + if (!hasColClasses && !hasClasses) return; + if (hasColClasses && hasClasses){ + classes = this.classes.concat(this.column.classes).join(' '); + } else if (hasColClasses) { + classes = this.column.classes.join(' '); + } else if (hasClasses){ + classes = this.classes.join(' '); + } + if (!F.is.emptyString(classes)){ + $el.addClass(classes); + } + }, + _setStyle: function($el){ + var hasColStyle = !F.is.emptyObject(this.column.style), + hasStyle = !F.is.emptyObject(this.style), + style = null; + $el.removeAttr('style'); + if (!hasColStyle && !hasStyle) return; + if (hasColStyle && hasStyle){ + style = $.extend({}, this.column.style, this.style); + } else if (hasColStyle) { + style = this.column.style; + } else if (hasStyle){ + style = this.style; + } + if (F.is.hash(style)){ + $el.css(style); + } + } + }); + +})(jQuery, FooTable); +(function($, F){ + + F.Column = F.Class.extend(/** @lends FooTable.Column */{ + /** + * The column class containing all the properties for columns. All members marked as "readonly" should not be used when defining {@link FooTable.Defaults#columns}. + * @constructs + * @extends FooTable.Class + * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this component belongs to. + * @param {object} definition - An object containing all the properties to set for the column. + * @param {string} [type] - The type of column, "text" by default. + * @returns {FooTable.Column} + * @this FooTable.Column + */ + construct: function(instance, definition, type){ + /** + * The root {@link FooTable.Table} for the column. + * @instance + * @readonly + * @type {FooTable.Table} + */ + this.ft = instance; + /** + * The type of data displayed by the column. + * @instance + * @readonly + * @type {string} + */ + this.type = F.is.emptyString(type) ? 'text' : type; + /** + * Whether or not the column was parsed from a standard table row containing data instead of from an actual header row. + * @instance + * @readonly + * @type {boolean} + */ + this.virtual = F.is.boolean(definition.virtual) ? definition.virtual : false; + /** + * The jQuery cell object for the column header. + * @instance + * @readonly + * @type {jQuery} + */ + this.$el = F.is.jq(definition.$el) ? definition.$el : null; + /** + * The index of the column in the table. This is set by the plugin during initialization. + * @instance + * @readonly + * @type {number} + * @default -1 + */ + this.index = F.is.number(definition.index) ? definition.index : -1; + /** + * Whether or not this in an internal only column. + * @instance + * @readonly + * @type {boolean} + * @description Internal columns or there cells will not be returned when calling methods such as `FooTable.Row#val`. + */ + this.internal = false; + this.define(definition); + this.$create(); + }, + /** + * This is supplied the column definition in the form of a simple object created by merging options supplied via the plugin constructor with those parsed from the DOM. + * @instance + * @protected + * @param {object} definition - The object containing the column definition. + * @this FooTable.Column + */ + define: function(definition){ + /** + * Whether or not this column is hidden from view and appears in the details row. + * @type {boolean} + * @default false + */ + this.hidden = F.is.boolean(definition.hidden) ? definition.hidden : false; + /** + * Whether or not this column is completely hidden from view and will not appear in the details row. + * @type {boolean} + * @default true + */ + this.visible = F.is.boolean(definition.visible) ? definition.visible : true; + + /** + * The name of the column. This name must correspond to the property name of the JSON row data. + * @type {string} + * @default null + */ + this.name = F.is.string(definition.name) ? definition.name : null; + if (this.name == null) this.name = 'col'+(definition.index+1); + /** + * The title to display in the column header, this can be HTML. + * @type {string} + * @default null + */ + this.title = F.is.string(definition.title) ? definition.title : null; + if (!this.virtual && this.title == null && F.is.jq(this.$el)) this.title = this.$el.html(); + if (this.title == null) this.title = 'Column '+(definition.index+1); + /** + * The styles to apply to all cells in this column. + * @type {object} + */ + this.style = F.is.hash(definition.style) ? definition.style : (F.is.string(definition.style) ? F.css2json(definition.style) : {}); + /** + * The classes to apply to all cells in this column. + * @type {Array.} + */ + this.classes = F.is.array(definition.classes) ? definition.classes : (F.is.string(definition.classes) ? definition.classes.match(/\S+/g) : []); + + // override any default functions ensuring when they are executed "this" within the context of the function points to the instance of this object. + this.parser = F.checkFnValue(this, definition.parser, this.parser); + this.formatter = F.checkFnValue(this, definition.formatter, this.formatter); + }, + /** + * After the column has been defined this ensures that the $el property is a jQuery object by either creating or updating the current value. + * @instance + * @protected + * @this FooTable.Column + */ + $create: function(){ + (this.$el = !this.virtual && F.is.jq(this.$el) ? this.$el : $('')).html(this.title).addClass(this.classes.join(' ')).css(this.style); + }, + /** + * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and will be provided to the {@link FooTable.Column#format} function + * to generate the cell contents. + * @instance + * @protected + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {string} + * @this FooTable.Column + */ + parser: function(valueOrElement){ + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ // use jQuery to get the value + var data = $(valueOrElement).data('value'); + return F.is.defined(data) ? data : $(valueOrElement).html(); + } + if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement+''; // use the native toString of the value + return null; // otherwise we have no value so return null + }, + /** + * This is supplied the value retrieved from the {@link FooTable.Column#parse} function and must return a string, HTMLElement or jQuery object. + * The return value from this function is what is displayed in the cell in the table. + * @instance + * @protected + * @param {string} value - The value to format. + * @param {object} options - The current plugin options. + * @param {object} rowData - An object containing the current row data. + * @returns {(string|HTMLElement|jQuery)} + * @this FooTable.Column + */ + formatter: function(value, options, rowData){ + return value == null ? '' : value; + }, + /** + * Creates a cell for this column from the supplied {@link FooTable.Row} object. This allows different column types to return different types of cells. + * @instance + * @protected + * @param {FooTable.Row} row - The row to create the cell from. + * @returns {FooTable.Cell} + * @this FooTable.Column + */ + createCell: function(row){ + var element = F.is.jq(row.$el) ? row.$el.children('td,th').get(this.index) : null, + data = F.is.hash(row.value) ? row.value[this.name] : null; + return new F.Cell(this.ft, row, this, element || data); + } + }); + + F.columns = new F.ClassFactory(); + + F.columns.register('text', F.Column); + +})(jQuery, FooTable); +(function ($, F) { + + F.Component = F.Class.extend(/** @lends FooTable.Component */{ + /** + * The base class for all FooTable components. + * @constructs + * @extends FooTable.Class + * @param {FooTable.Table} instance - The parent {@link FooTable.Table} object for the component. + * @param {boolean} enabled - Whether or not the component is enabled. + * @throws {TypeError} The instance parameter must be an instance of {@link FooTable.Table}. + * @returns {FooTable.Component} + */ + construct: function (instance, enabled) { + if (!(instance instanceof F.Table)) + throw new TypeError('The instance parameter must be an instance of FooTable.Table.'); + + /** + * The parent {@link FooTable.Table} for the component. + * @type {FooTable.Table} + */ + this.ft = instance; + /** + * Whether or not this component is enabled. Disabled components only have there preinit method called allowing for this value to be overridden. + * @type {boolean} + */ + this.enabled = F.is.boolean(enabled) ? enabled : false; + }, + /** + * The preinit method is called during the parent {@link FooTable.Table} constructor call. + * @param {object} data - The jQuery.data() object of the root table. + * @instance + * @protected + * @function + */ + preinit: function(data){}, + /** + * The init method is called during the parent {@link FooTable.Table} constructor call. + * @instance + * @protected + * @function + */ + init: function(){}, + /** + * This method is called from the {@link FooTable.Table#destroy} method. + * @instance + * @protected + * @function + */ + destroy: function(){}, + /** + * This method is called from the {@link FooTable.Table#draw} method. + * @instance + * @protected + * @function + */ + predraw: function(){}, + /** + * This method is called from the {@link FooTable.Table#draw} method. + * @instance + * @protected + * @function + */ + draw: function(){}, + /** + * This method is called from the {@link FooTable.Table#draw} method. + * @instance + * @protected + * @function + */ + postdraw: function(){} + }); + + F.components = new F.ClassFactory(); + +})(jQuery, FooTable); +(function ($, F) { + /** + * Contains all the available options for the FooTable plugin. + * @name FooTable.Defaults + * @function + * @constructor + * @returns {FooTable.Defaults} + */ + F.Defaults = function () { + /** + * Whether or not events raised using the {@link FooTable.Table#raise} method are propagated up the DOM. By default this is set to false and all events bubble up the DOM as per usual + * however the reason for this option is if we have nested tables. If false the parent table would receive all the events raised by it's children and any handlers bound to both the + * parent and child would be triggered which is not the desired behavior. + * @type {boolean} + * @default false + */ + this.stopPropagation = false; + /** + * An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s). + * @type {object.} + * @default NULL + * @example This example shows how to pass an object containing the events and handlers. + * "on": { + * "click": function(e){ + * // bind a custom click event to do something whenever the table is clicked + * }, + * "init.ft.table": function(e, ft){ + * // bind to the FooTable initialize event to do something + * } + * } + */ + this.on = null; + }; + + /** + * Contains all the default options for the plugin. + * @type {FooTable.Defaults} + */ + F.defaults = new F.Defaults(); + +})(jQuery, FooTable); +(function($, F){ + + F.Row = F.Class.extend(/** @lends FooTable.Row */{ + /** + * The row class containing all the properties for a row and its' cells. + * @constructs + * @extends FooTable.Class + * @param {FooTable.Table} table - The parent {@link FooTable.Table} this component belongs to. + * @param {Array.} columns - The array of {@link FooTable.Column} for this row. + * @param {(*|HTMLElement|jQuery)} dataOrElement - Either the data for the row (create) or the element (parse) for the row. + * @returns {FooTable.Row} + */ + construct: function (table, columns, dataOrElement) { + /** + * The {@link FooTable.Table} for the row. + * @type {FooTable.Table} + */ + this.ft = table; + /** + * The array of {@link FooTable.Column} for this row. + * @type {Array.} + */ + this.columns = columns; + + this.created = false; + this.define(dataOrElement); + }, + /** + * This is supplied either the object containing the values for the row or the row element/jQuery object if it exists. + * If supplied the element we need to set the $el property and parse the cells from it using the column index. + * If we have an object we parse the cells from it using the column name. + * @param {(object|jQuery)} dataOrElement - The row object or element to define the row. + */ + define: function(dataOrElement){ + /** + * The jQuery table row object this instance wraps. + * @instance + * @protected + * @type {jQuery} + */ + this.$el = F.is.element(dataOrElement) || F.is.jq(dataOrElement) ? $(dataOrElement) : null; + /** + * The jQuery toggle element for the row. + * @instance + * @protected + * @type {jQuery} + */ + this.$toggle = $('', {'class': 'footable-toggle fooicon fooicon-plus'}); + + var isObj = F.is.hash(dataOrElement), + hasOptions = isObj && F.is.hash(dataOrElement.options) && F.is.hash(dataOrElement.value); + + /** + * The value of the row. + * @instance + * @protected + * @type {Object} + */ + this.value = isObj ? (hasOptions ? dataOrElement.value : dataOrElement) : null; + + /** + * Contains any options for the row. + * @type {object} + */ + this.o = $.extend(true, { + expanded: false, + classes: null, + style: null + }, hasOptions ? dataOrElement.options : {}); + + /** + * Whether or not this row is expanded and will display it's detail row when there are any hidden columns. + * @instance + * @protected + * @type {boolean} + */ + this.expanded = F.is.jq(this.$el) ? (this.$el.data('expanded') || this.o.expanded) : this.o.expanded; + /** + * An array of CSS classes for the row. + * @instance + * @protected + * @type {Array.} + */ + this.classes = F.is.jq(this.$el) && this.$el.attr('class') ? this.$el.attr('class').match(/\S+/g) : (F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : [])); + /** + * The inline styles for the row. + * @instance + * @protected + * @type {object} + */ + this.style = F.is.jq(this.$el) && this.$el.attr('style') ? F.css2json(this.$el.attr('style')) : (F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {})); + + /** + * The cells array. This is populated before the call to the {@link FooTable.Row#$create} method. + * @instance + * @type {Array.} + */ + this.cells = this.createCells(); + + // this ensures the value contains the parsed cell values and not the supplied values + var self = this; + self.value = {}; + F.arr.each(self.cells, function(cell){ + self.value[cell.column.name] = cell.val(); + }); + }, + /** + * After the row has been defined this ensures that the $el property is a jQuery object by either creating or updating the current value. + * @instance + * @protected + * @this FooTable.Row + */ + $create: function(){ + if (this.created) return; + (this.$el = F.is.jq(this.$el) ? this.$el : $('')) + .data('__FooTableRow__', this); + + this._setClasses(this.$el); + this._setStyle(this.$el); + + if (this.ft.rows.toggleColumn == 'last') this.$toggle.addClass('last-column'); + + this.$details = $('', { 'class': 'footable-detail-row' }) + .append($('', { colspan: this.ft.columns.visibleColspan }) + .append($('', { 'class': 'footable-details ' + this.ft.classes.join(' ') }) + .append(''))); + + var self = this; + F.arr.each(self.cells, function(cell){ + if (!cell.created) cell.$create(); + self.$el.append(cell.$el); + }); + self.$el.off('click.ft.row').on('click.ft.row', { self: self }, self._onToggle); + this.created = true; + }, + /** + * This is called during the construct method and uses the current column definitions to create an array of {@link FooTable.Cell} objects for the row. + * @instance + * @protected + * @returns {Array.} + * @this FooTable.Row + */ + createCells: function(){ + var self = this; + return F.arr.map(self.columns, function(col){ + return col.createCell(self); + }); + }, + /** + * Allows easy access to getting or setting the row's data. If the data is set all associated properties are also updated along with the actual element. + * Using this method also allows us to supply an object containing options and the data for the row at the same time. + * @instance + * @param {object} [data] - The data to set for the row. If not supplied the current value of the row is returned. + * @param {boolean} [redraw=true] - Whether or not to redraw the table once the value has been set. + * @param {boolean} [redrawSelf=true] - Whether or not to redraw the row itself once the value has been set, if `false` this will override the supplied `redraw` value and prevent the table from redrawing as well. + * @returns {(*|undefined)} + */ + val: function(data, redraw, redrawSelf){ + var self = this; + if (!F.is.hash(data)){ + // get - check the value property and build it from the cells if required. + if (!F.is.hash(this.value) || F.is.emptyObject(this.value)){ + this.value = {}; + F.arr.each(this.cells, function(cell){ + if (!cell.column.internal){ + self.value[cell.column.name] = cell.val(); + } + }); + } + return this.value; + } + // set + this.collapse(false); + var isObj = F.is.hash(data), + hasOptions = isObj && F.is.hash(data.options) && F.is.hash(data.value); + + this.o = $.extend(true, { + expanded: self.expanded, + classes: self.classes, + style: self.style + }, hasOptions ? data.options : {}); + + this.expanded = this.o.expanded; + this.classes = F.is.array(this.o.classes) ? this.o.classes : (F.is.string(this.o.classes) ? this.o.classes.match(/\S+/g) : []); + this.style = F.is.hash(this.o.style) ? this.o.style : (F.is.string(this.o.style) ? F.css2json(this.o.style) : {}); + if (isObj) { + if ( hasOptions ) data = data.value; + if (F.is.hash(this.value)){ + for (var prop in data) { + if (!data.hasOwnProperty(prop)) continue; + this.value[prop] = data[prop]; + } + } else { + this.value = data; + } + } else { + this.value = null; + } + + redrawSelf = F.is.boolean(redrawSelf) ? redrawSelf : true; + F.arr.each(this.cells, function(cell){ + if (!cell.column.internal && F.is.defined(self.value[cell.column.name])){ + cell.val(self.value[cell.column.name], false, redrawSelf); + } + }); + + if (this.created && redrawSelf){ + this._setClasses(this.$el); + this._setStyle(this.$el); + if (F.is.boolean(redraw) ? redraw : true) this.draw(); + } + }, + _setClasses: function($el){ + var hasClasses = !F.is.emptyArray(this.classes), + classes = null; + $el.removeAttr('class'); + if (!hasClasses) return; + else classes = this.classes.join(' '); + if (!F.is.emptyString(classes)){ + $el.addClass(classes); + } + }, + _setStyle: function($el){ + var hasStyle = !F.is.emptyObject(this.style), + style = null; + $el.removeAttr('style'); + if (!hasStyle) return; + else style = this.style; + if (F.is.hash(style)){ + $el.css(style); + } + }, + /** + * Sets the current row to an expanded state displaying any hidden columns in a detail row just below it. + * @instance + * @fires FooTable.Row#"expand.ft.row" + */ + expand: function(){ + if (!this.created) return; + var self = this; + /** + * The expand.ft.row event is raised before the the row is expanded. + * Calling preventDefault on this event will stop the row being expanded. + * @event FooTable.Row#"expand.ft.row" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Row} row - The row about to be expanded. + */ + self.ft.raise('expand.ft.row',[self]).then(function(){ + self.__hidden__ = F.arr.map(self.cells, function(cell){ + return cell.column.hidden && cell.column.visible ? cell : null; + }); + + if (self.__hidden__.length > 0){ + self.$details.insertAfter(self.$el) + .children('td').first() + .attr('colspan', self.ft.columns.visibleColspan); + + F.arr.each(self.__hidden__, function(cell){ + cell.collapse(); + }); + } + self.$el.attr('data-expanded', true); + self.$toggle.removeClass('fooicon-plus').addClass('fooicon-minus'); + self.expanded = true; + self.ft.raise('expanded.ft.row', [self]); + }); + }, + /** + * Sets the current row to a collapsed state removing the detail row if it exists. + * @instance + * @param {boolean} [setExpanded] - Whether or not to set the {@link FooTable.Row#expanded} property to false. + * @fires FooTable.Row#"collapse.ft.row" + */ + collapse: function(setExpanded){ + if (!this.created) return; + var self = this; + /** + * The collapse.ft.row event is raised before the the row is collapsed. + * Calling preventDefault on this event will stop the row being collapsed. + * @event FooTable.Row#"collapse.ft.row" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Row} row - The row about to be expanded. + */ + self.ft.raise('collapse.ft.row',[self]).then(function(){ + F.arr.each(self.__hidden__, function(cell){ + cell.restore(); + }); + self.$details.detach(); + self.$el.removeAttr('data-expanded'); + self.$toggle.removeClass('fooicon-minus').addClass('fooicon-plus'); + if (F.is.boolean(setExpanded) ? setExpanded : true) self.expanded = false; + self.ft.raise('collapsed.ft.row', [self]); + }); + }, + /** + * Prior to drawing this moves the details contents back to there original cells and detaches the toggle element from the row. + * @instance + * @param {boolean} [detach] - Whether or not to detach the row. + * @this FooTable.Row + */ + predraw: function(detach){ + if (this.created){ + if (this.expanded){ + this.collapse(false); + } + this.$toggle.detach(); + detach = F.is.boolean(detach) ? detach : true; + if (detach) this.$el.detach(); + } + }, + /** + * Draws the current row and cells. + * @instance + * @this FooTable.Row + */ + draw: function($parent){ + if (!this.created) this.$create(); + if (F.is.jq($parent)) $parent.append(this.$el); + var self = this; + F.arr.each(self.cells, function(cell){ + cell.$el.css('display', (cell.column.hidden || !cell.column.visible ? 'none' : 'table-cell')); + if (self.ft.rows.showToggle && self.ft.columns.hasHidden){ + if ((self.ft.rows.toggleColumn == 'first' && cell.column.index == self.ft.columns.firstVisibleIndex) + || (self.ft.rows.toggleColumn == 'last' && cell.column.index == self.ft.columns.lastVisibleIndex)) { + cell.$el.prepend(self.$toggle); + } + } + cell.$el.add(cell.column.$el).removeClass('footable-first-visible footable-last-visible'); + if (cell.column.index == self.ft.columns.firstVisibleIndex){ + cell.$el.add(cell.column.$el).addClass('footable-first-visible'); + } + if (cell.column.index == self.ft.columns.lastVisibleIndex){ + cell.$el.add(cell.column.$el).addClass('footable-last-visible'); + } + }); + if (this.expanded){ + this.expand(); + } + }, + /** + * Toggles the row between it's expanded and collapsed state if there are hidden columns. + * @instance + * @this FooTable.Row + */ + toggle: function(){ + if (this.created && this.ft.columns.hasHidden){ + if (this.expanded) this.collapse(); + else this.expand(); + } + }, + /** + * Handles the toggle click event for rows. + * @instance + * @param {jQuery.Event} e - The jQuery.Event object for the click event. + * @private + * @this jQuery + */ + _onToggle: function (e) { + var self = e.data.self; + // only execute the toggle if the event.target is one of the approved initiators + if ($(e.target).is(self.ft.rows.toggleSelector)){ + self.toggle(); + } + } + }); + +})(jQuery, FooTable); + +(function ($, F) { + + /** + * An array of all currently loaded instances of the plugin. + * @protected + * @readonly + * @type {Array.} + */ + F.instances = []; + + F.Table = F.Class.extend(/** @lends FooTable.Table */{ + /** + * This class is the core of the plugin and drives the logic of all components. + * @constructs + * @this FooTable.Table + * @extends FooTable.Class + * @param {(HTMLTableElement|jQuery)} element - The element or jQuery table object to bind the plugin to. + * @param {object} options - The options to initialize the plugin with. + * @param {function} [ready] - A callback function to execute once the plugin is initialized. + * @returns {FooTable.Table} + */ + construct: function (element, options, ready) { + //BEGIN MEMBERS + /** + * The timeout ID for the resize event. + * @instance + * @private + * @type {?number} + */ + this._resizeTimeout = null; + /** + * The ID of the FooTable instance. + * @instance + * @type {number} + */ + this.id = F.instances.push(this); + /** + * Whether or not the plugin and all components and add-ons are fully initialized. + * @instance + * @type {boolean} + */ + this.initialized = false; + /** + * The jQuery table object the plugin is bound to. + * @instance + * @type {jQuery} + */ + this.$el = (F.is.jq(element) ? element : $(element)).first(); // ensure one table, one instance + /** + * A loader jQuery instance + * @instance + * @type {jQuery} + */ + this.$loader = $('
', { 'class': 'footable-loader' }).append($('', {'class': 'fooicon fooicon-loader'})); + /** + * The options for the plugin. This is a merge of user defined options and the default options. + * @instance + * @type {object} + */ + this.o = $.extend(true, {}, F.defaults, options); + /** + * The jQuery data object for the table at initialization. + * @instance + * @type {object} + */ + this.data = this.$el.data() || {}; + /** + * An array of all CSS classes on the table that do not start with "footable". + * @instance + * @protected + * @type {Array.} + */ + this.classes = []; + /** + * All components for this instance of the plugin. These are executed in the order they appear in the array for the initialize phase and in reverse order for the destroy phase of the plugin. + * @instance + * @protected + * @type {object} + * @prop {Array.} internal - The internal components for the plugin. These are executed either before all other components in the initialize phase or after them in the destroy phase of the plugin. + * @prop {Array.} core - The core components for the plugin. These are executed either after the internal components in the initialize phase or before them in the destroy phase of the plugin. + * @prop {Array.} custom - The custom components for the plugin. These are executed either after the core components in the initialize phase or before them in the destroy phase of the plugin. + */ + this.components = F.components.load((F.is.hash(this.data.components) ? this.data.components : this.o.components), this); + /** + * The breakpoints component for this instance of the plugin. + * @instance + * @type {FooTable.Breakpoints} + */ + this.breakpoints = this.use(FooTable.Breakpoints); + /** + * The columns component for this instance of the plugin. + * @instance + * @type {FooTable.Columns} + */ + this.columns = this.use(FooTable.Columns); + /** + * The rows component for this instance of the plugin. + * @instance + * @type {FooTable.Rows} + */ + this.rows = this.use(FooTable.Rows); + + //END MEMBERS + this._construct(ready); + }, + /** + * Once all properties are set this performs the actual initialization of the plugin calling the {@link FooTable.Table#_preinit} and + * {@link FooTable.Table#_init} methods as well as raising the {@link FooTable.Table#"ready.ft.table"} event. + * @this FooTable.Table + * @instance + * @param {function} [ready] - A callback function to execute once the plugin is initialized. + * @private + * @returns {jQuery.Promise} + * @fires FooTable.Table#"ready.ft.table" + */ + _construct: function(ready){ + var self = this; + return this._preinit().then(function(){ + return self._init().then(function(){ + /** + * The ready.ft.table event is raised after the plugin has been initialized and the table drawn. + * Calling preventDefault on this event will stop the ready callback being executed. + * @event FooTable.Table#"ready.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.raise('ready.ft.table').then(function(){ + if (F.is.fn(ready)) ready.call(self, self); + }); + }); + }).always(function(arg){ + self.$el.show(); + if (F.is.error(arg)){ + console.error('FooTable: unhandled error thrown during initialization.', arg); + } + }); + }, + /** + * The preinit method is called prior to the plugins actual initialization and provides itself and it's components an opportunity to parse any additional option values. + * @instance + * @private + * @returns {jQuery.Promise} + * @fires FooTable.Table#"preinit.ft.table" + */ + _preinit: function(){ + var self = this; + /** + * The preinit.ft.table event is raised before any components. + * Calling preventDefault on this event will disable the entire plugin. + * @event FooTable.Table#"preinit.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {object} data - The jQuery data object from the root table element. + */ + return this.raise('preinit.ft.table', [self.data]).then(function(){ + var classes = (self.$el.attr('class') || '').match(/\S+/g) || []; + + self.o.ajax = F.checkFnValue(self, self.data.ajax, self.o.ajax); + self.o.stopPropagation = F.is.boolean(self.data.stopPropagation) + ? self.data.stopPropagation + : self.o.stopPropagation; + + for (var i = 0, len = classes.length; i < len; i++){ + if (!F.str.startsWith(classes[i], 'footable')) self.classes.push(classes[i]); + } + + self.$el.hide().after(self.$loader); + return self.execute(false, false, 'preinit', self.data); + }); + }, + /** + * Initializes this instance of the plugin and calls the callback function if one is supplied once complete. + * @this FooTable.Table + * @instance + * @private + * @return {jQuery.Promise} + * @fires FooTable.Table#"init.ft.table" + */ + _init: function(){ + var self = this; + /** + * The init.ft.table event is raised before any components are initialized. + * Calling preventDefault on this event will disable the entire plugin. + * @event FooTable.Table#"init.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.raise('init.ft.table').then(function(){ + var $thead = self.$el.children('thead'), + $tbody = self.$el.children('tbody'), + $tfoot = self.$el.children('tfoot'); + self.$el.addClass('footable footable-' + self.id); + if (F.is.hash(self.o.on)) self.$el.on(self.o.on); + if ($tfoot.length == 0) self.$el.append($tfoot = $('
')); + if ($tbody.length == 0) self.$el.append(''); + if ($thead.length == 0) self.$el.prepend($thead = $('')); + return self.execute(false, true, 'init').then(function(){ + self.$el.data('__FooTable__', self); + if ($tfoot.children('tr').length == 0) $tfoot.remove(); + if ($thead.children('tr').length == 0) $thead.remove(); + + /** + * The postinit.ft.table event is raised after any components are initialized but before the table is + * drawn for the first time. + * Calling preventDefault on this event will disable the initial drawing of the table. + * @event FooTable.Table#"postinit.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.raise('postinit.ft.table').then(function(){ + return self.draw(); + }).always(function(){ + $(window).off('resize.ft'+self.id, self._onWindowResize) + .on('resize.ft'+self.id, { self: self }, self._onWindowResize); + self.initialized = true; + }); + }); + }); + }, + /** + * Destroys this plugin removing it from the table. + * @this FooTable.Table + * @instance + * @fires FooTable.Table#"destroy.ft.table" + */ + destroy: function () { + var self = this; + /** + * The destroy.ft.table event is called before all core components. + * Calling preventDefault on this event will prevent the entire plugin from being destroyed. + * @event FooTable.Table#"destroy.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.raise('destroy.ft.table').then(function(){ + return self.execute(true, true, 'destroy').then(function () { + self.$el.removeData('__FooTable__').removeClass('footable-' + self.id); + if (F.is.hash(self.o.on)) self.$el.off(self.o.on); + $(window).off('resize.ft'+self.id, self._onWindowResize); + self.initialized = false; + F.instances[self.id] = null; + }); + }).fail(function(err){ + if (F.is.error(err)){ + console.error('FooTable: unhandled error thrown while destroying the plugin.', err); + } + }); + }, + /** + * Raises an event on this instance supplying the args array as additional parameters to the handlers. + * @this FooTable.Table + * @instance + * @param {string} eventName - The name of the event to raise, this can include namespaces. + * @param {Array} [args] - An array containing additional parameters to be passed to any bound handlers. + * @returns {jQuery.Event} + */ + raise: function(eventName, args){ + var self = this, + debug = F.__debug__ && (F.is.emptyArray(F.__debug_options__.events) || F.arr.any(F.__debug_options__.events, function(name){ return F.str.contains(eventName, name); })); + args = args || []; + args.unshift(this); + return $.Deferred(function(d){ + var evt = $.Event(eventName); + if (self.o.stopPropagation == true){ + self.$el.one(eventName, function (e) {e.stopPropagation();}); + } + if (debug) console.log('FooTable:'+eventName+': ', args); + self.$el.trigger(evt, args); + if (evt.isDefaultPrevented()){ + if (debug) console.log('FooTable: default prevented for the "'+eventName+'" event.'); + d.reject(evt); + } else d.resolve(evt); + }); + }, + /** + * Attempts to retrieve the instance of the supplied component type for this instance. + * @this FooTable.Table + * @instance + * @param {object} type - The content type to retrieve for this instance. + * @returns {(*|null)} + */ + use: function(type){ + for (var i = 0, len = this.components.length; i < len; i++){ + if (this.components[i] instanceof type) return this.components[i]; + } + return null; + }, + /** + * Performs the drawing of the table. + * @this FooTable.Table + * @instance + * @protected + * @returns {jQuery.Promise} + * @fires FooTable.Table#"predraw.ft.table" + * @fires FooTable.Table#"draw.ft.table" + * @fires FooTable.Table#"postdraw.ft.table" + */ + draw: function () { + var self = this; + + // Clone the current table and insert it into the original's place + var $elCopy = self.$el.clone().insertBefore(self.$el); + + // Detach `self.$el` from the DOM, retaining its event handlers + self.$el.detach(); + + // when drawing the order that the components are executed is important so chain the methods but use promises to retain async safety. + return self.execute(false, true, 'predraw').then(function(){ + /** + * The predraw.ft.table event is raised after all core components and add-ons have executed there predraw functions but before they execute there draw functions. + * @event FooTable.Table#"predraw.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.raise('predraw.ft.table').then(function(){ + return self.execute(false, true, 'draw').then(function(){ + /** + * The draw.ft.table event is raised after all core components and add-ons have executed there draw functions. + * @event FooTable.Table#"draw.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.raise('draw.ft.table').then(function(){ + return self.execute(false, true, 'postdraw').then(function(){ + /** + * The postdraw.ft.table event is raised after all core components and add-ons have executed there postdraw functions. + * @event FooTable.Table#"postdraw.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.raise('postdraw.ft.table'); + }); + }); + }); + }); + }).fail(function(err){ + if (F.is.error(err)){ + console.error('FooTable: unhandled error thrown during a draw operation.', err); + } + }).always(function(){ + // Replace the copy that we added above with the modified `self.$el` + $elCopy.replaceWith(self.$el); + self.$loader.remove(); + }); + }, + /** + * Executes the specified method with the optional number of parameters on all components and waits for the promise from each to be resolved before executing the next. + * @this FooTable.Table + * @instance + * @protected + * @param {boolean} reverse - Whether or not to execute the component methods in the reverse order to what they were registered in. + * @param {boolean} enabled - Whether or not to execute the method on enabled components only. + * @param {string} methodName - The name of the method to execute. + * @param {*} [param1] - The first parameter for the method. + * @param {...*} [paramN] - Any number of additional parameters for the method. + * @returns {jQuery.Promise} + */ + execute: function(reverse, enabled, methodName, param1, paramN){ + var self = this, args = Array.prototype.slice.call(arguments); + reverse = args.shift(); + enabled = args.shift(); + var components = enabled ? F.arr.get(self.components, function(c){ return c.enabled; }) : self.components.slice(0); + args.unshift(reverse ? components.reverse() : components); + return self._execute.apply(self, args); + }, + /** + * Executes the specified method with the optional number of parameters on all supplied components waiting for the result of each before executing the next. + * @this FooTable.Table + * @instance + * @private + * @param {Array.} components - The components to call the method on. + * @param {string} methodName - The name of the method to execute + * @param {*} [param1] - The first parameter for the method. + * @param {...*} [paramN] - Any additional parameters for the method. + * @returns {jQuery.Promise} + */ + _execute: function(components, methodName, param1, paramN){ + if (!components || !components.length) return $.when(); + var self = this, args = Array.prototype.slice.call(arguments), + component; + components = args.shift(); + methodName = args.shift(); + component = components.shift(); + + if (!F.is.fn(component[methodName])) + return self._execute.apply(self, [components, methodName].concat(args)); + + return $.Deferred(function(d){ + try { + var result = component[methodName].apply(component, args); + if (F.is.promise(result)){ + return result.then(d.resolve, d.reject); + } else { + d.resolve(result); + } + } catch (err) { + d.reject(err); + } + }).then(function(){ + return self._execute.apply(self, [components, methodName].concat(args)); + }); + }, + /** + * Listens to the window resize event and performs a check to see if the breakpoint has changed. + * @this window + * @instance + * @private + * @fires FooTable.Table#"resize.ft.table" + */ + _onWindowResize: function (e) { + var self = e.data.self; + if (self._resizeTimeout != null) { clearTimeout(self._resizeTimeout); } + self._resizeTimeout = setTimeout(function () { + self._resizeTimeout = null; + /** + * The resize event is raised a short time after window resize operations cease. + * @event FooTable.Table#"resize.ft.table" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + self.raise('resize.ft.table').then(function(){ + self.breakpoints.check(); + }); + }, 300); + } + }); + +})(jQuery, FooTable); +(function($, F){ + + F.ArrayColumn = F.Column.extend(/** @lends FooTable.ArrayColumn */{ + /** + * @summary A column to handle Array values. + * @constructs + * @extends FooTable.Column + * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. + * @param {object} definition - An object containing all the properties to set for the column. + */ + construct: function(instance, definition) { + this._super(instance, definition, 'array'); + }, + /** + * @summary Parses the supplied value or element to retrieve a column value. + * @description This is supplied either the cell value or jQuery object to parse. This method will return either the Array containing the values or null. + * @instance + * @protected + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {(array|null)} + */ + parser: function(valueOrElement){ + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ // use jQuery to get the value + var $el = $(valueOrElement), data = $el.data('value'); // .data() will automatically convert a JSON string to an array + if (F.is.array(data)) return data; + data = $el.html(); + try { + data = JSON.parse(data); + } catch(err) { + data = null; + } + return F.is.array(data) ? data : null; // if we have an array return it + } + if (F.is.array(valueOrElement)) return valueOrElement; // if we have an array return it + return null; // otherwise we have no value so return null + }, + /** + * @summary Formats the column value and creates the HTML seen within a cell. + * @description This is supplied the value retrieved from the {@link FooTable.ArrayColumn#parser} function and must return a string, HTMLElement or jQuery object. + * The return value from this function is what is displayed in the cell in the table. + * @instance + * @protected + * @param {?Array} value - The value to format. + * @param {object} options - The current plugin options. + * @param {object} rowData - An object containing the current row data. + * @returns {(string|HTMLElement|jQuery)} + */ + formatter: function(value, options, rowData){ + return F.is.array(value) ? JSON.stringify(value) : ''; + } + }); + + F.columns.register('array', F.ArrayColumn); + +})(jQuery, FooTable); +(function($, F){ + + if (F.is.undef(window.moment)){ + // The DateColumn requires moment.js to parse and format date values. Goto http://momentjs.com/ to get it. + return; + } + + F.DateColumn = F.Column.extend(/** @lends FooTable.DateColumn */{ + /** + * The date column class is used to handle date values. This column is dependent on [moment.js]{@link http://momentjs.com/} to provide date parsing and formatting functionality. + * @constructs + * @extends FooTable.Column + * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. + * @param {object} definition - An object containing all the properties to set for the column. + * @returns {FooTable.DateColumn} + */ + construct: function(instance, definition){ + this._super(instance, definition, 'date'); + /** + * The format string to use when parsing and formatting dates. + * @instance + * @type {string} + */ + this.formatString = F.is.string(definition.formatString) ? definition.formatString : 'MM-DD-YYYY'; + }, + /** + * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and will be provided to the {@link FooTable.DateColumn#format} function + * to generate the cell contents. + * @instance + * @protected + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {(moment|null)} + * @this FooTable.DateColumn + */ + parser: function(valueOrElement){ + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ + var data = $(valueOrElement).data('value'); + valueOrElement = F.is.defined(data) ? data : $(valueOrElement).text(); + if (F.is.string(valueOrElement)) valueOrElement = isNaN(valueOrElement) ? valueOrElement : +valueOrElement; + } + if (F.is.date(valueOrElement)) return moment(valueOrElement); + if (F.is.object(valueOrElement) && F.is.boolean(valueOrElement._isAMomentObject)) return valueOrElement; + if (F.is.string(valueOrElement)){ + // if it looks like a number convert it and do nothing else otherwise create a new moment using the string value and formatString + if (isNaN(valueOrElement)){ + return moment(valueOrElement, this.formatString); + } else { + valueOrElement = +valueOrElement; + } + } + if (F.is.number(valueOrElement)){ + return moment(valueOrElement); + } + return null; + }, + /** + * This is supplied the value retrieved from the {@link FooTable.DateColumn#parser} function and must return a string, HTMLElement or jQuery object. + * The return value from this function is what is displayed in the cell in the table. + * @instance + * @protected + * @param {*} value - The value to format. + * @param {object} options - The current plugin options. + * @param {object} rowData - An object containing the current row data. + * @returns {(string|HTMLElement|jQuery)} + * @this FooTable.DateColumn + */ + formatter: function(value, options, rowData){ + return F.is.object(value) && F.is.boolean(value._isAMomentObject) && value.isValid() ? value.format(this.formatString) : ''; + }, + /** + * This is supplied either the cell value or jQuery object to parse. A string value must be returned from this method and will be used during filtering operations. + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {string} + * @this FooTable.DateColumn + */ + filterValue: function(valueOrElement){ + // if we have an element or a jQuery object use jQuery to get the value + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)) valueOrElement = $(valueOrElement).data('filterValue') || $(valueOrElement).text(); + // if options are supplied with the value + if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ + if (F.is.string(valueOrElement.options.filterValue)) valueOrElement = valueOrElement.options.filterValue; + if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value; + } + // if the value is a moment object just return the formatted value + if (F.is.object(valueOrElement) && F.is.boolean(valueOrElement._isAMomentObject)) return valueOrElement.format(this.formatString); + // if its a string + if (F.is.string(valueOrElement)){ + // if its not a number return it + if (isNaN(valueOrElement)){ + return valueOrElement; + } else { // otherwise convert it and carry on + valueOrElement = +valueOrElement; + } + } + // if the value is a number or date convert to a moment object and return the formatted result. + if (F.is.number(valueOrElement) || F.is.date(valueOrElement)){ + return moment(valueOrElement).format(this.formatString); + } + // try use the native toString of the value if its not undefined or null + if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement+''; + return ''; // otherwise we have no value so return an empty string + } + }); + + F.columns.register('date', F.DateColumn); + +})(jQuery, FooTable); + +(function($, F){ + + F.HTMLColumn = F.Column.extend(/** @lends FooTable.HTMLColumn */{ + /** + * The HTML column class is used to handle any raw HTML columns. + * @constructs + * @extends FooTable.Column + * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. + * @param {object} definition - An object containing all the properties to set for the column. + * @returns {FooTable.HTMLColumn} + */ + construct: function(instance, definition){ + this._super(instance, definition, 'html'); + }, + /** + * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and will be provided to the {@link FooTable.HTMLColumn#format} function + * to generate the cell contents. + * @instance + * @protected + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {(jQuery|null)} + * @this FooTable.HTMLColumn + */ + parser: function(valueOrElement){ + if (F.is.string(valueOrElement)) valueOrElement = $($.trim(valueOrElement)); + if (F.is.element(valueOrElement)) valueOrElement = $(valueOrElement); + if (F.is.jq(valueOrElement)){ + var tagName = valueOrElement.prop('tagName').toLowerCase(); + if (tagName == 'td' || tagName == 'th'){ + var data = valueOrElement.data('value'); + return F.is.defined(data) ? data : valueOrElement.contents(); + } + return valueOrElement; + } + return null; + } + }); + + F.columns.register('html', F.HTMLColumn); + +})(jQuery, FooTable); +(function($, F){ + + F.NumberColumn = F.Column.extend(/** @lends FooTable.NumberColumn */{ + /** + * The number column class is used to handle simple number columns. + * @constructs + * @extends FooTable.Column + * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. + * @param {object} definition - An object containing all the properties to set for the column. + * @returns {FooTable.NumberColumn} + */ + construct: function(instance, definition){ + this._super(instance, definition, 'number'); + this.decimalSeparator = F.is.string(definition.decimalSeparator) ? definition.decimalSeparator : '.'; + this.thousandSeparator = F.is.string(definition.thousandSeparator) ? definition.thousandSeparator : ','; + this.decimalSeparatorRegex = new RegExp(F.str.escapeRegExp(this.decimalSeparator), 'g'); + this.thousandSeparatorRegex = new RegExp(F.str.escapeRegExp(this.thousandSeparator), 'g'); + this.cleanRegex = new RegExp('[^\-0-9' + F.str.escapeRegExp(this.decimalSeparator) + ']', 'g'); + }, + /** + * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and will be provided to the {@link FooTable.Column#formatter} function + * to generate the cell contents. + * @instance + * @protected + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {(number|null)} + * @this FooTable.NumberColumn + */ + parser: function(valueOrElement){ + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ + var data = $(valueOrElement).data('value'); + valueOrElement = F.is.defined(data) ? data : $(valueOrElement).text().replace(this.cleanRegex, ''); + } + if (F.is.string(valueOrElement)){ + valueOrElement = valueOrElement.replace(this.thousandSeparatorRegex, '').replace(this.decimalSeparatorRegex, '.'); + valueOrElement = parseFloat(valueOrElement); + } + if (F.is.number(valueOrElement)) return valueOrElement; + return null; + }, + /** + * This is supplied the value retrieved from the {@link FooTable.NumberColumn#parse} function and must return a string, HTMLElement or jQuery object. + * The return value from this function is what is displayed in the cell in the table. + * @instance + * @protected + * @param {number} value - The value to format. + * @param {object} options - The current plugin options. + * @param {object} rowData - An object containing the current row data. + * @returns {(string|HTMLElement|jQuery)} + * @this FooTable.NumberColumn + */ + formatter: function(value, options, rowData){ + if (value == null) return ''; + var s = (value + '').split('.'); + if (s.length == 2 && s[0].length > 3) { + s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, this.thousandSeparator); + } + return s.join(this.decimalSeparator); + } + }); + + F.columns.register('number', F.NumberColumn); + +})(jQuery, FooTable); +(function($, F){ + + F.ObjectColumn = F.Column.extend(/** @lends FooTable.ObjectColumn */{ + /** + * @summary A column to handle Object values. + * @constructs + * @extends FooTable.Column + * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. + * @param {object} definition - An object containing all the properties to set for the column. + */ + construct: function(instance, definition) { + this._super(instance, definition, 'object'); + }, + /** + * @summary Parses the supplied value or element to retrieve a column value. + * @description This is supplied either the cell value or jQuery object to parse. This method will return either the Object containing the values or null. + * @instance + * @protected + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {(object|null)} + */ + parser: function(valueOrElement){ + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ // use jQuery to get the value + var $el = $(valueOrElement), data = $el.data('value'); // .data() will automatically convert a JSON string to an object + if (F.is.object(data)) return data; + data = $el.html(); + try { + data = JSON.parse(data); + } catch(err) { + data = null; + } + return F.is.object(data) ? data : null; // if we have an object return it + } + if (F.is.object(valueOrElement)) return valueOrElement; // if we have an object return it + return null; // otherwise we have no value so return null + }, + /** + * @summary Formats the column value and creates the HTML seen within a cell. + * @description This is supplied the value retrieved from the {@link FooTable.ObjectColumn#parser} function and must return a string, HTMLElement or jQuery object. + * The return value from this function is what is displayed in the cell in the table. + * @instance + * @protected + * @param {*} value - The value to format. + * @param {object} options - The current plugin options. + * @param {object} rowData - An object containing the current row data. + * @returns {(string|HTMLElement|jQuery)} + */ + formatter: function(value, options, rowData){ + return F.is.object(value) ? JSON.stringify(value) : ''; + } + }); + + F.columns.register('object', F.ObjectColumn); + +})(jQuery, FooTable); +(function($, F){ + + F.Breakpoint = F.Class.extend(/** @lends FooTable.Breakpoint */{ + /** + * The breakpoint class containing the name and maximum width for the breakpoint. + * @constructs + * @extends FooTable.Class + * @param {string} name - The name of the breakpoint. Must contain no spaces or special characters. + * @param {number} width - The width of the breakpoint in pixels. + * @returns {FooTable.Breakpoint} + */ + construct: function(name, width){ + /** + * The name of the breakpoint. + * @type {string} + */ + this.name = name; + /** + * The maximum width of the breakpoint in pixels. + * @type {number} + */ + this.width = width; + } + }); + +})(jQuery, FooTable); +(function($, F){ + F.Breakpoints = F.Component.extend(/** @lends FooTable.Breakpoints */{ + /** + * Contains the logic to calculate and apply breakpoints for the plugin. + * @constructs + * @extends FooTable.Component + * @param {FooTable.Table} table - The parent {@link FooTable.Table} this component belongs to. + * @returns {FooTable.Breakpoints} + */ + construct: function(table){ + // call the base class constructor + this._super(table, true); + + /* PROTECTED */ + /** + * This provides a shortcut to the {@link FooTable.Table#options} object. + * @protected + * @type {FooTable.Table#options} + */ + this.o = table.o; + + /* PUBLIC */ + /** + * The current breakpoint. + * @type {FooTable.Breakpoint} + */ + this.current = null; + /** + * An array of {@link FooTable.Breakpoint} objects created from parsing the options. + * @type {Array.} + */ + this.array = []; + /** + * Whether or not breakpoints cascade. When set to true all breakpoints larger than the current will be hidden along with it. + * @type {boolean} + */ + this.cascade = this.o.cascade; + /** + * Whether or not to calculate breakpoints on the width of the parent element rather than the viewport. + * @type {boolean} + */ + this.useParentWidth = this.o.useParentWidth; + /** + * This value is updated each time the current breakpoint changes and contains a space delimited string of the names of the current breakpoint and all those smaller than it. + * @type {string} + */ + this.hidden = null; + + /* PRIVATE */ + /** + * This value is set once when the {@link FooTable.Breakpoints#array} is generated and contains a space delimited string of all the breakpoint class names. + * @type {string} + * @private + */ + this._classNames = ''; + + // check if a function was supplied to override the default getWidth + this.getWidth = F.checkFnValue(this, this.o.getWidth, this.getWidth); + }, + + /* PROTECTED */ + /** + * Checks the supplied data and options for the breakpoints component. + * @instance + * @protected + * @param {object} data - The jQuery data object from the parent table. + * @fires FooTable.Breakpoints#"preinit.ft.breakpoints" + */ + preinit: function(data){ + var self = this; + /** + * The preinit.ft.breakpoints event is raised before any UI is created and provides the tables jQuery data object for additional options parsing. + * Calling preventDefault on this event will disable the entire plugin. + * @event FooTable.Breakpoints#"preinit.ft.breakpoints" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {object} data - The jQuery data object of the table raising the event. + */ + return this.ft.raise('preinit.ft.breakpoints', [data]).then(function(){ + self.cascade = F.is.boolean(data.cascade) ? data.cascade : self.cascade; + self.o.breakpoints = F.is.hash(data.breakpoints) ? data.breakpoints : self.o.breakpoints; + self.getWidth = F.checkFnValue(self, data.getWidth, self.getWidth); + if (self.o.breakpoints == null) self.o.breakpoints = { "xs": 480, "sm": 768, "md": 992, "lg": 1200 }; + // Create a nice friendly array to work with out of the breakpoints object. + for (var name in self.o.breakpoints) { + if (!self.o.breakpoints.hasOwnProperty(name)) continue; + self.array.push(new F.Breakpoint(name, self.o.breakpoints[name])); + self._classNames += 'breakpoint-' + name + ' '; + } + // Sort the breakpoints so the largest is checked first + self.array.sort(function (a, b) { + return b.width - a.width; + }); + }); + }, + /** + * Initializes the class parsing the options into a sorted array of {@link FooTable.Breakpoint} objects. + * @instance + * @protected + * @fires FooTable.Breakpoints#"init.ft.breakpoints" + */ + init: function(){ + var self = this; + /** + * The init.ft.breakpoints event is raised before any UI is generated. + * Calling preventDefault on this event will disable the entire plugin. + * @event FooTable.Breakpoints#"init.ft.breakpoints" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return this.ft.raise('init.ft.breakpoints').then(function(){ + self.current = self.get(); + }); + }, + /** + * Whenever the table is drawn this ensures the correct breakpoint class is applied to the table. + * @instance + * @protected + */ + draw: function(){ + this.ft.$el.removeClass(this._classNames).addClass('breakpoint-' + this.current.name); + }, + + /* PUBLIC */ + /** + * Calculates the current breakpoint from the {@link FooTable.Breakpoints#array} and sets the {@link FooTable.Breakpoints#current} property. + * @instance + * @returns {FooTable.Breakpoint} + */ + calculate: function(){ + var self = this, current = null, hidden = [], breakpoint, prev = null, width = self.getWidth(); + for (var i = 0, len = self.array.length; i < len; i++) { + breakpoint = self.array[i]; + // if the width is smaller than the smallest breakpoint set the smallest as the current. + // if the width is larger than the largest breakpoint set the largest as the current. + // otherwise if the width is somewhere in between check all breakpoints testing if the width + // is greater than the current but smaller than the previous. + if ((!current && i == len -1) + || (width >= breakpoint.width && (prev instanceof F.Breakpoint ? width < prev.width : true))) { + current = breakpoint; + } + if (!current) hidden.push(breakpoint.name); + prev = breakpoint; + } + hidden.push(current.name); + self.hidden = hidden.join(' '); + return current; + }, + /** + * Supplied a columns breakpoints this returns a boolean value indicating whether or not the column is visible. + * @param {string} breakpoints - A space separated string of breakpoint names. + * @returns {boolean} + */ + visible: function(breakpoints){ + if (F.is.emptyString(breakpoints)) return true; + if (breakpoints === 'all') return false; + var parts = breakpoints.split(' '), i = 0, len = parts.length; + for (; i < len; i++){ + if (this.cascade ? F.str.containsWord(this.hidden, parts[i]) : parts[i] == this.current.name) return false; + } + return true; + }, + /** + * Performs a check between the current breakpoint and the previous breakpoint and performs a redraw if they differ. + * @instance + * @fires FooTable.Breakpoints#"before.ft.breakpoints" + * @fires FooTable.Breakpoints#"after.ft.breakpoints" + */ + check: function(){ + var self = this, bp = self.get(); + if (!(bp instanceof F.Breakpoint) + || bp == self.current) + return; + + /** + * The before.ft.breakpoints event is raised if the breakpoint has changed but before the UI is redrawn and is supplied both the current breakpoint + * and the next "new" one that is about to be applied. + * Calling preventDefault on this event will prevent the next breakpoint from being applied. + * @event FooTable.Breakpoints#"before.ft.breakpoints" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Breakpoint} current - The current breakpoint. + * @param {FooTable.Breakpoint} next - The breakpoint that is about to be applied. + */ + self.ft.raise('before.ft.breakpoints', [self.current, bp]).then(function(){ + var previous = self.current; + self.current = bp; + return self.ft.draw().then(function(){ + /** + * The after.ft.breakpoints event is raised after the breakpoint has changed and the UI is redrawn and is supplied both the "new" current breakpoint + * and the previous one that was replaced. + * @event FooTable.Breakpoints#"after.ft.breakpoints" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Breakpoint} current - The current breakpoint. + * @param {FooTable.Breakpoint} previous - The breakpoint that was just replaced. + */ + self.ft.raise('after.ft.breakpoints', [self.current, previous]); + }); + }); + }, + /** + * Attempts to return a {@link FooTable.Breakpoint} instance when passed a {@link FooTable.Breakpoint}, + * the {@link FooTable.Breakpoint#name} string or if nothing is supplied the current breakpoint. + * @instance + * @param {(FooTable.Breakpoint|string|number)} [breakpoint] - The breakpoint to retrieve. + * @returns {FooTable.Breakpoint} + */ + get: function(breakpoint){ + if (F.is.undef(breakpoint)) return this.calculate(); + if (breakpoint instanceof F.Breakpoint) return breakpoint; + if (F.is.string(breakpoint)) return F.arr.first(this.array, function (bp) { return bp.name == breakpoint; }); + if (F.is.number(breakpoint)) return breakpoint >= 0 && breakpoint < this.array.length ? this.array[breakpoint] : null; + return null; + }, + /** + * Gets the width used to determine breakpoints whether it be from the viewport, parent or a custom function. + * @instance + * @returns {number} + */ + getWidth: function(){ + if (F.is.fn(this.o.getWidth)) return this.o.getWidth(this.ft); + if (this.useParentWidth == true) return this.getParentWidth(); + return this.getViewportWidth(); + }, + /** + * Gets the tables direct parents width. + * @instance + * @returns {number} + */ + getParentWidth: function(){ + return this.ft.$el.parent().width(); + }, + /** + * Gets the current viewport width. + * @instance + * @returns {number} + */ + getViewportWidth: function(){ + return Math.max(document.documentElement.clientWidth, window.innerWidth, 0); + } + }); + + F.components.register('breakpoints', F.Breakpoints, 1000); + +})(jQuery, FooTable); +(function(F){ + /** + * A space delimited string of breakpoint names that specify when the column will be hidden. You can also specify "all" to make a column permanently display in an expandable detail row. + * @type {string} + * @default null + * @example + * breakpoints: "md" + */ + F.Column.prototype.breakpoints = null; + + F.Column.prototype.__breakpoints_define__ = function(definition){ + this.breakpoints = F.is.emptyString(definition.breakpoints) ? null : definition.breakpoints; + }; + + F.Column.extend('define', function(definition){ + this._super(definition); + this.__breakpoints_define__(definition); + }); +})(FooTable); +(function(F){ + /** + * An object containing the breakpoints for the plugin. + * @type {object.} + * @default { "xs": 480, "sm": 768, "md": 992, "lg": 1200 } + */ + F.Defaults.prototype.breakpoints = null; + + /** + * Whether or not breakpoints cascade. When set to true all breakpoints larger than the current will also be hidden along with it. + * @type {boolean} + * @default false + */ + F.Defaults.prototype.cascade = false; + + /** + * Whether or not to calculate breakpoints on the width of the parent element rather than the viewport. + * @type {boolean} + * @default false + */ + F.Defaults.prototype.useParentWidth = false; + + /** + * A function used to override the default getWidth function with a custom one. + * @type {function} + * @default null + * @example + * getWidth: function(instance){ + * if (instance.o.useParentWidth == true) return instance.$el.parent().width(); + * return instance.breakpoints.getViewportWidth(); + * } + */ + F.Defaults.prototype.getWidth = null; +})(FooTable); +(function($, F){ + F.Columns = F.Component.extend(/** @lends FooTable.Columns */{ + /** + * The columns class contains all the logic for handling columns. + * @constructs + * @extends FooTable.Component + * @param {FooTable.Table} table - The parent {@link FooTable.Table} this component belongs to. + * @returns {FooTable.Columns} + */ + construct: function(table){ + // call the base class constructor + this._super(table, true); + + /* PROTECTED */ + /** + * This provides a shortcut to the {@link FooTable.Table#options} object. + * @protected + * @type {FooTable.Table#options} + */ + this.o = table.o; + + /* PUBLIC */ + /** + * An array of {@link FooTable.Column} objects created from parsing the options and/or DOM. + * @type {Array.} + */ + this.array = []; + /** + * The jQuery header row object. + * @type {jQuery} + */ + this.$header = null; + /** + * Whether or not to display the header row. + * @type {boolean} + */ + this.showHeader = table.o.showHeader; + + this._fromHTML = F.is.emptyArray(table.o.columns) && !F.is.promise(table.o.columns); + }, + + /* PROTECTED */ + /** + * This parses the columns from either the tables rows or the supplied options. + * @instance + * @protected + * @param {object} data - The tables jQuery data object. + * @returns {jQuery.Promise} + * @this FooTable.Columns + */ + parse: function(data){ + var self = this; + return $.Deferred(function(d){ + function merge(cols1, cols2){ + var merged = []; + // check if either of the arrays is empty as it can save us having to merge them by index. + if (cols1.length == 0 || cols2.length == 0){ + merged = cols1.concat(cols2); + } else { + // at this point we have two arrays of column definitions, we now need to merge them based on there index properties + // first figure out the highest column index provided so we can loop that many times to merge all columns and provide + // defaults where nothing was specified (fill in the gaps in the array as it were). + var highest = 0; + F.arr.each(cols1.concat(cols2), function(c){ + if (c.index > highest) highest = c.index; + }); + highest++; + for (var i = 0, cols1_c, cols2_c; i < highest; i++){ + cols1_c = {}; + F.arr.each(cols1, function(c){ + if (c.index == i){ + cols1_c = c; + return false; + } + }); + cols2_c = {}; + F.arr.each(cols2, function(c){ + if (c.index == i){ + cols2_c = c; + return false; + } + }); + merged.push($.extend(true, {}, cols1_c, cols2_c)); + } + } + return merged; + } + + var json = [], html = []; + // get the column options from the content + var $header = self.ft.$el.find('tr.footable-header, thead > tr:last:has([data-breakpoints]), tbody > tr:first:has([data-breakpoints]), thead > tr:last, tbody > tr:first').first(), $cell, cdata; + if ($header.length > 0){ + var virtual = $header.parent().is('tbody') && $header.children().length == $header.children('td').length; + if (!virtual) self.$header = $header.addClass('footable-header'); + $header.children('td,th').each(function(i, cell){ + $cell = $(cell); + cdata = $cell.data(); + cdata.index = i; + cdata.$el = $cell; + cdata.virtual = virtual; + html.push(cdata); + }); + if (virtual) self.showHeader = false; + } + // get the supplied column options + if (F.is.array(self.o.columns) && !F.is.emptyArray(self.o.columns)){ + F.arr.each(self.o.columns, function(c, i){ + c.index = i; + json.push(c); + }); + self.parseFinalize(d, merge(json, html)); + } else if (F.is.promise(self.o.columns)){ + self.o.columns.then(function(cols){ + F.arr.each(cols, function(c, i){ + c.index = i; + json.push(c); + }); + self.parseFinalize(d, merge(json, html)); + }, function(xhr){ + d.reject(Error('Columns ajax request error: ' + xhr.status + ' (' + xhr.statusText + ')')); + }); + } else { + self.parseFinalize(d, merge(json, html)); + } + }); + }, + /** + * Used to finalize the parsing of columns it is supplied the parse deferred object which must be resolved with an array of {@link FooTable.Column} objects + * or rejected with an error. + * @instance + * @protected + * @param {jQuery.Deferred} deferred - The deferred object used for parsing. + * @param {Array.} cols - An array of all merged column definitions. + */ + parseFinalize: function(deferred, cols){ + // we now have a merged array of all column definitions supplied to the plugin, time to make the objects. + var self = this, columns = [], column; + F.arr.each(cols, function(def){ + // if we have a column registered using the definition type then create an instance of that column otherwise just create a default text column. + if (column = F.columns.contains(def.type) ? F.columns.make(def.type, self.ft, def) : new F.Column(self.ft, def)) + columns.push(column); + }); + if (F.is.emptyArray(columns)){ + deferred.reject(Error("No columns supplied.")); + } else { + // make sure to sort by the column index as the merge process may have mixed them up + columns.sort(function(a, b){ return a.index - b.index; }); + deferred.resolve(columns); + } + }, + /** + * The columns preinit method is used to parse and check the column options supplied from both static content and through the constructor. + * @instance + * @protected + * @param {object} data - The jQuery data object from the root table element. + * @this FooTable.Columns + */ + preinit: function(data){ + var self = this; + /** + * The preinit.ft.columns event is raised before any UI is created and provides the tables jQuery data object for additional options parsing. + * Calling preventDefault on this event will disable the entire plugin. + * @event FooTable.Columns#"preinit.ft.columns" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {object} data - The jQuery data object of the table raising the event. + */ + return self.ft.raise('preinit.ft.columns', [data]).then(function(){ + return self.parse(data).then(function(columns){ + self.array = columns; + self.showHeader = F.is.boolean(data.showHeader) ? data.showHeader : self.showHeader; + }); + }); + }, + /** + * Initializes the columns creating the table header if required. + * @instance + * @protected + * @fires FooTable.Columns#"init.ft.columns" + * @this FooTable.Columns + */ + init: function(){ + var self = this; + /** + * The init.ft.columns event is raised after the header row is created/parsed for column data. + * @event FooTable.Columns#"init.ft.columns" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} instance - The instance of the plugin raising the event. + * @param {Array.} columns - The array of {@link FooTable.Column} objects parsed from the options and/or DOM. + */ + return this.ft.raise('init.ft.columns', [ self.array ]).then(function(){ + self.$create(); + }); + }, + /** + * Destroys the columns component removing any UI generated from the table. + * @instance + * @protected + * @fires FooTable.Columns#"destroy.ft.columns" + */ + destroy: function(){ + /** + * The destroy.ft.columns event is raised before its UI is removed. + * Calling preventDefault on this event will prevent the component from being destroyed. + * @event FooTable.Columns#"destroy.ft.columns" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + var self = this; + this.ft.raise('destroy.ft.columns').then(function(){ + if (!self._fromHTML) self.$header.remove(); + }); + }, + /** + * The predraw method called from within the {@link FooTable.Table#draw} method. + * @instance + * @protected + * @this FooTable.Columns + */ + predraw: function(){ + var self = this, first = true; + self.visibleColspan = 0; + self.firstVisibleIndex = 0; + self.lastVisibleIndex = 0; + self.hasHidden = false; + F.arr.each(self.array, function(col){ + col.hidden = !self.ft.breakpoints.visible(col.breakpoints); + if (!col.hidden && col.visible){ + if (first){ + self.firstVisibleIndex = col.index; + first = false; + } + self.lastVisibleIndex = col.index; + self.visibleColspan++; + } + if (col.hidden) self.hasHidden = true; + }); + self.ft.$el.toggleClass('breakpoint', self.hasHidden); + }, + /** + * Performs the actual drawing of the columns, hiding or displaying them depending on there breakpoints. + * @instance + * @protected + * @this FooTable.Columns + */ + draw: function(){ + F.arr.each(this.array, function(col){ + col.$el.css('display', (col.hidden || !col.visible ? 'none' : 'table-cell')); + }); + if (!this.showHeader && F.is.jq(this.$header.parent())){ + this.$header.detach(); + } + }, + /** + * Creates the header row for the table from the parsed column definitions. + * @instance + * @protected + * @this FooTable.Columns + */ + $create: function(){ + var self = this; + self.$header = F.is.jq(self.$header) ? self.$header : $('', {'class': 'footable-header'}); + self.$header.children('th,td').detach(); + F.arr.each(self.array, function(col){ + self.$header.append(col.$el); + }); + if (self.showHeader && !F.is.jq(self.$header.parent())){ + self.ft.$el.children('thead').append(self.$header); + } + }, + /** + * Attempts to return a {@link FooTable.Column} instance when passed the {@link FooTable.Column} instance, the {@link FooTable.Column#name} string or the {@link FooTable.Column#index} number. + * If supplied a function this will return an array by iterating all columns passing the index and column itself to the supplied callback as arguments. + * Returning true in the callback will include the column in the result. + * @instance + * @param {(FooTable.Column|string|number|function)} column - The column to retrieve. + * @returns {(Array.|FooTable.Column|null)} The column if one is found otherwise it returns NULL. + * @example + * var column = columns.get('id'); + * if (column instanceof FooTable.Column){ + * // found the "id" column + * } else { + * // no column with a name of "id" exists + * } + * // to get an array of all hidden columns + * var columns = columns.get(function(col){ + * return col.hidden; + * }); + */ + get: function(column){ + if (column instanceof F.Column) return column; + if (F.is.string(column)) return F.arr.first(this.array, function (col) { return col.name == column; }); + if (F.is.number(column)) return F.arr.first(this.array, function (col) { return col.index == column; }); + if (F.is.fn(column)) return F.arr.get(this.array, column); + return null; + }, + /** + * Takes an array of column names, index's or actual {@link FooTable.Column} and ensures that an array of only {@link FooTable.Column} is returned. + * @instance + * @param {(Array.|Array.|Array.)} columns - The array of column names, index's or {@link FooTable.Column} to check. + * @returns {Array.} + */ + ensure: function(columns){ + var self = this, result = []; + if (!F.is.array(columns)) return result; + F.arr.each(columns, function(name){ + result.push(self.get(name)); + }); + return result; + } + }); + + F.components.register('columns', F.Columns, 900); + +})(jQuery, FooTable); +(function(F){ + /** + * An array containing the column options or a jQuery promise that resolves returning the columns. The index of the definitions must match the index of each column as it should appear in the table. For more information on the options available see the {@link FooTable.Column} object. + * @type {(Array.|jQuery.Promise)} + * @default [] + * @example + * columns: [ + * { name: 'id', title: 'ID', type: 'number' }, + * { name: 'name', title: 'Name', sorted: true, direction: 'ASC' } + * { name: 'age', title: 'Age', type: 'number', breakpoints: 'xs' } + * ] + */ + F.Defaults.prototype.columns = []; + + /** + * Specifies whether or not the column headers should be displayed. + * @type {boolean} + * @default true + */ + F.Defaults.prototype.showHeader = true; +})(FooTable); +(function ($, F) { + F.Rows = F.Component.extend(/** @lends FooTable.Rows */{ + /** + * The rows class contains all the logic for handling rows. + * @constructs + * @extends FooTable.Component + * @param {FooTable.Table} table - The parent {@link FooTable.Table} this component belongs to. + * @returns {FooTable.Rows} + */ + construct: function (table) { + // call the base class constructor + this._super(table, true); + + /** + * This provides a shortcut to the {@link FooTable.Table#options} object. + * @instance + * @protected + * @type {FooTable.Table#options} + */ + this.o = table.o; + /** + * The current working array of {@link FooTable.Row} objects. + * @instance + * @protected + * @type {Array.} + * @default [] + */ + this.array = []; + /** + * The base array of rows parsed from either the DOM or the constructor options. + * The {@link FooTable.Rows#current} member is populated with a shallow clone of this array + * during the predraw operation before any core or custom components are executed. + * @instance + * @protected + * @type {Array.} + * @default [] + */ + this.all = []; + /** + * Whether or not to display a toggle in each row when it contains hidden columns. + * @type {boolean} + * @default true + */ + this.showToggle = table.o.showToggle; + /** + * The CSS selector used to filter row click events. If the event.target property matches the selector the row will be toggled. + * @type {string} + * @default "tr,td,.footable-toggle" + */ + this.toggleSelector = table.o.toggleSelector; + /** + * Specifies which column the row toggle is appended to. Supports only two values; "first" and "last" + * @type {string} + */ + this.toggleColumn = table.o.toggleColumn; + /** + * The text to display when the table has no rows. + * @type {string} + */ + this.emptyString = table.o.empty; + /** + * Whether or not the first rows details are expanded by default when displayed on a device that hides any columns. + * @type {boolean} + */ + this.expandFirst = table.o.expandFirst; + /** + * Whether or not all row details are expanded by default when displayed on a device that hides any columns. + * @type {boolean} + */ + this.expandAll = table.o.expandAll; + /** + * The jQuery object that contains the empty row control. + * @type {jQuery} + */ + this.$empty = null; + this._fromHTML = F.is.emptyArray(table.o.rows) && !F.is.promise(table.o.rows); + }, + /** + * This parses the rows from either the tables rows or the supplied options. + * @instance + * @protected + * @returns {jQuery.Promise} + */ + parse: function(){ + var self = this; + return $.Deferred(function(d){ + var $rows = self.ft.$el.children('tbody').children('tr'); + if (F.is.array(self.o.rows) && self.o.rows.length > 0){ + self.parseFinalize(d, self.o.rows); + } else if (F.is.promise(self.o.rows)){ + self.o.rows.then(function(rows){ + self.parseFinalize(d, rows); + }, function(xhr){ + d.reject(Error('Rows ajax request error: ' + xhr.status + ' (' + xhr.statusText + ')')); + }); + } else if (F.is.jq($rows)){ + self.parseFinalize(d, $rows); + $rows.detach(); + } else { + self.parseFinalize(d, []); + } + }); + }, + /** + * Used to finalize the parsing of rows it is supplied the parse deferred object which must be resolved with an array of {@link FooTable.Row} objects + * or rejected with an error. + * @instance + * @protected + * @param {jQuery.Deferred} deferred - The deferred object used for parsing. + * @param {(Array.|jQuery)} rows - An array of row values and options or the jQuery object containing all rows. + */ + parseFinalize: function(deferred, rows){ + var self = this, result = $.map(rows, function(r){ + return new F.Row(self.ft, self.ft.columns.array, r); + }); + deferred.resolve(result); + }, + /** + * The columns preinit method is used to parse and check the column options supplied from both static content and through the constructor. + * @instance + * @protected + * @param {object} data - The jQuery data object from the root table element. + * @fires FooTable.Rows#"preinit.ft.rows" + */ + preinit: function(data){ + var self = this; + /** + * The preinit.ft.rows event is raised before any UI is created and provides the tables jQuery data object for additional options parsing. + * Calling preventDefault on this event will disable the entire plugin. + * @event FooTable.Rows#"preinit.ft.rows" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {object} data - The jQuery data object of the table raising the event. + */ + return self.ft.raise('preinit.ft.rows', [data]).then(function(){ + return self.parse().then(function(rows){ + self.all = rows; + self.array = self.all.slice(0); + self.showToggle = F.is.boolean(data.showToggle) ? data.showToggle : self.showToggle; + self.toggleSelector = F.is.string(data.toggleSelector) ? data.toggleSelector : self.toggleSelector; + self.toggleColumn = F.is.string(data.toggleColumn) ? data.toggleColumn : self.toggleColumn; + if (self.toggleColumn != "first" && self.toggleColumn != "last") self.toggleColumn = "first"; + self.emptyString = F.is.string(data.empty) ? data.empty : self.emptyString; + self.expandFirst = F.is.boolean(data.expandFirst) ? data.expandFirst : self.expandFirst; + self.expandAll = F.is.boolean(data.expandAll) ? data.expandAll : self.expandAll; + }); + }); + }, + /** + * Initializes the rows class using the supplied table and options. + * @instance + * @protected + * @fires FooTable.Rows#"init.ft.rows" + */ + init: function () { + var self = this; + /** + * The init.ft.rows event is raised after the the rows are parsed from either the DOM or the options. + * Calling preventDefault on this event will disable the entire plugin. + * @event FooTable.Rows#"init.ft.rows" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} instance - The instance of the plugin raising the event. + * @param {Array.} rows - The array of {@link FooTable.Row} objects parsed from the DOM or the options. + */ + return self.ft.raise('init.ft.rows', [self.all]).then(function(){ + self.$create(); + }); + }, + /** + * Destroys the rows component removing any UI generated from the table. + * @instance + * @protected + * @fires FooTable.Rows#"destroy.ft.rows" + */ + destroy: function(){ + /** + * The destroy.ft.rows event is raised before its UI is removed. + * Calling preventDefault on this event will prevent the component from being destroyed. + * @event FooTable.Rows#"destroy.ft.rows" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + var self = this; + this.ft.raise('destroy.ft.rows').then(function(){ + F.arr.each(self.array, function(row){ + row.predraw(!self._fromHTML); + }); + self.all = self.array = []; + }); + }, + /** + * Performs the predraw operations that are required including creating the shallow clone of the {@link FooTable.Rows#array} to work with. + * @instance + * @protected + */ + predraw: function(){ + F.arr.each(this.array, function(row){ + row.predraw(); + }); + this.array = this.all.slice(0); + }, + $create: function(){ + this.$empty = $('', { 'class': 'footable-empty' }).append($('', {'class': 'footable-filtering'}).prependTo(self.ft.$el.children('thead')); + self.$cell = $(' + * query = "Dave AND Mary" - "Dave" is the left side of the query. + * query = "Dave AND Mary OR John" - "Dave and Mary" is the left side of the query. + */ + this.left = null; + /** + * The right side of the query if one exists. OR takes precedence over AND. + * @type {FooTable.Query} + * @example + * query = "Dave AND Mary" - "Mary" is the right side of the query. + * query = "Dave AND Mary OR John" - "John" is the right side of the query. + */ + this.right = null; + /** + * The parsed parts of the query. This contains the information used to actually perform a match against a string. + * @type {Array} + */ + this.parts = []; + /** + * The type of operand to apply to the results of the individual parts of the query. + * @type {string} + */ + this.operator = null; + this.val(query); + }, + /** + * Gets or sets the value for the query. During set the value is parsed setting all properties as required. + * @param {string} [value] - If supplied the value to set for this query. + * @returns {(string|undefined)} + */ + val: function(value){ + // get + if (F.is.emptyString(value)) return this._value; + + // set + if (F.is.emptyString(this._original)) this._original = value; + else if (this._original == value) return; + + this._value = value; + this._parse(); + }, + /** + * Tests the supplied string against the query. + * @param {string} str - The string to test. + * @returns {boolean} + */ + match: function(str){ + if (F.is.emptyString(this.operator) || this.operator === 'OR') + return this._left(str, false) || this._match(str, false) || this._right(str, false); + if (this.operator === 'AND') + return this._left(str, true) && this._match(str, true) && this._right(str, true); + }, + /** + * Matches this queries parts array against the supplied string. + * @param {string} str - The string to test. + * @param {boolean} def - The default value to return based on the operand. + * @returns {boolean} + * @private + */ + _match: function(str, def){ + var self = this, result = false, empty = F.is.emptyString(str); + if (F.is.emptyArray(self.parts) && self.left instanceof F.Query) return def; + if (F.is.emptyArray(self.parts)) return result; + if (self.space === 'OR'){ + // with OR we give the str every part to test and if any match it is a success, we do exit early if a negated match occurs + F.arr.each(self.parts, function(p){ + if (p.empty && empty){ + result = true; + if (p.negate){ + result = false; + return result; + } + } else { + var match = (p.exact ? F.str.containsExact : F.str.contains)(str, p.query, self.ignoreCase); + if (match && !p.negate) result = true; + if (match && p.negate) { + result = false; + return result; + } + } + }); + } else { + // otherwise with AND we check until the first failure and then exit + result = true; + F.arr.each(self.parts, function(p){ + if (p.empty){ + if ((!empty && !p.negate) || (empty && p.negate)) result = false; + return result; + } else { + var match = (p.exact ? F.str.containsExact : F.str.contains)(str, p.query, self.ignoreCase); + if ((!match && !p.negate) || (match && p.negate)) result = false; + return result; + } + }); + } + return result; + }, + /** + * Matches the left side of the query if one exists with the supplied string. + * @param {string} str - The string to test. + * @param {boolean} def - The default value to return based on the operand. + * @returns {boolean} + * @private + */ + _left: function(str, def){ + return (this.left instanceof F.Query) ? this.left.match(str) : def; + }, + /** + * Matches the right side of the query if one exists with the supplied string. + * @param {string} str - The string to test. + * @param {boolean} def - The default value to return based on the operand. + * @returns {boolean} + * @private + */ + _right: function(str, def){ + return (this.right instanceof F.Query) ? this.right.match(str) : def; + }, + /** + * Parses the private {@link FooTable.Query#_value} property and populates the object. + * @private + */ + _parse: function(){ + if (F.is.emptyString(this._value)) return; + // OR takes precedence so test for it first + if (/\sOR\s/.test(this._value)){ + // we have an OR so split the value on the first occurrence of OR to get the left and right sides of the statement + this.operator = 'OR'; + var or = this._value.split(/(?:\sOR\s)(.*)?/); + this.left = new F.Query(or[0], this.space, this.connectors, this.ignoreCase); + this.right = new F.Query(or[1], this.space, this.connectors, this.ignoreCase); + } else if (/\sAND\s/.test(this._value)) { + // there are no more OR's so start with AND + this.operator = 'AND'; + var and = this._value.split(/(?:\sAND\s)(.*)?/); + this.left = new F.Query(and[0], this.space, this.connectors, this.ignoreCase); + this.right = new F.Query(and[1], this.space, this.connectors, this.ignoreCase); + } else { + // we have no more statements to parse so set the parts array by parsing each part of the remaining query + var self = this; + this.parts = F.arr.map(this._value.match(/(?:[^\s"]+|"[^"]*")+/g), function(str){ + return self._part(str); + }); + } + }, + /** + * Parses a single part of a query into an object to use during matching. + * @param {string} str - The string representation of the part. + * @returns {{query: string, negate: boolean, phrase: boolean, exact: boolean}} + * @private + */ + _part: function(str){ + var p = { + query: str, + negate: false, + phrase: false, + exact: false, + empty: false + }; + // support for NEGATE operand - (minus sign). Remove this first so we can get onto phrase checking + if (F.str.startsWith(p.query, '-')){ + p.query = F.str.from(p.query, '-'); + p.negate = true; + } + // support for PHRASES (exact matches) + if (/^"(.*?)"$/.test(p.query)){ // if surrounded in quotes strip them and nothing else + p.query = p.query.replace(/^"(.*?)"$/, '$1'); + p.phrase = true; + p.exact = true; + } else if (this.connectors && /(?:\w)+?([-_\+\.])(?:\w)+?/.test(p.query)) { // otherwise replace supported phrase connectors (-_+.) with spaces + p.query = p.query.replace(/(?:\w)+?([-_\+\.])(?:\w)+?/g, function(match, p1){ + return match.replace(p1, ' '); + }); + p.phrase = true; + } + p.empty = p.phrase && F.is.emptyString(p.query); + return p; + } + }); + +})(FooTable); +(function(F){ + + /** + * The value used by the filtering component during filter operations. Must be a string and can be set using the data-filter-value attribute on the cell itself. + * If this is not supplied it is set to the result of the toString method called on the value for the cell. Added by the {@link FooTable.Filtering} component. + * @type {string} + * @default null + */ + F.Cell.prototype.filterValue = null; + + // this is used to define the filtering specific properties on cell creation + F.Cell.prototype.__filtering_define__ = function(valueOrElement){ + this.filterValue = this.column.filterValue.call(this.column, valueOrElement); + }; + + // this is used to update the filterValue property whenever the cell value is changed + F.Cell.prototype.__filtering_val__ = function(value){ + if (F.is.defined(value)){ + // set only + this.filterValue = this.column.filterValue.call(this.column, value); + } + }; + + // overrides the public define method and replaces it with our own + F.Cell.extend('define', function(valueOrElement){ + this._super(valueOrElement); + this.__filtering_define__(valueOrElement); + }); + // overrides the public val method and replaces it with our own + F.Cell.extend('val', function(value, redraw, redrawSelf){ + var val = this._super(value, redraw, redrawSelf); + this.__filtering_val__(value); + return val; + }); +})(FooTable); +(function($, F){ + /** + * Whether or not the column can be used during filtering. Added by the {@link FooTable.Filtering} component. + * @type {boolean} + * @default true + */ + F.Column.prototype.filterable = true; + + /** + * This is supplied either the cell value or jQuery object to parse. A string value must be returned from this method and will be used during filtering operations. + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {string} + * @this FooTable.Column + */ + F.Column.prototype.filterValue = function(valueOrElement){ + // if we have an element or a jQuery object use jQuery to get the value + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ + var data = $(valueOrElement).data('filterValue'); + return F.is.defined(data) ? ''+data : $(valueOrElement).text(); + } + // if options are supplied with the value + if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ + if (F.is.string(valueOrElement.options.filterValue)) return valueOrElement.options.filterValue; + if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value; + } + if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement+''; // use the native toString of the value + return ''; // otherwise we have no value so return an empty string + }; + + // this is used to define the filtering specific properties on column creation + F.Column.prototype.__filtering_define__ = function(definition){ + this.filterable = F.is.boolean(definition.filterable) ? definition.filterable : this.filterable; + this.filterValue = F.checkFnValue(this, definition.filterValue, this.filterValue); + }; + + // overrides the public define method and replaces it with our own + F.Column.extend('define', function(definition){ + this._super(definition); // call the base so we don't have to redefine any previously set properties + this.__filtering_define__(definition); // then call our own + }); +})(jQuery, FooTable); +(function(F){ + /** + * An object containing the filtering options for the plugin. Added by the {@link FooTable.Filtering} component. + * @type {object} + * @prop {boolean} enabled=false - Whether or not to allow filtering on the table. + * @prop {({name: string, query: (string|FooTable.Query), columns: (Array.|Array.|Array.)}|Array.)} filters - The filters to apply to the current {@link FooTable.Rows#array}. + * @prop {number} delay=1200 - The delay in milliseconds before the query is auto applied after a change (any value equal to or less than zero will disable this). + * @prop {number} min=1 - The minimum number of characters allowed in the search input before it is auto applied. + * @prop {string} space="AND" - Specifies how whitespace in a filter query is handled. + * @prop {string} placeholder="Search" - The string used as the placeholder for the search input. + * @prop {string} dropdownTitle=null - The title to display at the top of the search input column select. + * @prop {string} position="right" - The string used to specify the alignment of the search input. + * @prop {string} connectors=true - Whether or not to replace phrase connectors (+.-_) with space before executing the query. + * @prop {boolean} ignoreCase=true - Whether or not ignore case when matching. + * @prop {boolean} exactMatch=false - Whether or not search queries are treated as phrases when matching. + * @prop {boolean} focus=true - Whether or not to focus the search input after the search/clear button is clicked or after auto applying the search input query. + * @prop {string} container=null - A selector specifying where to place the filtering components form, if null the form is displayed within a row in the head of the table. + */ + F.Defaults.prototype.filtering = { + enabled: false, + filters: [], + delay: 1200, + min: 1, + space: 'AND', + placeholder: 'Search', + dropdownTitle: null, + position: 'right', + connectors: true, + ignoreCase: true, + exactMatch: false, + focus: true, + container: null + }; +})(FooTable); +(function(F){ + /** + * Checks if the row is filtered using the supplied filters. + * @this FooTable.Row + * @param {Array.} filters - The filters to apply. + * @returns {boolean} + */ + F.Row.prototype.filtered = function(filters){ + var result = true, self = this; + F.arr.each(filters, function(f){ + if ((result = f.matchRow(self)) == false) return false; + }); + return result; + }; +})(FooTable); +(function($, F){ + + F.Sorter = F.Class.extend(/** @lends FooTable.Sorter */{ + /** + * The sorter object contains the column and direction to sort by. + * @constructs + * @extends FooTable.Class + * @param {FooTable.Column} column - The column to sort. + * @param {string} direction - The direction to sort by. + * @returns {FooTable.Sorter} + */ + construct: function(column, direction){ + /** + * The column to sort. + * @type {FooTable.Column} + */ + this.column = column; + /** + * The direction to sort by. + * @type {string} + */ + this.direction = direction; + } + }); + +})(jQuery, FooTable); +(function ($, F) { + F.Sorting = F.Component.extend(/** @lends FooTable.Sorting */{ + /** + * The sorting component adds a small sort button to specified column headers allowing users to sort those columns in the table. + * @constructs + * @extends FooTable.Component + * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. + * @returns {FooTable.Sorting} + */ + construct: function (table) { + // call the constructor of the base class + this._super(table, table.o.sorting.enabled); + + /* PROTECTED */ + /** + * This provides a shortcut to the {@link FooTable.Table#options}.[sorting]{@link FooTable.Defaults#sorting} object. + * @instance + * @protected + * @type {object} + */ + this.o = table.o.sorting; + /** + * The current sorted column. + * @instance + * @type {FooTable.Column} + */ + this.column = null; + /** + * Whether or not to allow sorting to occur, should be set using the {@link FooTable.Sorting#toggleAllowed} method. + * @instance + * @type {boolean} + */ + this.allowed = true; + /** + * The initial sort state of the table, this value is used for determining if the sorting has occurred or to reset the state to default. + * @instance + * @type {{isset: boolean, rows: Array., column: string, direction: ?string}} + */ + this.initial = null; + }, + + /* PROTECTED */ + /** + * Checks the supplied data and options for the sorting component. + * @instance + * @protected + * @param {object} data - The jQuery data object from the parent table. + * @fires FooTable.Sorting#"preinit.ft.sorting" + * @this FooTable.Sorting + */ + preinit: function(data){ + var self = this; + /** + * The preinit.ft.sorting event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. + * Calling preventDefault on this event will disable the component. + * @event FooTable.Sorting#"preinit.ft.sorting" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {object} data - The jQuery data object of the table raising the event. + */ + this.ft.raise('preinit.ft.sorting', [data]).then(function(){ + if (self.ft.$el.hasClass('footable-sorting')) + self.enabled = true; + self.enabled = F.is.boolean(data.sorting) + ? data.sorting + : self.enabled; + if (!self.enabled) return; + self.column = F.arr.first(self.ft.columns.array, function(col){ return col.sorted; }); + }, function(){ + self.enabled = false; + }); + }, + /** + * Initializes the sorting component for the plugin using the supplied table and options. + * @instance + * @protected + * @fires FooTable.Sorting#"init.ft.sorting" + * @this FooTable.Sorting + */ + init: function () { + /** + * The init.ft.sorting event is raised before its UI is generated. + * Calling preventDefault on this event will disable the component. + * @event FooTable.Sorting#"init.ft.sorting" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + var self = this; + this.ft.raise('init.ft.sorting').then(function(){ + if (!self.initial){ + var isset = !!self.column; + self.initial = { + isset: isset, + // grab a shallow copy of the rows array prior to sorting - allows us to reset without an initial sort + rows: self.ft.rows.all.slice(0), + // if there is a sorted column store its name and direction + column: isset ? self.column.name : null, + direction: isset ? self.column.direction : null + } + } + F.arr.each(self.ft.columns.array, function(col){ + if (col.sortable){ + col.$el.addClass('footable-sortable').append($('', {'class': 'fooicon fooicon-sort'})); + } + }); + self.ft.$el.on('click.footable', '.footable-sortable', { self: self }, self._onSortClicked); + }, function(){ + self.enabled = false; + }); + }, + /** + * Destroys the sorting component removing any UI generated from the table. + * @instance + * @protected + * @fires FooTable.Sorting#"destroy.ft.sorting" + */ + destroy: function () { + /** + * The destroy.ft.sorting event is raised before its UI is removed. + * Calling preventDefault on this event will prevent the component from being destroyed. + * @event FooTable.Sorting#"destroy.ft.sorting" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + var self = this; + this.ft.raise('destroy.ft.paging').then(function(){ + self.ft.$el.off('click.footable', '.footable-sortable', self._onSortClicked); + self.ft.$el.children('thead').children('tr.footable-header') + .children('.footable-sortable').removeClass('footable-sortable footable-asc footable-desc') + .find('span.fooicon').remove(); + }); + }, + /** + * Performs the actual sorting against the {@link FooTable.Rows#current} array. + * @instance + * @protected + */ + predraw: function () { + if (!this.column) return; + var self = this, col = self.column; + self.ft.rows.array.sort(function (a, b) { + return col.direction == 'DESC' + ? col.sorter(b.cells[col.index].sortValue, a.cells[col.index].sortValue) + : col.sorter(a.cells[col.index].sortValue, b.cells[col.index].sortValue); + }); + }, + /** + * Updates the sorting UI setting the state of the sort buttons. + * @instance + * @protected + */ + draw: function () { + if (!this.column) return; + var self = this, + $sortable = self.ft.$el.find('thead > tr > .footable-sortable'), + $active = self.column.$el; + + $sortable.removeClass('footable-asc footable-desc').children('.fooicon').removeClass('fooicon-sort fooicon-sort-asc fooicon-sort-desc'); + $sortable.not($active).children('.fooicon').addClass('fooicon-sort'); + $active.addClass(self.column.direction == 'DESC' ? 'footable-desc' : 'footable-asc') + .children('.fooicon').addClass(self.column.direction == 'DESC' ? 'fooicon-sort-desc' : 'fooicon-sort-asc'); + }, + + /* PUBLIC */ + /** + * Sets the sorting options and calls the {@link FooTable.Table#draw} method to perform the actual sorting. + * @instance + * @param {(string|number|FooTable.Column)} column - The column name, index or the actual {@link FooTable.Column} object to sort by. + * @param {string} [direction="ASC"] - The direction to sort by, either ASC or DESC. + * @returns {jQuery.Promise} + * @fires FooTable.Sorting#"before.ft.sorting" + * @fires FooTable.Sorting#"after.ft.sorting" + */ + sort: function(column, direction){ + return this._sort(column, direction); + }, + /** + * Toggles whether or not sorting is currently allowed. + * @param {boolean} [state] - You can optionally specify the state you want it to be, if not supplied the current value is flipped. + */ + toggleAllowed: function(state){ + state = F.is.boolean(state) ? state : !this.allowed; + this.allowed = state; + this.ft.$el.toggleClass('footable-sorting-disabled', !this.allowed); + }, + /** + * Checks whether any sorting has occurred for the table. + * @returns {boolean} + */ + hasChanged: function(){ + return !(!this.initial || !this.column || + (this.column.name === this.initial.column && + (this.column.direction === this.initial.direction || (this.initial.direction === null && this.column.direction === 'ASC'))) + ); + }, + /** + * Resets the table sorting to the initial state recorded in the components init method. + */ + reset: function(){ + if (!!this.initial){ + if (this.initial.isset){ + // if the initial value specified a column, sort by it + this.sort(this.initial.column, this.initial.direction); + } else { + // if there was no initial column then we need to reset the rows to there original order + if (!!this.column){ + // if there is a currently sorted column remove the asc/desc classes and set it to null. + this.column.$el.removeClass('footable-asc footable-desc'); + this.column = null; + } + // replace the current all rows array with the one stored in the initial value + this.ft.rows.all = this.initial.rows; + // force the table to redraw itself using the updated rows array + this.ft.draw(); + } + } + }, + + /* PRIVATE */ + /** + * Performs the required steps to handle sorting including the raising of the {@link FooTable.Sorting#"before.ft.sorting"} and {@link FooTable.Sorting#"after.ft.sorting"} events. + * @instance + * @private + * @param {(string|number|FooTable.Column)} column - The column name, index or the actual {@link FooTable.Column} object to sort by. + * @param {string} [direction="ASC"] - The direction to sort by, either ASC or DESC. + * @returns {jQuery.Promise} + * @fires FooTable.Sorting#"before.ft.sorting" + * @fires FooTable.Sorting#"after.ft.sorting" + */ + _sort: function(column, direction){ + if (!this.allowed) return $.Deferred().reject('sorting disabled'); + var self = this; + var sorter = new F.Sorter(self.ft.columns.get(column), F.Sorting.dir(direction)); + /** + * The before.ft.sorting event is raised before a sort is applied and allows listeners to modify the sorter or cancel it completely by calling preventDefault on the jQuery.Event object. + * @event FooTable.Sorting#"before.ft.sorting" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Sorter} sorter - The sorter that is about to be applied. + */ + return self.ft.raise('before.ft.sorting', [sorter]).then(function(){ + F.arr.each(self.ft.columns.array, function(col){ + if (col != self.column) col.direction = null; + }); + self.column = self.ft.columns.get(sorter.column); + if (self.column) self.column.direction = F.Sorting.dir(sorter.direction); + return self.ft.draw().then(function(){ + /** + * The after.ft.sorting event is raised after a sorter has been applied. + * @event FooTable.Sorting#"after.ft.sorting" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Sorter} sorter - The sorter that has been applied. + */ + self.ft.raise('after.ft.sorting', [sorter]); + }); + }); + }, + /** + * Handles the sort button clicked event. + * @instance + * @private + * @param {jQuery.Event} e - The event object for the event. + */ + _onSortClicked: function (e) { + var self = e.data.self, $header = $(this).closest('th,td'), + direction = $header.is('.footable-asc, .footable-desc') + ? ($header.hasClass('footable-desc') ? 'ASC' : 'DESC') + : 'ASC'; + self._sort($header.index(), direction); + } + }); + + /** + * Checks the supplied string is a valid direction and if not returns ASC as default. + * @static + * @protected + * @param {string} str - The string to check. + */ + F.Sorting.dir = function(str){ + return F.is.string(str) && (str == 'ASC' || str == 'DESC') ? str : 'ASC'; + }; + + F.components.register('sorting', F.Sorting, 600); + +})(jQuery, FooTable); +(function(F){ + + /** + * The value used by the sorting component during sort operations. Can be set using the data-sort-value attribute on the cell itself. + * If this is not supplied it is set to the result of the toString method called on the value for the cell. Added by the {@link FooTable.Sorting} component. + * @type {string} + * @default null + */ + F.Cell.prototype.sortValue = null; + + // this is used to define the sorting specific properties on cell creation + F.Cell.prototype.__sorting_define__ = function(valueOrElement){ + this.sortValue = this.column.sortValue.call(this.column, valueOrElement); + }; + + // this is used to update the sortValue property whenever the cell value is changed + F.Cell.prototype.__sorting_val__ = function(value){ + if (F.is.defined(value)){ + // set only + this.sortValue = this.column.sortValue.call(this.column, value); + } + }; + + // overrides the public define method and replaces it with our own + F.Cell.extend('define', function(valueOrElement){ + this._super(valueOrElement); + this.__sorting_define__(valueOrElement); + }); + // overrides the public val method and replaces it with our own + F.Cell.extend('val', function(value, redraw, redrawSelf){ + var val = this._super(value, redraw, redrawSelf); + this.__sorting_val__(value); + return val; + }); +})(FooTable); +(function($, F){ + /** + * The direction to sort if the {@link FooTable.Column#sorted} property is set to true. Can be "ASC", "DESC" or NULL. Added by the {@link FooTable.Sorting} component. + * @type {string} + * @default null + */ + F.Column.prototype.direction = null; + /** + * Whether or not the column can be sorted. Added by the {@link FooTable.Sorting} component. + * @type {boolean} + * @default true + */ + F.Column.prototype.sortable = true; + /** + * Whether or not the column is sorted. Added by the {@link FooTable.Sorting} component. + * @type {boolean} + * @default false + */ + F.Column.prototype.sorted = false; + + /** + * This is supplied two values from the column for a comparison to be made and the result returned. Added by the {@link FooTable.Sorting} component. + * @param {*} a - The first value to be compared. + * @param {*} b - The second value to compare to the first. + * @returns {number} + * @example + * "sorter": function(a, b){ + * if (a is less than b by some ordering criterion) { + * return -1; + * } + * if (a is greater than b by the ordering criterion) { + * return 1; + * } + * // a must be equal to b + * return 0; + * } + */ + F.Column.prototype.sorter = function(a, b){ + if (typeof a === 'string') a = a.toLowerCase(); + if (typeof b === 'string') b = b.toLowerCase(); + if (a === b) return 0; + if (a < b) return -1; + return 1; + }; + + /** + * This is supplied either the cell value or jQuery object to parse. A value must be returned from this method and will be used during sorting operations. + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {*} + * @this FooTable.Column + */ + F.Column.prototype.sortValue = function(valueOrElement){ + // if we have an element or a jQuery object use jQuery to get the value + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ + var data = $(valueOrElement).data('sortValue'); + return F.is.defined(data) ? data : this.parser(valueOrElement); + } + // if options are supplied with the value + if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ + if (F.is.string(valueOrElement.options.sortValue)) return valueOrElement.options.sortValue; + if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value; + } + if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement; + return null; + }; + + // this is used to define the sorting specific properties on column creation + F.Column.prototype.__sorting_define__ = function(definition){ + this.sorter = F.checkFnValue(this, definition.sorter, this.sorter); + this.direction = F.is.type(definition.direction, 'string') ? F.Sorting.dir(definition.direction) : null; + this.sortable = F.is.boolean(definition.sortable) ? definition.sortable : true; + this.sorted = F.is.boolean(definition.sorted) ? definition.sorted : false; + this.sortValue = F.checkFnValue(this, definition.sortValue, this.sortValue); + }; + + // overrides the public define method and replaces it with our own + F.Column.extend('define', function(definition){ + this._super(definition); + this.__sorting_define__(definition); + }); + +})(jQuery, FooTable); +(function(F){ + /** + * An object containing the sorting options for the plugin. Added by the {@link FooTable.Sorting} component. + * @type {object} + * @prop {boolean} enabled=false - Whether or not to allow sorting on the table. + */ + F.Defaults.prototype.sorting = { + enabled: false + }; +})(FooTable); +(function($, F){ + + F.HTMLColumn.extend('__sorting_define__', function(definition){ + this._super(definition); + this.sortUse = F.is.string(definition.sortUse) && $.inArray(definition.sortUse, ['html','text']) !== -1 ? definition.sortUse : 'html'; + }); + + /** + * This is supplied either the cell value or jQuery object to parse. A value must be returned from this method and will be used during sorting operations. + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {*} + * @this FooTable.HTMLColumn + */ + F.HTMLColumn.prototype.sortValue = function(valueOrElement){ + // if we have an element or a jQuery object use jQuery to get the data value or pass it off to the parser + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ + var data = $(valueOrElement).data('sortValue'); + return F.is.defined(data) ? data : this.parser(valueOrElement); + } + // if options are supplied with the value + if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ + if (F.is.string(valueOrElement.options.sortValue)) return valueOrElement.options.sortValue; + if (F.is.defined(valueOrElement.value)) valueOrElement = valueOrElement.value; + } + if (F.is.defined(valueOrElement) && valueOrElement != null) return valueOrElement; + return null; + }; + +})(jQuery, FooTable); +(function($, F){ + + /** + * This is supplied either the cell value or jQuery object to parse. A value must be returned from this method and will be used during sorting operations. + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {*} + */ + F.NumberColumn.prototype.sortValue = function(valueOrElement){ + // if we have an element or a jQuery object use jQuery to get the data value or pass it off to the parser + if (F.is.element(valueOrElement) || F.is.jq(valueOrElement)){ + var data = $(valueOrElement).data('sortValue'); + return F.is.number(data) ? data : this.parser(valueOrElement); + } + // if options are supplied with the value + if (F.is.hash(valueOrElement) && F.is.hash(valueOrElement.options)){ + if (F.is.string(valueOrElement.options.sortValue)) return this.parser(valueOrElement); + if (F.is.number(valueOrElement.options.sortValue)) return valueOrElement.options.sortValue; + if (F.is.number(valueOrElement.value)) return valueOrElement.value; + } + if (F.is.string(valueOrElement)) return this.parser(valueOrElement); + if (F.is.number(valueOrElement)) return valueOrElement; + return null; + }; + +})(jQuery, FooTable); +(function(F){ + /** + * Sort the table using the specified column and direction. Added by the {@link FooTable.Sorting} component. + * @instance + * @param {(string|number|FooTable.Column)} column - The column name, index or the actual {@link FooTable.Column} object to sort by. + * @param {string} [direction="ASC"] - The direction to sort by, either ASC or DESC. + * @returns {jQuery.Promise} + * @fires FooTable.Sorting#"change.ft.sorting" + * @fires FooTable.Sorting#"changed.ft.sorting" + * @see FooTable.Sorting#sort + */ + F.Table.prototype.sort = function(column, direction){ + return this.use(F.Sorting).sort(column, direction); + }; +})(FooTable); +(function($, F){ + + F.Pager = F.Class.extend(/** @lends FooTable.Pager */{ + /** + * The pager object contains the page number and direction to page to. + * @constructs + * @extends FooTable.Class + * @param {number} total - The total number of pages available. + * @param {number} current - The current page number. + * @param {number} size - The number of rows per page. + * @param {number} page - The page number to goto. + * @param {boolean} forward - A boolean indicating the direction of paging, TRUE = forward, FALSE = back. + * @returns {FooTable.Pager} + */ + construct: function(total, current, size, page, forward){ + /** + * The total number of pages available. + * @type {number} + */ + this.total = total; + /** + * The current page number. + * @type {number} + */ + this.current = current; + /** + * The number of rows per page. + * @type {number} + */ + this.size = size; + /** + * The page number to goto. + * @type {number} + */ + this.page = page; + /** + * A boolean indicating the direction of paging, TRUE = forward, FALSE = back. + * @type {boolean} + */ + this.forward = forward; + } + }); + +})(jQuery, FooTable); +(function($, F){ + F.Paging = F.Component.extend(/** @lends FooTable.Paging */{ + /** + * The paging component adds a pagination control to the table allowing users to navigate table rows via pages. + * @constructs + * @extends FooTable.Component + * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. + * @returns {FooTable.Filtering} + */ + construct: function(table){ + // call the base constructor + this._super(table, table.o.paging.enabled); + + /* PROTECTED */ + /** + * An object containing the strings used by the paging buttons. + * @type {{ first: string, prev: string, next: string, last: string }} + */ + this.strings = table.o.paging.strings; + + /* PUBLIC */ + /** + * The current page number to display. + * @instance + * @type {number} + */ + this.current = table.o.paging.current; + /** + * The number of rows to display per page. + * @instance + * @type {number} + */ + this.size = table.o.paging.size; + /** + * The maximum number of page links to display at once. + * @instance + * @type {number} + */ + this.limit = table.o.paging.limit; + /** + * The position of the pagination control within the paging rows cell. + * @instance + * @type {string} + */ + this.position = table.o.paging.position; + /** + * The format string used to generate the text displayed under the pagination control. + * @instance + * @type {string} + */ + this.countFormat = table.o.paging.countFormat; + /** + * A selector specifying where to place the paging components UI, if null the UI is displayed within a row in the foot of the table. + * @instance + * @type {string} + */ + this.container = table.o.paging.container; + /** + * The total number of pages. + * @instance + * @type {number} + */ + this.total = -1; + /** + * The number of rows in the {@link FooTable.Rows#array} before paging is applied. + * @instance + * @type {number} + */ + this.totalRows = 0; + /** + * A number indicating the previous page displayed. + * @instance + * @type {number} + */ + this.previous = -1; + /** + * The count string generated using the {@link FooTable.Filtering#countFormat} option. This value is only set after the first call to the {@link FooTable.Filtering#predraw} method. + * @instance + * @type {string} + */ + this.formattedCount = null; + /** + * The jQuery object of the element containing the entire paging UI. + * @instance + * @type {jQuery} + */ + this.$container = null; + /** + * The jQuery object of the element wrapping all the paging UI elements. + * @instance + * @type {jQuery} + */ + this.$wrapper = null; + /** + + * The jQuery row object that contains all the paging specific elements. + * @instance + * @type {jQuery} + */ + this.$row = null; + /** + * The jQuery cell object that contains the pagination control and total count. + * @instance + * @type {jQuery} + */ + this.$cell = null; + /** + * The jQuery object that contains the links for the pagination control. + * @instance + * @type {jQuery} + */ + this.$pagination = null; + /** + * The jQuery object that contains the row count. + * @instance + * @type {jQuery} + */ + this.$count = null; + /** + * Whether or not the pagination row is detached from the table. + * @instance + * @type {boolean} + */ + this.detached = true; + + /* PRIVATE */ + /** + * Used to hold the number of page links created. + * @instance + * @type {number} + * @private + */ + this._createdLinks = 0; + }, + + /* PROTECTED */ + /** + * Checks the supplied data and options for the paging component. + * @instance + * @protected + * @param {object} data - The jQuery data object from the parent table. + * @fires FooTable.Paging#"preinit.ft.paging" + */ + preinit: function(data){ + var self = this; + /** + * The preinit.ft.paging event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. + * Calling preventDefault on this event will disable the component. + * @event FooTable.Paging#"preinit.ft.paging" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {object} data - The jQuery data object of the table raising the event. + */ + this.ft.raise('preinit.ft.paging', [data]).then(function(){ + if (self.ft.$el.hasClass('footable-paging')) + self.enabled = true; + self.enabled = F.is.boolean(data.paging) + ? data.paging + : self.enabled; + + if (!self.enabled) return; + + self.size = F.is.number(data.pagingSize) + ? data.pagingSize + : self.size; + + self.current = F.is.number(data.pagingCurrent) + ? data.pagingCurrent + : self.current; + + self.limit = F.is.number(data.pagingLimit) + ? data.pagingLimit + : self.limit; + + if (self.ft.$el.hasClass('footable-paging-left')) + self.position = 'left'; + if (self.ft.$el.hasClass('footable-paging-center')) + self.position = 'center'; + if (self.ft.$el.hasClass('footable-paging-right')) + self.position = 'right'; + + self.position = F.is.string(data.pagingPosition) + ? data.pagingPosition + : self.position; + + self.countFormat = F.is.string(data.pagingCountFormat) + ? data.pagingCountFormat + : self.countFormat; + + self.container = F.is.string(data.pagingContainer) + ? data.pagingContainer + : self.container; + + self.total = Math.ceil(self.ft.rows.all.length / self.size); + }, function(){ + self.enabled = false; + }); + }, + /** + * Initializes the paging component for the plugin using the supplied table and options. + * @instance + * @protected + * @fires FooTable.Paging#"init.ft.paging" + */ + init: function(){ + /** + * The init.ft.paging event is raised before its UI is generated. + * Calling preventDefault on this event will disable the component. + * @event FooTable.Paging#"init.ft.paging" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + var self = this; + this.ft.raise('init.ft.paging').then(function(){ + self.$create(); + }, function(){ + self.enabled = false; + }); + }, + /** + * Destroys the paging component removing any UI generated from the table. + * @instance + * @protected + * @fires FooTable.Paging#"destroy.ft.paging" + */ + destroy: function () { + /** + * The destroy.ft.paging event is raised before its UI is removed. + * Calling preventDefault on this event will prevent the component from being destroyed. + * @event FooTable.Paging#"destroy.ft.paging" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + var self = this; + this.ft.raise('destroy.ft.paging').then(function(){ + self.ft.$el.removeClass('footable-paging') + .find('tfoot > tr.footable-paging').remove(); + self.detached = true; + self._createdLinks = 0; + }); + }, + /** + * Performs the actual paging against the {@link FooTable.Rows#current} array removing all rows that are not on the current visible page. + * @instance + * @protected + */ + predraw: function(){ + this.total = Math.ceil(this.ft.rows.array.length / this.size); + this.current = this.current > this.total ? this.total : (this.current < 1 ? 1 : this.current); + this.totalRows = this.ft.rows.array.length; + if (this.totalRows > this.size){ + this.ft.rows.array = this.ft.rows.array.splice((this.current - 1) * this.size, this.size); + } + this.formattedCount = this.format(this.countFormat); + }, + /** + * Updates the paging UI setting the state of the pagination control. + * @instance + * @protected + */ + draw: function(){ + if (this.total <= 1){ + if (!this.detached){ + if (this.$row){ + this.$row.detach(); + } else { + this.$wrapper.detach(); + } + this.detached = true; + } + } else { + if (this.detached){ + if (this.$row){ + var $tfoot = this.ft.$el.children('tfoot'); + if ($tfoot.length == 0){ + $tfoot = $(''); + this.ft.$el.append($tfoot); + } + this.$row.appendTo($tfoot); + } else { + this.$wrapper.appendTo(this.$container); + } + this.detached = false; + } + if (F.is.jq(this.$cell)){ + this.$cell.attr('colspan', this.ft.columns.visibleColspan); + } + this._createLinks(); + this._setVisible(this.current, this.current > this.previous); + this._setNavigation(true); + this.$count.text(this.formattedCount); + } + }, + /** + * Creates the paging UI from the current options setting the various jQuery properties of this component. + * @instance + * @protected + */ + $create: function(){ + this._createdLinks = 0; + var position = 'footable-paging-center'; + switch (this.position){ + case 'left': position = 'footable-paging-left'; break; + case 'right': position = 'footable-paging-right'; break; + } + this.ft.$el.addClass('footable-paging').addClass(position); + + this.$container = this.container === null ? null : $(this.container).first(); + if (!F.is.jq(this.$container)){ + var $tfoot = this.ft.$el.children('tfoot'); + if ($tfoot.length == 0){ + $tfoot = $(''); + this.ft.$el.append($tfoot); + } + // add it to a row and then populate it with the search input and column selector dropdown. + this.$row = $('', {'class': 'footable-paging'}).prependTo($tfoot); + this.$container = this.$cell = $(''); + self.ft.$el.append($tfoot); + } + self.$row = $('', { 'class': 'footable-editing' }).append(self.$cell).appendTo($tfoot); + }, + /** + * Creates the show button for the editing component. + * @instance + * @protected + * @returns {(string|HTMLElement|jQuery)} + */ + $buttonShow: function(){ + return ''; + }, + /** + * Creates the hide button for the editing component. + * @instance + * @protected + * @returns {(string|HTMLElement|jQuery)} + */ + $buttonHide: function(){ + return ''; + }, + /** + * Creates the add button for the editing component. + * @instance + * @protected + * @returns {(string|HTMLElement|jQuery)} + */ + $buttonAdd: function(){ + return ' '; + }, + /** + * Creates the edit button for the editing component. + * @instance + * @protected + * @returns {(string|HTMLElement|jQuery)} + */ + $buttonEdit: function(){ + return ' '; + }, + /** + * Creates the delete button for the editing component. + * @instance + * @protected + * @returns {(string|HTMLElement|jQuery)} + */ + $buttonDelete: function(){ + return ''; + }, + /** + * Creates the view button for the editing component. + * @instance + * @protected + * @returns {(string|HTMLElement|jQuery)} + */ + $buttonView: function(){ + return ' '; + }, + /** + * Creates the button group for the row buttons. + * @instance + * @protected + * @returns {(string|HTMLElement|jQuery)} + */ + $rowButtons: function(){ + if (F.is.jq(this._$buttons)) return this._$buttons.clone(); + this._$buttons = $('
'); + if (this.allowView) this._$buttons.append(this.$buttonView()); + if (this.allowEdit) this._$buttons.append(this.$buttonEdit()); + if (this.allowDelete) this._$buttons.append(this.$buttonDelete()); + return this._$buttons; + }, + /** + * Performs the drawing of the component. + */ + draw: function(){ + this.$cell.attr('colspan', this.ft.columns.visibleColspan); + }, + /** + * Handles the edit button click event. + * @instance + * @private + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @fires FooTable.Editing#"edit.ft.editing" + */ + _onEditClick: function(e){ + e.preventDefault(); + var self = e.data.self, row = $(this).closest('tr').data('__FooTableRow__'); + if (row instanceof F.Row){ + /** + * The edit.ft.editing event is raised before its callback is executed. + * Calling preventDefault on this event will prevent the callback from being executed. + * @event FooTable.Editing#"edit.ft.editing" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Row} row - The row to be edited. + */ + self.ft.raise('edit.ft.editing', [row]).then(function(){ + self.callbacks.editRow.call(self.ft, row); + }); + } + }, + /** + * Handles the delete button click event. + * @instance + * @private + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @fires FooTable.Editing#"delete.ft.editing" + */ + _onDeleteClick: function(e){ + e.preventDefault(); + var self = e.data.self, row = $(this).closest('tr').data('__FooTableRow__'); + if (row instanceof F.Row){ + /** + * The delete.ft.editing event is raised before its callback is executed. + * Calling preventDefault on this event will prevent the callback from being executed. + * @event FooTable.Editing#"delete.ft.editing" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Row} row - The row to be deleted. + */ + self.ft.raise('delete.ft.editing', [row]).then(function(){ + self.callbacks.deleteRow.call(self.ft, row); + }); + } + }, + /** + * Handles the view button click event. + * @instance + * @private + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @fires FooTable.Editing#"view.ft.editing" + */ + _onViewClick: function(e){ + e.preventDefault(); + var self = e.data.self, row = $(this).closest('tr').data('__FooTableRow__'); + if (row instanceof F.Row){ + /** + * The view.ft.editing event is raised before its callback is executed. + * Calling preventDefault on this event will prevent the callback from being executed. + * @event FooTable.Editing#"view.ft.editing" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {FooTable.Row} row - The row to be viewed. + */ + self.ft.raise('view.ft.editing', [row]).then(function(){ + self.callbacks.viewRow.call(self.ft, row); + }); + } + }, + /** + * Handles the add button click event. + * @instance + * @private + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @fires FooTable.Editing#"add.ft.editing" + */ + _onAddClick: function(e){ + e.preventDefault(); + var self = e.data.self; + /** + * The add.ft.editing event is raised before its callback is executed. + * Calling preventDefault on this event will prevent the callback from being executed. + * @event FooTable.Editing#"add.ft.editing" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + self.ft.raise('add.ft.editing').then(function(){ + self.callbacks.addRow.call(self.ft); + }); + }, + /** + * Handles the show button click event. + * @instance + * @private + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @fires FooTable.Editing#"show.ft.editing" + */ + _onShowClick: function(e){ + e.preventDefault(); + var self = e.data.self; + /** + * The show.ft.editing event is raised before its callback is executed. + * Calling preventDefault on this event will prevent the callback from being executed. + * @event FooTable.Editing#"show.ft.editing" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + self.ft.raise('show.ft.editing').then(function(){ + self.ft.$el.addClass('footable-editing-show'); + self.column.visible = true; + self.ft.draw(); + }); + }, + /** + * Handles the hide button click event. + * @instance + * @private + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @fires FooTable.Editing#"show.ft.editing" + */ + _onHideClick: function(e){ + e.preventDefault(); + var self = e.data.self; + /** + * The hide.ft.editing event is raised before its callback is executed. + * Calling preventDefault on this event will prevent the callback from being executed. + * @event FooTable.Editing#"hide.ft.editing" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + self.ft.raise('hide.ft.editing').then(function(){ + self.ft.$el.removeClass('footable-editing-show'); + self.column.visible = false; + self.ft.draw(); + }); + } + }); + + F.components.register('editing', F.Editing, 850); + +})(jQuery, FooTable); + +(function($, F){ + + F.EditingColumn = F.Column.extend(/** @lends FooTable.EditingColumn */{ + /** + * The Editing column class is used to create the column containing the editing buttons. + * @constructs + * @extends FooTable.Column + * @param {FooTable.Table} instance - The parent {@link FooTable.Table} this column belongs to. + * @param {FooTable.Editing} editing - The parent {@link FooTable.Editing} component this column is used with. + * @param {object} definition - An object containing all the properties to set for the column. + * @returns {FooTable.EditingColumn} + */ + construct: function(instance, editing, definition){ + this._super(instance, definition, 'editing'); + this.editing = editing; + this.internal = true; + }, + /** + * After the column has been defined this ensures that the $el property is a jQuery object by either creating or updating the current value. + * @instance + * @protected + * @this FooTable.Column + */ + $create: function(){ + (this.$el = !this.virtual && F.is.jq(this.$el) ? this.$el : $('', - __( 'Date', WE_LS_SLUG ), - sprintf( '%s (%s)', __( 'Weight', WE_LS_SLUG ), ws_ls_get_unit() ), - __( 'Notes', WE_LS_SLUG ) + esc_html__( 'Date', WE_LS_SLUG ), + sprintf( '%s (%s)', esc_html__( 'Weight', WE_LS_SLUG ), ws_ls_get_unit() ), + esc_html__( 'Notes', WE_LS_SLUG ) ); foreach ( $weight_data as $weight_object ) { diff --git a/includes/core.php b/includes/core.php index 93c9807a..20be9031 100755 --- a/includes/core.php +++ b/includes/core.php @@ -11,22 +11,22 @@ function ws_ls_config_js() { $user_id = get_current_user_id(); $user_id = apply_filters( 'wlt-filter-js-ws-ls-config-user-id', $user_id ); $message_for_pounds = ( 'stones_pounds' == ws_ls_setting('weight-unit', $user_id ) ) ? - __( 'Please enter a value between 0-13.99 for pounds', WE_LS_SLUG ) : - __( 'Please enter a value between 1 and 5000 for pounds', WE_LS_SLUG ); + esc_html__( 'Please enter a value between 0-13.99 for pounds', WE_LS_SLUG ) : + esc_html__( 'Please enter a value between 1 and 5000 for pounds', WE_LS_SLUG ); $use_us_date = ws_ls_setting('use-us-dates', $user_id ); $config = [ 'us-date' => ( $use_us_date ) ? 'true' : 'false', 'date-format' => ( $use_us_date ) ? 'mm/dd/yy' : 'dd/mm/yy', - 'clear-target' => __( 'Are you sure you wish to clear your target weight?', WE_LS_SLUG ), + 'clear-target' => esc_html__( 'Are you sure you wish to clear your target weight?', WE_LS_SLUG ), 'validation-about-you-mandatory' => ( true === ws_ls_option_to_bool( 'ws-ls-about-you-mandatory', 'no', true ) ) ? 'true' : 'false', 'validation-we-ls-weight-pounds' => $message_for_pounds, - 'validation-we-ls-weight-kg' => __( 'Please enter a value between 1 and 5000 for Kg', WE_LS_SLUG ), - 'validation-we-ls-weight-stones' => __( 'Please enter a value between 1 and 5000 for Stones', WE_LS_SLUG ), - 'validation-we-ls-date' => __( 'Please enter a valid date', WE_LS_SLUG ), - 'validation-we-ls-history' => __( 'Please confirm that you wish to delete ALL of your user data', WE_LS_SLUG ), - 'validation-we-ls-photo' => __( 'Your photo must be less than ', WE_LS_SLUG ) . ws_ls_photo_display_max_upload_size(), - 'confirmation-delete' => __( 'Are you sure you wish to delete this entry? If so, press OK.', WE_LS_SLUG ), + 'validation-we-ls-weight-kg' => esc_html__( 'Please enter a value between 1 and 5000 for Kg', WE_LS_SLUG ), + 'validation-we-ls-weight-stones' => esc_html__( 'Please enter a value between 1 and 5000 for Stones', WE_LS_SLUG ), + 'validation-we-ls-date' => esc_html__( 'Please enter a valid date', WE_LS_SLUG ), + 'validation-we-ls-history' => esc_html__( 'Please confirm that you wish to delete ALL of your user data', WE_LS_SLUG ), + 'validation-we-ls-photo' => esc_html__( 'Your photo must be less than ', WE_LS_SLUG ) . ws_ls_photo_display_max_upload_size(), + 'confirmation-delete' => esc_html__( 'Are you sure you wish to delete this entry? If so, press OK.', WE_LS_SLUG ), 'ajax-url' => admin_url( 'admin-ajax.php' ), 'ajax-security-nonce' => wp_create_nonce( 'ws-ls-nonce' ), 'is-pro' => ( WS_LS_IS_PRO ) ? 'true' : 'false', @@ -45,11 +45,11 @@ function ws_ls_config_js() { if( true === ws_ls_option_to_bool( 'ws-ls-about-you-mandatory', 'no', true ) ) { $config['validation-user-pref-messages'] = [ - 'ws-ls-height' => __( 'Please select or enter a value for height.', WE_LS_SLUG ), - 'ws-ls-activity-level' => __( 'Please select or enter a value for activity level.', WE_LS_SLUG ), - 'ws-ls-gender' => __( 'Please select or enter a value for gender.', WE_LS_SLUG ), - 'we-ls-dob' => __( 'Please enter a valid date.', WE_LS_SLUG ), - 'ws-ls-aim' => __( 'Please select your aim.', WE_LS_SLUG ) + 'ws-ls-height' => esc_html__( 'Please select or enter a value for height.', WE_LS_SLUG ), + 'ws-ls-activity-level' => esc_html__( 'Please select or enter a value for activity level.', WE_LS_SLUG ), + 'ws-ls-gender' => esc_html__( 'Please select or enter a value for gender.', WE_LS_SLUG ), + 'we-ls-dob' => esc_html__( 'Please enter a valid date.', WE_LS_SLUG ), + 'ws-ls-aim' => esc_html__( 'Please select your aim.', WE_LS_SLUG ) ]; $config['validation-user-pref-rules'] = [ @@ -59,7 +59,7 @@ function ws_ls_config_js() { 'ws-ls-activity-level' => [ 'required' => true, 'min' => 1 ] ]; - $config['validation-required'] = __( 'This field is required.', WE_LS_SLUG ); + $config['validation-required'] = esc_html__( 'This field is required.', WE_LS_SLUG ); } $config[ 'load-entry-url' ] = add_query_arg( 'load-entry', '{entry-id}', $config[ 'load-entry-url' ] ); @@ -77,8 +77,8 @@ function ws_ls_config_js_datapicker_locale() { global $wp_locale; return [ - 'closeText' => __( 'Done', WE_LS_SLUG ), - 'currentText' => __( 'Today', WE_LS_SLUG ), + 'closeText' => esc_html__( 'Done', WE_LS_SLUG ), + 'currentText' => esc_html__( 'Today', WE_LS_SLUG ), 'monthNames' => array_values( $wp_locale->month ), 'monthNamesShort' => array_values( $wp_locale->month_abbrev ), 'dayNames' => array_values( $wp_locale->weekday ), @@ -86,6 +86,6 @@ function ws_ls_config_js_datapicker_locale() { 'dayNamesMin' => array_values( $wp_locale->weekday_initial ), // get the start of week from WP general setting 'firstDay' => get_option( 'start_of_week' ), - 'entry-found' => __( 'An entry has been found for this date. Would you like to load the existing entry?', WE_LS_SLUG ) . PHP_EOL . PHP_EOL . __( 'Note: Any unsaved data shall be lost!', WE_LS_SLUG ) + 'entry-found' => esc_html__( 'An entry has been found for this date. Would you like to load the existing entry?', WE_LS_SLUG ) . PHP_EOL . PHP_EOL . esc_html__( 'Note: Any unsaved data shall be lost!', WE_LS_SLUG ) ]; } \ No newline at end of file diff --git a/includes/db.php b/includes/db.php index 5b16bffe..b098ab0f 100755 --- a/includes/db.php +++ b/includes/db.php @@ -777,12 +777,12 @@ function ws_ls_db_entries_count( $user_id = NULL, $use_cache = true ) { if ( $use_cache && ! empty( $cache ) ) { return $cache; } - - $where = ( -1 !== $user_id ) ? ' where weight_user_id = ' . (int) $user_id : ''; - $where_weight = ' where weight_weight is not null' . ( ( -1 !== $user_id ) ? ' and weight_user_id = ' . (int) $user_id : '' ); - global $wpdb; + $where = ( -1 !== $user_id ) ? $wpdb->prepare( ' where weight_user_id = %d', $user_id ) : ''; + $where_weight = ' where weight_weight is not null' . ( ( -1 !== $user_id ) ? $wpdb->prepare( ' and weight_user_id = %d', $user_id ) : '' ); + + $stats = [ 'number-of-entries' => $wpdb->get_var('SELECT count( id ) FROM ' . $wpdb->prefix . WE_LS_TABLENAME . $where ), 'number-of-weight-entries' => $wpdb->get_var('SELECT count( id ) FROM ' . $wpdb->prefix . WE_LS_TABLENAME . $where_weight ), 'number-of-users' => $wpdb->get_var('SELECT count( id ) FROM ' . $wpdb->prefix . 'users'), diff --git a/includes/email-manager.php b/includes/email-manager.php index a352a7df..5810455f 100755 --- a/includes/email-manager.php +++ b/includes/email-manager.php @@ -671,9 +671,9 @@ function ws_ls_emailer_lists_default_setting() { */ function ws_ls_emailer_lists_default_labels() { $labels = [ - 'awards' => __( 'Notifications about new awards', WE_LS_SLUG ), - 'birthdays' => __( 'Birthday emails', WE_LS_SLUG ), - 'notes' => __( 'Notifications about new notes', WE_LS_SLUG ) + 'awards' => esc_html__( 'Notifications about new awards', WE_LS_SLUG ), + 'birthdays' => esc_html__( 'Birthday emails', WE_LS_SLUG ), + 'notes' => esc_html__( 'Notifications about new notes', WE_LS_SLUG ) ]; return apply_filters( 'wlt-filter-email-lists-default-labels', $labels ); @@ -698,7 +698,7 @@ function ws_ls_emailer_optout_form( $user_id = NULL, $uikit = true ) { $html_output .= ws_ls_form_field_select( [ 'key' => sprintf( 'email-optin-%s', $key ), 'label' => sprintf( '%s:', $labels[ $key ] ), 'uikit' => $uikit, - 'values' => [ 'true' => __( 'Yes', WE_LS_SLUG ), 'false' => __( 'No', WE_LS_SLUG ) ], + 'values' => [ 'true' => esc_html__( 'Yes', WE_LS_SLUG ), 'false' => esc_html__( 'No', WE_LS_SLUG ) ], 'selected' => ( true === ws_ls_emailer_user_has_optedin( $key, $user_id ) ) ? 'true' : 'false' ] ); } diff --git a/includes/form-handler.php b/includes/form-handler.php index a1b7e86b..1aaf8332 100755 --- a/includes/form-handler.php +++ b/includes/form-handler.php @@ -22,19 +22,19 @@ function ws_ls_form_post_handler(){ $user_hash = ws_ls_post_value( 'security' ); if ( true === empty( $user_hash ) ) { - return ws_ls_save_form_error_prep( $form_number, __( 'No user hash could be found', WE_LS_SLUG ) ); + return ws_ls_save_form_error_prep( $form_number, esc_html__( 'No user hash could be found', WE_LS_SLUG ) ); } // Got a user ID? $user_id = ws_ls_post_value( 'user-id' ); if ( true === empty( $user_id ) ) { - return ws_ls_save_form_error_prep( $form_number, __( 'No user ID has been found', WE_LS_SLUG ) ); + return ws_ls_save_form_error_prep( $form_number, esc_html__( 'No user ID has been found', WE_LS_SLUG ) ); } // Does the hash work for the given user ID? if( $user_hash !== wp_hash( $user_id ) ) { - return ws_ls_save_form_error_prep( $form_number, __( 'The given user hash did not match the logged in user', WE_LS_SLUG ) ); + return ws_ls_save_form_error_prep( $form_number, esc_html__( 'The given user hash did not match the logged in user', WE_LS_SLUG ) ); } do_action( 'wlt-hook-data-attempting-added-edited', $user_id ); @@ -54,7 +54,7 @@ function ws_ls_form_post_handler(){ } if ( true === empty( $result ) ) { - return ws_ls_save_form_error_prep( $form_number, __( 'An error occurred while saving your data', WE_LS_SLUG ) ); + return ws_ls_save_form_error_prep( $form_number, esc_html__( 'An error occurred while saving your data', WE_LS_SLUG ) ); } // Redirect? @@ -65,7 +65,7 @@ function ws_ls_form_post_handler(){ exit; } - $message = apply_filters( 'wlt-filter-form-saved-message', __( 'Your entry has been successfully saved.', WE_LS_SLUG ) ); + $message = apply_filters( 'wlt-filter-form-saved-message', esc_html__( 'Your entry has been successfully saved.', WE_LS_SLUG ) ); return ws_ls_save_form_error_prep( $form_number, $message, false ); } diff --git a/includes/functions.php b/includes/functions.php index 7665e475..c0e78aaa 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -66,7 +66,7 @@ function ws_ls_admin_check_mysql_tables_exist() { // Return error message if tables missing if (!empty($error_text)) { - return __('The following MySQL tables are missing for this plugin', WE_LS_SLUG) . ':
    ' . $error_text . '
'; + return esc_html__('The following MySQL tables are missing for this plugin', WE_LS_SLUG) . ':
    ' . $error_text . '
'; } return false; } @@ -92,10 +92,7 @@ function ws_ls_create_dialog_jquery_code( $title, $message, $class_used_to_promp $id_hash = md5($title . $message . $class_used_to_prompt_confirmation ); - printf('
-

%3$s

-
- ', + ', $id_hash, - esc_attr( $title ), wp_kses_post( $message ), esc_attr( $class_used_to_prompt_confirmation ), ( true === $js_call ) ? $js_call : 'window.location.href = target_url;' ); + printf('
+

%3$s

+
', + $id_hash, + esc_attr( $title ), + wp_kses_post( $message )); + + wp_add_inline_script( 'jquery-ui-dialog', $dialog_js ); } /** @@ -194,7 +198,7 @@ function ws_ls_week_ranges_get( $user_id = NULL ) { // Grab all the weekly intervals between those dates $interval = new DateInterval( 'P1W' ); $daterange = new DatePeriod( $start_date, $interval ,$end_date ); - $date_ranges = [ 0 => [ 'display' => __( 'View all weeks', WE_LS_SLUG ), 'start' => $start_date->format( 'Y-m-d' ), 'end' => $end_date->format( 'Y-m-d' ) ] ]; + $date_ranges = [ 0 => [ 'display' => esc_html__( 'View all weeks', WE_LS_SLUG ), 'start' => $start_date->format( 'Y-m-d' ), 'end' => $end_date->format( 'Y-m-d' ) ] ]; $date_format = ws_ls_get_date_format( $user_id ); $i = 1; @@ -204,7 +208,7 @@ function ws_ls_week_ranges_get( $user_id = NULL ) { $end_of_week = clone $date; $end_of_week = date_modify( $end_of_week, '+1 week' ); - $display = sprintf( '%s %d - %s %s %s', __( 'View Week', WE_LS_SLUG ), $i, $date->format( $date_format ), __('to', WE_LS_SLUG), $end_of_week->format( $date_format ) ); + $display = sprintf( '%s %d - %s %s %s', esc_html__( 'View Week', WE_LS_SLUG ), $i, $date->format( $date_format ), esc_html__('to', WE_LS_SLUG), $end_of_week->format( $date_format ) ); $date_ranges[ $i ] = [ 'start' => $date->format( 'Y-m-d' ), 'end' => $end_of_week->format( 'Y-m-d' ), @@ -818,6 +822,8 @@ function ws_ls_get_url( $base_64_encode = false ) { $current_url = ( is_ssl() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + $current_url = sanitize_url( $current_url ); + $current_url = remove_query_arg( [ 'group-id', 'removedata', 'removed' ] , $current_url ); return ( true === $base_64_encode ) ? base64_encode( $current_url ) : $current_url; @@ -908,7 +914,7 @@ function ws_ls_calculations_link( $link_only = false ) { $url = 'https://docs.yeken.uk/calculations.html'; - return ( false === $link_only ) ? sprintf('%s', $url, __( 'Read more about calculations', WE_LS_SLUG ) ) : $url; + return ( false === $link_only ) ? sprintf('%s', $url, esc_html__( 'Read more about calculations', WE_LS_SLUG ) ) : $url; } /** @@ -952,7 +958,7 @@ function ws_ls_display_data_saved_message( $uikit = false ) { if( 'n' !== ws_ls_querystring_value( 'ws-edit-saved', false, 'n' ) ) { - $message = __( 'Your modifications have been saved', WE_LS_SLUG ); + $message = esc_html__( 'Your modifications have been saved', WE_LS_SLUG ); if ( true === $uikit ) { return ws_ls_component_alert( [ 'message' => $message ] ); @@ -978,7 +984,7 @@ function ws_ls_display_data_saved_message( $uikit = false ) { function ws_ls_display_blockquote( $text, $class = '', $just_echo = false, $include_log_link = false ) { $login_link = ( true === $include_log_link ) ? - sprintf( ' .', esc_url( wp_login_url( get_permalink() ) ), __( 'Login' , WE_LS_SLUG ) ) : + sprintf( ' .', esc_url( wp_login_url( get_permalink() ) ), esc_html__( 'Login' , WE_LS_SLUG ) ) : ''; $html_output = sprintf('
@@ -1031,7 +1037,7 @@ function ws_ls_blockquote_error( $text, $class = 'ws-ls-error-text', $just_echo * @return string */ function ws_ls_blockquote_login_prompt( ) { - return ws_ls_display_blockquote( __( 'You must be logged in to view or edit your data.' , WE_LS_SLUG ) , '', false, true ); + return ws_ls_display_blockquote( esc_html__( 'You must be logged in to view or edit your data.' , WE_LS_SLUG ) , '', false, true ); } /** @@ -1185,11 +1191,11 @@ function ws_ls_display_pro_upgrade_notice( $prompt_level = '' ) { // Is there a certain Pro level we're prompting for? if ( 'pro-plus' === $prompt_level ) { - $title = __( 'Upgrade to Pro Plus and get more features!', WE_LS_SLUG ); - $message = __( 'Upgrade to Pro Plus version of this plugin to get additional features like Challenges, Harris Benedict, BMR, Macronutrients and much more!', WE_LS_SLUG ); + $title = esc_html__( 'Upgrade to Pro Plus and get more features!', WE_LS_SLUG ); + $message = esc_html__( 'Upgrade to Pro Plus version of this plugin to get additional features like Challenges, Harris Benedict, BMR, Macronutrients and much more!', WE_LS_SLUG ); } else { - $title = __( 'Upgrade Weight Tracker and get more features!', WE_LS_SLUG ); - $message = __( 'Upgrade to the latest Pro or Pro Plus version of this plugin to manipulate your user\'s data, add custom fields, BMR, Macronutrients and much more!', WE_LS_SLUG ); + $title = esc_html__( 'Upgrade Weight Tracker and get more features!', WE_LS_SLUG ); + $message = esc_html__( 'Upgrade to the latest Pro or Pro Plus version of this plugin to manipulate your user\'s data, add custom fields, BMR, Macronutrients and much more!', WE_LS_SLUG ); } ?> @@ -1197,7 +1203,7 @@ function ws_ls_display_pro_upgrade_notice( $prompt_level = '' ) {

-

+

@@ -1214,9 +1220,9 @@ function ws_ls_display_pro_upgrade_notice( $prompt_level = '' ) { function ws_ls_display_pro_upgrade_notice_for_shortcode ( $uikit = false ) { $message = sprintf( '

%s %s

', - __( 'To view this data/shortcode, you need to upgrade.', WE_LS_SLUG ), + esc_html__( 'To view this data/shortcode, you need to upgrade.', WE_LS_SLUG ), esc_url( admin_url('admin.php?page=ws-ls-license') ), - __( 'Upgrade now', WE_LS_SLUG ) + esc_html__( 'Upgrade now', WE_LS_SLUG ) ); if ( true === $uikit ) { @@ -1366,7 +1372,7 @@ function ws_ls_calculate_percentage_difference_as_number( $previous_weight, $cur */ function ws_ls_boolean_as_yes_no_string( $value, $true_value = 2 ) { - return ( (int) $true_value == (int) $value ) ? __('Yes', WE_LS_SLUG) : __('No', WE_LS_SLUG); + return ( (int) $true_value == (int) $value ) ? esc_html__('Yes', WE_LS_SLUG) : esc_html__('No', WE_LS_SLUG); } /** @@ -1473,7 +1479,7 @@ function ws_ls_weight_unit_label( $key ) { * @return array */ function ws_ls_weight_units() { - return [ 'kg' => __( 'Kg', WE_LS_SLUG ), 'pounds_only' => __( 'Pounds', WE_LS_SLUG ), 'stones_pounds' => __( 'Stones & Pounds', WE_LS_SLUG ) ]; + return [ 'kg' => esc_html__( 'Kg', WE_LS_SLUG ), 'pounds_only' => esc_html__( 'Pounds', WE_LS_SLUG ), 'stones_pounds' => esc_html__( 'Stones & Pounds', WE_LS_SLUG ) ]; } /** @@ -1640,13 +1646,13 @@ function ws_ls_get_unit() { switch ( ws_ls_setting() ) { case 'pounds_only': - $unit = __("lbs", WE_LS_SLUG); + $unit = esc_html__("lbs", WE_LS_SLUG); break; case 'kg': - $unit = __("Kg", WE_LS_SLUG); + $unit = esc_html__("Kg", WE_LS_SLUG); break; default: - $unit = __("St", WE_LS_SLUG) . " " . __("lbs", WE_LS_SLUG); + $unit = esc_html__("St", WE_LS_SLUG) . " " . esc_html__("lbs", WE_LS_SLUG); break; } @@ -1745,3 +1751,20 @@ function ws_ls_user_get_name( $user_id ) { return $name; } + +/** + * Redirect using JS + * @param $user_id + * + * @return string + */ +function ws_ls_js_redirect( $url ) { + + if ( true === empty( $url ) ) { + return; + } + + printf( '', esc_url( $url ) ); + + wp_enqueue_script( 'wt-js-simple-redirect', plugins_url( '../assets/js/simple-redirect.js', __FILE__ ), [ 'jquery' ], WE_LS_CURRENT_VERSION, true ); +} \ No newline at end of file diff --git a/includes/hooks.php b/includes/hooks.php index c1f01b2d..c34b14ce 100755 --- a/includes/hooks.php +++ b/includes/hooks.php @@ -12,22 +12,22 @@ function ws_ls_build_admin_menu() { add_menu_page( WE_LS_TITLE, WE_LS_TITLE, $minimum_role_to_view, 'ws-ls-data-home', 'ws_ls_admin_page_data_home', 'dashicons-chart-line'); // Display manage user screens to relevant roles. - add_submenu_page( 'ws-ls-data-home', __( 'User Data', WE_LS_SLUG ), __( 'User Data', WE_LS_SLUG ), $minimum_role_to_view, 'ws-ls-data-home', 'ws_ls_admin_page_data_home' ); - add_submenu_page( 'ws-ls-data-home', __( 'User Groups', WE_LS_SLUG ), __( 'User Groups', WE_LS_SLUG ), 'manage_options', 'ws-ls-user-groups', 'ws_ls_settings_page_group' ); - add_submenu_page( 'ws-ls-data-home', __( 'Custom Fields', WE_LS_SLUG ), __('Custom Fields', WE_LS_SLUG), 'manage_options', 'ws-ls-meta-fields', 'ws_ls_meta_fields_page' ); - add_submenu_page( 'ws-ls-data-home', __( 'Awards', WE_LS_SLUG ), __('Awards', WE_LS_SLUG), 'manage_options', 'ws-ls-awards', 'ws_ls_awards_page' ); - add_submenu_page( 'ws-ls-data-home', __( 'Challenges', WE_LS_SLUG ), __('Challenges', WE_LS_SLUG), 'manage_options', 'ws-ls-challenges', 'ws_ls_challenges_admin_page' ); + add_submenu_page( 'ws-ls-data-home', esc_html__( 'User Data', WE_LS_SLUG ), esc_html__( 'User Data', WE_LS_SLUG ), $minimum_role_to_view, 'ws-ls-data-home', 'ws_ls_admin_page_data_home' ); + add_submenu_page( 'ws-ls-data-home', esc_html__( 'User Groups', WE_LS_SLUG ), esc_html__( 'User Groups', WE_LS_SLUG ), 'manage_options', 'ws-ls-user-groups', 'ws_ls_settings_page_group' ); + add_submenu_page( 'ws-ls-data-home', esc_html__( 'Custom Fields', WE_LS_SLUG ), esc_html__('Custom Fields', WE_LS_SLUG), 'manage_options', 'ws-ls-meta-fields', 'ws_ls_meta_fields_page' ); + add_submenu_page( 'ws-ls-data-home', esc_html__( 'Awards', WE_LS_SLUG ), esc_html__('Awards', WE_LS_SLUG), 'manage_options', 'ws-ls-awards', 'ws_ls_awards_page' ); + add_submenu_page( 'ws-ls-data-home', esc_html__( 'Challenges', WE_LS_SLUG ), esc_html__('Challenges', WE_LS_SLUG), 'manage_options', 'ws-ls-challenges', 'ws_ls_challenges_admin_page' ); - $menu_text = ( false === WS_LS_IS_PRO && false === WS_LS_IS_PRO_PLUS ) ? __('Upgrade', WE_LS_SLUG) : __('Your License', WE_LS_SLUG); + $menu_text = ( false === WS_LS_IS_PRO && false === WS_LS_IS_PRO_PLUS ) ? esc_html__('Upgrade', WE_LS_SLUG) : esc_html__('Your License', WE_LS_SLUG); - add_submenu_page( 'ws-ls-data-home', __('Settings', WE_LS_SLUG), __('Settings', WE_LS_SLUG), 'manage_options', 'ws-ls-settings', 'ws_ls_settings_page'); + add_submenu_page( 'ws-ls-data-home', esc_html__('Settings', WE_LS_SLUG), esc_html__('Settings', WE_LS_SLUG), 'manage_options', 'ws-ls-settings', 'ws_ls_settings_page'); add_submenu_page( 'ws-ls-data-home', $menu_text, $menu_text, 'manage_options', 'ws-ls-license', 'ws_ls_advertise_pro'); if ( true === ws_ls_setup_wizard_show_notice() ) { - add_submenu_page( 'ws-ls-data-home', __('Setup Wizard', WE_LS_SLUG), __('Setup Wizard', WE_LS_SLUG), 'manage_options', 'ws-ls-data-setup-wizard', 'ws_ls_setup_wizard_page'); + add_submenu_page( 'ws-ls-data-home', esc_html__('Setup Wizard', WE_LS_SLUG), esc_html__('Setup Wizard', WE_LS_SLUG), 'manage_options', 'ws-ls-data-setup-wizard', 'ws_ls_setup_wizard_page'); } - add_submenu_page( 'ws-ls-data-home', __('Help & Log', WE_LS_SLUG), __('Help & Log', WE_LS_SLUG), 'manage_options', 'ws-ls-help', 'ws_ls_help_page'); + add_submenu_page( 'ws-ls-data-home', esc_html__('Help & Log', WE_LS_SLUG), esc_html__('Help & Log', WE_LS_SLUG), 'manage_options', 'ws-ls-help', 'ws_ls_help_page'); } add_action( 'admin_menu', 'ws_ls_build_admin_menu' ); @@ -236,9 +236,9 @@ function ws_ls_use_minified() { function ws_ls_admin_config() { return [ 'ajax-security-nonce' => wp_create_nonce( 'ws-ls-nonce' ), - 'preferences-save-ok' => __('The preferences for this user have been saved.', WE_LS_SLUG ), - 'preferences-saved-fail' => __('An error occurred while trying to save the user\'s preferences.', WE_LS_SLUG ), - 'preferences-page' => ws_ls_get_link_to_user_profile(( false === empty( $_GET[ 'user-id'] ) ) ? esc_attr( $_GET[ 'user-id' ] ) : '' ) ]; + 'preferences-save-ok' => esc_html__('The preferences for this user have been saved.', WE_LS_SLUG ), + 'preferences-saved-fail' => esc_html__('An error occurred while trying to save the user\'s preferences.', WE_LS_SLUG ), + 'preferences-page' => ws_ls_get_link_to_user_profile(( false === empty( $_GET[ 'user-id'] ) ) ? (int) $_GET[ 'user-id' ] : '' ) ]; } /** @@ -259,7 +259,7 @@ function ws_ls_tidy_cache_on_delete(){ function wlt_user_action_links( $actions, $user_object ) { $actions[ 'weight-tracker' ] = sprintf( '%s', ws_ls_get_link_to_user_profile( $user_object->ID ), - __( 'Weight entries', WE_LS_SLUG ) + esc_html__( 'Weight entries', WE_LS_SLUG ) ); return $actions; diff --git a/includes/license.php b/includes/license.php index b14e6d7b..8aba962e 100755 --- a/includes/license.php +++ b/includes/license.php @@ -97,10 +97,10 @@ function ws_ls_display_license_expiry_warning() {

%s: %s %d %s. Renew your license now

', ( $days_until_expiry < 7 ) ? 'error' : 'warning', - __('Weight Tracker License', WE_LS_SLUG ), - __('Your license expires in', WE_LS_SLUG ), + esc_html__('Weight Tracker License', WE_LS_SLUG ), + esc_html__('Your license expires in', WE_LS_SLUG ), $days_until_expiry, - __('days. Please renew your license as soon as possible', WE_LS_SLUG ), + esc_html__('days. Please renew your license as soon as possible', WE_LS_SLUG ), WE_LS_UPGRADE_TO_PRO_PLUS_URL, ws_ls_generate_site_hash() ); @@ -240,24 +240,24 @@ function ws_ls_license_remove( $type = 'both' ) { function ws_ls_license_validate($license) { if(true === empty($license)) { - return __('License missing', WE_LS_SLUG); + return esc_html__('License missing', WE_LS_SLUG); } // Decode license $license = ws_ls_license_decode($license); if (true === empty($license)) { - return __('Could not decode / verify license', WE_LS_SLUG); + return esc_html__('Could not decode / verify license', WE_LS_SLUG); } // Does site hash in license meet this site's actual hash? if ( true === empty($license['site-hash'])) { - return __('Invalid license hash', WE_LS_SLUG); + return esc_html__('Invalid license hash', WE_LS_SLUG); } // Match this site hash? if ( ws_ls_generate_site_hash() !== $license['site-hash']) { - return __('This license doesn\'t appear to be for this site (no match on site hash).', WE_LS_SLUG); + return esc_html__('This license doesn\'t appear to be for this site (no match on site hash).', WE_LS_SLUG); } // Valid date? @@ -265,7 +265,7 @@ function ws_ls_license_validate($license) { $expire_time = strtotime($license['expiry-date']); if ($expire_time < $today_time) { - return __('This license has expired.', WE_LS_SLUG); + return esc_html__('This license has expired.', WE_LS_SLUG); } return true; @@ -328,7 +328,7 @@ function ws_ls_generate_site_hash() { */ function ws_ls_license_display_name($license = false) { - $return_value = __('None', WE_LS_SLUG); + $return_value = esc_html__('None', WE_LS_SLUG); if( true === empty($license) ) { $license = ws_ls_license(); @@ -338,10 +338,10 @@ function ws_ls_license_display_name($license = false) { switch ($license) { case 'pro': - $return_value = __('Yearly Pro', WE_LS_SLUG); + $return_value = esc_html__('Yearly Pro', WE_LS_SLUG); break; case 'pro-plus': - $return_value = __('Pro Plus', WE_LS_SLUG); + $return_value = esc_html__('Pro Plus', WE_LS_SLUG); break; } diff --git a/includes/meal-tracker.php b/includes/meal-tracker.php index 65f81a87..3e9213f2 100644 --- a/includes/meal-tracker.php +++ b/includes/meal-tracker.php @@ -24,7 +24,7 @@ function wlt_user_profile_add_header_link( $links, $user_id ) { $links .= sprintf( ' %2$s', ws_ls_get_link_to_user_profile( $user_id ), - __('Weight Tracker Record', WE_LS_SLUG ) + esc_html__('Weight Tracker Record', WE_LS_SLUG ) ); return $links; diff --git a/includes/setup-wizard.php b/includes/setup-wizard.php index ea250456..7dd3c0d8 100644 --- a/includes/setup-wizard.php +++ b/includes/setup-wizard.php @@ -13,9 +13,9 @@ function ws_ls_setup_wizard_notice() {

%1$s %2$s! %3$s.

Run wizard

', - __( 'Welcome to' , WE_LS_SLUG), + esc_html__( 'Welcome to' , WE_LS_SLUG), WE_LS_TITLE, - __( 'You\'re almost there, but a wizard might help you setup the plugin' , WE_LS_SLUG), + esc_html__( 'You\'re almost there, but a wizard might help you setup the plugin' , WE_LS_SLUG), esc_url( ws_ls_setup_wizard_get_link() ) ); } @@ -83,8 +83,8 @@ function ws_ls_setup_wizard_dismiss_notice() { function wl_ls_setup_wizard_custom_notification_html() { ?> -

-

+

+

YeKen.uk / WordPress Profile / email@yeken.uk

@@ -99,11 +99,11 @@ function wl_ls_setup_wizard_custom_notification_html() { function wl_ls_setup_wizard_meal_tracker_html() { ?>

-

-

.

-

/ - / -

+

+

.

+

/ + / +


- .

-

: 50-OFF-PRO-PLUS

+ .

+

: 50-OFF-PRO-PLUS


diff --git a/includes/shortcode-wt.php b/includes/shortcode-wt.php index 7520ad4f..1310cea8 100644 --- a/includes/shortcode-wt.php +++ b/includes/shortcode-wt.php @@ -59,7 +59,7 @@ function ws_ls_shortcode_wt( $user_defined_arguments ) { $html = '
'; if ( false === is_user_logged_in() ) { - return ws_ls_component_alert( [ 'message' => __( 'You need to be logged in to record your weight.', WE_LS_SLUG ), 'type' => 'primary', 'closable' => false, 'include-login-link' => true ] ) . '
'; + return ws_ls_component_alert( [ 'message' => esc_html__( 'You need to be logged in to record your weight.', WE_LS_SLUG ), 'type' => 'primary', 'closable' => false, 'include-login-link' => true ] ) . ''; } $shortcode_arguments[ 'kiosk-mode' ] = ws_ls_to_bool( $shortcode_arguments[ 'kiosk-mode' ] ); @@ -111,7 +111,7 @@ function ws_ls_shortcode_wt( $user_defined_arguments ) { if ( true === $shortcode_arguments[ 'user-loaded' ] && false === ws_ls_to_bool( $shortcode_arguments[ 'kiosk-hide-editing-name' ] )) { $html .= sprintf( '%s: %s', - __( 'Editing', WE_LS_SLUG ), + esc_html__( 'Editing', WE_LS_SLUG ), ws_ls_user_get_name( $shortcode_arguments[ 'user-id' ] ) ); } @@ -158,9 +158,9 @@ function ws_ls_shortcode_wt( $user_defined_arguments ) { 'sort' => 'desc' ] ); if( 'true' === ws_ls_querystring_value( 'user-preference-saved', 'true' ) ) { - $html .= ws_ls_component_alert( [ 'message' => __( 'Your settings have been successfully saved!', WE_LS_SLUG ) ] ); + $html .= ws_ls_component_alert( [ 'message' => esc_html__( 'Your settings have been successfully saved!', WE_LS_SLUG ) ] ); } elseif( 'true' === ws_ls_querystring_value( 'user-delete-all', 'true' ) ) { - $html .= ws_ls_component_alert( [ 'message' => __( 'Your data has successfully been deleted.', WE_LS_SLUG ) ] ); + $html .= ws_ls_component_alert( [ 'message' => esc_html__( 'Your data has successfully been deleted.', WE_LS_SLUG ) ] ); } if ( true !== ws_ls_to_bool( $shortcode_arguments[ 'hide-notifications' ] ) && true === WS_LS_IS_PRO_PLUS ) { @@ -184,7 +184,7 @@ function ws_ls_shortcode_wt( $user_defined_arguments ) { $link = add_query_arg( [ 'group-view' => 'y', 'group-id' => $group_id ], ws_ls_get_url() ); $html .= sprintf( '
- %2$s', esc_url( $link ), __( 'View Group Weight Loss for today', WE_LS_SLUG ) ); + %2$s', esc_url( $link ), esc_html__( 'View Group Weight Loss for today', WE_LS_SLUG ) ); } return $html . ''; @@ -392,10 +392,10 @@ function ws_ls_wt_tab_home( $arguments = [] ) { $args[ 'legend-position' ] = 'bottom'; $args[ 'hide-custom-fields' ] = $arguments[ 'hide-custom-fields-chart' ]; - $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Weight entries', WE_LS_SLUG ), + $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Weight entries', WE_LS_SLUG ), 'body' => ws_ls_shortcode_embed_chart( $args[ 'weight-data' ] , $args ), 'footer-link' => '#', - 'footer-text' => __('View in tabular format', WE_LS_SLUG), + 'footer-text' => esc_html__('View in tabular format', WE_LS_SLUG), 'tab-changer' => 'history' ]); } @@ -420,7 +420,7 @@ function ws_ls_tab_settings( $arguments = [] ) { // Include target form? if ( true === ws_ls_targets_enabled() ) { - $html = ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Your target', WE_LS_SLUG ), + $html = ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Your target', WE_LS_SLUG ), 'body' => ws_ls_form_weight( [ 'type' => 'target', 'css-class-form' => 'ws-ls-target-form', 'user-id' => $arguments[ 'user-id' ], @@ -443,7 +443,7 @@ function ws_ls_tab_settings( $arguments = [] ) { 'redirect-url' => $redirect_url ]); - $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Settings', WE_LS_SLUG ), + $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Settings', WE_LS_SLUG ), 'body' => $settings ]); @@ -456,7 +456,7 @@ function ws_ls_tab_settings( $arguments = [] ) { 'redirect-url' => $redirect_url ]); - $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Delete your existing data', WE_LS_SLUG ), + $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Delete your existing data', WE_LS_SLUG ), 'body' => $settings ]); } @@ -475,7 +475,7 @@ function ws_ls_tab_notes( $arguments = [] ) { $content = ws_ls_note_shortcode( [ 'user-id' => $arguments[ 'user-id' ], 'notes-per-page' => 5, 'uikit' => true ]); - return ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Messages', WE_LS_SLUG ), + return ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Messages', WE_LS_SLUG ), 'body-class' => 'ykuk-text-small', 'body' => $content ]); @@ -488,7 +488,7 @@ function ws_ls_tab_notes( $arguments = [] ) { * @return string */ function ws_ls_tab_add_entry( $arguments = [] ) { - return ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Add a new entry', WE_LS_SLUG ), + return ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Add a new entry', WE_LS_SLUG ), 'body' => ws_ls_wt_form( $arguments ) ] ); } @@ -522,7 +522,7 @@ function ws_ls_wt_tab_table( $arguments = [] ) { 'enable-meta-fields' => ! $arguments[ 'hide-fields-meta' ] ] ); - $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Your entries', WE_LS_SLUG ), + $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Your entries', WE_LS_SLUG ), 'body-class' => 'ykuk-text-small', 'body' => $inner_html @@ -540,13 +540,13 @@ function ws_ls_wt_tab_table( $arguments = [] ) { function ws_ls_tab_gallery( $arguments = [] ) { $html = '
- ' . ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Latest Photo', WE_LS_SLUG ), + ' . ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Latest Photo', WE_LS_SLUG ), 'body-class' => 'ykuk-text-small ykuk-text-center', 'body' => ws_ls_photos_shortcode_recent( [ 'user-id' => $arguments[ 'user-id' ] ] ) ] ) . '
- ' . ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Oldest Photo', WE_LS_SLUG ), + ' . ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Oldest Photo', WE_LS_SLUG ), 'body-class' => 'ykuk-text-small ykuk-text-center', 'body' => ws_ls_photos_shortcode_oldest( [ 'user-id' => $arguments[ 'user-id' ] ] ) ] ) . ' @@ -555,7 +555,7 @@ function ws_ls_tab_gallery( $arguments = [] ) { '; - $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'All of your photos', WE_LS_SLUG ), + $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'All of your photos', WE_LS_SLUG ), 'body-class' => 'ykuk-text-small ykuk-text-center', 'body' => ws_ls_photos_shortcode_gallery( [ 'user-id' => $arguments[ 'user-id' ] ] ) ] ); @@ -576,7 +576,7 @@ function ws_ls_wt_tab_awards( $arguments = [] ) { $awards = ws_ls_awards_shortcode_gallery( $arguments ); - $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Awards', WE_LS_SLUG ), + $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Awards', WE_LS_SLUG ), 'body-class' => 'ykuk-text-small', 'body' => $awards ] ); @@ -601,13 +601,13 @@ function ws_ls_wt_tab_advanced( $arguments = [] ) { // -------------------- if( true === empty( $arguments[ 'hide-advanced-narrative' ] ) ) { - $nested_html .= ws_ls_component_modal_with_text_link( __( 'Learn more about suggested calorie intakes', WE_LS_SLUG ), - __( 'Once we know your BMR (the number of calories to keep you functioning at rest), we can go on to give you suggestions on how to spread your calorie intake across the day. Firstly we split the figures into daily calorie intake to maintain weight and daily calorie intake to lose weight. Daily calorie intake to lose weight is calculated based on NHS advice – they suggest to lose 1 – 2lbs a week you should subtract 600 calories from your BMR. The two daily figures can be further broken down by recommending how to split calorie intake across the day i.e. breakfast, lunch, dinner and snacks.', WE_LS_SLUG ) ); + $nested_html .= ws_ls_component_modal_with_text_link( esc_html__( 'Learn more about suggested calorie intakes', WE_LS_SLUG ), + esc_html__( 'Once we know your BMR (the number of calories to keep you functioning at rest), we can go on to give you suggestions on how to spread your calorie intake across the day. Firstly we split the figures into daily calorie intake to maintain weight and daily calorie intake to lose weight. Daily calorie intake to lose weight is calculated based on NHS advice – they suggest to lose 1 – 2lbs a week you should subtract 600 calories from your BMR. The two daily figures can be further broken down by recommending how to split calorie intake across the day i.e. breakfast, lunch, dinner and snacks.', WE_LS_SLUG ) ); } $calorie_html = ws_ls_harris_benedict_render_table( $arguments[ 'user-id' ], false, 'ws-ls-footable ykuk-table ykuk-table-striped ykuk-table-small' ); - $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Suggested Calorie Intake', WE_LS_SLUG ), + $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Suggested Calorie Intake', WE_LS_SLUG ), 'body-class' => 'ykuk-text-small', 'body' => $calorie_html . $nested_html ] ); @@ -617,14 +617,14 @@ function ws_ls_wt_tab_advanced( $arguments = [] ) { // -------------------- if( true === empty( $arguments[ 'hide-advanced-narrative' ] ) ) { - $nested_html = ws_ls_component_modal_with_text_link( __( 'Learn more about macronutrients', WE_LS_SLUG ), - __( 'With calories calculated, the we can recommend how those calories should be split into Fats, Carbohydrates and Proteins.', WE_LS_SLUG ) + $nested_html = ws_ls_component_modal_with_text_link( esc_html__( 'Learn more about macronutrients', WE_LS_SLUG ), + esc_html__( 'With calories calculated, the we can recommend how those calories should be split into Fats, Carbohydrates and Proteins.', WE_LS_SLUG ) ); } $calorie_html = ws_ls_macro_render_table( $arguments[ 'user-id' ], false, 'ws-ls-footable ykuk-table ykuk-table-striped ykuk-table-small' ); - $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => __( 'Macronutrients', WE_LS_SLUG ), + $html .= ws_ls_ui_kit_info_box_with_header_footer( [ 'header' => esc_html__( 'Macronutrients', WE_LS_SLUG ), 'body-class' => 'ykuk-text-small', 'body' => $calorie_html . $nested_html ] ); @@ -690,12 +690,12 @@ function ws_ls_shortcode_embed_chart( $weight_data, $shortcode_arguments ) { $html_output .= sprintf('
', - __( 'Add a weight entry', WE_LS_SLUG ) + esc_html__( 'Add a weight entry', WE_LS_SLUG ) ); } if ( false === $shortcode_arguments[ 'hide-title'] ) { - $html_output .= ws_ls_title( __( 'In a chart', WE_LS_SLUG ) ); + $html_output .= ws_ls_title( esc_html__( 'In a chart', WE_LS_SLUG ) ); } $html_output .= ws_ls_display_chart( $weight_data, [ 'custom-field-groups' => $shortcode_arguments[ 'custom-field-groups' ], @@ -707,7 +707,7 @@ function ws_ls_shortcode_embed_chart( $weight_data, $shortcode_arguments ) { } else { - $message = sprintf( __( 'A graph shall appear when %d or more weight entries have been entered.', WE_LS_SLUG ), + $message = sprintf( esc_html__( 'A graph shall appear when %d or more weight entries have been entered.', WE_LS_SLUG ), $shortcode_arguments[ 'min-chart-points' ] ); $html_output .= ( true === empty( $shortcode_arguments[ 'uikit' ] ) ) ? diff --git a/pro-features/email-notifications.php b/pro-features/email-notifications.php index c7a06721..280f10c0 100644 --- a/pro-features/email-notifications.php +++ b/pro-features/email-notifications.php @@ -58,14 +58,14 @@ function ws_ls_email_notification( $type, $weight_data ) { ''; $email_data = [ 'displayname' => ws_ls_user_display_name( $type[ 'user-id' ] ), - 'mode' => ('add' === $type[ 'mode' ] ) ? __( 'added' , WE_LS_SLUG ) : __( 'updated' , WE_LS_SLUG ), + 'mode' => ('add' === $type[ 'mode' ] ) ? esc_html__( 'added' , WE_LS_SLUG ) : esc_html__( 'updated' , WE_LS_SLUG ), 'type' => ( 'weight-measurements' === $type[ 'type' ] ) ? - __( 'their weight / custom fields for ' , WE_LS_SLUG) . ws_ls_convert_ISO_date_into_locale( $weight_data[ 'weight_date' ], 'display-date' ) : - __( 'their target to' , WE_LS_SLUG ), + esc_html__( 'their weight / custom fields for ' , WE_LS_SLUG) . ws_ls_convert_ISO_date_into_locale( $weight_data[ 'weight_date' ], 'display-date' ) : + esc_html__( 'their target to' , WE_LS_SLUG ), 'data' => ( false === empty( $display_weight ) ) ? sprintf('

%s

', $display_weight ) : '', 'subject' => sprintf( '%s %s %s%s', - ( 'weight-measurements' === $type[ 'type' ] ) ? __( 'Weight/Custom fields entry' , WE_LS_SLUG ) : __( 'Target' , WE_LS_SLUG ), - ('add' === $type[ 'mode' ] ) ? __( 'added for' , WE_LS_SLUG ) : __( 'updated for' , WE_LS_SLUG ), + ( 'weight-measurements' === $type[ 'type' ] ) ? esc_html__( 'Weight/Custom fields entry' , WE_LS_SLUG ) : esc_html__( 'Target' , WE_LS_SLUG ), + ('add' === $type[ 'mode' ] ) ? esc_html__( 'added for' , WE_LS_SLUG ) : esc_html__( 'updated for' , WE_LS_SLUG ), ws_ls_user_display_name( $type[ 'user-id' ] ), ( false === empty( $display_weight ) ) ? ': ' . $display_weight : '' ) @@ -78,7 +78,7 @@ function ws_ls_email_notification( $type, $weight_data ) { && false === empty( $weight_data[ 'notes' ] ) ) { - $email_data[ 'data' ] .= sprintf('

%s

%s

', __( 'Notes', WE_LS_SLUG ), esc_html( $weight_data[ 'notes' ] ) ); + $email_data[ 'data' ] .= sprintf('

%s

%s

', esc_html__( 'Notes', WE_LS_SLUG ), esc_html( $weight_data[ 'notes' ] ) ); } @@ -86,13 +86,13 @@ function ws_ls_email_notification( $type, $weight_data ) { if ( true === ws_ls_meta_fields_is_enabled() && false === empty( $weight_data['meta'] ) ) { - $email_data['data'] .= sprintf('

%s

', __( 'Custom Fields', WE_LS_SLUG ) ); + $email_data['data'] .= sprintf('

%s

', esc_html__( 'Custom Fields', WE_LS_SLUG ) ); foreach ( ws_ls_meta_fields_enabled() as $field ) { $value = ( false === empty( $weight_data[ 'meta' ][ $field[ 'id' ] ] ) ) ? ws_ls_fields_display_field_value( $weight_data[ 'meta' ][ $field[ 'id' ] ], $field[ 'id' ] ) : - __( 'Not specified', WE_LS_SLUG ); + esc_html__( 'Not specified', WE_LS_SLUG ); $email_data[ 'data' ] .= sprintf('

%s

%s

', esc_html( $field['field_name'] ), $value ); } @@ -105,7 +105,7 @@ function ws_ls_email_notification( $type, $weight_data ) { $current_user = get_userdata( $type[ 'user-id' ] ); if ( false === empty( $current_user->user_email ) ) { - $email_data[ 'data' ] .= sprintf('

%s

', __('User email address', WE_LS_SLUG) ); + $email_data[ 'data' ] .= sprintf('

%s

', esc_html__('User email address', WE_LS_SLUG) ); $email_data[ 'data' ] .= sprintf('

%1$s

', esc_html( $current_user->user_email ) ); } } @@ -134,12 +134,12 @@ function ws_ls_email_notification( $type, $weight_data ) { */ function ws_ls_email_user_summary( $user_id ) { - $summary = sprintf('

%s

', __( 'Weight Summary', WE_LS_SLUG ) ); + $summary = sprintf('

%s

', esc_html__( 'Weight Summary', WE_LS_SLUG ) ); $latest_entry = ws_ls_entry_get_latest( [ 'user-id' => $user_id, 'meta' => false ] ); if ( false === empty( $latest_entry ) ) { - $summary .= sprintf('
%s (%s)
', __( 'Most Recent Weight', WE_LS_SLUG ), ws_ls_convert_ISO_date_into_locale( $latest_entry[ 'weight_date' ], 'display-date', true ) ); + $summary .= sprintf('
%s (%s)
', esc_html__( 'Most Recent Weight', WE_LS_SLUG ), ws_ls_convert_ISO_date_into_locale( $latest_entry[ 'weight_date' ], 'display-date', true ) ); $summary .= sprintf('

%s

', ws_ls_weight_display( $latest_entry[ 'kg' ], $user_id, 'display', true ) ); } @@ -151,20 +151,20 @@ function ws_ls_email_user_summary( $user_id ) { $sign = ( $difference > 0 ) ? '+' : ''; $difference = ws_ls_weight_display( $difference, $user_id, false, true, true ); - $summary .= sprintf('
%s (%s)
', __( 'Previous Weight', WE_LS_SLUG ), ws_ls_convert_ISO_date_into_locale( $previous_entry[ 'weight_date' ], 'display-date', true ) ); + $summary .= sprintf('
%s (%s)
', esc_html__( 'Previous Weight', WE_LS_SLUG ), ws_ls_convert_ISO_date_into_locale( $previous_entry[ 'weight_date' ], 'display-date', true ) ); $summary .= sprintf('

%s

', ws_ls_weight_display( $previous_entry[ 'kg' ], $user_id, 'display', true ) ); - $summary .= sprintf('
%s
', __( 'Difference between recent and previous entries', WE_LS_SLUG ) ); + $summary .= sprintf('
%s
', esc_html__( 'Difference between recent and previous entries', WE_LS_SLUG ) ); $summary .= sprintf('

%s

', $difference[ 'display' ] ); } $start_entry = ws_ls_entry_get_oldest( [ 'user-id' => $user_id, 'meta' => false ] ); - $summary .= sprintf('
%s (%s)
', __( 'Start Weight', WE_LS_SLUG ), ws_ls_convert_ISO_date_into_locale( $start_entry[ 'weight_date' ], 'display-date', true ) ); + $summary .= sprintf('
%s (%s)
', esc_html__( 'Start Weight', WE_LS_SLUG ), ws_ls_convert_ISO_date_into_locale( $start_entry[ 'weight_date' ], 'display-date', true ) ); $summary .= sprintf('

%s

', ws_ls_weight_display( $latest_entry[ 'first_weight' ], $user_id, 'display', true ) ); - $summary .= sprintf('
%s
', __( 'Difference from Start Weight', WE_LS_SLUG ) ); + $summary .= sprintf('
%s
', esc_html__( 'Difference from Start Weight', WE_LS_SLUG ) ); $summary .= sprintf('

%s

', ws_ls_weight_display( $latest_entry[ 'difference_from_start_kg' ], $user_id, 'display', true ) ); return $summary; @@ -198,11 +198,11 @@ function ws_ls_email_notification_activate() { // Insert the notification template if ( false === ws_ls_emailer_get('email-notify') ) { - $email = sprintf( '

%s,

', __( 'Hello' , WE_LS_SLUG) ); - $email .= __( '

Just a quick email to let you know that "{displayname}" has {mode} {type}:

' , WE_LS_SLUG); - $email .= __( '

{data}

' , WE_LS_SLUG) . PHP_EOL . PHP_EOL; + $email = sprintf( '

%s,

', esc_html__( 'Hello' , WE_LS_SLUG) ); + $email .= esc_html__( '

Just a quick email to let you know that "{displayname}" has {mode} {type}:

' , WE_LS_SLUG); + $email .= esc_html__( '

{data}

' , WE_LS_SLUG) . PHP_EOL . PHP_EOL; - ws_ls_emailer_add( 'email-notify', 'Weight Tracker Update', '
' . $email . '
', __( 'Weight/Target update' , WE_LS_SLUG ) ); + ws_ls_emailer_add( 'email-notify', 'Weight Tracker Update', '
' . $email . '
', esc_html__( 'Weight/Target update' , WE_LS_SLUG ) ); } } } diff --git a/pro-features/export/admin.page.php b/pro-features/export/admin.page.php index c03936a0..f3e88626 100644 --- a/pro-features/export/admin.page.php +++ b/pro-features/export/admin.page.php @@ -39,7 +39,7 @@ function ws_ls_export_admin_page_summary() {
-

+

-

+

%s · ', ws_ls_export_link( 'view', [ 'delete' => $export[ 'id' ] ] ), - __( 'Delete', WE_LS_SLUG ) + esc_html__( 'Delete', WE_LS_SLUG ) ); if ( 100 === (int) $export[ 'step' ] ) { $title .= sprintf( '%s %s', ws_ls_export_file_url( $export[ 'id' ] ), - __( 'Download', WE_LS_SLUG ), + esc_html__( 'Download', WE_LS_SLUG ), esc_html( $export[ 'file' ] ) ); } else { $title .= sprintf( '%s >', admin_url( 'admin.php?page=ws-ls-export-data&mode=process&id='), $export[ 'id' ], - __( 'finish processing', WE_LS_SLUG ) + esc_html__( 'finish processing', WE_LS_SLUG ) ); } @@ -102,7 +102,7 @@ function ws_ls_export_admin_page_summary() { echo ''; } else { - printf( '

%s

', __( 'No data has been exported.' ) ); + printf( '

%s

', esc_html__( 'No data has been exported.' ) ); } ?>
@@ -137,7 +137,7 @@ function ws_ls_export_admin_page_new() {
-

+

'title', 'title' => __( 'Title', WE_LS_SLUG ), 'show-label' => true, 'required' => true, 'css-class' => 'set-title widefat', 'value' => $title ] ); + echo ws_ls_form_field_text( [ 'name' => 'title', 'title' => esc_html__( 'Title', WE_LS_SLUG ), 'show-label' => true, 'required' => true, 'css-class' => 'set-title widefat', 'value' => $title ] ); ?>
-

+

-

+

', $user_id ); - printf( '

%s

', __( 'User', WE_LS_SLUG ) ); + printf( '

%s

', esc_html__( 'User', WE_LS_SLUG ) ); $display_name = ws_ls_user_display_name( $user_id ); @@ -184,7 +184,7 @@ function ws_ls_export_admin_page_new() { printf( '', $user_group_id ); - printf( '

%s

', __( 'User Group', WE_LS_SLUG ) ); + printf( '

%s

', esc_html__( 'User Group', WE_LS_SLUG ) ); printf( '

%s (ID: %d)

', $user_group[ 'name' ], $user_group_id ); @@ -198,55 +198,46 @@ function ws_ls_export_admin_page_new() { if ( false === empty( $user_groups ) ) { - printf( '

%s

', __( 'User Group', WE_LS_SLUG ) ); + printf( '

%s

', esc_html__( 'User Group', WE_LS_SLUG ) ); - echo ws_ls_form_field_select( [ 'key' => 'user-group', 'label' => __( 'Group', WE_LS_SLUG ), 'values' => $user_groups, 'selected' => '', 'empty-option' => true, 'css-class' => 'widefat' ] ); + echo ws_ls_form_field_select( [ 'key' => 'user-group', 'label' => esc_html__( 'Group', WE_LS_SLUG ), 'values' => $user_groups, 'selected' => '', 'empty-option' => true, 'css-class' => 'widefat' ] ); } } } ?> -

+

-

+

'date-range', 'label' => __( 'Period', WE_LS_SLUG ), 'values' => ws_ls_export_date_ranges(), 'selected' => '', 'css-class' => 'widefat' ] ); + echo ws_ls_form_field_select( [ 'key' => 'date-range', 'label' => esc_html__( 'Period', WE_LS_SLUG ), 'values' => ws_ls_export_date_ranges(), 'selected' => '', 'css-class' => 'widefat' ] ); echo '
'; - echo ws_ls_form_field_date( [ 'name' => 'date-from', 'title' => __( 'From', WE_LS_SLUG ), 'show-label' => true, 'css-class' => 'we-ls-datepicker widefat', 'css-class-label' => 'ws-ls-block-it' ] ); + echo ws_ls_form_field_date( [ 'name' => 'date-from', 'title' => esc_html__( 'From', WE_LS_SLUG ), 'show-label' => true, 'css-class' => 'we-ls-datepicker widefat', 'css-class-label' => 'ws-ls-block-it' ] ); - echo ws_ls_form_field_date( [ 'name' => 'date-to', 'title' => __( 'To', WE_LS_SLUG ), 'show-label' => true, 'css-class' => 'we-ls-datepicker widefat', 'css-class-label' => 'ws-ls-block-it' ] ); + echo ws_ls_form_field_date( [ 'name' => 'date-to', 'title' => esc_html__( 'To', WE_LS_SLUG ), 'show-label' => true, 'css-class' => 'we-ls-datepicker widefat', 'css-class-label' => 'ws-ls-block-it' ] ); echo '
'; - if ( false === empty( $title ) ) { - - printf( ' ', esc_js( $title ) ); - - } ?>
-

+

-

+

Check AllUn-check All

'fields[]', 'title' => __( 'BMI Value (height required)', WE_LS_SLUG ), 'show-label' => true, 'value' => 'bmi-value', 'css-class' => 'report-column', 'checked' => false ] ); + echo ws_ls_form_field_checkbox( [ 'name' => 'fields[]', 'title' => esc_html__( 'BMI Value (height required)', WE_LS_SLUG ), 'show-label' => true, 'value' => 'bmi-value', 'css-class' => 'report-column', 'checked' => false ] ); - echo ws_ls_form_field_checkbox( [ 'name' => 'fields[]', 'title' => __( 'BMI Label (height required)', WE_LS_SLUG ), 'show-label' => true, 'value' => 'bmi-label', 'css-class' => 'report-column', 'checked' => true ] ); + echo ws_ls_form_field_checkbox( [ 'name' => 'fields[]', 'title' => esc_html__( 'BMI Label (height required)', WE_LS_SLUG ), 'show-label' => true, 'value' => 'bmi-label', 'css-class' => 'report-column', 'checked' => true ] ); - echo ws_ls_form_field_checkbox( [ 'name' => 'fields[]', 'title' => __( 'Difference between current and start weight', WE_LS_SLUG ), 'show-label' => true, 'value' => 'weight-diff-start', 'css-class' => 'report-column', 'checked' => true ] ); + echo ws_ls_form_field_checkbox( [ 'name' => 'fields[]', 'title' => esc_html__( 'Difference between current and start weight', WE_LS_SLUG ), 'show-label' => true, 'value' => 'weight-diff-start', 'css-class' => 'report-column', 'checked' => true ] ); - echo ws_ls_form_field_checkbox( [ 'name' => 'fields[]', 'title' => __( 'Notes', WE_LS_SLUG ), 'show-label' => true, 'value' => 'notes', 'css-class' => 'report-column', 'checked' => true ] ); + echo ws_ls_form_field_checkbox( [ 'name' => 'fields[]', 'title' => esc_html__( 'Notes', WE_LS_SLUG ), 'show-label' => true, 'value' => 'notes', 'css-class' => 'report-column', 'checked' => true ] ); $enabled_meta_fields = ws_ls_meta_fields_enabled(); @@ -266,21 +257,21 @@ function ws_ls_export_admin_page_new() {
-

+

'format', 'label' => __( 'Format', WE_LS_SLUG ), 'values' => [ 'csv' => __( 'CSV', WE_LS_SLUG ), 'json' => __( 'Json', WE_LS_SLUG ) ], 'selected' => $selected_format ] ); + echo ws_ls_form_field_select( [ 'key' => 'format', 'label' => esc_html__( 'Format', WE_LS_SLUG ), 'values' => [ 'csv' => esc_html__( 'CSV', WE_LS_SLUG ), 'json' => esc_html__( 'Json', WE_LS_SLUG ) ], 'selected' => $selected_format ] ); ?>
-

+

- +
@@ -313,24 +304,24 @@ function ws_ls_export_admin_page_process() {
-

+

-

+

'', - 'today' => __( 'Today', WE_LS_SLUG ), - 'last-7' => __( 'Last 7 Days', WE_LS_SLUG ), - 'last-31' => __( 'Last 31 Days', WE_LS_SLUG ), - 'custom' => __( 'Custom date range', WE_LS_SLUG ) + 'today' => esc_html__( 'Today', WE_LS_SLUG ), + 'last-7' => esc_html__( 'Last 7 Days', WE_LS_SLUG ), + 'last-31' => esc_html__( 'Last 31 Days', WE_LS_SLUG ), + 'custom' => esc_html__( 'Custom date range', WE_LS_SLUG ) ]; } @@ -205,10 +205,10 @@ function ws_ls_export_column_names( $export_criteria ) { 'user_nicename' => 'Nicename', 'date-display' => 'Date', 'weight' => ws_ls_settings_weight_unit_readable(), - 'difference_from_start_display' => __( 'Difference from start', WE_LS_SLUG ), - 'bmi' => __( 'BMI', WE_LS_SLUG ), - 'bmi-readable' => __( 'BMI Label', WE_LS_SLUG ), - 'weight_notes' => __( 'Notes', WE_LS_SLUG ) + 'difference_from_start_display' => esc_html__( 'Difference from start', WE_LS_SLUG ), + 'bmi' => esc_html__( 'BMI', WE_LS_SLUG ), + 'bmi-readable' => esc_html__( 'BMI Label', WE_LS_SLUG ), + 'weight_notes' => esc_html__( 'Notes', WE_LS_SLUG ) ]; $options = ( false === empty( $export_criteria[ 'options' ] ) ) ? $export_criteria[ 'options' ] : NULL; diff --git a/pro-features/export/hooks.php b/pro-features/export/hooks.php index e2f2e3a2..6ded9ad9 100644 --- a/pro-features/export/hooks.php +++ b/pro-features/export/hooks.php @@ -7,7 +7,7 @@ */ function ws_ls_export_admin_menu() { - add_submenu_page( 'ws-ls-data-home', __( 'Export Data', WE_LS_SLUG ), __( 'Export Data', WE_LS_SLUG ), ws_ls_permission_export_delete_role(), 'ws-ls-export-data', 'ws_ls_export_admin_page', 5 ); + add_submenu_page( 'ws-ls-data-home', esc_html__( 'Export Data', WE_LS_SLUG ), esc_html__( 'Export Data', WE_LS_SLUG ), ws_ls_permission_export_delete_role(), 'ws-ls-export-data', 'ws_ls_export_admin_page', 5 ); } add_action( 'admin_menu', 'ws_ls_export_admin_menu' ); @@ -26,13 +26,13 @@ function ws_ls_export_ajax_process() { $id = ws_ls_post_value( 'id' ); if ( true === empty( $id ) ) { - ws_ls_export_ajax_error( $return, __( 'Export ID could not be determined.' , WE_LS_SLUG ) ); + ws_ls_export_ajax_error( $return, esc_html__( 'Export ID could not be determined.' , WE_LS_SLUG ) ); } $export = ws_ls_db_export_criteria_get( $id ); if ( true === empty( $export ) ) { - ws_ls_export_ajax_error( $return, __( 'Export criteria could not be loaded.' , WE_LS_SLUG ) ); + ws_ls_export_ajax_error( $return, esc_html__( 'Export criteria could not be loaded.' , WE_LS_SLUG ) ); } $current_step = (int) $export[ 'step' ]; @@ -43,7 +43,7 @@ function ws_ls_export_ajax_process() { if ( 0 === $current_step ) { ws_ls_db_export_identify_weight_entries( $id ); - $return[ 'message' ] = __( 'Initialising: Rows have been identified for the export.', WE_LS_SLUG ); + $return[ 'message' ] = esc_html__( 'Initialising: Rows have been identified for the export.', WE_LS_SLUG ); $return[ 'percentage' ] = 40; ws_ls_db_export_criteria_step( $id, 1 ); @@ -55,16 +55,16 @@ function ws_ls_export_ajax_process() { $physical_path = ws_ls_export_file_physical_folder( $id ); if ( false === wp_mkdir_p( $physical_path ) ) { - ws_ls_export_ajax_error( $return, __( 'There was an issue creating the export folder: ' , WE_LS_SLUG ) . $physical_path ); + ws_ls_export_ajax_error( $return, esc_html__( 'There was an issue creating the export folder: ' , WE_LS_SLUG ) . $physical_path ); } $physical_path_to_file = ws_ls_export_file_physical_path( $id ); if ( false === touch( $physical_path_to_file ) ) { - ws_ls_export_ajax_error( $return, __( 'There was an issue creating the export file: ' , WE_LS_SLUG ) . $physical_path_to_file ); + ws_ls_export_ajax_error( $return, esc_html__( 'There was an issue creating the export file: ' , WE_LS_SLUG ) . $physical_path_to_file ); } - $return[ 'message' ] = __( 'Initialising: created empty file on disk.', WE_LS_SLUG ); + $return[ 'message' ] = esc_html__( 'Initialising: created empty file on disk.', WE_LS_SLUG ); $return[ 'percentage' ] = 70; ws_ls_db_export_criteria_step( $id, 2 ); @@ -78,7 +78,7 @@ function ws_ls_export_ajax_process() { ws_ls_db_export_criteria_count( $id, $number_of_records ); - $return['message'] = sprintf( 'Initialising: %d %s', $number_of_records, __( 'records have been identified for this report.', WE_LS_SLUG ) ); + $return['message'] = sprintf( 'Initialising: %d %s', $number_of_records, esc_html__( 'records have been identified for this report.', WE_LS_SLUG ) ); $return['percentage'] = 100; ws_ls_db_export_criteria_step( $id, 20 ); @@ -94,7 +94,7 @@ function ws_ls_export_ajax_process() { // There are no more rows to process if ( true === empty( $rows_to_process ) ) { - $return['message'] = __( 'Preparing data: Complete.', WE_LS_SLUG ); + $return['message'] = esc_html__( 'Preparing data: Complete.', WE_LS_SLUG ); $return['percentage'] = 100; ws_ls_db_export_criteria_step( $id, 40 ); @@ -104,7 +104,7 @@ function ws_ls_export_ajax_process() { foreach ( $rows_to_process as $row ) { if ( false === ws_ls_export_update_export_row( $export, $row ) ) { - ws_ls_export_ajax_error( $return, __( 'There was an error processing weight entry', WE_LS_SLUG ) . ': ' . $row['entry_id'] ); + ws_ls_export_ajax_error( $return, esc_html__( 'There was an error processing weight entry', WE_LS_SLUG ) . ': ' . $row['entry_id'] ); } } @@ -140,7 +140,7 @@ function ws_ls_export_ajax_process() { } ws_ls_db_export_criteria_step( $id, 42 ); - $return['message'] = __( 'Saving to disk: Column headers', WE_LS_SLUG ); + $return['message'] = esc_html__( 'Saving to disk: Column headers', WE_LS_SLUG ); $return['percentage'] = 5; // ------------------------------------------------------------------------------------------------------ @@ -152,7 +152,7 @@ function ws_ls_export_ajax_process() { if ( true === empty( $rows_to_write ) ) { - $return['message'] = __( 'Saving to disk: Complete.', WE_LS_SLUG ); + $return['message'] = esc_html__( 'Saving to disk: Complete.', WE_LS_SLUG ); $return['percentage'] = 100; ws_ls_db_export_criteria_step( $id, 47 ); @@ -218,7 +218,7 @@ function ws_ls_export_ajax_process() { ws_ls_db_export_criteria_step( $id, 100 ); - $return[ 'message' ] = sprintf( '%s', ws_ls_export_file_url( $id ), __( 'Download', WE_LS_SLUG ) ); + $return[ 'message' ] = sprintf( '%s', ws_ls_export_file_url( $id ), esc_html__( 'Download', WE_LS_SLUG ) ); $return[ 'percentage' ] = 100; $return[ 'continue' ] = false; } diff --git a/pro-features/feature-list.php b/pro-features/feature-list.php index 62a0ae16..00d44daa 100755 --- a/pro-features/feature-list.php +++ b/pro-features/feature-list.php @@ -8,18 +8,18 @@ */ function ws_ls_feature_list_pro_plus() { return [ - __(' All of the features that come with a standard Pro license.', WE_LS_SLUG ), - __( '[wt-kiosk] - allowing your administrators and staff to search and edit a user\'s record on the front end of your website.' , WE_LS_SLUG ), - __( 'Barcode scanner - Beta. Barcode scanner for scanning user IDs when using [wt-kiosk].' , WE_LS_SLUG ), - __(' Basal Metabolic Rate (BMR) calculations per user. Shortcodes and extended admin screens to display a user\'s BMR. For further information on BMR and how it is calculated visit our calculations page.', WE_LS_SLUG ), - __(' Harris Benedict formula. Shortcodes and extended admin screens to a view a person\'s calorie intake required to maintain and lose weight. For further information on Harris Benedict Formula and how it is calculated visit our calculations page.', WE_LS_SLUG ), - __(' Recommended calorie intake per meal time. Shortcodes and extended admin screens to recommend how a person should split their daily calorie intake across meals. For further information on how this is calculated please visit our calculations page.', WE_LS_SLUG ), - __(' Macronutrients Calculator. Shortcodes and extended admin screens to recommend how their calorie consumption should be split into fats, carbohydrates and proteins. For further information on the Macronutrients Calculator and how these calculations are performed please visit our calculations page.', WE_LS_SLUG ), - __(' Additional user preference fields. Additional user preference fields and shortcodes to display them: Activity Level, Date of Birth and Gender.', WE_LS_SLUG ), - __(' Awards. Awards and Badges! Set awards for: BMI Change, BMI Equals, Weight Gain / Loss from start and Percentage of weight lost from start.', WE_LS_SLUG ), - __(' Challenges. Set challenges for your user\'s within a given time period? Display Total Weight Lost, BMI Change, %Body Weight, Weight Tracker Streaks and Meal Tracker streaks achieved by each user in a league table. Besides viewing all your challenges and their data, the shortcode will allow you to display the league table in the public facing website.', WE_LS_SLUG ), - __(' BMI CalculatorA quick tool to allow your users to enter their measurements/weight to calculate their BMI.', WE_LS_SLUG ), - __(' Waist-to-Hip ratio CalculatorA quick tool to allow your users to enter their measurements to calculate their Waist-to-Hip ratio.', WE_LS_SLUG ) + esc_html__(' All of the features that come with a standard Pro license.', WE_LS_SLUG ), + esc_html__( '[wt-kiosk] - allowing your administrators and staff to search and edit a user\'s record on the front end of your website.' , WE_LS_SLUG ), + esc_html__( 'Barcode scanner - Beta. Barcode scanner for scanning user IDs when using [wt-kiosk].' , WE_LS_SLUG ), + esc_html__(' Basal Metabolic Rate (BMR) calculations per user. Shortcodes and extended admin screens to display a user\'s BMR. For further information on BMR and how it is calculated visit our calculations page.', WE_LS_SLUG ), + esc_html__(' Harris Benedict formula. Shortcodes and extended admin screens to a view a person\'s calorie intake required to maintain and lose weight. For further information on Harris Benedict Formula and how it is calculated visit our calculations page.', WE_LS_SLUG ), + esc_html__(' Recommended calorie intake per meal time. Shortcodes and extended admin screens to recommend how a person should split their daily calorie intake across meals. For further information on how this is calculated please visit our calculations page.', WE_LS_SLUG ), + esc_html__(' Macronutrients Calculator. Shortcodes and extended admin screens to recommend how their calorie consumption should be split into fats, carbohydrates and proteins. For further information on the Macronutrients Calculator and how these calculations are performed please visit our calculations page.', WE_LS_SLUG ), + esc_html__(' Additional user preference fields. Additional user preference fields and shortcodes to display them: Activity Level, Date of Birth and Gender.', WE_LS_SLUG ), + esc_html__(' Awards. Awards and Badges! Set awards for: BMI Change, BMI Equals, Weight Gain / Loss from start and Percentage of weight lost from start.', WE_LS_SLUG ), + esc_html__(' Challenges. Set challenges for your user\'s within a given time period? Display Total Weight Lost, BMI Change, %Body Weight, Weight Tracker Streaks and Meal Tracker streaks achieved by each user in a league table. Besides viewing all your challenges and their data, the shortcode will allow you to display the league table in the public facing website.', WE_LS_SLUG ), + esc_html__(' BMI CalculatorA quick tool to allow your users to enter their measurements/weight to calculate their BMI.', WE_LS_SLUG ), + esc_html__(' Waist-to-Hip ratio CalculatorA quick tool to allow your users to enter their measurements to calculate their Waist-to-Hip ratio.', WE_LS_SLUG ) ]; } @@ -29,33 +29,33 @@ function ws_ls_feature_list_pro_plus() { */ function ws_ls_feature_list_pro() { return [ - __(' Access your user\'s data. Admin can view, edit and delete user data. Various tools for viewing user\'s graphs, tables of entries, BMI, targets, weight lost / gained stats and much more.', WE_LS_SLUG ), - __(' Challenges. Create and display challenges for users over different time periods.', WE_LS_SLUG ), - __(' Custom Fields. Create and add your own questions to weight entry forms to gather additional information.', WE_LS_SLUG ), - __(' Photo Custom Fields. Add one or more photo fields to your weight entry forms and allow your users to upload photos of their progress. Photos can be viewed, updated and removed by the end user and administrators. Handy shortcodes are provided for displaying galleries, most recent and oldest photo.', WE_LS_SLUG ), - __(' Export all data or a particular user. Export in JSON or CSV format.', WE_LS_SLUG ), - __(' Webhooks, Zapier & Slack. Push weight entry data and targets to Slack channels, Zapier or your own custom Webhooks!', WE_LS_SLUG ), - __(' Groups. Define user groups and assign your user\'s to them. View Weight Difference statistics for the group as a whole.', WE_LS_SLUG ), - __(' Admin notes. Administrators have the ability to store notes against their users. If set to visible, the user can view these via [wt-notes] or receive emails with their content.', WE_LS_SLUG ), - __(' Gamification. Support for myCred, a popular gamification plugin. Reward your users for weight entries and setting their targets.', WE_LS_SLUG ), - __(' BMI. Allows a user to specify their height. Once specified, their BMI is displayed next to each weight entry. There is also a shortcode to render the latest BMI.', WE_LS_SLUG ), - __(' Email notifications. Receive email notifications when a person updates their target or adds / edits a weight.', WE_LS_SLUG ), - __(' Birthday Emails. Automatically send your user\'s a birthday email (when they have entered a date of birth)', WE_LS_SLUG ), - __(' Overall user stats. Shortcodes that allow you to display the total lost / gained for the community and another to display a league table.', WE_LS_SLUG ), - __(' Widgets. Widgets that allow you to display the graph and quick weight entry form within any widget area.', WE_LS_SLUG ), - __(' Chart and form Shortcodes. That allow you to display the graph and quick weight entry form by placing a shortcode on any post or page.', WE_LS_SLUG ), - __(' Progress Bar shortcode. A shortcode that visually displays the logged in user\'s progress towards their target', WE_LS_SLUG ), - __(' Reminder shortcode. A shortcode that can be used to remind the user to enter their target or weight for today.', WE_LS_SLUG ), - __(' Message shortcode A shortcode that allows you to congratulate a user when they lose weight x number of times. It also provides the opposite allowing you to provide encouragement when someone gains weight.', WE_LS_SLUG ), - __(' Text Shortcodes. Additional shortcodes for earliest and most recent dates entered.', WE_LS_SLUG ), - __(' Progress Bar shortcode / widget. Display a user\'s progress towards their weight target.', WE_LS_SLUG ), - __(' Reminder shortcode. Display a reminder to enter their weight for the given day or enter a target.', WE_LS_SLUG ), - __(' Admin: View / Delete user data. Admin will be able to view and delete existing user data.', WE_LS_SLUG ), - __(' User preferences. If enabled, the user will be able to select which unit they wish to store their weight in Metric or Imperial. They will also be able to specify date format and clear all their weight data.', WE_LS_SLUG ), - __(' Bar Charts. Fancy something different to a line chart? The plugin will also support Bar Charts.', WE_LS_SLUG ), - __(' Decimals. Decimals will be allowed weight in Pounds only or Kg modes.', WE_LS_SLUG ), - __(' Delete existing entry. A logged in user will be able to delete or edit an existing weight entry.', WE_LS_SLUG ), - __(' Better Tables.. Data tables in front end and admin will support paging and sorting.', WE_LS_SLUG ), - __(' Admin: Extra Settings. Extra settings to customise the plugin will be added e.g. number of plot points on graph, rows per page, etc.', WE_LS_SLUG ) + esc_html__(' Access your user\'s data. Admin can view, edit and delete user data. Various tools for viewing user\'s graphs, tables of entries, BMI, targets, weight lost / gained stats and much more.', WE_LS_SLUG ), + esc_html__(' Challenges. Create and display challenges for users over different time periods.', WE_LS_SLUG ), + esc_html__(' Custom Fields. Create and add your own questions to weight entry forms to gather additional information.', WE_LS_SLUG ), + esc_html__(' Photo Custom Fields. Add one or more photo fields to your weight entry forms and allow your users to upload photos of their progress. Photos can be viewed, updated and removed by the end user and administrators. Handy shortcodes are provided for displaying galleries, most recent and oldest photo.', WE_LS_SLUG ), + esc_html__(' Export all data or a particular user. Export in JSON or CSV format.', WE_LS_SLUG ), + esc_html__(' Webhooks, Zapier & Slack. Push weight entry data and targets to Slack channels, Zapier or your own custom Webhooks!', WE_LS_SLUG ), + esc_html__(' Groups. Define user groups and assign your user\'s to them. View Weight Difference statistics for the group as a whole.', WE_LS_SLUG ), + esc_html__(' Admin notes. Administrators have the ability to store notes against their users. If set to visible, the user can view these via [wt-notes] or receive emails with their content.', WE_LS_SLUG ), + esc_html__(' Gamification. Support for myCred, a popular gamification plugin. Reward your users for weight entries and setting their targets.', WE_LS_SLUG ), + esc_html__(' BMI. Allows a user to specify their height. Once specified, their BMI is displayed next to each weight entry. There is also a shortcode to render the latest BMI.', WE_LS_SLUG ), + esc_html__(' Email notifications. Receive email notifications when a person updates their target or adds / edits a weight.', WE_LS_SLUG ), + esc_html__(' Birthday Emails. Automatically send your user\'s a birthday email (when they have entered a date of birth)', WE_LS_SLUG ), + esc_html__(' Overall user stats. Shortcodes that allow you to display the total lost / gained for the community and another to display a league table.', WE_LS_SLUG ), + esc_html__(' Widgets. Widgets that allow you to display the graph and quick weight entry form within any widget area.', WE_LS_SLUG ), + esc_html__(' Chart and form Shortcodes. That allow you to display the graph and quick weight entry form by placing a shortcode on any post or page.', WE_LS_SLUG ), + esc_html__(' Progress Bar shortcode. A shortcode that visually displays the logged in user\'s progress towards their target', WE_LS_SLUG ), + esc_html__(' Reminder shortcode. A shortcode that can be used to remind the user to enter their target or weight for today.', WE_LS_SLUG ), + esc_html__(' Message shortcode A shortcode that allows you to congratulate a user when they lose weight x number of times. It also provides the opposite allowing you to provide encouragement when someone gains weight.', WE_LS_SLUG ), + esc_html__(' Text Shortcodes. Additional shortcodes for earliest and most recent dates entered.', WE_LS_SLUG ), + esc_html__(' Progress Bar shortcode / widget. Display a user\'s progress towards their weight target.', WE_LS_SLUG ), + esc_html__(' Reminder shortcode. Display a reminder to enter their weight for the given day or enter a target.', WE_LS_SLUG ), + esc_html__(' Admin: View / Delete user data. Admin will be able to view and delete existing user data.', WE_LS_SLUG ), + esc_html__(' User preferences. If enabled, the user will be able to select which unit they wish to store their weight in Metric or Imperial. They will also be able to specify date format and clear all their weight data.', WE_LS_SLUG ), + esc_html__(' Bar Charts. Fancy something different to a line chart? The plugin will also support Bar Charts.', WE_LS_SLUG ), + esc_html__(' Decimals. Decimals will be allowed weight in Pounds only or Kg modes.', WE_LS_SLUG ), + esc_html__(' Delete existing entry. A logged in user will be able to delete or edit an existing weight entry.', WE_LS_SLUG ), + esc_html__(' Better Tables.. Data tables in front end and admin will support paging and sorting.', WE_LS_SLUG ), + esc_html__(' Admin: Extra Settings. Extra settings to customise the plugin will be added e.g. number of plot points on graph, rows per page, etc.', WE_LS_SLUG ) ]; } diff --git a/pro-features/footable.php b/pro-features/footable.php index 8e3c2125..d973f8af 100755 --- a/pro-features/footable.php +++ b/pro-features/footable.php @@ -67,7 +67,7 @@ function ws_ls_data_table_render( $arguments = [] ) { $html .= sprintf( '

< %s

', ws_ls_wt_link_goto_tab( 'history' ), - __( 'return to all entries', WE_LS_SLUG ) ); + esc_html__( 'return to all entries', WE_LS_SLUG ) ); } $html .= ws_ls_form_weight( [ 'entry-id' => $entry_id, 'redirect-url' => $redirect_url, 'weight-mandatory' => $arguments[ 'weight-mandatory' ], @@ -124,13 +124,13 @@ function ws_ls_data_table_render( $arguments = [] ) { esc_attr( $arguments[ 'custom-field-restrict-rows' ] ), true === ws_ls_to_bool( $arguments[ 'uikit' ] ) ? 'true' : 'false', true === ws_ls_to_bool( $arguments[ 'show-refresh-button' ] ) ? - sprintf( '', __( 'Data has changed, refresh screen', WE_LS_SLUG ) ) : '', + sprintf( '', esc_html__( 'Data has changed, refresh screen', WE_LS_SLUG ) ) : '', esc_attr( $arguments[ 'name' ] ), esc_attr( $arguments[ 'table-id' ] ) ); if ( true === empty( $arguments[ 'user-id' ] ) ) { - $html .= sprintf( '

%s

', __( 'Please note: For performance reasons, this table will only update every 5 minutes.', WE_LS_SLUG ) ); + $html .= sprintf( '

%s

', esc_html__( 'Please note: For performance reasons, this table will only update every 5 minutes.', WE_LS_SLUG ) ); } } @@ -214,7 +214,7 @@ function ws_ls_datatable_rows( $arguments ) { if ( true === empty( $entry[ 'kg' ] ) ) { $gain_class = 'same'; - $gain_loss = __( 'No weight recorded', WE_LS_SLUG ); + $gain_loss = esc_html__( 'No weight recorded', WE_LS_SLUG ); } elseif( false === empty( $previous_user_weight[ $entry[ 'user_id' ] ] ) ) { if ( false === empty( $entry[ 'kg' ] ) ) { @@ -227,16 +227,16 @@ function ws_ls_datatable_rows( $arguments ) { $gain_class = 'loss'; } elseif ( $entry['kg'] == $previous_user_weight[ $entry[ 'user_id' ] ] ) { $gain_class = 'same'; - $gain_loss = __( 'No Change', WE_LS_SLUG ); + $gain_loss = esc_html__( 'No Change', WE_LS_SLUG ); } $row[ 'previous-weight-diff' ] = $entry['kg'] - $previous_user_weight[ $entry[ 'user_id' ] ]; } } elseif ( true === empty( $arguments[ 'user-id' ] )) { - $gain_loss = $entry[ 'user_profile' ] = sprintf('%s', ws_ls_get_link_to_user_profile( $entry[ 'user_id' ] ), __( 'Check record', WE_LS_SLUG ) ); + $gain_loss = $entry[ 'user_profile' ] = sprintf('%s', ws_ls_get_link_to_user_profile( $entry[ 'user_id' ] ), esc_html__( 'Check record', WE_LS_SLUG ) ); } elseif ( false === empty( $entry[ 'kg' ] ) ) { - $gain_loss = __( 'First weight entry', WE_LS_SLUG ); + $gain_loss = esc_html__( 'First weight entry', WE_LS_SLUG ); } if ( false === empty( $entry[ 'kg' ] ) ) { @@ -254,7 +254,7 @@ function ws_ls_datatable_rows( $arguments ) { if ( true === empty( $entry[ 'kg' ] ) ) { $gain_class = 'same'; - $gain_loss = __( 'No weight recorded', WE_LS_SLUG ); + $gain_loss = esc_html__( 'No weight recorded', WE_LS_SLUG ); } elseif( false === empty( $start_weight ) ) { $start_weight = (float) $start_weight; @@ -271,14 +271,14 @@ function ws_ls_datatable_rows( $arguments ) { $gain_class = 'loss'; } elseif ( $entry['kg'] == $start_weight ) { $gain_class = 'same'; - $gain_loss = __( 'No Change', WE_LS_SLUG ); + $gain_loss = esc_html__( 'No Change', WE_LS_SLUG ); } } } elseif ( true === empty( $arguments[ 'user-id' ] )) { - $gain_loss = $entry[ 'user_profile' ] = sprintf('%s', ws_ls_get_link_to_user_profile( $entry[ 'user_id' ] ), __( 'Check record', WE_LS_SLUG ) ); + $gain_loss = $entry[ 'user_profile' ] = sprintf('%s', ws_ls_get_link_to_user_profile( $entry[ 'user_id' ] ), esc_html__( 'Check record', WE_LS_SLUG ) ); } elseif ( false === empty( $entry[ 'kg' ] ) ) { - $gain_loss = __( 'First weight entry', WE_LS_SLUG ); + $gain_loss = esc_html__( 'First weight entry', WE_LS_SLUG ); } if ( true === is_numeric( $gain_loss ) ) { @@ -297,7 +297,7 @@ function ws_ls_datatable_rows( $arguments ) { true === ws_ls_bmi_in_tables() ) { if ( false === empty( $entry[ 'kg' ] ) ) { - $row[ 'bmi' ] = [ 'value' => ws_ls_get_bmi_for_table( ws_ls_user_preferences_get( 'height', $entry[ 'user_id' ] ), $entry[ 'kg' ], __( 'No height', WE_LS_SLUG ), $arguments[ 'bmi-format'] ), + $row[ 'bmi' ] = [ 'value' => ws_ls_get_bmi_for_table( ws_ls_user_preferences_get( 'height', $entry[ 'user_id' ] ), $entry[ 'kg' ], esc_html__( 'No height', WE_LS_SLUG ), $arguments[ 'bmi-format'] ), 'options' => [ 'classes' => '' ] ]; } else { @@ -399,19 +399,19 @@ function ws_ls_datatable_columns( $arguments = [] ) { // If not front end, add nice name if ( false === $arguments[ 'front-end' ] ) { - $columns[] = [ 'name' => 'user_nicename', 'title' => __( 'User', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'text' ]; + $columns[] = [ 'name' => 'user_nicename', 'title' => esc_html__( 'User', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'text' ]; } else { // If in the front end, switch to smaller width (hide meta fields etc) $arguments[ 'small-width' ] = $arguments[ 'front-end' ]; } - $columns[] = [ 'name' => 'date', 'title' => __( 'Date', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'date' ]; + $columns[] = [ 'name' => 'date', 'title' => esc_html__( 'Date', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'date' ]; if ( true === $arguments[ 'enable-weight' ] ) { - $columns[] = [ 'name' => 'kg', 'title' => __( 'Weight', WE_LS_SLUG ), 'visible'=> true, 'type' => 'text' ]; + $columns[] = [ 'name' => 'kg', 'title' => esc_html__( 'Weight', WE_LS_SLUG ), 'visible'=> true, 'type' => 'text' ]; if ( false === $arguments[ 'front-end' ] || true === WS_LS_IS_PRO ) { - $columns[] = [ 'name' => 'gainloss', 'title' => ws_ls_tooltip('+/-', __( 'Difference', WE_LS_SLUG ) ), 'visible'=> true, 'breakpoints'=> 'xs', 'type' => 'text' ]; + $columns[] = [ 'name' => 'gainloss', 'title' => ws_ls_tooltip('+/-', esc_html__( 'Difference', WE_LS_SLUG ) ), 'visible'=> true, 'breakpoints'=> 'xs', 'type' => 'text' ]; } } @@ -419,7 +419,7 @@ function ws_ls_datatable_columns( $arguments = [] ) { if( true === $arguments[ 'enable-bmi' ] && true === ws_ls_bmi_in_tables() ) { - $columns[] = [ 'name' => 'bmi', 'title' => ws_ls_tooltip( __( 'BMI', WE_LS_SLUG ), __( 'Body Mass Index', WE_LS_SLUG ) ), 'breakpoints'=> 'xs', 'type' => 'text' ]; + $columns[] = [ 'name' => 'bmi', 'title' => ws_ls_tooltip( esc_html__( 'BMI', WE_LS_SLUG ), esc_html__( 'Body Mass Index', WE_LS_SLUG ) ), 'breakpoints'=> 'xs', 'type' => 'text' ]; } if ( true === $arguments[ 'enable-meta' ] && @@ -456,7 +456,7 @@ function ws_ls_datatable_columns( $arguments = [] ) { } if ( true === $arguments[ 'enable-notes' ] ) { - $columns[] = [ 'name' => 'notes', 'title' => __( 'Notes', WE_LS_SLUG ), 'breakpoints'=> 'lg', 'type' => 'text' ]; + $columns[] = [ 'name' => 'notes', 'title' => esc_html__( 'Notes', WE_LS_SLUG ), 'breakpoints'=> 'lg', 'type' => 'text' ]; } return apply_filters( 'wlt-filter-front-end-data-table-columns', $columns, $arguments[ 'front-end' ] ); @@ -487,13 +487,13 @@ function ws_ls_data_js_config() { 'base-url' => ws_ls_get_link_to_user_data(), 'base-url-meta-fields' => ws_ls_meta_fields_base_url(), 'base-url-awards' => ws_ls_awards_base_url(), - 'label-add' => __( 'Add' , WE_LS_SLUG ), - 'label-meta-fields-add-button' => __( 'Add Custom Field', WE_LS_SLUG ), - 'label-awards-add-button' => __( 'Add Award', WE_LS_SLUG ), - 'label-confirm-delete' => __( 'Are you sure you want to delete the row?', WE_LS_SLUG ), - 'label-error-delete' => __( 'Unfortunately there was an error deleting the row.', WE_LS_SLUG ), - 'locale-search-text' => __( 'Search', WE_LS_SLUG ), - 'locale-no-results' => __( 'No data found', WE_LS_SLUG ), + 'label-add' => esc_html__( 'Add' , WE_LS_SLUG ), + 'label-meta-fields-add-button' => esc_html__( 'Add Custom Field', WE_LS_SLUG ), + 'label-awards-add-button' => esc_html__( 'Add Award', WE_LS_SLUG ), + 'label-confirm-delete' => esc_html__( 'Are you sure you want to delete the row?', WE_LS_SLUG ), + 'label-error-delete' => esc_html__( 'Unfortunately there was an error deleting the row.', WE_LS_SLUG ), + 'locale-search-text' => esc_html__( 'Search', WE_LS_SLUG ), + 'locale-no-results' => esc_html__( 'No data found', WE_LS_SLUG ), 'hide-display-name' => false ]; // Add some extra config settings if not in admin diff --git a/pro-features/functions-pages.php b/pro-features/functions-pages.php index 3a850196..48bfb995 100644 --- a/pro-features/functions-pages.php +++ b/pro-features/functions-pages.php @@ -8,7 +8,7 @@ function ws_ls_box_user_search_form( $ajax_mode = false ) { - ?>

+ ?>

method="get" action="" @@ -99,7 +99,7 @@ function ws_ls_user_side_bar( $user_id ) { */ function ws_ls_postbox_user_search( $class = 'ws-ls-user-summary-two' ) { - $title = apply_filters( 'wlt-filter-user-search-title', __( 'User Search', WE_LS_SLUG ) ); + $title = apply_filters( 'wlt-filter-user-search-title', esc_html__( 'User Search', WE_LS_SLUG ) ); ?>
The below shows how this value would be setThe below shows what the default getWidth function would look like.
This example shows retrieving a column by name assuming a column called "id" exists. The columns object is an instance of {@link FooTable.Columns}.The below shows column definitions for a row defined as { id: Number, name: String, age: Number }. The ID column has a fixed width, the table is initially sorted on the Name column and the Age column will be hidden on phones.
').text(this.emptyString)); + }, + /** + * Performs the actual drawing of the table rows. + * @instance + * @protected + */ + draw: function(){ + var self = this, $tbody = self.ft.$el.children('tbody'), first = true; + // if we have rows + if (self.array.length > 0){ + self.$empty.detach(); + // loop through them appending to the tbody and then drawing + F.arr.each(self.array, function(row){ + if ((self.expandFirst && first) || self.expandAll){ + row.expanded = true; + first = false; + } + row.draw($tbody); + }); + } else { + // otherwise display the $empty row + self.$empty.children('td').attr('colspan', self.ft.columns.visibleColspan); + $tbody.append(self.$empty); + } + }, + /** + * Loads a JSON array of row objects into the table + * @instance + * @param {Array.} data - An array of row objects to load. + * @param {boolean} [append=false] - Whether or not to append the new rows to the current rows array or to replace them entirely. + */ + load: function(data, append){ + var self = this, rows = $.map(data, function(r){ + return new F.Row(self.ft, self.ft.columns.array, r); + }); + F.arr.each(this.array, function(row){ + row.predraw(); + }); + this.all = (F.is.boolean(append) ? append : false) ? this.all.concat(rows) : rows; + this.array = this.all.slice(0); + this.ft.draw(); + }, + /** + * Expands all visible rows. + * @instance + */ + expand: function(){ + F.arr.each(this.array, function(row){ + row.expand(); + }); + }, + /** + * Collapses all visible rows. + * @instance + */ + collapse: function(){ + F.arr.each(this.array, function(row){ + row.collapse(); + }); + } + }); + + F.components.register('rows', F.Rows, 800); + +})(jQuery, FooTable); +(function(F){ + /** + * An array of JSON objects containing the row data or a jQuery promise that resolves returning the row data. + * @type {(Array.|jQuery.Promise)} + * @default [] + */ + F.Defaults.prototype.rows = []; + + /** + * A string to display when there are no rows in the table. + * @type {string} + * @default "No results" + */ + F.Defaults.prototype.empty = 'No results'; + + /** + * Whether or not the toggle is appended to each row. + * @type {boolean} + * @default true + */ + F.Defaults.prototype.showToggle = true; + + /** + * The CSS selector used to filter row click events. If the event.target property matches the selector the row will be toggled. + * @type {string} + * @default "tr,td,.footable-toggle" + */ + F.Defaults.prototype.toggleSelector = 'tr,td,.footable-toggle'; + + /** + * Specifies which column to display the row toggle in. The only supported values are "first" or "last". + * @type {string} + * @default "first" + */ + F.Defaults.prototype.toggleColumn = 'first'; + + /** + * Whether or not the first rows details are expanded by default when displayed on a device that hides any columns. + * @type {boolean} + */ + F.Defaults.prototype.expandFirst = false; + + /** + * Whether or not all row details are expanded by default when displayed on a device that hides any columns. + * @type {boolean} + */ + F.Defaults.prototype.expandAll = false; +})(FooTable); +(function(F){ + /** + * Loads a JSON array of row objects into the table + * @param {Array.} data - An array of row objects to load. + * @param {boolean} [append=false] - Whether or not to append the new rows to the current rows array or to replace them entirely. + */ + F.Table.prototype.loadRows = function(data, append){ + this.rows.load(data, append); + }; +})(FooTable); +(function(F){ + F.Filter = F.Class.extend(/** @lends FooTable.Filter */{ + /** + * The filter object contains the query to filter by and the columns to apply it to. + * @constructs + * @extends FooTable.Class + * @param {string} name - The name for the filter. + * @param {(string|FooTable.Query)} query - The query for the filter. + * @param {Array.} columns - The columns to apply the query to. + * @param {string} [space="AND"] - How the query treats space chars. + * @param {boolean} [connectors=true] - Whether or not to replace phrase connectors (+.-_) with spaces. + * @param {boolean} [ignoreCase=true] - Whether or not ignore case when matching. + * @param {boolean} [hidden=true] - Whether or not this is a hidden filter. + * @returns {FooTable.Filter} + */ + construct: function(name, query, columns, space, connectors, ignoreCase, hidden){ + /** + * The name of the filter. + * @instance + * @type {string} + */ + this.name = name; + /** + * A string specifying how the filter treats space characters. Can be either "OR" or "AND". + * @instance + * @type {string} + */ + this.space = F.is.string(space) && (space == 'OR' || space == 'AND') ? space : 'AND'; + /** + * Whether or not to replace phrase connectors (+.-_) with spaces before executing the query. + * @instance + * @type {boolean} + */ + this.connectors = F.is.boolean(connectors) ? connectors : true; + /** + * Whether or not ignore case when matching. + * @instance + * @type {boolean} + */ + this.ignoreCase = F.is.boolean(ignoreCase) ? ignoreCase : true; + /** + * Whether or not this is a hidden filter. + * @instance + * @type {boolean} + */ + this.hidden = F.is.boolean(hidden) ? hidden : false; + /** + * The query for the filter. + * @instance + * @type {(string|FooTable.Query)} + */ + this.query = query instanceof F.Query ? query : new F.Query(query, this.space, this.connectors, this.ignoreCase); + /** + * The columns to apply the query to. + * @instance + * @type {Array.} + */ + this.columns = columns; + }, + /** + * Checks if the current filter matches the supplied string. + * If the current query property is a string it will be auto converted to a {@link FooTable.Query} object to perform the match. + * @instance + * @param {string} str - The string to check. + * @returns {boolean} + */ + match: function(str){ + if (!F.is.string(str)) return false; + if (F.is.string(this.query)){ + this.query = new F.Query(this.query, this.space, this.connectors, this.ignoreCase); + } + return this.query instanceof F.Query ? this.query.match(str) : false; + }, + /** + * Checks if the current filter matches the supplied {@link FooTable.Row}. + * @instance + * @param {FooTable.Row} row - The row to check. + * @returns {boolean} + */ + matchRow: function(row){ + var self = this, text = F.arr.map(row.cells, function(cell){ + return F.arr.contains(self.columns, cell.column) ? cell.filterValue : null; + }).join(' '); + return self.match(text); + } + }); + +})(FooTable); +(function ($, F) { + F.Filtering = F.Component.extend(/** @lends FooTable.Filtering */{ + /** + * The filtering component adds a search input and column selector dropdown to the table allowing users to filter the using space delimited queries. + * @constructs + * @extends FooTable.Component + * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. + * @returns {FooTable.Filtering} + */ + construct: function (table) { + // call the constructor of the base class + this._super(table, table.o.filtering.enabled); + + /* PUBLIC */ + /** + * The filters to apply to the current {@link FooTable.Rows#array}. + * @instance + * @type {Array.} + */ + this.filters = table.o.filtering.filters; + /** + * The delay in milliseconds before the query is auto applied after a change. + * @instance + * @type {number} + */ + this.delay = table.o.filtering.delay; + /** + * The minimum number of characters allowed in the search input before it is auto applied. + * @instance + * @type {number} + */ + this.min = table.o.filtering.min; + /** + * Specifies how whitespace in a filter query is handled. + * @instance + * @type {string} + */ + this.space = table.o.filtering.space; + /** + * Whether or not to replace phrase connectors (+.-_) with spaces before executing the query. + * @instance + * @type {boolean} + */ + this.connectors = table.o.filtering.connectors; + /** + * Whether or not ignore case when matching. + * @instance + * @type {boolean} + */ + this.ignoreCase = table.o.filtering.ignoreCase; + /** + * Whether or not search queries are treated as phrases when matching. + * @instance + * @type {boolean} + */ + this.exactMatch = table.o.filtering.exactMatch; + /** + * The placeholder text to display within the search $input. + * @instance + * @type {string} + */ + this.placeholder = table.o.filtering.placeholder; + /** + * The title to display at the top of the search input column select. + * @type {string} + */ + this.dropdownTitle = table.o.filtering.dropdownTitle; + /** + * The position of the $search input within the filtering rows cell. + * @type {string} + */ + this.position = table.o.filtering.position; + /** + * Whether or not to focus the search input after the search/clear button is clicked or after auto applying the search input query. + * @type {boolean} + */ + this.focus = table.o.filtering.focus; + /** + * A selector specifying where to place the filtering components form, if null the form is displayed within a row in the head of the table. + * @type {string} + */ + this.container = table.o.filtering.container; + /** + * The jQuery object of the element containing the entire filtering form. + * @instance + * @type {jQuery} + */ + this.$container = null; + /** + * The jQuery row object that contains all the filtering specific elements. + * @instance + * @type {jQuery} + */ + this.$row = null; + /** + * The jQuery cell object that contains the search input and column selector. + * @instance + * @type {jQuery} + */ + this.$cell = null; + /** + * The jQuery form object of the form that contains the search input and column selector. + * @instance + * @type {jQuery} + */ + this.$form = null; + /** + * The jQuery object of the column selector dropdown. + * @instance + * @type {jQuery} + */ + this.$dropdown = null; + /** + * The jQuery object of the search input. + * @instance + * @type {jQuery} + */ + this.$input = null; + /** + * The jQuery object of the search button. + * @instance + * @type {jQuery} + */ + this.$button = null; + + /* PRIVATE */ + /** + * The timeout ID for the filter changed event. + * @instance + * @private + * @type {?number} + */ + this._filterTimeout = null; + /** + * The regular expression used to check for encapsulating quotations. + * @instance + * @private + * @type {RegExp} + */ + this._exactRegExp = /^"(.*?)"$/; + }, + + /* PROTECTED */ + /** + * Checks the supplied data and options for the filtering component. + * @instance + * @protected + * @param {object} data - The jQuery data object from the parent table. + * @fires FooTable.Filtering#"preinit.ft.filtering" + */ + preinit: function(data){ + var self = this; + /** + * The preinit.ft.filtering event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. + * Calling preventDefault on this event will disable the component. + * @event FooTable.Filtering#"preinit.ft.filtering" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {object} data - The jQuery data object of the table raising the event. + */ + return self.ft.raise('preinit.ft.filtering').then(function(){ + // first check if filtering is enabled via the class being applied + if (self.ft.$el.hasClass('footable-filtering')) + self.enabled = true; + // then check if the data-filtering-enabled attribute has been set + self.enabled = F.is.boolean(data.filtering) + ? data.filtering + : self.enabled; + + // if filtering is not enabled exit early as we don't need to do anything else + if (!self.enabled) return; + + self.space = F.is.string(data.filterSpace) + ? data.filterSpace + : self.space; + + self.min = F.is.number(data.filterMin) + ? data.filterMin + : self.min; + + self.connectors = F.is.boolean(data.filterConnectors) + ? data.filterConnectors + : self.connectors; + + self.ignoreCase = F.is.boolean(data.filterIgnoreCase) + ? data.filterIgnoreCase + : self.ignoreCase; + + self.exactMatch = F.is.boolean(data.filterExactMatch) + ? data.filterExactMatch + : self.exactMatch; + + self.focus = F.is.boolean(data.filterFocus) + ? data.filterFocus + : self.focus; + + self.delay = F.is.number(data.filterDelay) + ? data.filterDelay + : self.delay; + + self.placeholder = F.is.string(data.filterPlaceholder) + ? data.filterPlaceholder + : self.placeholder; + + self.dropdownTitle = F.is.string(data.filterDropdownTitle) + ? data.filterDropdownTitle + : self.dropdownTitle; + + self.container = F.is.string(data.filterContainer) + ? data.filterContainer + : self.container; + + self.filters = F.is.array(data.filterFilters) + ? self.ensure(data.filterFilters) + : self.ensure(self.filters); + + if (self.ft.$el.hasClass('footable-filtering-left')) + self.position = 'left'; + if (self.ft.$el.hasClass('footable-filtering-center')) + self.position = 'center'; + if (self.ft.$el.hasClass('footable-filtering-right')) + self.position = 'right'; + + self.position = F.is.string(data.filterPosition) + ? data.filterPosition + : self.position; + },function(){ + self.enabled = false; + }); + }, + /** + * Initializes the filtering component for the plugin. + * @instance + * @protected + * @fires FooTable.Filtering#"init.ft.filtering" + */ + init: function () { + var self = this; + /** + * The init.ft.filtering event is raised before its UI is generated. + * Calling preventDefault on this event will disable the component. + * @event FooTable.Filtering#"init.ft.filtering" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.ft.raise('init.ft.filtering').then(function(){ + self.$create(); + }, function(){ + self.enabled = false; + }); + }, + /** + * Destroys the filtering component removing any UI from the table. + * @instance + * @protected + * @fires FooTable.Filtering#"destroy.ft.filtering" + */ + destroy: function () { + var self = this; + /** + * The destroy.ft.filtering event is raised before its UI is removed. + * Calling preventDefault on this event will prevent the component from being destroyed. + * @event FooTable.Filtering#"destroy.ft.filtering" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + */ + return self.ft.raise('destroy.ft.filtering').then(function(){ + self.ft.$el.removeClass('footable-filtering') + .find('thead > tr.footable-filtering').remove(); + }); + }, + /** + * Creates the filtering UI from the current options setting the various jQuery properties of this component. + * @instance + * @protected + * @this FooTable.Filtering + */ + $create: function () { + var self = this; + // generate the cell that actually contains all the UI. + var $form_grp = $('
', {'class': 'form-group footable-filtering-search'}) + .append($('
').attr('colspan', self.ft.columns.visibleColspan).appendTo(self.$row); + self.$container = self.$cell; + } else { + self.$container.addClass('footable-filtering-external').addClass(position); + } + self.$form = $('
', {'class': 'form-inline'}).append($form_grp).appendTo(self.$container); + + self.$input = $('', {type: 'text', 'class': 'form-control', placeholder: self.placeholder}); + + self.$button = $('
The below shows what is meant by the "left" side of a queryThe below shows what is meant by the "right" side of a queryThis example shows using pseudo code what a sort function would look like.
').attr('colspan', this.ft.columns.visibleColspan).appendTo(this.$row); + } else { + this.$container.addClass('footable-paging-external').addClass(position); + } + this.$wrapper = $('
', {'class': 'footable-pagination-wrapper'}).appendTo(this.$container); + this.$pagination = $('
').attr('colspan', self.ft.columns.visibleColspan).append(self.$buttonShow()); + if (self.allowAdd){ + self.$cell.append(self.$buttonAdd()); + } + self.$cell.append(self.$buttonHide()); + + if (self.alwaysShow){ + self.ft.$el.addClass('footable-editing-always-show'); + } + + if (!self.allowAdd) self.ft.$el.addClass('footable-editing-no-add'); + if (!self.allowEdit) self.ft.$el.addClass('footable-editing-no-edit'); + if (!self.allowDelete) self.ft.$el.addClass('footable-editing-no-delete'); + if (!self.allowView) self.ft.$el.addClass('footable-editing-no-view'); + + var $tfoot = self.ft.$el.children('tfoot'); + if ($tfoot.length == 0){ + $tfoot = $('
', {'class': 'footable-editing'})).html(this.title); + }, + /** + * This is supplied either the cell value or jQuery object to parse. Any value can be returned from this method and + * will be provided to the {@link FooTable.EditingColumn#format} function + * to generate the cell contents. + * @instance + * @protected + * @param {(*|jQuery)} valueOrElement - The value or jQuery cell object. + * @returns {(jQuery)} + */ + parser: function(valueOrElement){ + if (F.is.string(valueOrElement)) valueOrElement = $($.trim(valueOrElement)); + if (F.is.element(valueOrElement)) valueOrElement = $(valueOrElement); + if (F.is.jq(valueOrElement)){ + var tagName = valueOrElement.prop('tagName').toLowerCase(); + if (tagName == 'td' || tagName == 'th') return valueOrElement.data('value') || valueOrElement.contents(); + return valueOrElement; + } + return null; + }, + /** + * Creates a cell to be used in the supplied row for this column. + * @param {FooTable.Row} row - The row to create the cell for. + * @returns {FooTable.Cell} + */ + createCell: function(row){ + var $buttons = this.editing.$rowButtons(), $cell = $('').append($buttons); + if (F.is.jq(row.$el)){ + if (this.index === 0){ + $cell.prependTo(row.$el); + } else { + $cell.insertAfter(row.$el.children().eq(this.index-1)); + } + } + return new F.Cell(this.ft, row, this, $cell || $cell.html()); + } + }); + + F.columns.register('editing', F.EditingColumn); + +})(jQuery, FooTable); +(function($, F) { + + /** + * An object containing the editing options for the plugin. Added by the {@link FooTable.Editing} component. + * @type {object} + * @prop {boolean} enabled=false - Whether or not to allow editing on the table. + * @prop {boolean} pageToNew=true - Whether or not to automatically page to a new row when it is added to the table. + * @prop {string} position="right" - The position of the editing column in the table as well as the alignment of the buttons. + * @prop {boolean} alwaysShow=false - Whether or not the editing column and add row button are always visible. + * @prop {function} addRow - The callback function to execute when the add row button is clicked. + * @prop {function} editRow - The callback function to execute when the edit row button is clicked. + * @prop {function} deleteRow - The callback function to execute when the delete row button is clicked. + * @prop {function} viewRow - The callback function to execute when the view row button is clicked. + * @prop {string} showText - The text that appears in the show button. This can contain HTML. + * @prop {string} hideText - The text that appears in the hide button. This can contain HTML. + * @prop {string} addText - The text that appears in the add button. This can contain HTML. + * @prop {string} editText - The text that appears in the edit button. This can contain HTML. + * @prop {string} deleteText - The text that appears in the delete button. This can contain HTML. + * @prop {string} viewText - The text that appears in the view button. This can contain HTML. + * @prop {boolean} allowAdd - Whether or not to show the Add Row button. + * @prop {boolean} allowEdit - Whether or not to show the Edit Row button. + * @prop {boolean} allowDelete - Whether or not to show the Delete Row button. + * @prop {boolean} allowView - Whether or not to show the View Row button. + * @prop {object} column - The options for the editing column. @see {@link FooTable.EditingColumn} for more info. + * @prop {string} column.classes="footable-editing" - A space separated string of class names to apply to all cells in the column. + * @prop {string} column.name="editing" - The name of the column. + * @prop {string} column.title="" - The title displayed in the header row of the table for the column. + * @prop {boolean} column.filterable=false - Whether or not the column should be filterable when using the filtering component. + * @prop {boolean} column.sortable=false - Whether or not the column should be sortable when using the sorting component. + */ + F.Defaults.prototype.editing = { + enabled: false, + pageToNew: true, + position: 'right', + alwaysShow: false, + addRow: function(){}, + editRow: function(row){}, + deleteRow: function(row){}, + viewRow: function(row){}, + showText: ' Edit rows', + hideText: 'Cancel', + addText: 'New row', + editText: '', + deleteText: '', + viewText: '', + allowAdd: true, + allowEdit: true, + allowDelete: true, + allowView: false, + column: { + classes: 'footable-editing', + name: 'editing', + title: '', + filterable: false, + sortable: false + } + }; + +})(jQuery, FooTable); + +(function($, F){ + + if (F.is.defined(F.Paging)){ + /** + * Holds a shallow clone of the un-paged {@link FooTable.Rows#array} value before paging occurs and superfluous rows are removed. Added by the {@link FooTable.Editing} component. + * @instance + * @public + * @type {Array} + */ + F.Paging.prototype.unpaged = []; + + // override the default predraw method with one that sets the unpaged property. + F.Paging.extend('predraw', function(){ + this.unpaged = this.ft.rows.array.slice(0); // create a shallow clone for later use + this._super(); // call the original method + }); + } + +})(jQuery, FooTable); +(function($, F){ + + /** + * Adds the row to the table. + * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this + * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. + * @returns {jQuery.Deferred} + */ + F.Row.prototype.add = function(redraw){ + redraw = F.is.boolean(redraw) ? redraw : true; + var self = this; + return $.Deferred(function(d){ + var index = self.ft.rows.all.push(self) - 1; + if (redraw){ + return self.ft.draw().then(function(){ + d.resolve(index); + }); + } else { + d.resolve(index); + } + }); + }; + + /** + * Removes the row from the table. + * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this + * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. + * @returns {jQuery.Deferred} + */ + F.Row.prototype.delete = function(redraw){ + redraw = F.is.boolean(redraw) ? redraw : true; + var self = this; + return $.Deferred(function(d){ + var index = self.ft.rows.all.indexOf(self); + if (F.is.number(index) && index >= 0 && index < self.ft.rows.all.length){ + self.ft.rows.all.splice(index, 1); + if (redraw){ + return self.ft.draw().then(function(){ + d.resolve(self); + }); + } + } + d.resolve(self); + }); + }; + + if (F.is.defined(F.Paging)){ + // override the default add method with one that supports paging + F.Row.extend('add', function(redraw){ + redraw = F.is.boolean(redraw) ? redraw : true; + var self = this, + added = this._super(redraw), + editing = self.ft.use(F.Editing), + paging; + if (editing && editing.pageToNew && (paging = self.ft.use(F.Paging)) && redraw){ + return added.then(function(){ + var index = paging.unpaged.indexOf(self); // find this row in the unpaged array (this array will be sorted and filtered) + var page = Math.ceil((index + 1) / paging.size); // calculate the page the new row is on + if (paging.current !== page){ // goto the page if we need to + return paging.goto(page); + } + }); + } + return added; + }); + } + + if (F.is.defined(F.Sorting)){ + // override the default val method with one that supports sorting and paging + F.Row.extend('val', function(data, redraw){ + redraw = F.is.boolean(redraw) ? redraw : true; + var result = this._super(data); + if (!F.is.hash(data)){ + return result; + } + var self = this; + if (redraw){ + self.ft.draw().then(function(){ + var editing = self.ft.use(F.Editing), paging; + if (F.is.defined(F.Paging) && editing && editing.pageToNew && (paging = self.ft.use(F.Paging))){ + var index = paging.unpaged.indexOf(self); // find this row in the unpaged array (this array will be sorted and filtered) + var page = Math.ceil((index + 1) / paging.size); // calculate the page the new row is on + if (paging.current !== page){ // goto the page if we need to + return paging.goto(page); + } + } + }); + } + return result; + }); + } + +})(jQuery, FooTable); +(function(F){ + + /** + * Adds a row to the underlying {@link FooTable.Rows#all} array. + * @param {(object|FooTable.Row)} dataOrRow - A hash containing the row values or an actual {@link FooTable.Row} object. + * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this + * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. + */ + F.Rows.prototype.add = function(dataOrRow, redraw){ + var row = dataOrRow; + if (F.is.hash(dataOrRow)){ + row = new FooTable.Row(this.ft, this.ft.columns.array, dataOrRow); + } + if (row instanceof FooTable.Row){ + row.add(redraw); + } + }; + + /** + * Updates a row in the underlying {@link FooTable.Rows#all} array. + * @param {(number|FooTable.Row)} indexOrRow - The index to update or the actual {@link FooTable.Row} object. + * @param {object} data - A hash containing the new row values. + * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this + * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. + */ + F.Rows.prototype.update = function(indexOrRow, data, redraw){ + var len = this.ft.rows.all.length, + row = indexOrRow; + if (F.is.number(indexOrRow) && indexOrRow >= 0 && indexOrRow < len){ + row = this.ft.rows.all[indexOrRow]; + } + if (row instanceof FooTable.Row && F.is.hash(data)){ + row.val(data, redraw); + } + }; + + /** + * Deletes a row from the underlying {@link FooTable.Rows#all} array. + * @param {(number|FooTable.Row)} indexOrRow - The index to delete or the actual {@link FooTable.Row} object. + * @param {boolean} [redraw=true] - Whether or not to redraw the table, defaults to true but for bulk operations this + * can be set to false and then followed by a call to the {@link FooTable.Table#draw} method. + */ + F.Rows.prototype.delete = function(indexOrRow, redraw){ + var len = this.ft.rows.all.length, + row = indexOrRow; + if (F.is.number(indexOrRow) && indexOrRow >= 0 && indexOrRow < len){ + row = this.ft.rows.all[indexOrRow]; + } + if (row instanceof FooTable.Row){ + row.delete(redraw); + } + }; + +})(FooTable); + +(function($, F){ + + // global int to use if the table has no ID + var _uid = 0, + // a hash value for the current url + _url_hash = (function(str){ + var i, l, hval = 0x811c9dc5; + for (i = 0, l = str.length; i < l; i++) { + hval ^= str.charCodeAt(i); + hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); + } + return hval >>> 0; + })(location.origin + location.pathname); + + F.State = F.Component.extend(/** @lends FooTable.State */{ + /** + * The state component adds the ability for the table to remember its basic state for filtering, paging and sorting. + * @constructs + * @extends FooTable.Component + * @param {FooTable.Table} table - The parent {@link FooTable.Table} object for the component. + * @returns {FooTable.State} + */ + construct: function(table){ + // call the constructor of the base class + this._super(table, table.o.state.enabled); + // Change this value if an update to this component requires any stored data to be reset + this._key = '1'; + /** + * The key to use to store the state for this table. + * @type {(null|string)} + */ + this.key = this._key + (F.is.string(table.o.state.key) ? table.o.state.key : this._uid()); + /** + * Whether or not to allow the filtering component to store it's state. + * @type {boolean} + */ + this.filtering = F.is.boolean(table.o.state.filtering) ? table.o.state.filtering : true; + /** + * Whether or not to allow the paging component to store it's state. + * @type {boolean} + */ + this.paging = F.is.boolean(table.o.state.paging) ? table.o.state.paging : true; + /** + * Whether or not to allow the sorting component to store it's state. + * @type {boolean} + */ + this.sorting = F.is.boolean(table.o.state.sorting) ? table.o.state.sorting : true; + }, + /* PROTECTED */ + /** + * Checks the supplied data and options for the state component. + * @instance + * @protected + * @param {object} data - The jQuery data object from the parent table. + * @fires FooTable.State#"preinit.ft.state" + * @this FooTable.State + */ + preinit: function(data){ + var self = this; + /** + * The preinit.ft.state event is raised before the UI is created and provides the tables jQuery data object for additional options parsing. + * Calling preventDefault on this event will disable the component. + * @event FooTable.State#"preinit.ft.state" + * @param {jQuery.Event} e - The jQuery.Event object for the event. + * @param {FooTable.Table} ft - The instance of the plugin raising the event. + * @param {object} data - The jQuery data object of the table raising the event. + */ + this.ft.raise('preinit.ft.state', [data]).then(function(){ + + self.enabled = F.is.boolean(data.state) + ? data.state + : self.enabled; + + if (!self.enabled) return; + + self.key = self._key + (F.is.string(data.stateKey) ? data.stateKey : self.key); + + self.filtering = F.is.boolean(data.stateFiltering) ? data.stateFiltering : self.filtering; + + self.paging = F.is.boolean(data.statePaging) ? data.statePaging : self.paging; + + self.sorting = F.is.boolean(data.stateSorting) ? data.stateSorting : self.sorting; + + }, function(){ + self.enabled = false; + }); + }, + /** + * Gets the state value for the specified key for this table. + * @instance + * @param {string} key - The key to get the value for. + * @returns {(*|null)} + */ + get: function(key){ + return JSON.parse(localStorage.getItem(this.key + ':' + key)); + }, + /** + * Sets the state value for the specified key for this table. + * @instance + * @param {string} key - The key to set the value for. + * @param {*} data - The value to store for the key. This value must be JSON.stringify friendly. + */ + set: function(key, data){ + localStorage.setItem(this.key + ':' + key, JSON.stringify(data)); + }, + /** + * Clears the state value for the specified key for this table. + * @instance + * @param {string} key - The key to clear the value for. + */ + remove: function(key){ + localStorage.removeItem(this.key + ':' + key); + }, + /** + * Executes the {@link FooTable.Component#readState} function on all components. + * @instance + */ + read: function(){ + this.ft.execute(false, true, 'readState'); + }, + /** + * Executes the {@link FooTable.Component#writeState} function on all components. + * @instance + */ + write: function(){ + this.ft.execute(false, true, 'writeState'); + }, + /** + * Executes the {@link FooTable.Component#clearState} function on all components. + * @instance + */ + clear: function(){ + this.ft.execute(false, true, 'clearState'); + }, + /** + * Generates a unique identifier for the current {@link FooTable.Table} if one is not supplied through the options. + * This value is a combination of the url hash and either the element ID or an incremented global int value. + * @instance + * @returns {*} + * @private + */ + _uid: function(){ + var id = this.ft.$el.attr('id'); + return _url_hash + '_' + (F.is.string(id) ? id : ++_uid); + } + }); + + F.components.register('state', F.State, 700); + +})(jQuery, FooTable); +(function(F){ + + /** + * This method is called from the {@link FooTable.State#read} method and allows a component to retrieve its' stored state. + * @instance + * @protected + * @function + */ + F.Component.prototype.readState = function(){}; + + /** + * This method is called from the {@link FooTable.State#write} method and allows a component to write its' current state to the store. + * @instance + * @protected + * @function + */ + F.Component.prototype.writeState = function(){}; + + /** + * This method is called from the {@link FooTable.State#clear} method and allows a component to clear any stored state. + * @instance + * @protected + * @function + */ + F.Component.prototype.clearState = function(){}; + +})(FooTable); +(function(F){ + + /** + * An object containing the state options for the plugin. Added by the {@link FooTable.State} component. + * @type {object} + * @prop {boolean} enabled=false - Whether or not to allow state to be stored for the table. This overrides the individual component enable options. + * @prop {boolean} filtering=true - Whether or not to allow the filtering state to be stored. + * @prop {boolean} paging=true - Whether or not to allow the filtering state to be stored. + * @prop {boolean} sorting=true - Whether or not to allow the filtering state to be stored. + * @prop {string} key=null - The unique key to use to store the table's data. + */ + F.Defaults.prototype.state = { + enabled: false, + filtering: true, + paging: true, + sorting: true, + key: null + }; + +})(FooTable); +(function(F){ + + if (!F.Filtering) return; + + /** + * Allows the filtering component to retrieve its' stored state. + */ + F.Filtering.prototype.readState = function(){ + if (this.ft.state.filtering){ + var state = this.ft.state.get('filtering'); + if (F.is.hash(state) && !F.is.emptyArray(state.filters)){ + this.filters = this.ensure(state.filters); + } + } + }; + + /** + * Allows the filtering component to write its' current state to the store. + */ + F.Filtering.prototype.writeState = function(){ + if (this.ft.state.filtering) { + var filters = F.arr.map(this.filters, function (f) { + return { + name: f.name, + query: f.query instanceof F.Query ? f.query.val() : f.query, + columns: F.arr.map(f.columns, function (c) { + return c.name; + }), + hidden: f.hidden, + space: f.space, + connectors: f.connectors, + ignoreCase: f.ignoreCase + }; + }); + this.ft.state.set('filtering', {filters: filters}); + } + }; + + /** + * Allows the filtering component to clear any stored state. + */ + F.Filtering.prototype.clearState = function(){ + if (this.ft.state.filtering) { + this.ft.state.remove('filtering'); + } + }; + +})(FooTable); +(function(F){ + + if (!F.Paging) return; + + /** + * Allows the paging component to retrieve its' stored state. + */ + F.Paging.prototype.readState = function(){ + if (this.ft.state.paging) { + var state = this.ft.state.get('paging'); + if (F.is.hash(state)) { + this.current = state.current; + this.size = state.size; + } + } + }; + + /** + * Allows the paging component to write its' current state to the store. + */ + F.Paging.prototype.writeState = function(){ + if (this.ft.state.paging) { + this.ft.state.set('paging', { + current: this.current, + size: this.size + }); + } + }; + + /** + * Allows the paging component to clear any stored state. + */ + F.Paging.prototype.clearState = function(){ + if (this.ft.state.paging) { + this.ft.state.remove('paging'); + } + }; + +})(FooTable); +(function(F){ + + if (!F.Sorting) return; + + /** + * Allows the sorting component to retrieve its' stored state. + */ + F.Sorting.prototype.readState = function(){ + if (this.ft.state.sorting) { + var state = this.ft.state.get('sorting'); + if (F.is.hash(state)) { + var column = this.ft.columns.get(state.column); + if (column instanceof F.Column) { + this.column = column; + this.column.direction = state.direction; + } + } + } + }; + + /** + * Allows the sorting component to write its' current state to the store. + */ + F.Sorting.prototype.writeState = function(){ + if (this.ft.state.sorting && this.column instanceof F.Column){ + this.ft.state.set('sorting', { + column: this.column.name, + direction: this.column.direction + }); + } + }; + + /** + * Allows the sorting component to clear any stored state. + */ + F.Sorting.prototype.clearState = function(){ + if (this.ft.state.sorting) { + this.ft.state.remove('sorting'); + } + }; + +})(FooTable); +(function(F){ + + // hook into the _construct method so we can add the state property to the table. + F.Table.extend('_construct', function(ready){ + this.state = this.use(FooTable.State); + return this._super(ready); + }); + + // hook into the _preinit method so we can trigger a plugin wide read state operation. + F.Table.extend('_preinit', function(){ + var self = this; + return self._super().then(function(){ + if (self.state.enabled){ + self.state.read(); + } + }); + }); + + // hook into the draw method so we can trigger a plugin wide write state operation. + F.Table.extend('draw', function(){ + var self = this; + return self._super().then(function(){ + if (self.state.enabled){ + self.state.write(); + } + }); + }); + +})(FooTable); +(function($, F){ + + F.Export = F.Component.extend(/** @lends FooTable.Export */{ + /** + * @summary This component provides some basic export functionality. + * @memberof FooTable + * @constructs Export + * @param {FooTable.Table} table - The current instance of the plugin. + */ + construct: function(table){ + // call the constructor of the base class + this._super(table, true); + /** + * @summary A snapshot of the working set of rows prior to being trimmed by the paging component. + * @memberof FooTable.Export# + * @name snapshot + * @type {FooTable.Row[]} + */ + this.snapshot = []; + }, + /** + * @summary Hooks into the predraw pipeline after sorting and filtering have taken place but prior to paging. + * @memberof FooTable.Export# + * @function predraw + * @description This method allows us to take a snapshot of the working set of rows before they are trimmed by the paging component and is called by the plugin instance. + */ + predraw: function(){ + this.snapshot = this.ft.rows.array.slice(0); + }, + /** + * @summary Return the columns as simple JavaScript objects in an array. + * @memberof FooTable.Export# + * @function columns + * @returns {Object[]} + */ + columns: function(){ + var result = []; + F.arr.each(this.ft.columns.array, function(column){ + if (!column.internal){ + result.push({ + type: column.type, + name: column.name, + title: column.title, + visible: column.visible, + hidden: column.hidden, + classes: column.classes, + style: column.style + }); + } + }); + return result; + }, + /** + * @summary Return the rows as simple JavaScript objects in an array. + * @memberof FooTable.Export# + * @function rows + * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. + * @returns {Object[]} + */ + rows: function(filtered){ + filtered = F.is.boolean(filtered) ? filtered : false; + var rows = filtered ? this.ft.rows.all : this.snapshot, result = []; + F.arr.each(rows, function(row){ + result.push(row.val()); + }); + return result; + }, + /** + * @summary Return the columns and rows as a properly formatted JSON object. + * @memberof FooTable.Export# + * @function json + * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. + * @returns {Object} + */ + json: function(filtered){ + return JSON.parse(JSON.stringify({columns: this.columns(),rows: this.rows(filtered)})); + }, + /** + * @summary Return the columns and rows as a properly formatted CSV value. + * @memberof FooTable.Export# + * @function csv + * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. + * @returns {string} + */ + csv: function(filtered){ + var csv = "", columns = this.columns(), value, escaped; + F.arr.each(columns, function(column, i){ + escaped = '"' + column.title.replace(/"/g, '""') + '"'; + csv += (i === 0 ? escaped : "," + escaped); + }); + csv += "\n"; + + var rows = filtered ? this.ft.rows.all : this.snapshot; + F.arr.each(rows, function(row){ + F.arr.each(row.cells, function(cell, i){ + if (!cell.column.internal){ + value = cell.column.stringify.call(cell.column, cell.value, cell.ft.o, cell.row.value); + escaped = '"' + value.replace(/"/g, '""') + '"'; + csv += (i === 0 ? escaped : "," + escaped); + } + }); + csv += "\n"; + }); + return csv; + } + }); + + // register the component using a priority of 490 which falls just after filtering (500) and before paging (400). + F.components.register("export", F.Export, 490); + +})(jQuery, FooTable); +(function(F){ + // this is used to define the filtering specific properties on column creation + F.Column.prototype.__export_define__ = function(definition){ + this.stringify = F.checkFnValue(this, definition.stringify, this.stringify); + }; + + // overrides the public define method and replaces it with our own + F.Column.extend('define', function(definition){ + this._super(definition); // call the base so we don't have to redefine any previously set properties + this.__export_define__(definition); // then call our own + }); + + /** + * @summary Return the supplied value as a string. + * @memberof FooTable.Column# + * @function stringify + * @returns {string} + */ + F.Column.prototype.stringify = function(value, options, rowData){ + return value + ""; + }; + + if (F.is.defined(F.DateColumn)){ + // override the base method for DateColumns + F.DateColumn.prototype.stringify = function(value, options, rowData){ + return F.is.object(value) && F.is.boolean(value._isAMomentObject) && value.isValid() ? value.format(this.formatString) : ''; + }; + } + + // override the base method for ObjectColumns + F.ObjectColumn.prototype.stringify = function(value, options, rowData){ + return F.is.object(value) ? JSON.stringify(value) : ""; + }; + + // override the base method for ArrayColumns + F.ArrayColumn.prototype.stringify = function(value, options, rowData){ + return F.is.array(value) ? JSON.stringify(value) : ""; + }; + +})(FooTable); +(function(F){ + /** + * @summary Return the columns and rows as a properly formatted JSON object. + * @memberof FooTable.Table# + * @function toJSON + * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. + * @returns {Object} + */ + F.Table.prototype.toJSON = function(filtered){ + return this.use(F.Export).json(filtered); + }; + + /** + * @summary Return the columns and rows as a properly formatted CSV value. + * @memberof FooTable.Table# + * @function toCSV + * @param {boolean} [filtered=false] - Whether or not to exclude filtered rows from the result. + * @returns {string} + */ + F.Table.prototype.toCSV = function(filtered){ + return this.use(F.Export).csv(filtered); + }; + +})(FooTable); \ No newline at end of file diff --git a/assets/js/libraries/jquery.validate.js b/assets/js/libraries/jquery.validate.js new file mode 100644 index 00000000..b8d8a7a1 --- /dev/null +++ b/assets/js/libraries/jquery.validate.js @@ -0,0 +1,1365 @@ +/*! + * jQuery Validation Plugin v1.13.1 + * + * http://jqueryvalidation.org/ + * + * Copyright (c) 2014 Jörn Zaefferer + * Released under the MIT license + */ +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + define( ["jquery"], factory ); + } else { + factory( jQuery ); + } +}(function( $ ) { + +$.extend($.fn, { + // http://jqueryvalidation.org/validate/ + validate: function( options ) { + + // if nothing is selected, return nothing; can't chain anyway + if ( !this.length ) { + if ( options && options.debug && window.console ) { + console.warn( "Nothing selected, can't validate, returning nothing." ); + } + return; + } + + // check if a validator for this form was already created + var validator = $.data( this[ 0 ], "validator" ); + if ( validator ) { + return validator; + } + + // Add novalidate tag if HTML5. + this.attr( "novalidate", "novalidate" ); + + validator = new $.validator( options, this[ 0 ] ); + $.data( this[ 0 ], "validator", validator ); + + if ( validator.settings.onsubmit ) { + + this.validateDelegate( ":submit", "click", function( event ) { + if ( validator.settings.submitHandler ) { + validator.submitButton = event.target; + } + // allow suppressing validation by adding a cancel class to the submit button + if ( $( event.target ).hasClass( "cancel" ) ) { + validator.cancelSubmit = true; + } + + // allow suppressing validation by adding the html5 formnovalidate attribute to the submit button + if ( $( event.target ).attr( "formnovalidate" ) !== undefined ) { + validator.cancelSubmit = true; + } + }); + + // validate the form on submit + this.submit( function( event ) { + if ( validator.settings.debug ) { + // prevent form submit to be able to see console output + event.preventDefault(); + } + function handle() { + var hidden, result; + if ( validator.settings.submitHandler ) { + if ( validator.submitButton ) { + // insert a hidden input as a replacement for the missing submit button + hidden = $( "" ) + .attr( "name", validator.submitButton.name ) + .val( $( validator.submitButton ).val() ) + .appendTo( validator.currentForm ); + } + result = validator.settings.submitHandler.call( validator, validator.currentForm, event ); + if ( validator.submitButton ) { + // and clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + if ( result !== undefined ) { + return result; + } + return false; + } + return true; + } + + // prevent submit for invalid forms or custom submit handlers + if ( validator.cancelSubmit ) { + validator.cancelSubmit = false; + return handle(); + } + if ( validator.form() ) { + if ( validator.pendingRequest ) { + validator.formSubmitted = true; + return false; + } + return handle(); + } else { + validator.focusInvalid(); + return false; + } + }); + } + + return validator; + }, + // http://jqueryvalidation.org/valid/ + valid: function() { + var valid, validator; + + if ( $( this[ 0 ] ).is( "form" ) ) { + valid = this.validate().form(); + } else { + valid = true; + validator = $( this[ 0 ].form ).validate(); + this.each( function() { + valid = validator.element( this ) && valid; + }); + } + return valid; + }, + // attributes: space separated list of attributes to retrieve and remove + removeAttrs: function( attributes ) { + var result = {}, + $element = this; + $.each( attributes.split( /\s/ ), function( index, value ) { + result[ value ] = $element.attr( value ); + $element.removeAttr( value ); + }); + return result; + }, + // http://jqueryvalidation.org/rules/ + rules: function( command, argument ) { + var element = this[ 0 ], + settings, staticRules, existingRules, data, param, filtered; + + if ( command ) { + settings = $.data( element.form, "validator" ).settings; + staticRules = settings.rules; + existingRules = $.validator.staticRules( element ); + switch ( command ) { + case "add": + $.extend( existingRules, $.validator.normalizeRule( argument ) ); + // remove messages from rules, but allow them to be set separately + delete existingRules.messages; + staticRules[ element.name ] = existingRules; + if ( argument.messages ) { + settings.messages[ element.name ] = $.extend( settings.messages[ element.name ], argument.messages ); + } + break; + case "remove": + if ( !argument ) { + delete staticRules[ element.name ]; + return existingRules; + } + filtered = {}; + $.each( argument.split( /\s/ ), function( index, method ) { + filtered[ method ] = existingRules[ method ]; + delete existingRules[ method ]; + if ( method === "required" ) { + $( element ).removeAttr( "aria-required" ); + } + }); + return filtered; + } + } + + data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.classRules( element ), + $.validator.attributeRules( element ), + $.validator.dataRules( element ), + $.validator.staticRules( element ) + ), element ); + + // make sure required is at front + if ( data.required ) { + param = data.required; + delete data.required; + data = $.extend( { required: param }, data ); + $( element ).attr( "aria-required", "true" ); + } + + // make sure remote is at back + if ( data.remote ) { + param = data.remote; + delete data.remote; + data = $.extend( data, { remote: param }); + } + + return data; + } +}); + +// Custom selectors +$.extend( $.expr[ ":" ], { + // http://jqueryvalidation.org/blank-selector/ + blank: function( a ) { + return !$.trim( "" + $( a ).val() ); + }, + // http://jqueryvalidation.org/filled-selector/ + filled: function( a ) { + return !!$.trim( "" + $( a ).val() ); + }, + // http://jqueryvalidation.org/unchecked-selector/ + unchecked: function( a ) { + return !$( a ).prop( "checked" ); + } +}); + +// constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +// http://jqueryvalidation.org/jQuery.validator.format/ +$.validator.format = function( source, params ) { + if ( arguments.length === 1 ) { + return function() { + var args = $.makeArray( arguments ); + args.unshift( source ); + return $.validator.format.apply( this, args ); + }; + } + if ( arguments.length > 2 && params.constructor !== Array ) { + params = $.makeArray( arguments ).slice( 1 ); + } + if ( params.constructor !== Array ) { + params = [ params ]; + } + $.each( params, function( i, n ) { + source = source.replace( new RegExp( "\\{" + i + "\\}", "g" ), function() { + return n; + }); + }); + return source; +}; + +$.extend( $.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + validClass: "valid", + errorElement: "label", + focusCleanup: false, + focusInvalid: true, + errorContainer: $( [] ), + errorLabelContainer: $( [] ), + onsubmit: true, + ignore: ":hidden", + ignoreTitle: false, + onfocusin: function( element ) { + this.lastActive = element; + + // Hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup ) { + if ( this.settings.unhighlight ) { + this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + } + this.hideThese( this.errorsFor( element ) ); + } + }, + onfocusout: function( element ) { + if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) { + this.element( element ); + } + }, + onkeyup: function( element, event ) { + if ( event.which === 9 && this.elementValue( element ) === "" ) { + return; + } else if ( element.name in this.submitted || element === this.lastElement ) { + this.element( element ); + } + }, + onclick: function( element ) { + // click on selects, radiobuttons and checkboxes + if ( element.name in this.submitted ) { + this.element( element ); + + // or option elements, check parent select in that case + } else if ( element.parentNode.name in this.submitted ) { + this.element( element.parentNode ); + } + }, + highlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName( element.name ).addClass( errorClass ).removeClass( validClass ); + } else { + $( element ).addClass( errorClass ).removeClass( validClass ); + } + }, + unhighlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName( element.name ).removeClass( errorClass ).addClass( validClass ); + } else { + $( element ).removeClass( errorClass ).addClass( validClass ); + } + } + }, + + // http://jqueryvalidation.org/jQuery.validator.setDefaults/ + setDefaults: function( settings ) { + $.extend( $.validator.defaults, settings ); + }, + + messages: { + required: "This field is required.", + remote: "Please fix this field.", + email: "Please enter a valid email address.", + url: "Please enter a valid URL.", + date: "Please enter a valid date.", + dateISO: "Please enter a valid date ( ISO ).", + number: "Please enter a valid number.", + digits: "Please enter only digits.", + creditcard: "Please enter a valid credit card number.", + equalTo: "Please enter the same value again.", + maxlength: $.validator.format( "Please enter no more than {0} characters." ), + minlength: $.validator.format( "Please enter at least {0} characters." ), + rangelength: $.validator.format( "Please enter a value between {0} and {1} characters long." ), + range: $.validator.format( "Please enter a value between {0} and {1}." ), + max: $.validator.format( "Please enter a value less than or equal to {0}." ), + min: $.validator.format( "Please enter a value greater than or equal to {0}." ) + }, + + autoCreateRanges: false, + + prototype: { + + init: function() { + this.labelContainer = $( this.settings.errorLabelContainer ); + this.errorContext = this.labelContainer.length && this.labelContainer || $( this.currentForm ); + this.containers = $( this.settings.errorContainer ).add( this.settings.errorLabelContainer ); + this.submitted = {}; + this.valueCache = {}; + this.pendingRequest = 0; + this.pending = {}; + this.invalid = {}; + this.reset(); + + var groups = ( this.groups = {} ), + rules; + $.each( this.settings.groups, function( key, value ) { + if ( typeof value === "string" ) { + value = value.split( /\s/ ); + } + $.each( value, function( index, name ) { + groups[ name ] = key; + }); + }); + rules = this.settings.rules; + $.each( rules, function( key, value ) { + rules[ key ] = $.validator.normalizeRule( value ); + }); + + function delegate( event ) { + var validator = $.data( this[ 0 ].form, "validator" ), + eventType = "on" + event.type.replace( /^validate/, "" ), + settings = validator.settings; + if ( settings[ eventType ] && !this.is( settings.ignore ) ) { + settings[ eventType ].call( validator, this[ 0 ], event ); + } + } + $( this.currentForm ) + .validateDelegate( ":text, [type='password'], [type='file'], select, textarea, " + + "[type='number'], [type='search'] ,[type='tel'], [type='url'], " + + "[type='email'], [type='datetime'], [type='date'], [type='month'], " + + "[type='week'], [type='time'], [type='datetime-local'], " + + "[type='range'], [type='color'], [type='radio'], [type='checkbox']", + "focusin focusout keyup", delegate) + // Support: Chrome, oldIE + // "select" is provided as event.target when clicking a option + .validateDelegate("select, option, [type='radio'], [type='checkbox']", "click", delegate); + + if ( this.settings.invalidHandler ) { + $( this.currentForm ).bind( "invalid-form.validate", this.settings.invalidHandler ); + } + + // Add aria-required to any Static/Data/Class required fields before first validation + // Screen readers require this attribute to be present before the initial submission http://www.w3.org/TR/WCAG-TECHS/ARIA2.html + $( this.currentForm ).find( "[required], [data-rule-required], .required" ).attr( "aria-required", "true" ); + }, + + // http://jqueryvalidation.org/Validator.form/ + form: function() { + this.checkForm(); + $.extend( this.submitted, this.errorMap ); + this.invalid = $.extend({}, this.errorMap ); + if ( !this.valid() ) { + $( this.currentForm ).triggerHandler( "invalid-form", [ this ]); + } + this.showErrors(); + return this.valid(); + }, + + checkForm: function() { + this.prepareForm(); + for ( var i = 0, elements = ( this.currentElements = this.elements() ); elements[ i ]; i++ ) { + this.check( elements[ i ] ); + } + return this.valid(); + }, + + // http://jqueryvalidation.org/Validator.element/ + element: function( element ) { + var cleanElement = this.clean( element ), + checkElement = this.validationTargetFor( cleanElement ), + result = true; + + this.lastElement = checkElement; + + if ( checkElement === undefined ) { + delete this.invalid[ cleanElement.name ]; + } else { + this.prepareElement( checkElement ); + this.currentElements = $( checkElement ); + + result = this.check( checkElement ) !== false; + if ( result ) { + delete this.invalid[ checkElement.name ]; + } else { + this.invalid[ checkElement.name ] = true; + } + } + // Add aria-invalid status for screen readers + $( element ).attr( "aria-invalid", !result ); + + if ( !this.numberOfInvalids() ) { + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + return result; + }, + + // http://jqueryvalidation.org/Validator.showErrors/ + showErrors: function( errors ) { + if ( errors ) { + // add items to error list and map + $.extend( this.errorMap, errors ); + this.errorList = []; + for ( var name in errors ) { + this.errorList.push({ + message: errors[ name ], + element: this.findByName( name )[ 0 ] + }); + } + // remove items from success list + this.successList = $.grep( this.successList, function( element ) { + return !( element.name in errors ); + }); + } + if ( this.settings.showErrors ) { + this.settings.showErrors.call( this, this.errorMap, this.errorList ); + } else { + this.defaultShowErrors(); + } + }, + + // http://jqueryvalidation.org/Validator.resetForm/ + resetForm: function() { + if ( $.fn.resetForm ) { + $( this.currentForm ).resetForm(); + } + this.submitted = {}; + this.lastElement = null; + this.prepareForm(); + this.hideErrors(); + this.elements() + .removeClass( this.settings.errorClass ) + .removeData( "previousValue" ) + .removeAttr( "aria-invalid" ); + }, + + numberOfInvalids: function() { + return this.objectLength( this.invalid ); + }, + + objectLength: function( obj ) { + /* jshint unused: false */ + var count = 0, + i; + for ( i in obj ) { + count++; + } + return count; + }, + + hideErrors: function() { + this.hideThese( this.toHide ); + }, + + hideThese: function( errors ) { + errors.not( this.containers ).text( "" ); + this.addWrapper( errors ).hide(); + }, + + valid: function() { + return this.size() === 0; + }, + + size: function() { + return this.errorList.length; + }, + + focusInvalid: function() { + if ( this.settings.focusInvalid ) { + try { + $( this.findLastActive() || this.errorList.length && this.errorList[ 0 ].element || []) + .filter( ":visible" ) + .focus() + // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find + .trigger( "focusin" ); + } catch ( e ) { + // ignore IE throwing errors when focusing hidden elements + } + } + }, + + findLastActive: function() { + var lastActive = this.lastActive; + return lastActive && $.grep( this.errorList, function( n ) { + return n.element.name === lastActive.name; + }).length === 1 && lastActive; + }, + + elements: function() { + var validator = this, + rulesCache = {}; + + // select all valid inputs inside the form (no submit or reset buttons) + return $( this.currentForm ) + .find( "input, select, textarea" ) + .not( ":submit, :reset, :image, [disabled], [readonly]" ) + .not( this.settings.ignore ) + .filter( function() { + if ( !this.name && validator.settings.debug && window.console ) { + console.error( "%o has no name assigned", this ); + } + + // select only the first element for each name, and only those with rules specified + if ( this.name in rulesCache || !validator.objectLength( $( this ).rules() ) ) { + return false; + } + + rulesCache[ this.name ] = true; + return true; + }); + }, + + clean: function( selector ) { + return $( selector )[ 0 ]; + }, + + errors: function() { + var errorClass = this.settings.errorClass.split( " " ).join( "." ); + return $( this.settings.errorElement + "." + errorClass, this.errorContext ); + }, + + reset: function() { + this.successList = []; + this.errorList = []; + this.errorMap = {}; + this.toShow = $( [] ); + this.toHide = $( [] ); + this.currentElements = $( [] ); + }, + + prepareForm: function() { + this.reset(); + this.toHide = this.errors().add( this.containers ); + }, + + prepareElement: function( element ) { + this.reset(); + this.toHide = this.errorsFor( element ); + }, + + elementValue: function( element ) { + var val, + $element = $( element ), + type = element.type; + + if ( type === "radio" || type === "checkbox" ) { + return $( "input[name='" + element.name + "']:checked" ).val(); + } else if ( type === "number" && typeof element.validity !== "undefined" ) { + return element.validity.badInput ? false : $element.val(); + } + + val = $element.val(); + if ( typeof val === "string" ) { + return val.replace(/\r/g, "" ); + } + return val; + }, + + check: function( element ) { + element = this.validationTargetFor( this.clean( element ) ); + + var rules = $( element ).rules(), + rulesCount = $.map( rules, function( n, i ) { + return i; + }).length, + dependencyMismatch = false, + val = this.elementValue( element ), + result, method, rule; + + for ( method in rules ) { + rule = { method: method, parameters: rules[ method ] }; + try { + + result = $.validator.methods[ method ].call( this, val, element, rule.parameters ); + + // if a method indicates that the field is optional and therefore valid, + // don't mark it as valid when there are no other rules + if ( result === "dependency-mismatch" && rulesCount === 1 ) { + dependencyMismatch = true; + continue; + } + dependencyMismatch = false; + + if ( result === "pending" ) { + this.toHide = this.toHide.not( this.errorsFor( element ) ); + return; + } + + if ( !result ) { + this.formatAndAdd( element, rule ); + return false; + } + } catch ( e ) { + if ( this.settings.debug && window.console ) { + console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); + } + throw e; + } + } + if ( dependencyMismatch ) { + return; + } + if ( this.objectLength( rules ) ) { + this.successList.push( element ); + } + return true; + }, + + // return the custom message for the given element and validation method + // specified in the element's HTML5 data attribute + // return the generic message if present and no method specific message is present + customDataMessage: function( element, method ) { + return $( element ).data( "msg" + method.charAt( 0 ).toUpperCase() + + method.substring( 1 ).toLowerCase() ) || $( element ).data( "msg" ); + }, + + // return the custom message for the given element name and validation method + customMessage: function( name, method ) { + var m = this.settings.messages[ name ]; + return m && ( m.constructor === String ? m : m[ method ]); + }, + + // return the first defined argument, allowing empty strings + findDefined: function() { + for ( var i = 0; i < arguments.length; i++) { + if ( arguments[ i ] !== undefined ) { + return arguments[ i ]; + } + } + return undefined; + }, + + defaultMessage: function( element, method ) { + return this.findDefined( + this.customMessage( element.name, method ), + this.customDataMessage( element, method ), + // title is never undefined, so handle empty string as undefined + !this.settings.ignoreTitle && element.title || undefined, + $.validator.messages[ method ], + "Warning: No message defined for " + element.name + "" + ); + }, + + formatAndAdd: function( element, rule ) { + var message = this.defaultMessage( element, rule.method ), + theregex = /\$?\{(\d+)\}/g; + if ( typeof message === "function" ) { + message = message.call( this, rule.parameters, element ); + } else if ( theregex.test( message ) ) { + message = $.validator.format( message.replace( theregex, "{$1}" ), rule.parameters ); + } + this.errorList.push({ + message: message, + element: element, + method: rule.method + }); + + this.errorMap[ element.name ] = message; + this.submitted[ element.name ] = message; + }, + + addWrapper: function( toToggle ) { + if ( this.settings.wrapper ) { + toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); + } + return toToggle; + }, + + defaultShowErrors: function() { + var i, elements, error; + for ( i = 0; this.errorList[ i ]; i++ ) { + error = this.errorList[ i ]; + if ( this.settings.highlight ) { + this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); + } + this.showLabel( error.element, error.message ); + } + if ( this.errorList.length ) { + this.toShow = this.toShow.add( this.containers ); + } + if ( this.settings.success ) { + for ( i = 0; this.successList[ i ]; i++ ) { + this.showLabel( this.successList[ i ] ); + } + } + if ( this.settings.unhighlight ) { + for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) { + this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass ); + } + } + this.toHide = this.toHide.not( this.toShow ); + this.hideErrors(); + this.addWrapper( this.toShow ).show(); + }, + + validElements: function() { + return this.currentElements.not( this.invalidElements() ); + }, + + invalidElements: function() { + return $( this.errorList ).map(function() { + return this.element; + }); + }, + + showLabel: function( element, message ) { + var place, group, errorID, + error = this.errorsFor( element ), + elementID = this.idOrName( element ), + describedBy = $( element ).attr( "aria-describedby" ); + if ( error.length ) { + // refresh error/success class + error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); + // replace message on existing label + error.html( message ); + } else { + // create error element + error = $( "<" + this.settings.errorElement + ">" ) + .attr( "id", elementID + "-error" ) + .addClass( this.settings.errorClass ) + .html( message || "" ); + + // Maintain reference to the element to be placed into the DOM + place = error; + if ( this.settings.wrapper ) { + // make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + place = error.hide().show().wrap( "<" + this.settings.wrapper + "/>" ).parent(); + } + if ( this.labelContainer.length ) { + this.labelContainer.append( place ); + } else if ( this.settings.errorPlacement ) { + this.settings.errorPlacement( place, $( element ) ); + } else { + place.insertAfter( element ); + } + + // Link error back to the element + if ( error.is( "label" ) ) { + // If the error is a label, then associate using 'for' + error.attr( "for", elementID ); + } else if ( error.parents( "label[for='" + elementID + "']" ).length === 0 ) { + // If the element is not a child of an associated label, then it's necessary + // to explicitly apply aria-describedby + + errorID = error.attr( "id" ).replace( /(:|\.|\[|\])/g, "\\$1"); + // Respect existing non-error aria-describedby + if ( !describedBy ) { + describedBy = errorID; + } else if ( !describedBy.match( new RegExp( "\\b" + errorID + "\\b" ) ) ) { + // Add to end of list if not already present + describedBy += " " + errorID; + } + $( element ).attr( "aria-describedby", describedBy ); + + // If this element is grouped, then assign to all elements in the same group + group = this.groups[ element.name ]; + if ( group ) { + $.each( this.groups, function( name, testgroup ) { + if ( testgroup === group ) { + $( "[name='" + name + "']", this.currentForm ) + .attr( "aria-describedby", error.attr( "id" ) ); + } + }); + } + } + } + if ( !message && this.settings.success ) { + error.text( "" ); + if ( typeof this.settings.success === "string" ) { + error.addClass( this.settings.success ); + } else { + this.settings.success( error, element ); + } + } + this.toShow = this.toShow.add( error ); + }, + + errorsFor: function( element ) { + var name = this.idOrName( element ), + describer = $( element ).attr( "aria-describedby" ), + selector = "label[for='" + name + "'], label[for='" + name + "'] *"; + + // aria-describedby should directly reference the error element + if ( describer ) { + selector = selector + ", #" + describer.replace( /\s+/g, ", #" ); + } + return this + .errors() + .filter( selector ); + }, + + idOrName: function( element ) { + return this.groups[ element.name ] || ( this.checkable( element ) ? element.name : element.id || element.name ); + }, + + validationTargetFor: function( element ) { + + // If radio/checkbox, validate first element in group instead + if ( this.checkable( element ) ) { + element = this.findByName( element.name ); + } + + // Always apply ignore filter + return $( element ).not( this.settings.ignore )[ 0 ]; + }, + + checkable: function( element ) { + return ( /radio|checkbox/i ).test( element.type ); + }, + + findByName: function( name ) { + return $( this.currentForm ).find( "[name='" + name + "']" ); + }, + + getLength: function( value, element ) { + switch ( element.nodeName.toLowerCase() ) { + case "select": + return $( "option:selected", element ).length; + case "input": + if ( this.checkable( element ) ) { + return this.findByName( element.name ).filter( ":checked" ).length; + } + } + return value.length; + }, + + depend: function( param, element ) { + return this.dependTypes[typeof param] ? this.dependTypes[typeof param]( param, element ) : true; + }, + + dependTypes: { + "boolean": function( param ) { + return param; + }, + "string": function( param, element ) { + return !!$( param, element.form ).length; + }, + "function": function( param, element ) { + return param( element ); + } + }, + + optional: function( element ) { + var val = this.elementValue( element ); + return !$.validator.methods.required.call( this, val, element ) && "dependency-mismatch"; + }, + + startRequest: function( element ) { + if ( !this.pending[ element.name ] ) { + this.pendingRequest++; + this.pending[ element.name ] = true; + } + }, + + stopRequest: function( element, valid ) { + this.pendingRequest--; + // sometimes synchronization fails, make sure pendingRequest is never < 0 + if ( this.pendingRequest < 0 ) { + this.pendingRequest = 0; + } + delete this.pending[ element.name ]; + if ( valid && this.pendingRequest === 0 && this.formSubmitted && this.form() ) { + $( this.currentForm ).submit(); + this.formSubmitted = false; + } else if (!valid && this.pendingRequest === 0 && this.formSubmitted ) { + $( this.currentForm ).triggerHandler( "invalid-form", [ this ]); + this.formSubmitted = false; + } + }, + + previousValue: function( element ) { + return $.data( element, "previousValue" ) || $.data( element, "previousValue", { + old: null, + valid: true, + message: this.defaultMessage( element, "remote" ) + }); + } + + }, + + classRuleSettings: { + required: { required: true }, + email: { email: true }, + url: { url: true }, + date: { date: true }, + dateISO: { dateISO: true }, + number: { number: true }, + digits: { digits: true }, + creditcard: { creditcard: true } + }, + + addClassRules: function( className, rules ) { + if ( className.constructor === String ) { + this.classRuleSettings[ className ] = rules; + } else { + $.extend( this.classRuleSettings, className ); + } + }, + + classRules: function( element ) { + var rules = {}, + classes = $( element ).attr( "class" ); + + if ( classes ) { + $.each( classes.split( " " ), function() { + if ( this in $.validator.classRuleSettings ) { + $.extend( rules, $.validator.classRuleSettings[ this ]); + } + }); + } + return rules; + }, + + attributeRules: function( element ) { + var rules = {}, + $element = $( element ), + type = element.getAttribute( "type" ), + method, value; + + for ( method in $.validator.methods ) { + + // support for in both html5 and older browsers + if ( method === "required" ) { + value = element.getAttribute( method ); + // Some browsers return an empty string for the required attribute + // and non-HTML5 browsers might have required="" markup + if ( value === "" ) { + value = true; + } + // force non-HTML5 browsers to return bool + value = !!value; + } else { + value = $element.attr( method ); + } + + // convert the value to a number for number inputs, and for text for backwards compability + // allows type="date" and others to be compared as strings + if ( /min|max/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) { + value = Number( value ); + } + + if ( value || value === 0 ) { + rules[ method ] = value; + } else if ( type === method && type !== "range" ) { + // exception: the jquery validate 'range' method + // does not test for the html5 'range' type + rules[ method ] = true; + } + } + + // maxlength may be returned as -1, 2147483647 ( IE ) and 524288 ( safari ) for text inputs + if ( rules.maxlength && /-1|2147483647|524288/.test( rules.maxlength ) ) { + delete rules.maxlength; + } + + return rules; + }, + + dataRules: function( element ) { + var method, value, + rules = {}, $element = $( element ); + for ( method in $.validator.methods ) { + value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() ); + if ( value !== undefined ) { + rules[ method ] = value; + } + } + return rules; + }, + + staticRules: function( element ) { + var rules = {}, + validator = $.data( element.form, "validator" ); + + if ( validator.settings.rules ) { + rules = $.validator.normalizeRule( validator.settings.rules[ element.name ] ) || {}; + } + return rules; + }, + + normalizeRules: function( rules, element ) { + // handle dependency check + $.each( rules, function( prop, val ) { + // ignore rule when param is explicitly false, eg. required:false + if ( val === false ) { + delete rules[ prop ]; + return; + } + if ( val.param || val.depends ) { + var keepRule = true; + switch ( typeof val.depends ) { + case "string": + keepRule = !!$( val.depends, element.form ).length; + break; + case "function": + keepRule = val.depends.call( element, element ); + break; + } + if ( keepRule ) { + rules[ prop ] = val.param !== undefined ? val.param : true; + } else { + delete rules[ prop ]; + } + } + }); + + // evaluate parameters + $.each( rules, function( rule, parameter ) { + rules[ rule ] = $.isFunction( parameter ) ? parameter( element ) : parameter; + }); + + // clean number parameters + $.each([ "minlength", "maxlength" ], function() { + if ( rules[ this ] ) { + rules[ this ] = Number( rules[ this ] ); + } + }); + $.each([ "rangelength", "range" ], function() { + var parts; + if ( rules[ this ] ) { + if ( $.isArray( rules[ this ] ) ) { + rules[ this ] = [ Number( rules[ this ][ 0 ]), Number( rules[ this ][ 1 ] ) ]; + } else if ( typeof rules[ this ] === "string" ) { + parts = rules[ this ].replace(/[\[\]]/g, "" ).split( /[\s,]+/ ); + rules[ this ] = [ Number( parts[ 0 ]), Number( parts[ 1 ] ) ]; + } + } + }); + + if ( $.validator.autoCreateRanges ) { + // auto-create ranges + if ( rules.min != null && rules.max != null ) { + rules.range = [ rules.min, rules.max ]; + delete rules.min; + delete rules.max; + } + if ( rules.minlength != null && rules.maxlength != null ) { + rules.rangelength = [ rules.minlength, rules.maxlength ]; + delete rules.minlength; + delete rules.maxlength; + } + } + + return rules; + }, + + // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} + normalizeRule: function( data ) { + if ( typeof data === "string" ) { + var transformed = {}; + $.each( data.split( /\s/ ), function() { + transformed[ this ] = true; + }); + data = transformed; + } + return data; + }, + + // http://jqueryvalidation.org/jQuery.validator.addMethod/ + addMethod: function( name, method, message ) { + $.validator.methods[ name ] = method; + $.validator.messages[ name ] = message !== undefined ? message : $.validator.messages[ name ]; + if ( method.length < 3 ) { + $.validator.addClassRules( name, $.validator.normalizeRule( name ) ); + } + }, + + methods: { + + // http://jqueryvalidation.org/required-method/ + required: function( value, element, param ) { + // check if dependency is met + if ( !this.depend( param, element ) ) { + return "dependency-mismatch"; + } + if ( element.nodeName.toLowerCase() === "select" ) { + // could be an array for select-multiple or a string, both are fine this way + var val = $( element ).val(); + return val && val.length > 0; + } + if ( this.checkable( element ) ) { + return this.getLength( value, element ) > 0; + } + return $.trim( value ).length > 0; + }, + + // http://jqueryvalidation.org/email-method/ + email: function( value, element ) { + // From http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#e-mail-state-%28type=email%29 + // Retrieved 2014-01-14 + // If you have a problem with this implementation, report a bug against the above spec + // Or use custom methods to implement your own email validation + return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value ); + }, + + // http://jqueryvalidation.org/url-method/ + url: function( value, element ) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ + return this.optional( element ) || /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test( value ); + }, + + // http://jqueryvalidation.org/date-method/ + date: function( value, element ) { + return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() ); + }, + + // http://jqueryvalidation.org/dateISO-method/ + dateISO: function( value, element ) { + return this.optional( element ) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test( value ); + }, + + // http://jqueryvalidation.org/number-method/ + number: function( value, element ) { + return this.optional( element ) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value ); + }, + + // http://jqueryvalidation.org/digits-method/ + digits: function( value, element ) { + return this.optional( element ) || /^\d+$/.test( value ); + }, + + // http://jqueryvalidation.org/creditcard-method/ + // based on http://en.wikipedia.org/wiki/Luhn/ + creditcard: function( value, element ) { + if ( this.optional( element ) ) { + return "dependency-mismatch"; + } + // accept only spaces, digits and dashes + if ( /[^0-9 \-]+/.test( value ) ) { + return false; + } + var nCheck = 0, + nDigit = 0, + bEven = false, + n, cDigit; + + value = value.replace( /\D/g, "" ); + + // Basing min and max length on + // http://developer.ean.com/general_info/Valid_Credit_Card_Types + if ( value.length < 13 || value.length > 19 ) { + return false; + } + + for ( n = value.length - 1; n >= 0; n--) { + cDigit = value.charAt( n ); + nDigit = parseInt( cDigit, 10 ); + if ( bEven ) { + if ( ( nDigit *= 2 ) > 9 ) { + nDigit -= 9; + } + } + nCheck += nDigit; + bEven = !bEven; + } + + return ( nCheck % 10 ) === 0; + }, + + // http://jqueryvalidation.org/minlength-method/ + minlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || length >= param; + }, + + // http://jqueryvalidation.org/maxlength-method/ + maxlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || length <= param; + }, + + // http://jqueryvalidation.org/rangelength-method/ + rangelength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] ); + }, + + // http://jqueryvalidation.org/min-method/ + min: function( value, element, param ) { + return this.optional( element ) || value >= param; + }, + + // http://jqueryvalidation.org/max-method/ + max: function( value, element, param ) { + return this.optional( element ) || value <= param; + }, + + // http://jqueryvalidation.org/range-method/ + range: function( value, element, param ) { + return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] ); + }, + + // http://jqueryvalidation.org/equalTo-method/ + equalTo: function( value, element, param ) { + // bind to the blur event of the target in order to revalidate whenever the target field is updated + // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead + var target = $( param ); + if ( this.settings.onfocusout ) { + target.unbind( ".validate-equalTo" ).bind( "blur.validate-equalTo", function() { + $( element ).valid(); + }); + } + return value === target.val(); + }, + + // http://jqueryvalidation.org/remote-method/ + remote: function( value, element, param ) { + if ( this.optional( element ) ) { + return "dependency-mismatch"; + } + + var previous = this.previousValue( element ), + validator, data; + + if (!this.settings.messages[ element.name ] ) { + this.settings.messages[ element.name ] = {}; + } + previous.originalMessage = this.settings.messages[ element.name ].remote; + this.settings.messages[ element.name ].remote = previous.message; + + param = typeof param === "string" && { url: param } || param; + + if ( previous.old === value ) { + return previous.valid; + } + + previous.old = value; + validator = this; + this.startRequest( element ); + data = {}; + data[ element.name ] = value; + $.ajax( $.extend( true, { + url: param, + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + context: validator.currentForm, + success: function( response ) { + var valid = response === true || response === "true", + errors, message, submitted; + + validator.settings.messages[ element.name ].remote = previous.originalMessage; + if ( valid ) { + submitted = validator.formSubmitted; + validator.prepareElement( element ); + validator.formSubmitted = submitted; + validator.successList.push( element ); + delete validator.invalid[ element.name ]; + validator.showErrors(); + } else { + errors = {}; + message = response || validator.defaultMessage( element, "remote" ); + errors[ element.name ] = previous.message = $.isFunction( message ) ? message( value ) : message; + validator.invalid[ element.name ] = true; + validator.showErrors( errors ); + } + previous.valid = valid; + validator.stopRequest( element, valid ); + } + }, param ) ); + return "pending"; + } + + } + +}); + +$.format = function deprecated() { + throw "$.format has been deprecated. Please use $.validator.format instead."; +}; + +// ajax mode: abort +// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() + +var pendingRequests = {}, + ajax; +// Use a prefilter if available (1.5+) +if ( $.ajaxPrefilter ) { + $.ajaxPrefilter(function( settings, _, xhr ) { + var port = settings.port; + if ( settings.mode === "abort" ) { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + pendingRequests[port] = xhr; + } + }); +} else { + // Proxy ajax + ajax = $.ajax; + $.ajax = function( settings ) { + var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, + port = ( "port" in settings ? settings : $.ajaxSettings ).port; + if ( mode === "abort" ) { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + pendingRequests[port] = ajax.apply(this, arguments); + return pendingRequests[port]; + } + return ajax.apply(this, arguments); + }; +} + +// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation +// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target + +$.extend($.fn, { + validateDelegate: function( delegate, type, handler ) { + return this.bind(type, function( event ) { + var target = $(event.target); + if ( target.is(delegate) ) { + return handler.apply(target, arguments); + } + }); + } +}); + +})); \ No newline at end of file diff --git a/assets/js/libraries/jquery.validate.min.js b/assets/js/libraries/jquery.validate.min.js index 3a8cdb2d..b3973a4e 100644 --- a/assets/js/libraries/jquery.validate.min.js +++ b/assets/js/libraries/jquery.validate.min.js @@ -1,4 +1,21 @@ -/*! jQuery Validation Plugin - v1.13.1 - 10/14/2014 - * http://jqueryvalidation.org/ - * Copyright (c) 2014 Jörn Zaefferer; Licensed MIT */ +/** + * selectize.js (v0.13.6) + * Copyright (c) 2013–2015 Brian Reavis & contributors + * Copyright (c) 2020-2022 Selectize Team & contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. You may obtain a copy of the License at: + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF + * ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + * + * @author Brian Reavis + * @author Ris Adams + */ + +/*jshint curly:false */ +/*jshint browser:true */ !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.validateDelegate(":submit","click",function(b){c.settings.submitHandler&&(c.submitButton=b.target),a(b.target).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(b.target).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.submit(function(b){function d(){var d,e;return c.settings.submitHandler?(c.submitButton&&(d=a("").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),e=c.settings.submitHandler.call(c,c.currentForm,b),c.submitButton&&d.remove(),void 0!==e?e:!1):!0}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c;return a(this[0]).is("form")?b=this.validate().form():(b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b})),b},removeAttrs:function(b){var c={},d=this;return a.each(b.split(/\s/),function(a,b){c[b]=d.attr(b),d.removeAttr(b)}),c},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(b,c){i[c]=f[c],delete f[c],"required"===c&&a(j).removeAttr("aria-required")}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g),a(j).attr("aria-required","true")),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}),a.extend(a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){return!!a.trim(""+a(b).val())},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(a,b){(9!==b.which||""!==this.elementValue(a))&&(a.name in this.submitted||a===this.lastElement)&&this.element(a)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.",url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date ( ISO ).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c=a.data(this[0].form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!this.is(e.ignore)&&e[d].call(c,this[0],b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).validateDelegate(":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'] ,[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox']","focusin focusout keyup",b).validateDelegate("select, option, [type='radio'], [type='checkbox']","click",b),this.settings.invalidHandler&&a(this.currentForm).bind("invalid-form.validate",this.settings.invalidHandler),a(this.currentForm).find("[required], [data-rule-required], .required").attr("aria-required","true")},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]);return this.valid()},element:function(b){var c=this.clean(b),d=this.validationTargetFor(c),e=!0;return this.lastElement=d,void 0===d?delete this.invalid[c.name]:(this.prepareElement(d),this.currentElements=a(d),e=this.check(d)!==!1,e?delete this.invalid[d.name]:this.invalid[d.name]=!0),a(b).attr("aria-invalid",!e),this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),e},showErrors:function(b){if(b){a.extend(this.errorMap,b),this.errorList=[];for(var c in b)this.errorList.push({message:b[c],element:this.findByName(c)[0]});this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.submitted={},this.lastElement=null,this.prepareForm(),this.hideErrors(),this.elements().removeClass(this.settings.errorClass).removeData("previousValue").removeAttr("aria-invalid")},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, [disabled], [readonly]").not(this.settings.ignore).filter(function(){return!this.name&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.name in c||!b.objectLength(a(this).rules())?!1:(c[this.name]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},reset:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([]),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d=a(b),e=b.type;return"radio"===e||"checkbox"===e?a("input[name='"+b.name+"']:checked").val():"number"===e&&"undefined"!=typeof b.validity?b.validity.badInput?!1:d.val():(c=d.val(),"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f=a(b).rules(),g=a.map(f,function(a,b){return b}).length,h=!1,i=this.elementValue(b);for(d in f){e={method:d,parameters:f[d]};try{if(c=a.validator.methods[d].call(this,i,b,e.parameters),"dependency-mismatch"===c&&1===g){h=!0;continue}if(h=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(j){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",j),j}}if(!h)return this.objectLength(f)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+"")},formatAndAdd:function(b,c){var d=this.defaultMessage(b,c.method),e=/\$?\{(\d+)\}/g;"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),this.errorList.push({message:d,element:b,method:c.method}),this.errorMap[b.name]=d,this.submitted[b.name]=d},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass);this.toHide=this.toHide.not(this.toShow),this.hideErrors(),this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g=this.errorsFor(b),h=this.idOrName(b),i=a(b).attr("aria-describedby");g.length?(g.removeClass(this.settings.validClass).addClass(this.settings.errorClass),g.html(c)):(g=a("<"+this.settings.errorElement+">").attr("id",h+"-error").addClass(this.settings.errorClass).html(c||""),d=g,this.settings.wrapper&&(d=g.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement(d,a(b)):d.insertAfter(b),g.is("label")?g.attr("for",h):0===g.parents("label[for='"+h+"']").length&&(f=g.attr("id").replace(/(:|\.|\[|\])/g,"\\$1"),i?i.match(new RegExp("\\b"+f+"\\b"))||(i+=" "+f):i=f,a(b).attr("aria-describedby",i),e=this.groups[b.name],e&&a.each(this.groups,function(b,c){c===e&&a("[name='"+b+"']",this.currentForm).attr("aria-describedby",g.attr("id"))}))),!c&&this.settings.success&&(g.text(""),"string"==typeof this.settings.success?g.addClass(this.settings.success):this.settings.success(g,b)),this.toShow=this.toShow.add(g)},errorsFor:function(b){var c=this.idOrName(b),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+d.replace(/\s+/g,", #")),this.errors().filter(e)},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+b+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return this.dependTypes[typeof a]?this.dependTypes[typeof a](a,b):!0},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(a){this.pending[a.name]||(this.pendingRequest++,this.pending[a.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b){return a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,"remote")})}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),/min|max/.test(c)&&(null===g||/number|range|text/.test(g))&&(d=Number(d)),d||0===d?e[c]=d:g===c&&"range"!==g&&(e[c]=!0);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b);for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),void 0!==d&&(e[c]=d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0!==e.param?e.param:!0:delete b[d]}}),a.each(b,function(d,e){b[d]=a.isFunction(e)?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:a.trim(b).length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},creditcard:function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||d>=e},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||c>=a},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.unbind(".validate-equalTo").bind("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d){if(this.optional(c))return"dependency-mismatch";var e,f,g=this.previousValue(c);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),g.originalMessage=this.settings.messages[c.name].remote,this.settings.messages[c.name].remote=g.message,d="string"==typeof d&&{url:d}||d,g.old===b?g.valid:(g.old=b,e=this,this.startRequest(c),f={},f[c.name]=b,a.ajax(a.extend(!0,{url:d,mode:"abort",port:"validate"+c.name,dataType:"json",data:f,context:e.currentForm,success:function(d){var f,h,i,j=d===!0||"true"===d;e.settings.messages[c.name].remote=g.originalMessage,j?(i=e.formSubmitted,e.prepareElement(c),e.formSubmitted=i,e.successList.push(c),delete e.invalid[c.name],e.showErrors()):(f={},h=d||e.defaultMessage(c,"remote"),f[c.name]=g.message=a.isFunction(h)?h(b):h,e.invalid[c.name]=!0,e.showErrors(f)),g.valid=j,e.stopRequest(c,j)}},d)),"pending")}}}),a.format=function(){throw"$.format has been deprecated. Please use $.validator.format instead."};var b,c={};a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)}),a.extend(a.fn,{validateDelegate:function(b,c,d){return this.bind(c,function(c){var e=a(c.target);return e.is(b)?d.apply(e,arguments):void 0})}})}); \ No newline at end of file diff --git a/assets/js/libraries/progress-bar.js b/assets/js/libraries/progress-bar.js index d2301645..c0a8dae4 100644 --- a/assets/js/libraries/progress-bar.js +++ b/assets/js/libraries/progress-bar.js @@ -2,5 +2,2433 @@ // https://kimmobrunfeldt.github.io/progressbar.js // License: MIT -!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.ProgressBar=a()}}(function(){var a;return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};a[g][0].call(k.exports,function(b){var c=a[g][1][b];return e(c?c:b)},k,k.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;ga?0:(a-f)/e;for(h in b)b.hasOwnProperty(h)&&(i=g[h],k="function"==typeof i?i:o[i],b[h]=j(c[h],d[h],k,l));return b}function j(a,b,c,d){return a+(b-a)*c(d)}function k(a,b){var c=n.prototype.filter,d=a._filterArgs;f(c,function(e){"undefined"!=typeof c[e][b]&&c[e][b].apply(a,d)})}function l(a,b,c,d,e,f,g,h,j,l,m){v=b+c+d,w=Math.min(m||u(),v),x=w>=v,y=d-(v-w),a.isPlaying()&&(x?(j(g,a._attachment,y),a.stop(!0)):(a._scheduleId=l(a._timeoutHandler,s),k(a,"beforeTween"),b+c>w?i(1,e,f,g,1,1,h):i(w,e,f,g,d,b+c,h),k(a,"afterTween"),j(e,a._attachment,y)))}function m(a,b){var c={},d=typeof b;return"string"===d||"function"===d?f(a,function(a){c[a]=b}):f(a,function(a){c[a]||(c[a]=b[a]||q)}),c}function n(a,b){this._currentState=a||{},this._configured=!1,this._scheduleFunction=p,"undefined"!=typeof b&&this.setConfig(b)}var o,p,q="linear",r=500,s=1e3/60,t=Date.now?Date.now:function(){return+new Date},u="undefined"!=typeof SHIFTY_DEBUG_NOW?SHIFTY_DEBUG_NOW:t;p="undefined"!=typeof window?window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||window.mozCancelRequestAnimationFrame&&window.mozRequestAnimationFrame||setTimeout:setTimeout;var v,w,x,y;return n.prototype.tween=function(a){return this._isTweening?this:(void 0===a&&this._configured||this.setConfig(a),this._timestamp=u(),this._start(this.get(),this._attachment),this.resume())},n.prototype.setConfig=function(a){a=a||{},this._configured=!0,this._attachment=a.attachment,this._pausedAtTime=null,this._scheduleId=null,this._delay=a.delay||0,this._start=a.start||e,this._step=a.step||e,this._finish=a.finish||e,this._duration=a.duration||r,this._currentState=g({},a.from)||this.get(),this._originalState=this.get(),this._targetState=g({},a.to)||this.get();var b=this;this._timeoutHandler=function(){l(b,b._timestamp,b._delay,b._duration,b._currentState,b._originalState,b._targetState,b._easing,b._step,b._scheduleFunction)};var c=this._currentState,d=this._targetState;return h(d,c),this._easing=m(c,a.easing||q),this._filterArgs=[c,this._originalState,d,this._easing],k(this,"tweenCreated"),this},n.prototype.get=function(){return g({},this._currentState)},n.prototype.set=function(a){this._currentState=a},n.prototype.pause=function(){return this._pausedAtTime=u(),this._isPaused=!0,this},n.prototype.resume=function(){return this._isPaused&&(this._timestamp+=u()-this._pausedAtTime),this._isPaused=!1,this._isTweening=!0,this._timeoutHandler(),this},n.prototype.seek=function(a){a=Math.max(a,0);var b=u();return this._timestamp+a===0?this:(this._timestamp=b-a,this.isPlaying()||(this._isTweening=!0,this._isPaused=!1,l(this,this._timestamp,this._delay,this._duration,this._currentState,this._originalState,this._targetState,this._easing,this._step,this._scheduleFunction,b),this.pause()),this)},n.prototype.stop=function(a){return this._isTweening=!1,this._isPaused=!1,this._timeoutHandler=e,(b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.oCancelAnimationFrame||b.msCancelAnimationFrame||b.mozCancelRequestAnimationFrame||b.clearTimeout)(this._scheduleId),a&&(k(this,"beforeTween"),i(1,this._currentState,this._originalState,this._targetState,1,0,this._easing),k(this,"afterTween"),k(this,"afterTweenEnd"),this._finish.call(this,this._currentState,this._attachment)),this},n.prototype.isPlaying=function(){return this._isTweening&&!this._isPaused},n.prototype.setScheduleFunction=function(a){this._scheduleFunction=a},n.prototype.dispose=function(){var a;for(a in this)this.hasOwnProperty(a)&&delete this[a]},n.prototype.filter={},n.prototype.formula={linear:function(a){return a}},o=n.prototype.formula,g(n,{now:u,each:f,tweenProps:i,tweenProp:j,applyFilter:k,shallowCopy:g,defaults:h,composeEasingObject:m}),"function"==typeof SHIFTY_DEBUG_NOW&&(b.timeoutHandler=l),"object"==typeof d?c.exports=n:"function"==typeof a&&a.amd?a(function(){return n}):"undefined"==typeof b.Tweenable&&(b.Tweenable=n),n}();!function(){e.shallowCopy(e.prototype.formula,{easeInQuad:function(a){return Math.pow(a,2)},easeOutQuad:function(a){return-(Math.pow(a-1,2)-1)},easeInOutQuad:function(a){return(a/=.5)<1?.5*Math.pow(a,2):-.5*((a-=2)*a-2)},easeInCubic:function(a){return Math.pow(a,3)},easeOutCubic:function(a){return Math.pow(a-1,3)+1},easeInOutCubic:function(a){return(a/=.5)<1?.5*Math.pow(a,3):.5*(Math.pow(a-2,3)+2)},easeInQuart:function(a){return Math.pow(a,4)},easeOutQuart:function(a){return-(Math.pow(a-1,4)-1)},easeInOutQuart:function(a){return(a/=.5)<1?.5*Math.pow(a,4):-.5*((a-=2)*Math.pow(a,3)-2)},easeInQuint:function(a){return Math.pow(a,5)},easeOutQuint:function(a){return Math.pow(a-1,5)+1},easeInOutQuint:function(a){return(a/=.5)<1?.5*Math.pow(a,5):.5*(Math.pow(a-2,5)+2)},easeInSine:function(a){return-Math.cos(a*(Math.PI/2))+1},easeOutSine:function(a){return Math.sin(a*(Math.PI/2))},easeInOutSine:function(a){return-.5*(Math.cos(Math.PI*a)-1)},easeInExpo:function(a){return 0===a?0:Math.pow(2,10*(a-1))},easeOutExpo:function(a){return 1===a?1:-Math.pow(2,-10*a)+1},easeInOutExpo:function(a){return 0===a?0:1===a?1:(a/=.5)<1?.5*Math.pow(2,10*(a-1)):.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return-(Math.sqrt(1-a*a)-1)},easeOutCirc:function(a){return Math.sqrt(1-Math.pow(a-1,2))},easeInOutCirc:function(a){return(a/=.5)<1?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)},easeOutBounce:function(a){return 1/2.75>a?7.5625*a*a:2/2.75>a?7.5625*(a-=1.5/2.75)*a+.75:2.5/2.75>a?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375},easeInBack:function(a){var b=1.70158;return a*a*((b+1)*a-b)},easeOutBack:function(a){var b=1.70158;return(a-=1)*a*((b+1)*a+b)+1},easeInOutBack:function(a){var b=1.70158;return(a/=.5)<1?.5*(a*a*(((b*=1.525)+1)*a-b)):.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},elastic:function(a){return-1*Math.pow(4,-8*a)*Math.sin((6*a-1)*(2*Math.PI)/2)+1},swingFromTo:function(a){var b=1.70158;return(a/=.5)<1?.5*(a*a*(((b*=1.525)+1)*a-b)):.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},swingFrom:function(a){var b=1.70158;return a*a*((b+1)*a-b)},swingTo:function(a){var b=1.70158;return(a-=1)*a*((b+1)*a+b)+1},bounce:function(a){return 1/2.75>a?7.5625*a*a:2/2.75>a?7.5625*(a-=1.5/2.75)*a+.75:2.5/2.75>a?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375},bouncePast:function(a){return 1/2.75>a?7.5625*a*a:2/2.75>a?2-(7.5625*(a-=1.5/2.75)*a+.75):2.5/2.75>a?2-(7.5625*(a-=2.25/2.75)*a+.9375):2-(7.5625*(a-=2.625/2.75)*a+.984375)},easeFromTo:function(a){return(a/=.5)<1?.5*Math.pow(a,4):-.5*((a-=2)*Math.pow(a,3)-2)},easeFrom:function(a){return Math.pow(a,4)},easeTo:function(a){return Math.pow(a,.25)}})}(),function(){function a(a,b,c,d,e,f){function g(a){return((n*a+o)*a+p)*a}function h(a){return((q*a+r)*a+s)*a}function i(a){return(3*n*a+2*o)*a+p}function j(a){return 1/(200*a)}function k(a,b){return h(m(a,b))}function l(a){return a>=0?a:0-a}function m(a,b){var c,d,e,f,h,j;for(e=a,j=0;8>j;j++){if(f=g(e)-a,l(f)e)return c;if(e>d)return d;for(;d>c;){if(f=g(e),l(f-a)f?c=e:d=e,e=.5*(d-c)+c}return e}var n=0,o=0,p=0,q=0,r=0,s=0;return p=3*b,o=3*(d-b)-p,n=1-p-o,s=3*c,r=3*(e-c)-s,q=1-s-r,k(a,j(f))}function b(b,c,d,e){return function(f){return a(f,b,c,d,e,1)}}e.setBezierFunction=function(a,c,d,f,g){var h=b(c,d,f,g);return h.displayName=a,h.x1=c,h.y1=d,h.x2=f,h.y2=g,e.prototype.formula[a]=h},e.unsetBezierFunction=function(a){delete e.prototype.formula[a]}}(),function(){function a(a,b,c,d,f,g){return e.tweenProps(d,b,a,c,1,g,f)}var b=new e;b._filterArgs=[],e.interpolate=function(c,d,f,g,h){var i=e.shallowCopy({},c),j=h||0,k=e.composeEasingObject(c,g||"linear");b.set({});var l=b._filterArgs;l.length=0,l[0]=i,l[1]=c,l[2]=d,l[3]=k,e.applyFilter(b,"tweenCreated"),e.applyFilter(b,"beforeTween");var m=a(c,i,d,f,k,j);return e.applyFilter(b,"afterTween"),m}}(),function(a){function b(a,b){var c,d=[],e=a.length;for(c=0;e>c;c++)d.push("_"+b+"_"+c);return d}function c(a){var b=a.match(v);return b?(1===b.length||a[0].match(u))&&b.unshift(""):b=["",""],b.join(A)}function d(b){a.each(b,function(a){var c=b[a];"string"==typeof c&&c.match(z)&&(b[a]=e(c))})}function e(a){return i(z,a,f)}function f(a){var b=g(a);return"rgb("+b[0]+","+b[1]+","+b[2]+")"}function g(a){return a=a.replace(/#/,""),3===a.length&&(a=a.split(""),a=a[0]+a[0]+a[1]+a[1]+a[2]+a[2]),B[0]=h(a.substr(0,2)),B[1]=h(a.substr(2,2)),B[2]=h(a.substr(4,2)),B}function h(a){return parseInt(a,16)}function i(a,b,c){var d=b.match(a),e=b.replace(a,A);if(d)for(var f,g=d.length,h=0;g>h;h++)f=d.shift(),e=e.replace(A,c(f));return e}function j(a){return i(x,a,k)}function k(a){for(var b=a.match(w),c=b.length,d=a.match(y)[0],e=0;c>e;e++)d+=parseInt(b[e],10)+",";return d=d.slice(0,-1)+")"}function l(d){var e={};return a.each(d,function(a){var f=d[a];if("string"==typeof f){var g=r(f);e[a]={formatString:c(f),chunkNames:b(g,a)}}}),e}function m(b,c){a.each(c,function(a){for(var d=b[a],e=r(d),f=e.length,g=0;f>g;g++)b[c[a].chunkNames[g]]=+e[g];delete b[a]})}function n(b,c){a.each(c,function(a){var d=b[a],e=o(b,c[a].chunkNames),f=p(e,c[a].chunkNames);d=q(c[a].formatString,f),b[a]=j(d)})}function o(a,b){for(var c,d={},e=b.length,f=0;e>f;f++)c=b[f],d[c]=a[c],delete a[c];return d}function p(a,b){C.length=0;for(var c=b.length,d=0;c>d;d++)C.push(a[b[d]]);return C}function q(a,b){for(var c=a,d=b.length,e=0;d>e;e++)c=c.replace(A,+b[e].toFixed(4));return c}function r(a){return a.match(w)}function s(b,c){a.each(c,function(a){var d,e=c[a],f=e.chunkNames,g=f.length,h=b[a];if("string"==typeof h){var i=h.split(" "),j=i[i.length-1];for(d=0;g>d;d++)b[f[d]]=i[d]||j}else for(d=0;g>d;d++)b[f[d]]=h;delete b[a]})}function t(b,c){a.each(c,function(a){var d=c[a],e=d.chunkNames,f=e.length,g=b[e[0]],h=typeof g;if("string"===h){for(var i="",j=0;f>j;j++)i+=" "+b[e[j]],delete b[e[j]];b[a]=i.substr(1)}else b[a]=g})}var u=/(\d|\-|\.)/,v=/([^\-0-9\.]+)/g,w=/[0-9.\-]+/g,x=new RegExp("rgb\\("+w.source+/,\s*/.source+w.source+/,\s*/.source+w.source+"\\)","g"),y=/^.*\(/,z=/#([0-9]|[a-f]){3,6}/gi,A="VAL",B=[],C=[];a.prototype.filter.token={tweenCreated:function(a,b,c,e){d(a),d(b),d(c),this._tokenData=l(a)},beforeTween:function(a,b,c,d){s(d,this._tokenData),m(a,this._tokenData),m(b,this._tokenData),m(c,this._tokenData)},afterTween:function(a,b,c,d){n(a,this._tokenData),n(b,this._tokenData),n(c,this._tokenData),t(d,this._tokenData)}}}(e)}).call(null)},{}],2:[function(a,b,c){var d=a("./shape"),e=a("./utils"),f=function(a,b){this._pathTemplate="M 50,50 m 0,-{radius} a {radius},{radius} 0 1 1 0,{2radius} a {radius},{radius} 0 1 1 0,-{2radius}",this.containerAspectRatio=1,d.apply(this,arguments)};f.prototype=new d,f.prototype.constructor=f,f.prototype._pathString=function(a){var b=a.strokeWidth;a.trailWidth&&a.trailWidth>a.strokeWidth&&(b=a.trailWidth);var c=50-b/2;return e.render(this._pathTemplate,{radius:c,"2radius":2*c})},f.prototype._trailString=function(a){return this._pathString(a)},b.exports=f},{"./shape":7,"./utils":8}],3:[function(a,b,c){var d=a("./shape"),e=a("./utils"),f=function(a,b){this._pathTemplate="M 0,{center} L 100,{center}",d.apply(this,arguments)};f.prototype=new d,f.prototype.constructor=f,f.prototype._initializeSvg=function(a,b){a.setAttribute("viewBox","0 0 100 "+b.strokeWidth),a.setAttribute("preserveAspectRatio","none")},f.prototype._pathString=function(a){return e.render(this._pathTemplate,{center:a.strokeWidth/2})},f.prototype._trailString=function(a){return this._pathString(a)},b.exports=f},{"./shape":7,"./utils":8}],4:[function(a,b,c){b.exports={Line:a("./line"),Circle:a("./circle"),SemiCircle:a("./semicircle"),Path:a("./path"),Shape:a("./shape"),utils:a("./utils")}},{"./circle":2,"./line":3,"./path":5,"./semicircle":6,"./shape":7,"./utils":8}],5:[function(a,b,c){var d=a("shifty"),e=a("./utils"),f={easeIn:"easeInCubic",easeOut:"easeOutCubic",easeInOut:"easeInOutCubic"},g=function h(a,b){if(!(this instanceof h))throw new Error("Constructor was called without new keyword");b=e.extend({duration:800,easing:"linear",from:{},to:{},step:function(){}},b);var c;c=e.isString(a)?document.querySelector(a):a,this.path=c,this._opts=b,this._tweenable=null;var d=this.path.getTotalLength();this.path.style.strokeDasharray=d+" "+d,this.set(0)};g.prototype.value=function(){var a=this._getComputedDashOffset(),b=this.path.getTotalLength(),c=1-a/b;return parseFloat(c.toFixed(6),10)},g.prototype.set=function(a){this.stop(),this.path.style.strokeDashoffset=this._progressToOffset(a);var b=this._opts.step;if(e.isFunction(b)){var c=this._easing(this._opts.easing),d=this._calculateTo(a,c),f=this._opts.shape||this;b(d,f,this._opts.attachment)}},g.prototype.stop=function(){this._stopTween(),this.path.style.strokeDashoffset=this._getComputedDashOffset()},g.prototype.animate=function(a,b,c){b=b||{},e.isFunction(b)&&(c=b,b={});var f=e.extend({},b),g=e.extend({},this._opts);b=e.extend(g,b);var h=this._easing(b.easing),i=this._resolveFromAndTo(a,h,f);this.stop(),this.path.getBoundingClientRect();var j=this._getComputedDashOffset(),k=this._progressToOffset(a),l=this;this._tweenable=new d,this._tweenable.tween({from:e.extend({offset:j},i.from),to:e.extend({offset:k},i.to),duration:b.duration,easing:h,step:function(a){l.path.style.strokeDashoffset=a.offset;var c=b.shape||l;b.step(a,c,b.attachment)},finish:function(a){e.isFunction(c)&&c()}})},g.prototype._getComputedDashOffset=function(){var a=window.getComputedStyle(this.path,null);return parseFloat(a.getPropertyValue("stroke-dashoffset"),10)},g.prototype._progressToOffset=function(a){var b=this.path.getTotalLength();return b-a*b},g.prototype._resolveFromAndTo=function(a,b,c){return c.from&&c.to?{from:c.from,to:c.to}:{from:this._calculateFrom(b),to:this._calculateTo(a,b)}},g.prototype._calculateFrom=function(a){return d.interpolate(this._opts.from,this._opts.to,this.value(),a)},g.prototype._calculateTo=function(a,b){return d.interpolate(this._opts.from,this._opts.to,a,b)},g.prototype._stopTween=function(){null!==this._tweenable&&(this._tweenable.stop(),this._tweenable=null)},g.prototype._easing=function(a){return f.hasOwnProperty(a)?f[a]:a},b.exports=g},{"./utils":8,shifty:1}],6:[function(a,b,c){var d=a("./shape"),e=a("./circle"),f=a("./utils"),g=function(a,b){this._pathTemplate="M 50,50 m -{radius},0 a {radius},{radius} 0 1 1 {2radius},0",this.containerAspectRatio=2,d.apply(this,arguments)};g.prototype=new d,g.prototype.constructor=g,g.prototype._initializeSvg=function(a,b){a.setAttribute("viewBox","0 0 100 50")},g.prototype._initializeTextContainer=function(a,b,c){a.text.style&&(c.style.top="auto",c.style.bottom="0",a.text.alignToBottom?f.setStyle(c,"transform","translate(-50%, 0)"):f.setStyle(c,"transform","translate(-50%, 50%)"))},g.prototype._pathString=e.prototype._pathString,g.prototype._trailString=e.prototype._trailString,b.exports=g},{"./circle":2,"./shape":7,"./utils":8}],7:[function(a,b,c){var d=a("./path"),e=a("./utils"),f="Object is destroyed",g=function h(a,b){if(!(this instanceof h))throw new Error("Constructor was called without new keyword");if(0!==arguments.length){this._opts=e.extend({color:"#555",strokeWidth:1,trailColor:null,trailWidth:null,fill:null,text:{style:{color:null,position:"absolute",left:"50%",top:"50%",padding:0,margin:0,transform:{prefix:!0,value:"translate(-50%, -50%)"}},autoStyleContainer:!0,alignToBottom:!0,value:null,className:"progressbar-text"},svgStyle:{display:"block",width:"100%"},warnings:!1},b,!0),e.isObject(b)&&void 0!==b.svgStyle&&(this._opts.svgStyle=b.svgStyle),e.isObject(b)&&e.isObject(b.text)&&void 0!==b.text.style&&(this._opts.text.style=b.text.style);var c,f=this._createSvgView(this._opts);if(c=e.isString(a)?document.querySelector(a):a,!c)throw new Error("Container does not exist: "+a);this._container=c,this._container.appendChild(f.svg),this._opts.warnings&&this._warnContainerAspectRatio(this._container),this._opts.svgStyle&&e.setStyles(f.svg,this._opts.svgStyle),this.svg=f.svg,this.path=f.path,this.trail=f.trail,this.text=null;var g=e.extend({attachment:void 0,shape:this},this._opts);this._progressPath=new d(f.path,g),e.isObject(this._opts.text)&&null!==this._opts.text.value&&this.setText(this._opts.text.value)}};g.prototype.animate=function(a,b,c){if(null===this._progressPath)throw new Error(f);this._progressPath.animate(a,b,c)},g.prototype.stop=function(){if(null===this._progressPath)throw new Error(f);void 0!==this._progressPath&&this._progressPath.stop()},g.prototype.destroy=function(){if(null===this._progressPath)throw new Error(f);this.stop(),this.svg.parentNode.removeChild(this.svg),this.svg=null,this.path=null,this.trail=null,this._progressPath=null,null!==this.text&&(this.text.parentNode.removeChild(this.text),this.text=null)},g.prototype.set=function(a){if(null===this._progressPath)throw new Error(f);this._progressPath.set(a)},g.prototype.value=function(){if(null===this._progressPath)throw new Error(f);return void 0===this._progressPath?0:this._progressPath.value()},g.prototype.setText=function(a){if(null===this._progressPath)throw new Error(f);null===this.text&&(this.text=this._createTextContainer(this._opts,this._container),this._container.appendChild(this.text)),e.isObject(a)?(e.removeChildren(this.text),this.text.appendChild(a)):this.text.innerHTML=a},g.prototype._createSvgView=function(a){var b=document.createElementNS("http://www.w3.org/2000/svg","svg");this._initializeSvg(b,a);var c=null;(a.trailColor||a.trailWidth)&&(c=this._createTrail(a),b.appendChild(c));var d=this._createPath(a);return b.appendChild(d),{svg:b,path:d,trail:c}},g.prototype._initializeSvg=function(a,b){a.setAttribute("viewBox","0 0 100 100")},g.prototype._createPath=function(a){var b=this._pathString(a);return this._createPathElement(b,a)},g.prototype._createTrail=function(a){var b=this._trailString(a),c=e.extend({},a);return c.trailColor||(c.trailColor="#eee"),c.trailWidth||(c.trailWidth=c.strokeWidth),c.color=c.trailColor,c.strokeWidth=c.trailWidth,c.fill=null,this._createPathElement(b,c)},g.prototype._createPathElement=function(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg","path");return c.setAttribute("d",a),c.setAttribute("stroke",b.color),c.setAttribute("stroke-width",b.strokeWidth),b.fill?c.setAttribute("fill",b.fill):c.setAttribute("fill-opacity","0"),c},g.prototype._createTextContainer=function(a,b){var c=document.createElement("div");c.className=a.text.className;var d=a.text.style;return d&&(a.text.autoStyleContainer&&(b.style.position="relative"),e.setStyles(c,d),d.color||(c.style.color=a.color)),this._initializeTextContainer(a,b,c),c},g.prototype._initializeTextContainer=function(a,b,c){},g.prototype._pathString=function(a){throw new Error("Override this function for each progress bar")},g.prototype._trailString=function(a){throw new Error("Override this function for each progress bar")},g.prototype._warnContainerAspectRatio=function(a){if(this.containerAspectRatio){var b=window.getComputedStyle(a,null),c=parseFloat(b.getPropertyValue("width"),10),d=parseFloat(b.getPropertyValue("height"),10);e.floatEquals(this.containerAspectRatio,c/d)||(console.warn("Incorrect aspect ratio of container","#"+a.id,"detected:",b.getPropertyValue("width")+"(width)","/",b.getPropertyValue("height")+"(height)","=",c/d),console.warn("Aspect ratio of should be",this.containerAspectRatio))}},b.exports=g},{"./path":5,"./utils":8}],8:[function(a,b,c){function d(a,b,c){a=a||{},b=b||{},c=c||!1;for(var e in b)if(b.hasOwnProperty(e)){var f=a[e],g=b[e];c&&l(f)&&l(g)?a[e]=d(f,g,c):a[e]=g}return a}function e(a,b){var c=a;for(var d in b)if(b.hasOwnProperty(d)){var e=b[d],f="\\{"+d+"\\}",g=new RegExp(f,"g");c=c.replace(g,e)}return c}function f(a,b,c){for(var d=a.style,e=0;e= timeoutHandler_endTime; + + timeoutHandler_offset = duration - ( + timeoutHandler_endTime - timeoutHandler_currentTime); + + if (tweenable.isPlaying()) { + if (timeoutHandler_isEnded) { + step(targetState, tweenable._attachment, timeoutHandler_offset); + tweenable.stop(true); + } else { + tweenable._scheduleId = + schedule(tweenable._timeoutHandler, UPDATE_TIME); + + applyFilter(tweenable, 'beforeTween'); + + // If the animation has not yet reached the start point (e.g., there was + // delay that has not yet completed), just interpolate the starting + // position of the tween. + if (timeoutHandler_currentTime < (timestamp + delay)) { + tweenProps(1, currentState, originalState, targetState, 1, 1, easing); + } else { + tweenProps(timeoutHandler_currentTime, currentState, originalState, + targetState, duration, timestamp + delay, easing); + } + + applyFilter(tweenable, 'afterTween'); + + step(currentState, tweenable._attachment, timeoutHandler_offset); + } + } + } + + + /** + * Creates a usable easing Object from a string, a function or another easing + * Object. If `easing` is an Object, then this function clones it and fills + * in the missing properties with `"linear"`. + * @param {Object.} fromTweenParams + * @param {Object|string|Function} easing + * @return {Object.} + * @private + */ + function composeEasingObject (fromTweenParams, easing) { + var composedEasing = {}; + var typeofEasing = typeof easing; + + if (typeofEasing === 'string' || typeofEasing === 'function') { + each(fromTweenParams, function (prop) { + composedEasing[prop] = easing; + }); + } else { + each(fromTweenParams, function (prop) { + if (!composedEasing[prop]) { + composedEasing[prop] = easing[prop] || DEFAULT_EASING; + } + }); + } + + return composedEasing; + } + + /** + * Tweenable constructor. + * @class Tweenable + * @param {Object=} opt_initialState The values that the initial tween should + * start at if a `from` object is not provided to `{{#crossLink + * "Tweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink + * "Tweenable/setConfig:method"}}{{/crossLink}}`. + * @param {Object=} opt_config Configuration object to be passed to + * `{{#crossLink "Tweenable/setConfig:method"}}{{/crossLink}}`. + * @module Tweenable + * @constructor + */ + function Tweenable (opt_initialState, opt_config) { + this._currentState = opt_initialState || {}; + this._configured = false; + this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION; + + // To prevent unnecessary calls to setConfig do not set default + // configuration here. Only set default configuration immediately before + // tweening if none has been set. + if (typeof opt_config !== 'undefined') { + this.setConfig(opt_config); + } + } + + /** + * Configure and start a tween. + * @method tween + * @param {Object=} opt_config Configuration object to be passed to + * `{{#crossLink "Tweenable/setConfig:method"}}{{/crossLink}}`. + * @chainable + */ + Tweenable.prototype.tween = function (opt_config) { + if (this._isTweening) { + return this; + } + + // Only set default config if no configuration has been set previously and + // none is provided now. + if (opt_config !== undefined || !this._configured) { + this.setConfig(opt_config); + } + + this._timestamp = now(); + this._start(this.get(), this._attachment); + return this.resume(); + }; + + /** + * Configure a tween that will start at some point in the future. + * + * @method setConfig + * @param {Object} config The following values are valid: + * - __from__ (_Object=_): Starting position. If omitted, `{{#crossLink + * "Tweenable/get:method"}}get(){{/crossLink}}` is used. + * - __to__ (_Object=_): Ending position. + * - __duration__ (_number=_): How many milliseconds to animate for. + * - __delay__ (_delay=_): How many milliseconds to wait before starting the + * tween. + * - __start__ (_Function(Object, *)_): Function to execute when the tween + * begins. Receives the state of the tween as the first parameter and + * `attachment` as the second parameter. + * - __step__ (_Function(Object, *, number)_): Function to execute on every + * tick. Receives `{{#crossLink + * "Tweenable/get:method"}}get(){{/crossLink}}` as the first parameter, + * `attachment` as the second parameter, and the time elapsed since the + * start of the tween as the third. This function is not called on the + * final step of the animation, but `finish` is. + * - __finish__ (_Function(Object, *)_): Function to execute upon tween + * completion. Receives the state of the tween as the first parameter and + * `attachment` as the second parameter. + * - __easing__ (_Object.|string|Function=_): Easing curve + * name(s) or function(s) to use for the tween. + * - __attachment__ (_*_): Cached value that is passed to the + * `step`/`start`/`finish` methods. + * @chainable + */ + Tweenable.prototype.setConfig = function (config) { + config = config || {}; + this._configured = true; + + // Attach something to this Tweenable instance (e.g.: a DOM element, an + // object, a string, etc.); + this._attachment = config.attachment; + + // Init the internal state + this._pausedAtTime = null; + this._scheduleId = null; + this._delay = config.delay || 0; + this._start = config.start || noop; + this._step = config.step || noop; + this._finish = config.finish || noop; + this._duration = config.duration || DEFAULT_DURATION; + this._currentState = shallowCopy({}, config.from) || this.get(); + this._originalState = this.get(); + this._targetState = shallowCopy({}, config.to) || this.get(); + + var self = this; + this._timeoutHandler = function () { + timeoutHandler(self, + self._timestamp, + self._delay, + self._duration, + self._currentState, + self._originalState, + self._targetState, + self._easing, + self._step, + self._scheduleFunction + ); + }; + + // Aliases used below + var currentState = this._currentState; + var targetState = this._targetState; + + // Ensure that there is always something to tween to. + defaults(targetState, currentState); + + this._easing = composeEasingObject( + currentState, config.easing || DEFAULT_EASING); + + this._filterArgs = + [currentState, this._originalState, targetState, this._easing]; + + applyFilter(this, 'tweenCreated'); + return this; + }; + + /** + * @method get + * @return {Object} The current state. + */ + Tweenable.prototype.get = function () { + return shallowCopy({}, this._currentState); + }; + + /** + * @method set + * @param {Object} state The current state. + */ + Tweenable.prototype.set = function (state) { + this._currentState = state; + }; + + /** + * Pause a tween. Paused tweens can be resumed from the point at which they + * were paused. This is different from `{{#crossLink + * "Tweenable/stop:method"}}{{/crossLink}}`, as that method + * causes a tween to start over when it is resumed. + * @method pause + * @chainable + */ + Tweenable.prototype.pause = function () { + this._pausedAtTime = now(); + this._isPaused = true; + return this; + }; + + /** + * Resume a paused tween. + * @method resume + * @chainable + */ + Tweenable.prototype.resume = function () { + if (this._isPaused) { + this._timestamp += now() - this._pausedAtTime; + } + + this._isPaused = false; + this._isTweening = true; + + this._timeoutHandler(); + + return this; + }; + + /** + * Move the state of the animation to a specific point in the tween's + * timeline. If the animation is not running, this will cause the `step` + * handlers to be called. + * @method seek + * @param {millisecond} millisecond The millisecond of the animation to seek + * to. This must not be less than `0`. + * @chainable + */ + Tweenable.prototype.seek = function (millisecond) { + millisecond = Math.max(millisecond, 0); + var currentTime = now(); + + if ((this._timestamp + millisecond) === 0) { + return this; + } + + this._timestamp = currentTime - millisecond; + + if (!this.isPlaying()) { + this._isTweening = true; + this._isPaused = false; + + // If the animation is not running, call timeoutHandler to make sure that + // any step handlers are run. + timeoutHandler(this, + this._timestamp, + this._delay, + this._duration, + this._currentState, + this._originalState, + this._targetState, + this._easing, + this._step, + this._scheduleFunction, + currentTime + ); + + this.pause(); + } + + return this; + }; + + /** + * Stops and cancels a tween. + * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at + * its current state, and the `finish` handler is not invoked. If `true`, + * the tweened object's values are instantly set to the target values, and + * `finish` is invoked. + * @method stop + * @chainable + */ + Tweenable.prototype.stop = function (gotoEnd) { + this._isTweening = false; + this._isPaused = false; + this._timeoutHandler = noop; + + (root.cancelAnimationFrame || + root.webkitCancelAnimationFrame || + root.oCancelAnimationFrame || + root.msCancelAnimationFrame || + root.mozCancelRequestAnimationFrame || + root.clearTimeout)(this._scheduleId); + + if (gotoEnd) { + applyFilter(this, 'beforeTween'); + tweenProps( + 1, + this._currentState, + this._originalState, + this._targetState, + 1, + 0, + this._easing + ); + applyFilter(this, 'afterTween'); + applyFilter(this, 'afterTweenEnd'); + this._finish.call(this, this._currentState, this._attachment); + } + + return this; + }; + + /** + * @method isPlaying + * @return {boolean} Whether or not a tween is running. + */ + Tweenable.prototype.isPlaying = function () { + return this._isTweening && !this._isPaused; + }; + + /** + * Set a custom schedule function. + * + * If a custom function is not set, + * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame) + * is used if available, otherwise + * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout) + * is used. + * @method setScheduleFunction + * @param {Function(Function,number)} scheduleFunction The function to be + * used to schedule the next frame to be rendered. + */ + Tweenable.prototype.setScheduleFunction = function (scheduleFunction) { + this._scheduleFunction = scheduleFunction; + }; + + /** + * `delete` all "own" properties. Call this when the `Tweenable` instance + * is no longer needed to free memory. + * @method dispose + */ + Tweenable.prototype.dispose = function () { + var prop; + for (prop in this) { + if (this.hasOwnProperty(prop)) { + delete this[prop]; + } + } + }; + + /** + * Filters are used for transforming the properties of a tween at various + * points in a Tweenable's life cycle. See the README for more info on this. + * @private + */ + Tweenable.prototype.filter = {}; + + /** + * This object contains all of the tweens available to Shifty. It is + * extensible - simply attach properties to the `Tweenable.prototype.formula` + * Object following the same format as `linear`. + * + * `pos` should be a normalized `number` (between 0 and 1). + * @property formula + * @type {Object(function)} + */ + Tweenable.prototype.formula = { + linear: function (pos) { + return pos; + } + }; + + formula = Tweenable.prototype.formula; + + shallowCopy(Tweenable, { + 'now': now + ,'each': each + ,'tweenProps': tweenProps + ,'tweenProp': tweenProp + ,'applyFilter': applyFilter + ,'shallowCopy': shallowCopy + ,'defaults': defaults + ,'composeEasingObject': composeEasingObject + }); + + // `root` is provided in the intro/outro files. + + // A hook used for unit testing. + if (typeof SHIFTY_DEBUG_NOW === 'function') { + root.timeoutHandler = timeoutHandler; + } + + // Bootstrap Tweenable appropriately for the environment. + if (typeof exports === 'object') { + // CommonJS + module.exports = Tweenable; + } else if (typeof define === 'function' && define.amd) { + // AMD + define(function () {return Tweenable;}); + } else if (typeof root.Tweenable === 'undefined') { + // Browser: Make `Tweenable` globally accessible. + root.Tweenable = Tweenable; + } + + return Tweenable; + +} ()); + +/*! + * All equations are adapted from Thomas Fuchs' + * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js). + * + * Based on Easing Equations (c) 2003 [Robert + * Penner](http://www.robertpenner.com/), all rights reserved. This work is + * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html). + */ + +/*! + * TERMS OF USE - EASING EQUATIONS + * Open source under the BSD License. + * Easing Equations (c) 2003 Robert Penner, all rights reserved. + */ + +;(function () { + + Tweenable.shallowCopy(Tweenable.prototype.formula, { + easeInQuad: function (pos) { + return Math.pow(pos, 2); + }, + + easeOutQuad: function (pos) { + return -(Math.pow((pos - 1), 2) - 1); + }, + + easeInOutQuad: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);} + return -0.5 * ((pos -= 2) * pos - 2); + }, + + easeInCubic: function (pos) { + return Math.pow(pos, 3); + }, + + easeOutCubic: function (pos) { + return (Math.pow((pos - 1), 3) + 1); + }, + + easeInOutCubic: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);} + return 0.5 * (Math.pow((pos - 2),3) + 2); + }, + + easeInQuart: function (pos) { + return Math.pow(pos, 4); + }, + + easeOutQuart: function (pos) { + return -(Math.pow((pos - 1), 4) - 1); + }, + + easeInOutQuart: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);} + return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2); + }, + + easeInQuint: function (pos) { + return Math.pow(pos, 5); + }, + + easeOutQuint: function (pos) { + return (Math.pow((pos - 1), 5) + 1); + }, + + easeInOutQuint: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);} + return 0.5 * (Math.pow((pos - 2),5) + 2); + }, + + easeInSine: function (pos) { + return -Math.cos(pos * (Math.PI / 2)) + 1; + }, + + easeOutSine: function (pos) { + return Math.sin(pos * (Math.PI / 2)); + }, + + easeInOutSine: function (pos) { + return (-0.5 * (Math.cos(Math.PI * pos) - 1)); + }, + + easeInExpo: function (pos) { + return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1)); + }, + + easeOutExpo: function (pos) { + return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1; + }, + + easeInOutExpo: function (pos) { + if (pos === 0) {return 0;} + if (pos === 1) {return 1;} + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));} + return 0.5 * (-Math.pow(2, -10 * --pos) + 2); + }, + + easeInCirc: function (pos) { + return -(Math.sqrt(1 - (pos * pos)) - 1); + }, + + easeOutCirc: function (pos) { + return Math.sqrt(1 - Math.pow((pos - 1), 2)); + }, + + easeInOutCirc: function (pos) { + if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);} + return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1); + }, + + easeOutBounce: function (pos) { + if ((pos) < (1 / 2.75)) { + return (7.5625 * pos * pos); + } else if (pos < (2 / 2.75)) { + return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75); + } else if (pos < (2.5 / 2.75)) { + return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375); + } else { + return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375); + } + }, + + easeInBack: function (pos) { + var s = 1.70158; + return (pos) * pos * ((s + 1) * pos - s); + }, + + easeOutBack: function (pos) { + var s = 1.70158; + return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1; + }, + + easeInOutBack: function (pos) { + var s = 1.70158; + if ((pos /= 0.5) < 1) { + return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)); + } + return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2); + }, + + elastic: function (pos) { + // jshint maxlen:90 + return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1; + }, + + swingFromTo: function (pos) { + var s = 1.70158; + return ((pos /= 0.5) < 1) ? + 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) : + 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2); + }, + + swingFrom: function (pos) { + var s = 1.70158; + return pos * pos * ((s + 1) * pos - s); + }, + + swingTo: function (pos) { + var s = 1.70158; + return (pos -= 1) * pos * ((s + 1) * pos + s) + 1; + }, + + bounce: function (pos) { + if (pos < (1 / 2.75)) { + return (7.5625 * pos * pos); + } else if (pos < (2 / 2.75)) { + return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75); + } else if (pos < (2.5 / 2.75)) { + return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375); + } else { + return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375); + } + }, + + bouncePast: function (pos) { + if (pos < (1 / 2.75)) { + return (7.5625 * pos * pos); + } else if (pos < (2 / 2.75)) { + return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75); + } else if (pos < (2.5 / 2.75)) { + return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375); + } else { + return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375); + } + }, + + easeFromTo: function (pos) { + if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);} + return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2); + }, + + easeFrom: function (pos) { + return Math.pow(pos,4); + }, + + easeTo: function (pos) { + return Math.pow(pos,0.25); + } + }); + +}()); + +// jshint maxlen:100 +/** + * The Bezier magic in this file is adapted/copied almost wholesale from + * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js), + * which was adapted from Apple code (which probably came from + * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)). + * Special thanks to Apple and Thomas Fuchs for much of this code. + */ + +/** + * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder(s) nor the names of any + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +;(function () { + // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/ + function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) { + var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0; + function sampleCurveX(t) { + return ((ax * t + bx) * t + cx) * t; + } + function sampleCurveY(t) { + return ((ay * t + by) * t + cy) * t; + } + function sampleCurveDerivativeX(t) { + return (3.0 * ax * t + 2.0 * bx) * t + cx; + } + function solveEpsilon(duration) { + return 1.0 / (200.0 * duration); + } + function solve(x,epsilon) { + return sampleCurveY(solveCurveX(x, epsilon)); + } + function fabs(n) { + if (n >= 0) { + return n; + } else { + return 0 - n; + } + } + function solveCurveX(x, epsilon) { + var t0,t1,t2,x2,d2,i; + for (t2 = x, i = 0; i < 8; i++) { + x2 = sampleCurveX(t2) - x; + if (fabs(x2) < epsilon) { + return t2; + } + d2 = sampleCurveDerivativeX(t2); + if (fabs(d2) < 1e-6) { + break; + } + t2 = t2 - x2 / d2; + } + t0 = 0.0; + t1 = 1.0; + t2 = x; + if (t2 < t0) { + return t0; + } + if (t2 > t1) { + return t1; + } + while (t0 < t1) { + x2 = sampleCurveX(t2); + if (fabs(x2 - x) < epsilon) { + return t2; + } + if (x > x2) { + t0 = t2; + }else { + t1 = t2; + } + t2 = (t1 - t0) * 0.5 + t0; + } + return t2; // Failure. + } + cx = 3.0 * p1x; + bx = 3.0 * (p2x - p1x) - cx; + ax = 1.0 - cx - bx; + cy = 3.0 * p1y; + by = 3.0 * (p2y - p1y) - cy; + ay = 1.0 - cy - by; + return solve(t, solveEpsilon(duration)); + } + /** + * getCubicBezierTransition(x1, y1, x2, y2) -> Function + * + * Generates a transition easing function that is compatible + * with WebKit's CSS transitions `-webkit-transition-timing-function` + * CSS property. + * + * The W3C has more information about CSS3 transition timing functions: + * http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag + * + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @return {function} + * @private + */ + function getCubicBezierTransition (x1, y1, x2, y2) { + return function (pos) { + return cubicBezierAtTime(pos,x1,y1,x2,y2,1); + }; + } + // End ported code + + /** + * Create a Bezier easing function and attach it to `{{#crossLink + * "Tweenable/formula:property"}}Tweenable#formula{{/crossLink}}`. This + * function gives you total control over the easing curve. Matthew Lein's + * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing + * the curves you can make with this function. + * @method setBezierFunction + * @param {string} name The name of the easing curve. Overwrites the old + * easing function on `{{#crossLink + * "Tweenable/formula:property"}}Tweenable#formula{{/crossLink}}` if it + * exists. + * @param {number} x1 + * @param {number} y1 + * @param {number} x2 + * @param {number} y2 + * @return {function} The easing function that was attached to + * Tweenable.prototype.formula. + */ + Tweenable.setBezierFunction = function (name, x1, y1, x2, y2) { + var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2); + cubicBezierTransition.displayName = name; + cubicBezierTransition.x1 = x1; + cubicBezierTransition.y1 = y1; + cubicBezierTransition.x2 = x2; + cubicBezierTransition.y2 = y2; + + return Tweenable.prototype.formula[name] = cubicBezierTransition; + }; + + + /** + * `delete` an easing function from `{{#crossLink + * "Tweenable/formula:property"}}Tweenable#formula{{/crossLink}}`. Be + * careful with this method, as it `delete`s whatever easing formula matches + * `name` (which means you can delete standard Shifty easing functions). + * @method unsetBezierFunction + * @param {string} name The name of the easing function to delete. + * @return {function} + */ + Tweenable.unsetBezierFunction = function (name) { + delete Tweenable.prototype.formula[name]; + }; + +})(); + +;(function () { + + function getInterpolatedValues ( + from, current, targetState, position, easing, delay) { + return Tweenable.tweenProps( + position, current, from, targetState, 1, delay, easing); + } + + // Fake a Tweenable and patch some internals. This approach allows us to + // skip uneccessary processing and object recreation, cutting down on garbage + // collection pauses. + var mockTweenable = new Tweenable(); + mockTweenable._filterArgs = []; + + /** + * Compute the midpoint of two Objects. This method effectively calculates a + * specific frame of animation that `{{#crossLink + * "Tweenable/tween:method"}}{{/crossLink}}` does many times over the course + * of a full tween. + * + * var interpolatedValues = Tweenable.interpolate({ + * width: '100px', + * opacity: 0, + * color: '#fff' + * }, { + * width: '200px', + * opacity: 1, + * color: '#000' + * }, 0.5); + * + * console.log(interpolatedValues); + * // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"} + * + * @static + * @method interpolate + * @param {Object} from The starting values to tween from. + * @param {Object} targetState The ending values to tween to. + * @param {number} position The normalized position value (between `0.0` and + * `1.0`) to interpolate the values between `from` and `to` for. `from` + * represents `0` and `to` represents `1`. + * @param {Object.|string|Function} easing The easing + * curve(s) to calculate the midpoint against. You can reference any easing + * function attached to `Tweenable.prototype.formula`, or provide the easing + * function(s) directly. If omitted, this defaults to "linear". + * @param {number=} opt_delay Optional delay to pad the beginning of the + * interpolated tween with. This increases the range of `position` from (`0` + * through `1`) to (`0` through `1 + opt_delay`). So, a delay of `0.5` would + * increase all valid values of `position` to numbers between `0` and `1.5`. + * @return {Object} + */ + Tweenable.interpolate = function ( + from, targetState, position, easing, opt_delay) { + + var current = Tweenable.shallowCopy({}, from); + var delay = opt_delay || 0; + var easingObject = Tweenable.composeEasingObject( + from, easing || 'linear'); + + mockTweenable.set({}); + + // Alias and reuse the _filterArgs array instead of recreating it. + var filterArgs = mockTweenable._filterArgs; + filterArgs.length = 0; + filterArgs[0] = current; + filterArgs[1] = from; + filterArgs[2] = targetState; + filterArgs[3] = easingObject; + + // Any defined value transformation must be applied + Tweenable.applyFilter(mockTweenable, 'tweenCreated'); + Tweenable.applyFilter(mockTweenable, 'beforeTween'); + + var interpolatedValues = getInterpolatedValues( + from, current, targetState, position, easingObject, delay); + + // Transform values back into their original format + Tweenable.applyFilter(mockTweenable, 'afterTween'); + + return interpolatedValues; + }; + +}()); + +/** + * This module adds string interpolation support to Shifty. + * + * The Token extension allows Shifty to tween numbers inside of strings. Among + * other things, this allows you to animate CSS properties. For example, you + * can do this: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { transform: 'translateX(45px)' }, + * to: { transform: 'translateX(90xp)' } + * }); + * + * `translateX(45)` will be tweened to `translateX(90)`. To demonstrate: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { transform: 'translateX(45px)' }, + * to: { transform: 'translateX(90px)' }, + * step: function (state) { + * console.log(state.transform); + * } + * }); + * + * The above snippet will log something like this in the console: + * + * translateX(60.3px) + * ... + * translateX(76.05px) + * ... + * translateX(90px) + * + * Another use for this is animating colors: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { color: 'rgb(0,255,0)' }, + * to: { color: 'rgb(255,0,255)' }, + * step: function (state) { + * console.log(state.color); + * } + * }); + * + * The above snippet will log something like this: + * + * rgb(84,170,84) + * ... + * rgb(170,84,170) + * ... + * rgb(255,0,255) + * + * This extension also supports hexadecimal colors, in both long (`#ff00ff`) + * and short (`#f0f`) forms. Be aware that hexadecimal input values will be + * converted into the equivalent RGB output values. This is done to optimize + * for performance. + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { color: '#0f0' }, + * to: { color: '#f0f' }, + * step: function (state) { + * console.log(state.color); + * } + * }); + * + * This snippet will generate the same output as the one before it because + * equivalent values were supplied (just in hexadecimal form rather than RGB): + * + * rgb(84,170,84) + * ... + * rgb(170,84,170) + * ... + * rgb(255,0,255) + * + * ## Easing support + * + * Easing works somewhat differently in the Token extension. This is because + * some CSS properties have multiple values in them, and you might need to + * tween each value along its own easing curve. A basic example: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { transform: 'translateX(0px) translateY(0px)' }, + * to: { transform: 'translateX(100px) translateY(100px)' }, + * easing: { transform: 'easeInQuad' }, + * step: function (state) { + * console.log(state.transform); + * } + * }); + * + * The above snippet will create values like this: + * + * translateX(11.56px) translateY(11.56px) + * ... + * translateX(46.24px) translateY(46.24px) + * ... + * translateX(100px) translateY(100px) + * + * In this case, the values for `translateX` and `translateY` are always the + * same for each step of the tween, because they have the same start and end + * points and both use the same easing curve. We can also tween `translateX` + * and `translateY` along independent curves: + * + * var tweenable = new Tweenable(); + * tweenable.tween({ + * from: { transform: 'translateX(0px) translateY(0px)' }, + * to: { transform: 'translateX(100px) translateY(100px)' }, + * easing: { transform: 'easeInQuad bounce' }, + * step: function (state) { + * console.log(state.transform); + * } + * }); + * + * The above snippet will create values like this: + * + * translateX(10.89px) translateY(82.35px) + * ... + * translateX(44.89px) translateY(86.73px) + * ... + * translateX(100px) translateY(100px) + * + * `translateX` and `translateY` are not in sync anymore, because `easeInQuad` + * was specified for `translateX` and `bounce` for `translateY`. Mixing and + * matching easing curves can make for some interesting motion in your + * animations. + * + * The order of the space-separated easing curves correspond the token values + * they apply to. If there are more token values than easing curves listed, + * the last easing curve listed is used. + * @submodule Tweenable.token + */ + +// token function is defined above only so that dox-foundation sees it as +// documentation and renders it. It is never used, and is optimized away at +// build time. + +;(function (Tweenable) { + + /** + * @typedef {{ + * formatString: string + * chunkNames: Array. + * }} + * @private + */ + var formatManifest; + + // CONSTANTS + + var R_NUMBER_COMPONENT = /(\d|\-|\.)/; + var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g; + var R_UNFORMATTED_VALUES = /[0-9.\-]+/g; + var R_RGB = new RegExp( + 'rgb\\(' + R_UNFORMATTED_VALUES.source + + (/,\s*/.source) + R_UNFORMATTED_VALUES.source + + (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g'); + var R_RGB_PREFIX = /^.*\(/; + var R_HEX = /#([0-9]|[a-f]){3,6}/gi; + var VALUE_PLACEHOLDER = 'VAL'; + + // HELPERS + + /** + * @param {Array.number} rawValues + * @param {string} prefix + * + * @return {Array.} + * @private + */ + function getFormatChunksFrom (rawValues, prefix) { + var accumulator = []; + + var rawValuesLength = rawValues.length; + var i; + + for (i = 0; i < rawValuesLength; i++) { + accumulator.push('_' + prefix + '_' + i); + } + + return accumulator; + } + + /** + * @param {string} formattedString + * + * @return {string} + * @private + */ + function getFormatStringFrom (formattedString) { + var chunks = formattedString.match(R_FORMAT_CHUNKS); + + if (!chunks) { + // chunks will be null if there were no tokens to parse in + // formattedString (for example, if formattedString is '2'). Coerce + // chunks to be useful here. + chunks = ['', '']; + + // If there is only one chunk, assume that the string is a number + // followed by a token... + // NOTE: This may be an unwise assumption. + } else if (chunks.length === 1 || + // ...or if the string starts with a number component (".", "-", or a + // digit)... + formattedString[0].match(R_NUMBER_COMPONENT)) { + // ...prepend an empty string here to make sure that the formatted number + // is properly replaced by VALUE_PLACEHOLDER + chunks.unshift(''); + } + + return chunks.join(VALUE_PLACEHOLDER); + } + + /** + * Convert all hex color values within a string to an rgb string. + * + * @param {Object} stateObject + * + * @return {Object} The modified obj + * @private + */ + function sanitizeObjectForHexProps (stateObject) { + Tweenable.each(stateObject, function (prop) { + var currentProp = stateObject[prop]; + + if (typeof currentProp === 'string' && currentProp.match(R_HEX)) { + stateObject[prop] = sanitizeHexChunksToRGB(currentProp); + } + }); + } + + /** + * @param {string} str + * + * @return {string} + * @private + */ + function sanitizeHexChunksToRGB (str) { + return filterStringChunks(R_HEX, str, convertHexToRGB); + } + + /** + * @param {string} hexString + * + * @return {string} + * @private + */ + function convertHexToRGB (hexString) { + var rgbArr = hexToRGBArray(hexString); + return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')'; + } + + var hexToRGBArray_returnArray = []; + /** + * Convert a hexadecimal string to an array with three items, one each for + * the red, blue, and green decimal values. + * + * @param {string} hex A hexadecimal string. + * + * @returns {Array.} The converted Array of RGB values if `hex` is a + * valid string, or an Array of three 0's. + * @private + */ + function hexToRGBArray (hex) { + + hex = hex.replace(/#/, ''); + + // If the string is a shorthand three digit hex notation, normalize it to + // the standard six digit notation + if (hex.length === 3) { + hex = hex.split(''); + hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; + } + + hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2)); + hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2)); + hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2)); + + return hexToRGBArray_returnArray; + } + + /** + * Convert a base-16 number to base-10. + * + * @param {Number|String} hex The value to convert + * + * @returns {Number} The base-10 equivalent of `hex`. + * @private + */ + function hexToDec (hex) { + return parseInt(hex, 16); + } + + /** + * Runs a filter operation on all chunks of a string that match a RegExp + * + * @param {RegExp} pattern + * @param {string} unfilteredString + * @param {function(string)} filter + * + * @return {string} + * @private + */ + function filterStringChunks (pattern, unfilteredString, filter) { + var pattenMatches = unfilteredString.match(pattern); + var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER); + + if (pattenMatches) { + var pattenMatchesLength = pattenMatches.length; + var currentChunk; + + for (var i = 0; i < pattenMatchesLength; i++) { + currentChunk = pattenMatches.shift(); + filteredString = filteredString.replace( + VALUE_PLACEHOLDER, filter(currentChunk)); + } + } + + return filteredString; + } + + /** + * Check for floating point values within rgb strings and rounds them. + * + * @param {string} formattedString + * + * @return {string} + * @private + */ + function sanitizeRGBChunks (formattedString) { + return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk); + } + + /** + * @param {string} rgbChunk + * + * @return {string} + * @private + */ + function sanitizeRGBChunk (rgbChunk) { + var numbers = rgbChunk.match(R_UNFORMATTED_VALUES); + var numbersLength = numbers.length; + var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0]; + + for (var i = 0; i < numbersLength; i++) { + sanitizedString += parseInt(numbers[i], 10) + ','; + } + + sanitizedString = sanitizedString.slice(0, -1) + ')'; + + return sanitizedString; + } + + /** + * @param {Object} stateObject + * + * @return {Object} An Object of formatManifests that correspond to + * the string properties of stateObject + * @private + */ + function getFormatManifests (stateObject) { + var manifestAccumulator = {}; + + Tweenable.each(stateObject, function (prop) { + var currentProp = stateObject[prop]; + + if (typeof currentProp === 'string') { + var rawValues = getValuesFrom(currentProp); + + manifestAccumulator[prop] = { + 'formatString': getFormatStringFrom(currentProp) + ,'chunkNames': getFormatChunksFrom(rawValues, prop) + }; + } + }); + + return manifestAccumulator; + } + + /** + * @param {Object} stateObject + * @param {Object} formatManifests + * @private + */ + function expandFormattedProperties (stateObject, formatManifests) { + Tweenable.each(formatManifests, function (prop) { + var currentProp = stateObject[prop]; + var rawValues = getValuesFrom(currentProp); + var rawValuesLength = rawValues.length; + + for (var i = 0; i < rawValuesLength; i++) { + stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i]; + } + + delete stateObject[prop]; + }); + } + + /** + * @param {Object} stateObject + * @param {Object} formatManifests + * @private + */ + function collapseFormattedProperties (stateObject, formatManifests) { + Tweenable.each(formatManifests, function (prop) { + var currentProp = stateObject[prop]; + var formatChunks = extractPropertyChunks( + stateObject, formatManifests[prop].chunkNames); + var valuesList = getValuesList( + formatChunks, formatManifests[prop].chunkNames); + currentProp = getFormattedValues( + formatManifests[prop].formatString, valuesList); + stateObject[prop] = sanitizeRGBChunks(currentProp); + }); + } + + /** + * @param {Object} stateObject + * @param {Array.} chunkNames + * + * @return {Object} The extracted value chunks. + * @private + */ + function extractPropertyChunks (stateObject, chunkNames) { + var extractedValues = {}; + var currentChunkName, chunkNamesLength = chunkNames.length; + + for (var i = 0; i < chunkNamesLength; i++) { + currentChunkName = chunkNames[i]; + extractedValues[currentChunkName] = stateObject[currentChunkName]; + delete stateObject[currentChunkName]; + } + + return extractedValues; + } + + var getValuesList_accumulator = []; + /** + * @param {Object} stateObject + * @param {Array.} chunkNames + * + * @return {Array.} + * @private + */ + function getValuesList (stateObject, chunkNames) { + getValuesList_accumulator.length = 0; + var chunkNamesLength = chunkNames.length; + + for (var i = 0; i < chunkNamesLength; i++) { + getValuesList_accumulator.push(stateObject[chunkNames[i]]); + } + + return getValuesList_accumulator; + } + + /** + * @param {string} formatString + * @param {Array.} rawValues + * + * @return {string} + * @private + */ + function getFormattedValues (formatString, rawValues) { + var formattedValueString = formatString; + var rawValuesLength = rawValues.length; + + for (var i = 0; i < rawValuesLength; i++) { + formattedValueString = formattedValueString.replace( + VALUE_PLACEHOLDER, +rawValues[i].toFixed(4)); + } + + return formattedValueString; + } + + /** + * Note: It's the duty of the caller to convert the Array elements of the + * return value into numbers. This is a performance optimization. + * + * @param {string} formattedString + * + * @return {Array.|null} + * @private + */ + function getValuesFrom (formattedString) { + return formattedString.match(R_UNFORMATTED_VALUES); + } + + /** + * @param {Object} easingObject + * @param {Object} tokenData + * @private + */ + function expandEasingObject (easingObject, tokenData) { + Tweenable.each(tokenData, function (prop) { + var currentProp = tokenData[prop]; + var chunkNames = currentProp.chunkNames; + var chunkLength = chunkNames.length; + + var easing = easingObject[prop]; + var i; + + if (typeof easing === 'string') { + var easingChunks = easing.split(' '); + var lastEasingChunk = easingChunks[easingChunks.length - 1]; + + for (i = 0; i < chunkLength; i++) { + easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk; + } + + } else { + for (i = 0; i < chunkLength; i++) { + easingObject[chunkNames[i]] = easing; + } + } + + delete easingObject[prop]; + }); + } + + /** + * @param {Object} easingObject + * @param {Object} tokenData + * @private + */ + function collapseEasingObject (easingObject, tokenData) { + Tweenable.each(tokenData, function (prop) { + var currentProp = tokenData[prop]; + var chunkNames = currentProp.chunkNames; + var chunkLength = chunkNames.length; + + var firstEasing = easingObject[chunkNames[0]]; + var typeofEasings = typeof firstEasing; + + if (typeofEasings === 'string') { + var composedEasingString = ''; + + for (var i = 0; i < chunkLength; i++) { + composedEasingString += ' ' + easingObject[chunkNames[i]]; + delete easingObject[chunkNames[i]]; + } + + easingObject[prop] = composedEasingString.substr(1); + } else { + easingObject[prop] = firstEasing; + } + }); + } + + Tweenable.prototype.filter.token = { + 'tweenCreated': function (currentState, fromState, toState, easingObject) { + sanitizeObjectForHexProps(currentState); + sanitizeObjectForHexProps(fromState); + sanitizeObjectForHexProps(toState); + this._tokenData = getFormatManifests(currentState); + }, + + 'beforeTween': function (currentState, fromState, toState, easingObject) { + expandEasingObject(easingObject, this._tokenData); + expandFormattedProperties(currentState, this._tokenData); + expandFormattedProperties(fromState, this._tokenData); + expandFormattedProperties(toState, this._tokenData); + }, + + 'afterTween': function (currentState, fromState, toState, easingObject) { + collapseFormattedProperties(currentState, this._tokenData); + collapseFormattedProperties(fromState, this._tokenData); + collapseFormattedProperties(toState, this._tokenData); + collapseEasingObject(easingObject, this._tokenData); + } + }; + +} (Tweenable)); + +}).call(null); + +},{}],2:[function(require,module,exports){ +// Circle shaped progress bar + +var Shape = require('./shape'); +var utils = require('./utils'); + +var Circle = function Circle(container, options) { + // Use two arcs to form a circle + // See this answer http://stackoverflow.com/a/10477334/1446092 + this._pathTemplate = + 'M 50,50 m 0,-{radius}' + + ' a {radius},{radius} 0 1 1 0,{2radius}' + + ' a {radius},{radius} 0 1 1 0,-{2radius}'; + + this.containerAspectRatio = 1; + + Shape.apply(this, arguments); +}; + +Circle.prototype = new Shape(); +Circle.prototype.constructor = Circle; + +Circle.prototype._pathString = function _pathString(opts) { + var widthOfWider = opts.strokeWidth; + if (opts.trailWidth && opts.trailWidth > opts.strokeWidth) { + widthOfWider = opts.trailWidth; + } + + var r = 50 - widthOfWider / 2; + + return utils.render(this._pathTemplate, { + radius: r, + '2radius': r * 2 + }); +}; + +Circle.prototype._trailString = function _trailString(opts) { + return this._pathString(opts); +}; + +module.exports = Circle; + +},{"./shape":7,"./utils":8}],3:[function(require,module,exports){ +// Line shaped progress bar + +var Shape = require('./shape'); +var utils = require('./utils'); + +var Line = function Line(container, options) { + this._pathTemplate = 'M 0,{center} L 100,{center}'; + Shape.apply(this, arguments); +}; + +Line.prototype = new Shape(); +Line.prototype.constructor = Line; + +Line.prototype._initializeSvg = function _initializeSvg(svg, opts) { + svg.setAttribute('viewBox', '0 0 100 ' + opts.strokeWidth); + svg.setAttribute('preserveAspectRatio', 'none'); +}; + +Line.prototype._pathString = function _pathString(opts) { + return utils.render(this._pathTemplate, { + center: opts.strokeWidth / 2 + }); +}; + +Line.prototype._trailString = function _trailString(opts) { + return this._pathString(opts); +}; + +module.exports = Line; + +},{"./shape":7,"./utils":8}],4:[function(require,module,exports){ +module.exports = { + // Higher level API, different shaped progress bars + Line: require('./line'), + Circle: require('./circle'), + SemiCircle: require('./semicircle'), + + // Lower level API to use any SVG path + Path: require('./path'), + + // Base-class for creating new custom shapes + // to be in line with the API of built-in shapes + // Undocumented. + Shape: require('./shape'), + + // Internal utils, undocumented. + utils: require('./utils') +}; + +},{"./circle":2,"./line":3,"./path":5,"./semicircle":6,"./shape":7,"./utils":8}],5:[function(require,module,exports){ +// Lower level API to animate any kind of svg path + +var Tweenable = require('shifty'); +var utils = require('./utils'); + +var EASING_ALIASES = { + easeIn: 'easeInCubic', + easeOut: 'easeOutCubic', + easeInOut: 'easeInOutCubic' +}; + +var Path = function Path(path, opts) { + // Throw a better error if not initialized with `new` keyword + if (!(this instanceof Path)) { + throw new Error('Constructor was called without new keyword'); + } + + // Default parameters for animation + opts = utils.extend({ + duration: 800, + easing: 'linear', + from: {}, + to: {}, + step: function() {} + }, opts); + + var element; + if (utils.isString(path)) { + element = document.querySelector(path); + } else { + element = path; + } + + // Reveal .path as public attribute + this.path = element; + this._opts = opts; + this._tweenable = null; + + // Set up the starting positions + var length = this.path.getTotalLength(); + this.path.style.strokeDasharray = length + ' ' + length; + this.set(0); +}; + +Path.prototype.value = function value() { + var offset = this._getComputedDashOffset(); + var length = this.path.getTotalLength(); + + var progress = 1 - offset / length; + // Round number to prevent returning very small number like 1e-30, which + // is practically 0 + return parseFloat(progress.toFixed(6), 10); +}; + +Path.prototype.set = function set(progress) { + this.stop(); + + this.path.style.strokeDashoffset = this._progressToOffset(progress); + + var step = this._opts.step; + if (utils.isFunction(step)) { + var easing = this._easing(this._opts.easing); + var values = this._calculateTo(progress, easing); + var reference = this._opts.shape || this; + step(values, reference, this._opts.attachment); + } +}; + +Path.prototype.stop = function stop() { + this._stopTween(); + this.path.style.strokeDashoffset = this._getComputedDashOffset(); +}; + +// Method introduced here: +// http://jakearchibald.com/2013/animated-line-drawing-svg/ +Path.prototype.animate = function animate(progress, opts, cb) { + opts = opts || {}; + + if (utils.isFunction(opts)) { + cb = opts; + opts = {}; + } + + var passedOpts = utils.extend({}, opts); + + // Copy default opts to new object so defaults are not modified + var defaultOpts = utils.extend({}, this._opts); + opts = utils.extend(defaultOpts, opts); + + var shiftyEasing = this._easing(opts.easing); + var values = this._resolveFromAndTo(progress, shiftyEasing, passedOpts); + + this.stop(); + + // Trigger a layout so styles are calculated & the browser + // picks up the starting position before animating + this.path.getBoundingClientRect(); + + var offset = this._getComputedDashOffset(); + var newOffset = this._progressToOffset(progress); + + var self = this; + this._tweenable = new Tweenable(); + this._tweenable.tween({ + from: utils.extend({ offset: offset }, values.from), + to: utils.extend({ offset: newOffset }, values.to), + duration: opts.duration, + easing: shiftyEasing, + step: function(state) { + self.path.style.strokeDashoffset = state.offset; + var reference = opts.shape || self; + opts.step(state, reference, opts.attachment); + }, + finish: function(state) { + if (utils.isFunction(cb)) { + cb(); + } + } + }); +}; + +Path.prototype._getComputedDashOffset = function _getComputedDashOffset() { + var computedStyle = window.getComputedStyle(this.path, null); + return parseFloat(computedStyle.getPropertyValue('stroke-dashoffset'), 10); +}; + +Path.prototype._progressToOffset = function _progressToOffset(progress) { + var length = this.path.getTotalLength(); + return length - progress * length; +}; + +// Resolves from and to values for animation. +Path.prototype._resolveFromAndTo = function _resolveFromAndTo(progress, easing, opts) { + if (opts.from && opts.to) { + return { + from: opts.from, + to: opts.to + }; + } + + return { + from: this._calculateFrom(easing), + to: this._calculateTo(progress, easing) + }; +}; + +// Calculate `from` values from options passed at initialization +Path.prototype._calculateFrom = function _calculateFrom(easing) { + return Tweenable.interpolate(this._opts.from, this._opts.to, this.value(), easing); +}; + +// Calculate `to` values from options passed at initialization +Path.prototype._calculateTo = function _calculateTo(progress, easing) { + return Tweenable.interpolate(this._opts.from, this._opts.to, progress, easing); +}; + +Path.prototype._stopTween = function _stopTween() { + if (this._tweenable !== null) { + this._tweenable.stop(); + this._tweenable = null; + } +}; + +Path.prototype._easing = function _easing(easing) { + if (EASING_ALIASES.hasOwnProperty(easing)) { + return EASING_ALIASES[easing]; + } + + return easing; +}; + +module.exports = Path; + +},{"./utils":8,"shifty":1}],6:[function(require,module,exports){ +// Semi-SemiCircle shaped progress bar + +var Shape = require('./shape'); +var Circle = require('./circle'); +var utils = require('./utils'); + +var SemiCircle = function SemiCircle(container, options) { + // Use one arc to form a SemiCircle + // See this answer http://stackoverflow.com/a/10477334/1446092 + this._pathTemplate = + 'M 50,50 m -{radius},0' + + ' a {radius},{radius} 0 1 1 {2radius},0'; + + this.containerAspectRatio = 2; + + Shape.apply(this, arguments); +}; + +SemiCircle.prototype = new Shape(); +SemiCircle.prototype.constructor = SemiCircle; + +SemiCircle.prototype._initializeSvg = function _initializeSvg(svg, opts) { + svg.setAttribute('viewBox', '0 0 100 50'); +}; + +SemiCircle.prototype._initializeTextContainer = function _initializeTextContainer( + opts, + container, + textContainer +) { + if (opts.text.style) { + // Reset top style + textContainer.style.top = 'auto'; + textContainer.style.bottom = '0'; + + if (opts.text.alignToBottom) { + utils.setStyle(textContainer, 'transform', 'translate(-50%, 0)'); + } else { + utils.setStyle(textContainer, 'transform', 'translate(-50%, 50%)'); + } + } +}; + +// Share functionality with Circle, just have different path +SemiCircle.prototype._pathString = Circle.prototype._pathString; +SemiCircle.prototype._trailString = Circle.prototype._trailString; + +module.exports = SemiCircle; + +},{"./circle":2,"./shape":7,"./utils":8}],7:[function(require,module,exports){ +// Base object for different progress bar shapes + +var Path = require('./path'); +var utils = require('./utils'); + +var DESTROYED_ERROR = 'Object is destroyed'; + +var Shape = function Shape(container, opts) { + // Throw a better error if progress bars are not initialized with `new` + // keyword + if (!(this instanceof Shape)) { + throw new Error('Constructor was called without new keyword'); + } + + // Prevent calling constructor without parameters so inheritance + // works correctly. To understand, this is how Shape is inherited: + // + // Line.prototype = new Shape(); + // + // We just want to set the prototype for Line. + if (arguments.length === 0) { + return; + } + + // Default parameters for progress bar creation + this._opts = utils.extend({ + color: '#555', + strokeWidth: 1.0, + trailColor: null, + trailWidth: null, + fill: null, + text: { + style: { + color: null, + position: 'absolute', + left: '50%', + top: '50%', + padding: 0, + margin: 0, + transform: { + prefix: true, + value: 'translate(-50%, -50%)' + } + }, + autoStyleContainer: true, + alignToBottom: true, + value: null, + className: 'progressbar-text' + }, + svgStyle: { + display: 'block', + width: '100%' + }, + warnings: false + }, opts, true); // Use recursive extend + + // If user specifies e.g. svgStyle or text style, the whole object + // should replace the defaults to make working with styles easier + if (utils.isObject(opts) && opts.svgStyle !== undefined) { + this._opts.svgStyle = opts.svgStyle; + } + if (utils.isObject(opts) && utils.isObject(opts.text) && opts.text.style !== undefined) { + this._opts.text.style = opts.text.style; + } + + var svgView = this._createSvgView(this._opts); + + var element; + if (utils.isString(container)) { + element = document.querySelector(container); + } else { + element = container; + } + + if (!element) { + throw new Error('Container does not exist: ' + container); + } + + this._container = element; + this._container.appendChild(svgView.svg); + if (this._opts.warnings) { + this._warnContainerAspectRatio(this._container); + } + + if (this._opts.svgStyle) { + utils.setStyles(svgView.svg, this._opts.svgStyle); + } + + // Expose public attributes before Path initialization + this.svg = svgView.svg; + this.path = svgView.path; + this.trail = svgView.trail; + this.text = null; + + var newOpts = utils.extend({ + attachment: undefined, + shape: this + }, this._opts); + this._progressPath = new Path(svgView.path, newOpts); + + if (utils.isObject(this._opts.text) && this._opts.text.value !== null) { + this.setText(this._opts.text.value); + } +}; + +Shape.prototype.animate = function animate(progress, opts, cb) { + if (this._progressPath === null) { + throw new Error(DESTROYED_ERROR); + } + + this._progressPath.animate(progress, opts, cb); +}; + +Shape.prototype.stop = function stop() { + if (this._progressPath === null) { + throw new Error(DESTROYED_ERROR); + } + + // Don't crash if stop is called inside step function + if (this._progressPath === undefined) { + return; + } + + this._progressPath.stop(); +}; + +Shape.prototype.destroy = function destroy() { + if (this._progressPath === null) { + throw new Error(DESTROYED_ERROR); + } + + this.stop(); + this.svg.parentNode.removeChild(this.svg); + this.svg = null; + this.path = null; + this.trail = null; + this._progressPath = null; + + if (this.text !== null) { + this.text.parentNode.removeChild(this.text); + this.text = null; + } +}; + +Shape.prototype.set = function set(progress) { + if (this._progressPath === null) { + throw new Error(DESTROYED_ERROR); + } + + this._progressPath.set(progress); +}; + +Shape.prototype.value = function value() { + if (this._progressPath === null) { + throw new Error(DESTROYED_ERROR); + } + + if (this._progressPath === undefined) { + return 0; + } + + return this._progressPath.value(); +}; + +Shape.prototype.setText = function setText(newText) { + if (this._progressPath === null) { + throw new Error(DESTROYED_ERROR); + } + + if (this.text === null) { + // Create new text node + this.text = this._createTextContainer(this._opts, this._container); + this._container.appendChild(this.text); + } + + // Remove previous text and add new + if (utils.isObject(newText)) { + utils.removeChildren(this.text); + this.text.appendChild(newText); + } else { + this.text.innerHTML = newText; + } +}; + +Shape.prototype._createSvgView = function _createSvgView(opts) { + var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + this._initializeSvg(svg, opts); + + var trailPath = null; + // Each option listed in the if condition are 'triggers' for creating + // the trail path + if (opts.trailColor || opts.trailWidth) { + trailPath = this._createTrail(opts); + svg.appendChild(trailPath); + } + + var path = this._createPath(opts); + svg.appendChild(path); + + return { + svg: svg, + path: path, + trail: trailPath + }; +}; + +Shape.prototype._initializeSvg = function _initializeSvg(svg, opts) { + svg.setAttribute('viewBox', '0 0 100 100'); +}; + +Shape.prototype._createPath = function _createPath(opts) { + var pathString = this._pathString(opts); + return this._createPathElement(pathString, opts); +}; + +Shape.prototype._createTrail = function _createTrail(opts) { + // Create path string with original passed options + var pathString = this._trailString(opts); + + // Prevent modifying original + var newOpts = utils.extend({}, opts); + + // Defaults for parameters which modify trail path + if (!newOpts.trailColor) { + newOpts.trailColor = '#eee'; + } + if (!newOpts.trailWidth) { + newOpts.trailWidth = newOpts.strokeWidth; + } + + newOpts.color = newOpts.trailColor; + newOpts.strokeWidth = newOpts.trailWidth; + + // When trail path is set, fill must be set for it instead of the + // actual path to prevent trail stroke from clipping + newOpts.fill = null; + + return this._createPathElement(pathString, newOpts); +}; + +Shape.prototype._createPathElement = function _createPathElement(pathString, opts) { + var path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + path.setAttribute('d', pathString); + path.setAttribute('stroke', opts.color); + path.setAttribute('stroke-width', opts.strokeWidth); + + if (opts.fill) { + path.setAttribute('fill', opts.fill); + } else { + path.setAttribute('fill-opacity', '0'); + } + + return path; +}; + +Shape.prototype._createTextContainer = function _createTextContainer(opts, container) { + var textContainer = document.createElement('div'); + textContainer.className = opts.text.className; + + var textStyle = opts.text.style; + if (textStyle) { + if (opts.text.autoStyleContainer) { + container.style.position = 'relative'; + } + + utils.setStyles(textContainer, textStyle); + // Default text color to progress bar's color + if (!textStyle.color) { + textContainer.style.color = opts.color; + } + } + + this._initializeTextContainer(opts, container, textContainer); + return textContainer; +}; + +// Give custom shapes possibility to modify text element +Shape.prototype._initializeTextContainer = function(opts, container, element) { + // By default, no-op + // Custom shapes should respect API options, such as text.style +}; + +Shape.prototype._pathString = function _pathString(opts) { + throw new Error('Override this function for each progress bar'); +}; + +Shape.prototype._trailString = function _trailString(opts) { + throw new Error('Override this function for each progress bar'); +}; + +Shape.prototype._warnContainerAspectRatio = function _warnContainerAspectRatio(container) { + if (!this.containerAspectRatio) { + return; + } + + var computedStyle = window.getComputedStyle(container, null); + var width = parseFloat(computedStyle.getPropertyValue('width'), 10); + var height = parseFloat(computedStyle.getPropertyValue('height'), 10); + if (!utils.floatEquals(this.containerAspectRatio, width / height)) { + console.warn( + 'Incorrect aspect ratio of container', + '#' + container.id, + 'detected:', + computedStyle.getPropertyValue('width') + '(width)', + '/', + computedStyle.getPropertyValue('height') + '(height)', + '=', + width / height + ); + + console.warn( + 'Aspect ratio of should be', + this.containerAspectRatio + ); + } +}; + +module.exports = Shape; + +},{"./path":5,"./utils":8}],8:[function(require,module,exports){ +// Utility functions + +var PREFIXES = 'Webkit Moz O ms'.split(' '); +var FLOAT_COMPARISON_EPSILON = 0.001; + +// Copy all attributes from source object to destination object. +// destination object is mutated. +function extend(destination, source, recursive) { + destination = destination || {}; + source = source || {}; + recursive = recursive || false; + + for (var attrName in source) { + if (source.hasOwnProperty(attrName)) { + var destVal = destination[attrName]; + var sourceVal = source[attrName]; + if (recursive && isObject(destVal) && isObject(sourceVal)) { + destination[attrName] = extend(destVal, sourceVal, recursive); + } else { + destination[attrName] = sourceVal; + } + } + } + + return destination; +} + +// Renders templates with given variables. Variables must be surrounded with +// braces without any spaces, e.g. {variable} +// All instances of variable placeholders will be replaced with given content +// Example: +// render('Hello, {message}!', {message: 'world'}) +function render(template, vars) { + var rendered = template; + + for (var key in vars) { + if (vars.hasOwnProperty(key)) { + var val = vars[key]; + var regExpString = '\\{' + key + '\\}'; + var regExp = new RegExp(regExpString, 'g'); + + rendered = rendered.replace(regExp, val); + } + } + + return rendered; +} + +function setStyle(element, style, value) { + var elStyle = element.style; // cache for performance + + for (var i = 0; i < PREFIXES.length; ++i) { + var prefix = PREFIXES[i]; + elStyle[prefix + capitalize(style)] = value; + } + + elStyle[style] = value; +} + +function setStyles(element, styles) { + forEachObject(styles, function(styleValue, styleName) { + // Allow disabling some individual styles by setting them + // to null or undefined + if (styleValue === null || styleValue === undefined) { + return; + } + + // If style's value is {prefix: true, value: '50%'}, + // Set also browser prefixed styles + if (isObject(styleValue) && styleValue.prefix === true) { + setStyle(element, styleName, styleValue.value); + } else { + element.style[styleName] = styleValue; + } + }); +} + +function capitalize(text) { + return text.charAt(0).toUpperCase() + text.slice(1); +} + +function isString(obj) { + return typeof obj === 'string' || obj instanceof String; +} + +function isFunction(obj) { + return typeof obj === 'function'; +} + +function isArray(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; +} + +// Returns true if `obj` is object as in {a: 1, b: 2}, not if it's function or +// array +function isObject(obj) { + if (isArray(obj)) { + return false; + } + + var type = typeof obj; + return type === 'object' && !!obj; +} + +function forEachObject(object, callback) { + for (var key in object) { + if (object.hasOwnProperty(key)) { + var val = object[key]; + callback(val, key); + } + } +} + +function floatEquals(a, b) { + return Math.abs(a - b) < FLOAT_COMPARISON_EPSILON; +} + +// https://coderwall.com/p/nygghw/don-t-use-innerhtml-to-empty-dom-elements +function removeChildren(el) { + while (el.firstChild) { + el.removeChild(el.firstChild); + } +} + +module.exports = { + extend: extend, + render: render, + setStyle: setStyle, + setStyles: setStyles, + capitalize: capitalize, + isString: isString, + isFunction: isFunction, + isObject: isObject, + forEachObject: forEachObject, + floatEquals: floatEquals, + removeChildren: removeChildren +}; + +},{}]},{},[4])(4) +}); \ No newline at end of file diff --git a/assets/js/libraries/progress-bar.min.js b/assets/js/libraries/progress-bar.min.js new file mode 100644 index 00000000..d2301645 --- /dev/null +++ b/assets/js/libraries/progress-bar.min.js @@ -0,0 +1,6 @@ +// ProgressBar.js 1.0.1 +// https://kimmobrunfeldt.github.io/progressbar.js +// License: MIT + +!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,b.ProgressBar=a()}}(function(){var a;return function b(a,c,d){function e(g,h){if(!c[g]){if(!a[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};a[g][0].call(k.exports,function(b){var c=a[g][1][b];return e(c?c:b)},k,k.exports,b,a,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;ga?0:(a-f)/e;for(h in b)b.hasOwnProperty(h)&&(i=g[h],k="function"==typeof i?i:o[i],b[h]=j(c[h],d[h],k,l));return b}function j(a,b,c,d){return a+(b-a)*c(d)}function k(a,b){var c=n.prototype.filter,d=a._filterArgs;f(c,function(e){"undefined"!=typeof c[e][b]&&c[e][b].apply(a,d)})}function l(a,b,c,d,e,f,g,h,j,l,m){v=b+c+d,w=Math.min(m||u(),v),x=w>=v,y=d-(v-w),a.isPlaying()&&(x?(j(g,a._attachment,y),a.stop(!0)):(a._scheduleId=l(a._timeoutHandler,s),k(a,"beforeTween"),b+c>w?i(1,e,f,g,1,1,h):i(w,e,f,g,d,b+c,h),k(a,"afterTween"),j(e,a._attachment,y)))}function m(a,b){var c={},d=typeof b;return"string"===d||"function"===d?f(a,function(a){c[a]=b}):f(a,function(a){c[a]||(c[a]=b[a]||q)}),c}function n(a,b){this._currentState=a||{},this._configured=!1,this._scheduleFunction=p,"undefined"!=typeof b&&this.setConfig(b)}var o,p,q="linear",r=500,s=1e3/60,t=Date.now?Date.now:function(){return+new Date},u="undefined"!=typeof SHIFTY_DEBUG_NOW?SHIFTY_DEBUG_NOW:t;p="undefined"!=typeof window?window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||window.mozCancelRequestAnimationFrame&&window.mozRequestAnimationFrame||setTimeout:setTimeout;var v,w,x,y;return n.prototype.tween=function(a){return this._isTweening?this:(void 0===a&&this._configured||this.setConfig(a),this._timestamp=u(),this._start(this.get(),this._attachment),this.resume())},n.prototype.setConfig=function(a){a=a||{},this._configured=!0,this._attachment=a.attachment,this._pausedAtTime=null,this._scheduleId=null,this._delay=a.delay||0,this._start=a.start||e,this._step=a.step||e,this._finish=a.finish||e,this._duration=a.duration||r,this._currentState=g({},a.from)||this.get(),this._originalState=this.get(),this._targetState=g({},a.to)||this.get();var b=this;this._timeoutHandler=function(){l(b,b._timestamp,b._delay,b._duration,b._currentState,b._originalState,b._targetState,b._easing,b._step,b._scheduleFunction)};var c=this._currentState,d=this._targetState;return h(d,c),this._easing=m(c,a.easing||q),this._filterArgs=[c,this._originalState,d,this._easing],k(this,"tweenCreated"),this},n.prototype.get=function(){return g({},this._currentState)},n.prototype.set=function(a){this._currentState=a},n.prototype.pause=function(){return this._pausedAtTime=u(),this._isPaused=!0,this},n.prototype.resume=function(){return this._isPaused&&(this._timestamp+=u()-this._pausedAtTime),this._isPaused=!1,this._isTweening=!0,this._timeoutHandler(),this},n.prototype.seek=function(a){a=Math.max(a,0);var b=u();return this._timestamp+a===0?this:(this._timestamp=b-a,this.isPlaying()||(this._isTweening=!0,this._isPaused=!1,l(this,this._timestamp,this._delay,this._duration,this._currentState,this._originalState,this._targetState,this._easing,this._step,this._scheduleFunction,b),this.pause()),this)},n.prototype.stop=function(a){return this._isTweening=!1,this._isPaused=!1,this._timeoutHandler=e,(b.cancelAnimationFrame||b.webkitCancelAnimationFrame||b.oCancelAnimationFrame||b.msCancelAnimationFrame||b.mozCancelRequestAnimationFrame||b.clearTimeout)(this._scheduleId),a&&(k(this,"beforeTween"),i(1,this._currentState,this._originalState,this._targetState,1,0,this._easing),k(this,"afterTween"),k(this,"afterTweenEnd"),this._finish.call(this,this._currentState,this._attachment)),this},n.prototype.isPlaying=function(){return this._isTweening&&!this._isPaused},n.prototype.setScheduleFunction=function(a){this._scheduleFunction=a},n.prototype.dispose=function(){var a;for(a in this)this.hasOwnProperty(a)&&delete this[a]},n.prototype.filter={},n.prototype.formula={linear:function(a){return a}},o=n.prototype.formula,g(n,{now:u,each:f,tweenProps:i,tweenProp:j,applyFilter:k,shallowCopy:g,defaults:h,composeEasingObject:m}),"function"==typeof SHIFTY_DEBUG_NOW&&(b.timeoutHandler=l),"object"==typeof d?c.exports=n:"function"==typeof a&&a.amd?a(function(){return n}):"undefined"==typeof b.Tweenable&&(b.Tweenable=n),n}();!function(){e.shallowCopy(e.prototype.formula,{easeInQuad:function(a){return Math.pow(a,2)},easeOutQuad:function(a){return-(Math.pow(a-1,2)-1)},easeInOutQuad:function(a){return(a/=.5)<1?.5*Math.pow(a,2):-.5*((a-=2)*a-2)},easeInCubic:function(a){return Math.pow(a,3)},easeOutCubic:function(a){return Math.pow(a-1,3)+1},easeInOutCubic:function(a){return(a/=.5)<1?.5*Math.pow(a,3):.5*(Math.pow(a-2,3)+2)},easeInQuart:function(a){return Math.pow(a,4)},easeOutQuart:function(a){return-(Math.pow(a-1,4)-1)},easeInOutQuart:function(a){return(a/=.5)<1?.5*Math.pow(a,4):-.5*((a-=2)*Math.pow(a,3)-2)},easeInQuint:function(a){return Math.pow(a,5)},easeOutQuint:function(a){return Math.pow(a-1,5)+1},easeInOutQuint:function(a){return(a/=.5)<1?.5*Math.pow(a,5):.5*(Math.pow(a-2,5)+2)},easeInSine:function(a){return-Math.cos(a*(Math.PI/2))+1},easeOutSine:function(a){return Math.sin(a*(Math.PI/2))},easeInOutSine:function(a){return-.5*(Math.cos(Math.PI*a)-1)},easeInExpo:function(a){return 0===a?0:Math.pow(2,10*(a-1))},easeOutExpo:function(a){return 1===a?1:-Math.pow(2,-10*a)+1},easeInOutExpo:function(a){return 0===a?0:1===a?1:(a/=.5)<1?.5*Math.pow(2,10*(a-1)):.5*(-Math.pow(2,-10*--a)+2)},easeInCirc:function(a){return-(Math.sqrt(1-a*a)-1)},easeOutCirc:function(a){return Math.sqrt(1-Math.pow(a-1,2))},easeInOutCirc:function(a){return(a/=.5)<1?-.5*(Math.sqrt(1-a*a)-1):.5*(Math.sqrt(1-(a-=2)*a)+1)},easeOutBounce:function(a){return 1/2.75>a?7.5625*a*a:2/2.75>a?7.5625*(a-=1.5/2.75)*a+.75:2.5/2.75>a?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375},easeInBack:function(a){var b=1.70158;return a*a*((b+1)*a-b)},easeOutBack:function(a){var b=1.70158;return(a-=1)*a*((b+1)*a+b)+1},easeInOutBack:function(a){var b=1.70158;return(a/=.5)<1?.5*(a*a*(((b*=1.525)+1)*a-b)):.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},elastic:function(a){return-1*Math.pow(4,-8*a)*Math.sin((6*a-1)*(2*Math.PI)/2)+1},swingFromTo:function(a){var b=1.70158;return(a/=.5)<1?.5*(a*a*(((b*=1.525)+1)*a-b)):.5*((a-=2)*a*(((b*=1.525)+1)*a+b)+2)},swingFrom:function(a){var b=1.70158;return a*a*((b+1)*a-b)},swingTo:function(a){var b=1.70158;return(a-=1)*a*((b+1)*a+b)+1},bounce:function(a){return 1/2.75>a?7.5625*a*a:2/2.75>a?7.5625*(a-=1.5/2.75)*a+.75:2.5/2.75>a?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375},bouncePast:function(a){return 1/2.75>a?7.5625*a*a:2/2.75>a?2-(7.5625*(a-=1.5/2.75)*a+.75):2.5/2.75>a?2-(7.5625*(a-=2.25/2.75)*a+.9375):2-(7.5625*(a-=2.625/2.75)*a+.984375)},easeFromTo:function(a){return(a/=.5)<1?.5*Math.pow(a,4):-.5*((a-=2)*Math.pow(a,3)-2)},easeFrom:function(a){return Math.pow(a,4)},easeTo:function(a){return Math.pow(a,.25)}})}(),function(){function a(a,b,c,d,e,f){function g(a){return((n*a+o)*a+p)*a}function h(a){return((q*a+r)*a+s)*a}function i(a){return(3*n*a+2*o)*a+p}function j(a){return 1/(200*a)}function k(a,b){return h(m(a,b))}function l(a){return a>=0?a:0-a}function m(a,b){var c,d,e,f,h,j;for(e=a,j=0;8>j;j++){if(f=g(e)-a,l(f)e)return c;if(e>d)return d;for(;d>c;){if(f=g(e),l(f-a)f?c=e:d=e,e=.5*(d-c)+c}return e}var n=0,o=0,p=0,q=0,r=0,s=0;return p=3*b,o=3*(d-b)-p,n=1-p-o,s=3*c,r=3*(e-c)-s,q=1-s-r,k(a,j(f))}function b(b,c,d,e){return function(f){return a(f,b,c,d,e,1)}}e.setBezierFunction=function(a,c,d,f,g){var h=b(c,d,f,g);return h.displayName=a,h.x1=c,h.y1=d,h.x2=f,h.y2=g,e.prototype.formula[a]=h},e.unsetBezierFunction=function(a){delete e.prototype.formula[a]}}(),function(){function a(a,b,c,d,f,g){return e.tweenProps(d,b,a,c,1,g,f)}var b=new e;b._filterArgs=[],e.interpolate=function(c,d,f,g,h){var i=e.shallowCopy({},c),j=h||0,k=e.composeEasingObject(c,g||"linear");b.set({});var l=b._filterArgs;l.length=0,l[0]=i,l[1]=c,l[2]=d,l[3]=k,e.applyFilter(b,"tweenCreated"),e.applyFilter(b,"beforeTween");var m=a(c,i,d,f,k,j);return e.applyFilter(b,"afterTween"),m}}(),function(a){function b(a,b){var c,d=[],e=a.length;for(c=0;e>c;c++)d.push("_"+b+"_"+c);return d}function c(a){var b=a.match(v);return b?(1===b.length||a[0].match(u))&&b.unshift(""):b=["",""],b.join(A)}function d(b){a.each(b,function(a){var c=b[a];"string"==typeof c&&c.match(z)&&(b[a]=e(c))})}function e(a){return i(z,a,f)}function f(a){var b=g(a);return"rgb("+b[0]+","+b[1]+","+b[2]+")"}function g(a){return a=a.replace(/#/,""),3===a.length&&(a=a.split(""),a=a[0]+a[0]+a[1]+a[1]+a[2]+a[2]),B[0]=h(a.substr(0,2)),B[1]=h(a.substr(2,2)),B[2]=h(a.substr(4,2)),B}function h(a){return parseInt(a,16)}function i(a,b,c){var d=b.match(a),e=b.replace(a,A);if(d)for(var f,g=d.length,h=0;g>h;h++)f=d.shift(),e=e.replace(A,c(f));return e}function j(a){return i(x,a,k)}function k(a){for(var b=a.match(w),c=b.length,d=a.match(y)[0],e=0;c>e;e++)d+=parseInt(b[e],10)+",";return d=d.slice(0,-1)+")"}function l(d){var e={};return a.each(d,function(a){var f=d[a];if("string"==typeof f){var g=r(f);e[a]={formatString:c(f),chunkNames:b(g,a)}}}),e}function m(b,c){a.each(c,function(a){for(var d=b[a],e=r(d),f=e.length,g=0;f>g;g++)b[c[a].chunkNames[g]]=+e[g];delete b[a]})}function n(b,c){a.each(c,function(a){var d=b[a],e=o(b,c[a].chunkNames),f=p(e,c[a].chunkNames);d=q(c[a].formatString,f),b[a]=j(d)})}function o(a,b){for(var c,d={},e=b.length,f=0;e>f;f++)c=b[f],d[c]=a[c],delete a[c];return d}function p(a,b){C.length=0;for(var c=b.length,d=0;c>d;d++)C.push(a[b[d]]);return C}function q(a,b){for(var c=a,d=b.length,e=0;d>e;e++)c=c.replace(A,+b[e].toFixed(4));return c}function r(a){return a.match(w)}function s(b,c){a.each(c,function(a){var d,e=c[a],f=e.chunkNames,g=f.length,h=b[a];if("string"==typeof h){var i=h.split(" "),j=i[i.length-1];for(d=0;g>d;d++)b[f[d]]=i[d]||j}else for(d=0;g>d;d++)b[f[d]]=h;delete b[a]})}function t(b,c){a.each(c,function(a){var d=c[a],e=d.chunkNames,f=e.length,g=b[e[0]],h=typeof g;if("string"===h){for(var i="",j=0;f>j;j++)i+=" "+b[e[j]],delete b[e[j]];b[a]=i.substr(1)}else b[a]=g})}var u=/(\d|\-|\.)/,v=/([^\-0-9\.]+)/g,w=/[0-9.\-]+/g,x=new RegExp("rgb\\("+w.source+/,\s*/.source+w.source+/,\s*/.source+w.source+"\\)","g"),y=/^.*\(/,z=/#([0-9]|[a-f]){3,6}/gi,A="VAL",B=[],C=[];a.prototype.filter.token={tweenCreated:function(a,b,c,e){d(a),d(b),d(c),this._tokenData=l(a)},beforeTween:function(a,b,c,d){s(d,this._tokenData),m(a,this._tokenData),m(b,this._tokenData),m(c,this._tokenData)},afterTween:function(a,b,c,d){n(a,this._tokenData),n(b,this._tokenData),n(c,this._tokenData),t(d,this._tokenData)}}}(e)}).call(null)},{}],2:[function(a,b,c){var d=a("./shape"),e=a("./utils"),f=function(a,b){this._pathTemplate="M 50,50 m 0,-{radius} a {radius},{radius} 0 1 1 0,{2radius} a {radius},{radius} 0 1 1 0,-{2radius}",this.containerAspectRatio=1,d.apply(this,arguments)};f.prototype=new d,f.prototype.constructor=f,f.prototype._pathString=function(a){var b=a.strokeWidth;a.trailWidth&&a.trailWidth>a.strokeWidth&&(b=a.trailWidth);var c=50-b/2;return e.render(this._pathTemplate,{radius:c,"2radius":2*c})},f.prototype._trailString=function(a){return this._pathString(a)},b.exports=f},{"./shape":7,"./utils":8}],3:[function(a,b,c){var d=a("./shape"),e=a("./utils"),f=function(a,b){this._pathTemplate="M 0,{center} L 100,{center}",d.apply(this,arguments)};f.prototype=new d,f.prototype.constructor=f,f.prototype._initializeSvg=function(a,b){a.setAttribute("viewBox","0 0 100 "+b.strokeWidth),a.setAttribute("preserveAspectRatio","none")},f.prototype._pathString=function(a){return e.render(this._pathTemplate,{center:a.strokeWidth/2})},f.prototype._trailString=function(a){return this._pathString(a)},b.exports=f},{"./shape":7,"./utils":8}],4:[function(a,b,c){b.exports={Line:a("./line"),Circle:a("./circle"),SemiCircle:a("./semicircle"),Path:a("./path"),Shape:a("./shape"),utils:a("./utils")}},{"./circle":2,"./line":3,"./path":5,"./semicircle":6,"./shape":7,"./utils":8}],5:[function(a,b,c){var d=a("shifty"),e=a("./utils"),f={easeIn:"easeInCubic",easeOut:"easeOutCubic",easeInOut:"easeInOutCubic"},g=function h(a,b){if(!(this instanceof h))throw new Error("Constructor was called without new keyword");b=e.extend({duration:800,easing:"linear",from:{},to:{},step:function(){}},b);var c;c=e.isString(a)?document.querySelector(a):a,this.path=c,this._opts=b,this._tweenable=null;var d=this.path.getTotalLength();this.path.style.strokeDasharray=d+" "+d,this.set(0)};g.prototype.value=function(){var a=this._getComputedDashOffset(),b=this.path.getTotalLength(),c=1-a/b;return parseFloat(c.toFixed(6),10)},g.prototype.set=function(a){this.stop(),this.path.style.strokeDashoffset=this._progressToOffset(a);var b=this._opts.step;if(e.isFunction(b)){var c=this._easing(this._opts.easing),d=this._calculateTo(a,c),f=this._opts.shape||this;b(d,f,this._opts.attachment)}},g.prototype.stop=function(){this._stopTween(),this.path.style.strokeDashoffset=this._getComputedDashOffset()},g.prototype.animate=function(a,b,c){b=b||{},e.isFunction(b)&&(c=b,b={});var f=e.extend({},b),g=e.extend({},this._opts);b=e.extend(g,b);var h=this._easing(b.easing),i=this._resolveFromAndTo(a,h,f);this.stop(),this.path.getBoundingClientRect();var j=this._getComputedDashOffset(),k=this._progressToOffset(a),l=this;this._tweenable=new d,this._tweenable.tween({from:e.extend({offset:j},i.from),to:e.extend({offset:k},i.to),duration:b.duration,easing:h,step:function(a){l.path.style.strokeDashoffset=a.offset;var c=b.shape||l;b.step(a,c,b.attachment)},finish:function(a){e.isFunction(c)&&c()}})},g.prototype._getComputedDashOffset=function(){var a=window.getComputedStyle(this.path,null);return parseFloat(a.getPropertyValue("stroke-dashoffset"),10)},g.prototype._progressToOffset=function(a){var b=this.path.getTotalLength();return b-a*b},g.prototype._resolveFromAndTo=function(a,b,c){return c.from&&c.to?{from:c.from,to:c.to}:{from:this._calculateFrom(b),to:this._calculateTo(a,b)}},g.prototype._calculateFrom=function(a){return d.interpolate(this._opts.from,this._opts.to,this.value(),a)},g.prototype._calculateTo=function(a,b){return d.interpolate(this._opts.from,this._opts.to,a,b)},g.prototype._stopTween=function(){null!==this._tweenable&&(this._tweenable.stop(),this._tweenable=null)},g.prototype._easing=function(a){return f.hasOwnProperty(a)?f[a]:a},b.exports=g},{"./utils":8,shifty:1}],6:[function(a,b,c){var d=a("./shape"),e=a("./circle"),f=a("./utils"),g=function(a,b){this._pathTemplate="M 50,50 m -{radius},0 a {radius},{radius} 0 1 1 {2radius},0",this.containerAspectRatio=2,d.apply(this,arguments)};g.prototype=new d,g.prototype.constructor=g,g.prototype._initializeSvg=function(a,b){a.setAttribute("viewBox","0 0 100 50")},g.prototype._initializeTextContainer=function(a,b,c){a.text.style&&(c.style.top="auto",c.style.bottom="0",a.text.alignToBottom?f.setStyle(c,"transform","translate(-50%, 0)"):f.setStyle(c,"transform","translate(-50%, 50%)"))},g.prototype._pathString=e.prototype._pathString,g.prototype._trailString=e.prototype._trailString,b.exports=g},{"./circle":2,"./shape":7,"./utils":8}],7:[function(a,b,c){var d=a("./path"),e=a("./utils"),f="Object is destroyed",g=function h(a,b){if(!(this instanceof h))throw new Error("Constructor was called without new keyword");if(0!==arguments.length){this._opts=e.extend({color:"#555",strokeWidth:1,trailColor:null,trailWidth:null,fill:null,text:{style:{color:null,position:"absolute",left:"50%",top:"50%",padding:0,margin:0,transform:{prefix:!0,value:"translate(-50%, -50%)"}},autoStyleContainer:!0,alignToBottom:!0,value:null,className:"progressbar-text"},svgStyle:{display:"block",width:"100%"},warnings:!1},b,!0),e.isObject(b)&&void 0!==b.svgStyle&&(this._opts.svgStyle=b.svgStyle),e.isObject(b)&&e.isObject(b.text)&&void 0!==b.text.style&&(this._opts.text.style=b.text.style);var c,f=this._createSvgView(this._opts);if(c=e.isString(a)?document.querySelector(a):a,!c)throw new Error("Container does not exist: "+a);this._container=c,this._container.appendChild(f.svg),this._opts.warnings&&this._warnContainerAspectRatio(this._container),this._opts.svgStyle&&e.setStyles(f.svg,this._opts.svgStyle),this.svg=f.svg,this.path=f.path,this.trail=f.trail,this.text=null;var g=e.extend({attachment:void 0,shape:this},this._opts);this._progressPath=new d(f.path,g),e.isObject(this._opts.text)&&null!==this._opts.text.value&&this.setText(this._opts.text.value)}};g.prototype.animate=function(a,b,c){if(null===this._progressPath)throw new Error(f);this._progressPath.animate(a,b,c)},g.prototype.stop=function(){if(null===this._progressPath)throw new Error(f);void 0!==this._progressPath&&this._progressPath.stop()},g.prototype.destroy=function(){if(null===this._progressPath)throw new Error(f);this.stop(),this.svg.parentNode.removeChild(this.svg),this.svg=null,this.path=null,this.trail=null,this._progressPath=null,null!==this.text&&(this.text.parentNode.removeChild(this.text),this.text=null)},g.prototype.set=function(a){if(null===this._progressPath)throw new Error(f);this._progressPath.set(a)},g.prototype.value=function(){if(null===this._progressPath)throw new Error(f);return void 0===this._progressPath?0:this._progressPath.value()},g.prototype.setText=function(a){if(null===this._progressPath)throw new Error(f);null===this.text&&(this.text=this._createTextContainer(this._opts,this._container),this._container.appendChild(this.text)),e.isObject(a)?(e.removeChildren(this.text),this.text.appendChild(a)):this.text.innerHTML=a},g.prototype._createSvgView=function(a){var b=document.createElementNS("http://www.w3.org/2000/svg","svg");this._initializeSvg(b,a);var c=null;(a.trailColor||a.trailWidth)&&(c=this._createTrail(a),b.appendChild(c));var d=this._createPath(a);return b.appendChild(d),{svg:b,path:d,trail:c}},g.prototype._initializeSvg=function(a,b){a.setAttribute("viewBox","0 0 100 100")},g.prototype._createPath=function(a){var b=this._pathString(a);return this._createPathElement(b,a)},g.prototype._createTrail=function(a){var b=this._trailString(a),c=e.extend({},a);return c.trailColor||(c.trailColor="#eee"),c.trailWidth||(c.trailWidth=c.strokeWidth),c.color=c.trailColor,c.strokeWidth=c.trailWidth,c.fill=null,this._createPathElement(b,c)},g.prototype._createPathElement=function(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg","path");return c.setAttribute("d",a),c.setAttribute("stroke",b.color),c.setAttribute("stroke-width",b.strokeWidth),b.fill?c.setAttribute("fill",b.fill):c.setAttribute("fill-opacity","0"),c},g.prototype._createTextContainer=function(a,b){var c=document.createElement("div");c.className=a.text.className;var d=a.text.style;return d&&(a.text.autoStyleContainer&&(b.style.position="relative"),e.setStyles(c,d),d.color||(c.style.color=a.color)),this._initializeTextContainer(a,b,c),c},g.prototype._initializeTextContainer=function(a,b,c){},g.prototype._pathString=function(a){throw new Error("Override this function for each progress bar")},g.prototype._trailString=function(a){throw new Error("Override this function for each progress bar")},g.prototype._warnContainerAspectRatio=function(a){if(this.containerAspectRatio){var b=window.getComputedStyle(a,null),c=parseFloat(b.getPropertyValue("width"),10),d=parseFloat(b.getPropertyValue("height"),10);e.floatEquals(this.containerAspectRatio,c/d)||(console.warn("Incorrect aspect ratio of container","#"+a.id,"detected:",b.getPropertyValue("width")+"(width)","/",b.getPropertyValue("height")+"(height)","=",c/d),console.warn("Aspect ratio of should be",this.containerAspectRatio))}},b.exports=g},{"./path":5,"./utils":8}],8:[function(a,b,c){function d(a,b,c){a=a||{},b=b||{},c=c||!1;for(var e in b)if(b.hasOwnProperty(e)){var f=a[e],g=b[e];c&&l(f)&&l(g)?a[e]=d(f,g,c):a[e]=g}return a}function e(a,b){var c=a;for(var d in b)if(b.hasOwnProperty(d)){var e=b[d],f="\\{"+d+"\\}",g=new RegExp(f,"g");c=c.replace(g,e)}return c}function f(a,b,c){for(var d=a.style,e=0;e + * @author Ris Adams + */ + +/*jshint curly:false */ +/*jshint browser:true */ + +(function(root, factory) { + if (typeof define === 'function' && define.amd) { + define(['jquery','sifter','microplugin'], factory); + } else if (typeof module === 'object' && typeof module.exports === 'object') { + module.exports = factory(require('jquery'), require('sifter'), require('microplugin')); + } else { + root.Selectize = factory(root.jQuery, root.Sifter, root.MicroPlugin); + } +}(this, function($, Sifter, MicroPlugin) { + 'use strict'; + + var highlight = function($element, pattern) { + if (typeof pattern === 'string' && !pattern.length) return; + var regex = (typeof pattern === 'string') ? new RegExp(pattern, 'i') : pattern; + + var highlight = function(node) { + var skip = 0; + // Wrap matching part of text node with highlighting , e.g. + // Soccer -> Soccer for regex = /soc/i + if (node.nodeType === 3) { + var pos = node.data.search(regex); + if (pos >= 0 && node.data.length > 0) { + var match = node.data.match(regex); + var spannode = document.createElement('span'); + spannode.className = 'highlight'; + var middlebit = node.splitText(pos); + var endbit = middlebit.splitText(match[0].length); + var middleclone = middlebit.cloneNode(true); + spannode.appendChild(middleclone); + middlebit.parentNode.replaceChild(spannode, middlebit); + skip = 1; + } + } + // Recurse element node, looking for child text nodes to highlight, unless element + // is childless, - - - + ?>
-

+

%s %s %s.

', - __('This user has uploaded ', WE_LS_SLUG), + esc_html__('This user has uploaded ', WE_LS_SLUG), $photo_count, _n( 'photo', 'photos', $photo_count, WE_LS_SLUG ) ); - echo ws_ls_photos_shortcode_gallery([ 'error-message' => __('No photos could be found for this user.', WE_LS_SLUG), + echo ws_ls_photos_shortcode_gallery([ 'error-message' => esc_html__('No photos could be found for this user.', WE_LS_SLUG), 'user-id' => $user_id, 'width' => '1200', 'direction' => 'desc', @@ -52,16 +52,16 @@ function ws_ls_admin_page_photos() { echo sprintf('

%s %s.

', ws_ls_meta_fields_base_url(), - __('Add and enable a Photo Custom Field', WE_LS_SLUG), - __('to allow a users to upload photos of their progress' , WE_LS_SLUG) + esc_html__('Add and enable a Photo Custom Field', WE_LS_SLUG), + esc_html__('to allow a users to upload photos of their progress' , WE_LS_SLUG) ); } else { echo sprintf('

%s %s.

', ws_ls_upgrade_link(), - __('Upgrade to Pro', WE_LS_SLUG), - __('to allow a user to upload photos of their progress' , WE_LS_SLUG) + esc_html__('Upgrade to Pro', WE_LS_SLUG), + esc_html__('to allow a user to upload photos of their progress' , WE_LS_SLUG) ); } ?> diff --git a/includes/admin-pages/user-data/data-search-results.php b/includes/admin-pages/user-data/data-search-results.php index 89ce7bc1..2939cc15 100755 --- a/includes/admin-pages/user-data/data-search-results.php +++ b/includes/admin-pages/user-data/data-search-results.php @@ -8,14 +8,14 @@ function ws_ls_admin_page_search_results() { ?>
-

+

-

+

%1$d %2$s: "%3$s"

', count( $search_results ), - __( 'results were found for', WE_LS_SLUG ), + esc_html__( 'results were found for', WE_LS_SLUG ), esc_html( $search_term ) ); @@ -41,12 +41,12 @@ function ws_ls_admin_page_search_results() { - - - - - - + + + + + + %s: "%s"

', - __( 'No users were found for the given search criteria:', WE_LS_SLUG ), + esc_html__( 'No users were found for the given search criteria:', WE_LS_SLUG ), esc_html( $search_term ) ); } } else { - echo __( 'No search terms were specified', WE_LS_SLUG ); + echo esc_html__( 'No search terms were specified', WE_LS_SLUG ); } ?> diff --git a/includes/admin-pages/user-data/data-summary.php b/includes/admin-pages/user-data/data-summary.php index 05e58a21..a667bbba 100755 --- a/includes/admin-pages/user-data/data-summary.php +++ b/includes/admin-pages/user-data/data-summary.php @@ -18,7 +18,7 @@ function ws_ls_admin_page_data_summary() { ws_ls_data_table_enqueue_scripts(); ?>
-

+

@@ -80,15 +80,15 @@ function ws_ls_admin_page_data_summary() {

', + ws_ls_create_dialog_jquery_code(esc_html__('Are you sure you?', WE_LS_SLUG), + esc_html__('Are you sure you wish to remove all user data?', WE_LS_SLUG) . '

', 'delete-confirm'); } function ws_ls_postbox_quick_stats() { ?>
- __( 'Quick Stats', WE_LS_SLUG ), 'postbox-id' => 'quick-stats', 'postbox-col' => 'ws-ls-user-summary-two' ] ); ?> + esc_html__( 'Quick Stats', WE_LS_SLUG ), 'postbox-id' => 'quick-stats', 'postbox-col' => 'ws-ls-user-summary-two' ] ); ?>

(* %7$s %8$s)

', - __('No. of WordPress users', WE_LS_SLUG), + esc_html__('No. of WordPress users', WE_LS_SLUG), $entry_counts['number-of-users'], - __('No. of Weight Entries', WE_LS_SLUG), + esc_html__('No. of Weight Entries', WE_LS_SLUG), ws_ls_blur_text( $entry_counts['number-of-entries'] ), - __('No. of Target Entries', WE_LS_SLUG), + esc_html__('No. of Target Entries', WE_LS_SLUG), ws_ls_blur_text( $entry_counts['number-of-targets'] ), - __('refreshed every 15 minutes', WE_LS_SLUG), + esc_html__('refreshed every 15 minutes', WE_LS_SLUG), 'Regenerate these stats', ws_ls_blur( false, false ) ); @@ -132,11 +132,11 @@ function ws_ls_postbox_quick_stats() { function ws_ls_postbox_view_all() { ?>
- __( 'View all data', WE_LS_SLUG ), 'postbox-id' => 'view-all', 'postbox-col' => 'ws-ls-user-summary-two' ] ); ?> + esc_html__( 'View all data', WE_LS_SLUG ), 'postbox-id' => 'view-all', 'postbox-col' => 'ws-ls-user-summary-two' ] ); ?>
@@ -146,18 +146,18 @@ function ws_ls_postbox_view_all() { function ws_ls_postbox_export() { ?>
- __( 'Export all data', WE_LS_SLUG ), 'postbox-id' => 'export', 'postbox-col' => 'ws-ls-user-summary-two' ] ); ?> + esc_html__( 'Export all data', WE_LS_SLUG ), 'postbox-id' => 'export', 'postbox-col' => 'ws-ls-user-summary-two' ] ); ?>
- %s

', __('You do not have permission to do this.', WE_LS_SLUG ) ); ?> + %s

', esc_html__('You do not have permission to do this.', WE_LS_SLUG ) ); ?> - + - + - + - +
@@ -168,14 +168,14 @@ function ws_ls_postbox_export() { function ws_ls_postbox_delete_data() { ?>
- __( 'Delete Data', WE_LS_SLUG ), 'postbox-id' => 'delete-data', 'postbox-col' => 'ws-ls-user-summary-two' ] ); ?> + esc_html__( 'Delete Data', WE_LS_SLUG ), 'postbox-id' => 'delete-data', 'postbox-col' => 'ws-ls-user-summary-two' ] ); ?>
- %s

', __('You do not have permission to do this.', WE_LS_SLUG ) ); ?> + %s

', esc_html__('You do not have permission to do this.', WE_LS_SLUG ) ); ?> - +
@@ -197,7 +197,7 @@ function ws_ls_postbox_league_table() { // Are we wanting to see who has lost the most? Or gained? $show_gain = (bool) get_option( 'ws-ls-show-gains' ); - $title = ( false === $show_gain ) ? __( 'League table for those that have lost the most', WE_LS_SLUG ) : __( 'League Table for those that have gained the most', WE_LS_SLUG ); + $title = ( false === $show_gain ) ? esc_html__( 'League table for those that have lost the most', WE_LS_SLUG ) : esc_html__( 'League Table for those that have gained the most', WE_LS_SLUG ); ws_ls_postbox_header( [ 'title' => $title, 'postbox-id' => 'league-table' ] ); ?> @@ -218,18 +218,18 @@ function ws_ls_postbox_league_table() { if( true === WS_LS_IS_PRO ) { ?>

- +

+ class="fa fa-refresh"> %s', admin_url( 'admin.php?page=ws-ls-data-home&show-gain=' ) . ( ( false === $show_gain ) ? 'y' : 'n' ), - ( false === $show_gain ) ? __( 'Show who has gained the most', WE_LS_SLUG ) : __( 'Show who has lost the most', WE_LS_SLUG ) + ( false === $show_gain ) ? esc_html__( 'Show who has gained the most', WE_LS_SLUG ) : esc_html__( 'Show who has lost the most', WE_LS_SLUG ) ); } ?> @@ -257,7 +257,7 @@ function ws_ls_postbox_latest_entries() {?> $entries_limit = (int) get_option( 'ws-ls-entries-limit', 100 ); $show_meta = (bool) get_option( 'ws-ls-show-meta' ); - $title = ( false === empty( $entries_limit ) ) ? sprintf( 'Last %d entries', $entries_limit ) : __( 'All entries', WE_LS_SLUG ); + $title = ( false === empty( $entries_limit ) ) ? sprintf( 'Last %d entries', $entries_limit ) : esc_html__( 'All entries', WE_LS_SLUG ); ws_ls_postbox_header( [ 'title' => $title, 'postbox-id' => 'summary-entries' ] ); ?> @@ -274,7 +274,7 @@ function ws_ls_postbox_latest_entries() {?> echo sprintf( ' %s ', admin_url( 'admin.php?page=ws-ls-data-home&entries-limit=100' ), - __( 'Show 100 recent entries', WE_LS_SLUG ) + esc_html__( 'Show 100 recent entries', WE_LS_SLUG ) ); } @@ -282,15 +282,15 @@ function ws_ls_postbox_latest_entries() {?> echo sprintf( ' %s ', admin_url( 'admin.php?page=ws-ls-data-home&entries-limit=500' ), - __( 'Show 500 recent entries', WE_LS_SLUG ) + esc_html__( 'Show 500 recent entries', WE_LS_SLUG ) ); } if ( false === empty( $entries_limit ) ) { echo sprintf(' %s (%s) ', admin_url( 'admin.php?page=ws-ls-data-home&entries-limit=0' ), - __( 'Show all entries', WE_LS_SLUG ), - __( 'slow!', WE_LS_SLUG ) + esc_html__( 'Show all entries', WE_LS_SLUG ), + esc_html__( 'slow!', WE_LS_SLUG ) ); } @@ -298,7 +298,7 @@ function ws_ls_postbox_latest_entries() {?> echo sprintf( ' %s', admin_url( 'admin.php?page=ws-ls-data-home&show-meta=' ) . ( ( false === $show_meta ) ? 'y' : 'n'), - ( false === $show_meta ) ? __( 'Include Custom Fields (Slower)', WE_LS_SLUG ) : __( 'Hide Custom Fields (Quicker)', WE_LS_SLUG ) + ( false === $show_meta ) ? esc_html__( 'Include Custom Fields (Slower)', WE_LS_SLUG ) : esc_html__( 'Hide Custom Fields (Quicker)', WE_LS_SLUG ) ); } @@ -318,7 +318,7 @@ function ws_ls_postbox_change_by_groups() { ?>
__( 'Weight change by group', WE_LS_SLUG ), 'postbox-id' => 'weight-change-by-group' ] ); + ws_ls_postbox_header( [ 'title' => esc_html__( 'Weight change by group', WE_LS_SLUG ), 'postbox-id' => 'weight-change-by-group' ] ); ?>
- - + +
diff --git a/includes/admin-pages/user-data/data-user-edit-settings.php b/includes/admin-pages/user-data/data-user-edit-settings.php index 3a5cedf2..47f29c55 100755 --- a/includes/admin-pages/user-data/data-user-edit-settings.php +++ b/includes/admin-pages/user-data/data-user-edit-settings.php @@ -23,7 +23,7 @@ function ws_ls_admin_page_settings_user() { } ?>
-

+


%s %s.

', ws_ls_upgrade_link(), - __('Upgrade to Pro', WE_LS_SLUG), - __('to save changes to your user\'s settings' , WE_LS_SLUG) + esc_html__('Upgrade to Pro', WE_LS_SLUG), + esc_html__('to save changes to your user\'s settings' , WE_LS_SLUG) ); } diff --git a/includes/admin-pages/user-data/data-user.php b/includes/admin-pages/user-data/data-user.php index f92176ee..19971851 100755 --- a/includes/admin-pages/user-data/data-user.php +++ b/includes/admin-pages/user-data/data-user.php @@ -26,13 +26,13 @@ function ws_ls_admin_page_data_user() { $user_data = get_userdata( $user_id ); ?> -

+

-

+

@@ -113,7 +113,7 @@ function ws_ls_admin_page_data_user() { function ws_ls_postbox_chart( $user_id ) { ?>
- __( 'Chart', WE_LS_SLUG ), 'postbox-id' => 'chart', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?> + esc_html__( 'Chart', WE_LS_SLUG ), 'postbox-id' => 'chart', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?>
%s %s.

', ws_ls_upgrade_link(), - __( 'Upgrade to Pro', WE_LS_SLUG ), - __( 'to view the a chart of the user\'s weight entries.' , WE_LS_SLUG ) + esc_html__( 'Upgrade to Pro', WE_LS_SLUG ), + esc_html__( 'to view the a chart of the user\'s weight entries.' , WE_LS_SLUG ) ); } else { @@ -145,7 +145,7 @@ function ws_ls_postbox_chart( $user_id ) { function ws_ls_postbox_photos( $user_id ) { ?>
- __( 'Photos', WE_LS_SLUG ), 'postbox-id' => 'photos', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?> + esc_html__( 'Photos', WE_LS_SLUG ), 'postbox-id' => 'photos', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?>
%s %s %s. %s.

', - __( 'This user has uploaded ', WE_LS_SLUG ), + esc_html__( 'This user has uploaded ', WE_LS_SLUG ), $photo_count, _n( 'photo', 'photos', $photo_count, WE_LS_SLUG ), ws_ls_get_link_to_photos( $user_id ), - __( 'View all photos', WE_LS_SLUG ) + esc_html__( 'View all photos', WE_LS_SLUG ) ); if ( $photo_count >= 1 ) { echo ws_ls_photos_shortcode_gallery( [ - 'error-message' => __( 'No photos could be found for this user.', WE_LS_SLUG ), + 'error-message' => esc_html__( 'No photos could be found for this user.', WE_LS_SLUG ), 'mode' => 'tilesgrid', 'limit' => 20, 'direction' => 'desc', @@ -174,16 +174,16 @@ function ws_ls_postbox_photos( $user_id ) { echo sprintf('

%s %s.

', ws_ls_meta_fields_base_url(), - __( 'Add and enable a Photo Custom Field', WE_LS_SLUG ), - __( 'to allow a users to upload photos of their progress' , WE_LS_SLUG ) + esc_html__( 'Add and enable a Photo Custom Field', WE_LS_SLUG ), + esc_html__( 'to allow a users to upload photos of their progress' , WE_LS_SLUG ) ); } else { echo sprintf('

%s %s.

', ws_ls_upgrade_link(), - __( 'Upgrade to Pro', WE_LS_SLUG ), - __( 'to allow a user to upload photos of their progress. Before a user can upload photos, you must add one or more custom fields' , WE_LS_SLUG ) + esc_html__( 'Upgrade to Pro', WE_LS_SLUG ), + esc_html__( 'to allow a user to upload photos of their progress. Before a user can upload photos, you must add one or more custom fields' , WE_LS_SLUG ) ); } ?> @@ -199,7 +199,7 @@ function ws_ls_postbox_photos( $user_id ) { function ws_ls_postbox_macros( $user_id ) { ?>
- __('Macronutrient Calculator', WE_LS_SLUG), 'postbox-id' => 'macros', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?> + esc_html__('Macronutrient Calculator', WE_LS_SLUG), 'postbox-id' => 'macros', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?>
%s - %s - %s. %s.

', ws_ls_upgrade_link(), - __( 'Upgrade to Pro Plus', WE_LS_SLUG ), - __( 'Macronutrient Calculator', WE_LS_SLUG ), - __( 'view the user\'s suggested Macronutrient intake based on their recommended calorie intake' , WE_LS_SLUG ), + esc_html__( 'Upgrade to Pro Plus', WE_LS_SLUG ), + esc_html__( 'Macronutrient Calculator', WE_LS_SLUG ), + esc_html__( 'view the user\'s suggested Macronutrient intake based on their recommended calorie intake' , WE_LS_SLUG ), ws_ls_calculations_link() ); } ?> @@ -228,7 +228,7 @@ function ws_ls_postbox_macros( $user_id ) { function ws_ls_postbox_daily_calories( $user_id ) { ?>
- __('Daily calorie needs', WE_LS_SLUG), 'postbox-id' => 'daily-calories', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?> + esc_html__('Daily calorie needs', WE_LS_SLUG), 'postbox-id' => 'daily-calories', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?>
%s - %s - %s. %s.

', ws_ls_upgrade_link(), - __( 'Upgrade to Pro Plus', WE_LS_SLUG ), - __( 'Daily calorie needs', WE_LS_SLUG ), - __( 'view the user\'s daily calorie intake required to either maintain or lose weight (Harris Benedict formula)' , WE_LS_SLUG ), + esc_html__( 'Upgrade to Pro Plus', WE_LS_SLUG ), + esc_html__( 'Daily calorie needs', WE_LS_SLUG ), + esc_html__( 'view the user\'s daily calorie intake required to either maintain or lose weight (Harris Benedict formula)' , WE_LS_SLUG ), ws_ls_calculations_link() ); } ?> @@ -257,7 +257,7 @@ function ws_ls_postbox_daily_calories( $user_id ) { function ws_ls_postbox_awards( $user_id ) { ?>
- __('Awards', WE_LS_SLUG), 'postbox-id' => 'awards', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?> + esc_html__('Awards', WE_LS_SLUG), 'postbox-id' => 'awards', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?>
', esc_url( admin_url( 'admin.php?page=ws-ls-data-home&mode=user&remove-awards=y&user-id=' . (int) $user_id ) ), - __( 'Delete ALL existing awards', WE_LS_SLUG ) + esc_html__( 'Delete ALL existing awards', WE_LS_SLUG ) ); - ws_ls_create_dialog_jquery_code( __('Are you sure you?', WE_LS_SLUG ), - __('Are you sure you wish to remove all awards for this user? Note, when the user next enters a weight the awards will be re-calculated.', WE_LS_SLUG) . '

', + ws_ls_create_dialog_jquery_code( esc_html__('Are you sure you?', WE_LS_SLUG ), + esc_html__('Are you sure you wish to remove all awards for this user? Note, when the user next enters a weight the awards will be re-calculated.', WE_LS_SLUG) . '

', 'delete-awards-confirm' ); } @@ -286,11 +286,11 @@ function ws_ls_postbox_awards( $user_id ) { } else { printf('

%s - %s - %s. %s.

', ws_ls_upgrade_link(), - __( 'Upgrade to Pro Plus', WE_LS_SLUG ), - __( 'Awards', WE_LS_SLUG ), - __( 'view and issue awards based upon users meeting your certain goals' , WE_LS_SLUG ), + esc_html__( 'Upgrade to Pro Plus', WE_LS_SLUG ), + esc_html__( 'Awards', WE_LS_SLUG ), + esc_html__( 'view and issue awards based upon users meeting your certain goals' , WE_LS_SLUG ), ws_ls_awards_base_url(), - __( 'Modify your awards', WE_LS_SLUG ) + esc_html__( 'Modify your awards', WE_LS_SLUG ) ); } ?>
@@ -304,7 +304,7 @@ function ws_ls_postbox_awards( $user_id ) { function ws_ls_postbox_user_entries( $user_id ) { ?>
- __( 'Entries for this user', WE_LS_SLUG ), 'postbox-id' => 'user-entries', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?> + esc_html__( 'Entries for this user', WE_LS_SLUG ), 'postbox-id' => 'user-entries', 'postbox-col' => 'ws-ls-user-data-one' ] ); ?>

@@ -39,21 +39,21 @@ function ws_ls_admin_page_view_all() { %s: %s | %s: %s | %s: %s | %s | %s

', - __( 'Number of WordPress users', WE_LS_SLUG ), + esc_html__( 'Number of WordPress users', WE_LS_SLUG ), ws_ls_round_number( $entry_counts['number-of-users'] ), - __( 'Number of weight entries', WE_LS_SLUG ), + esc_html__( 'Number of weight entries', WE_LS_SLUG ), ws_ls_round_number( $entry_counts['number-of-entries'] ), - __( 'Number of targets entered', WE_LS_SLUG ), + esc_html__( 'Number of targets entered', WE_LS_SLUG ), ws_ls_round_number( $entry_counts['number-of-targets'] ), ws_ls_get_link_to_export(), - __( 'Export to CSV', WE_LS_SLUG ), + esc_html__( 'Export to CSV', WE_LS_SLUG ), ws_ls_get_link_to_export( 'json' ), - __( 'Export to JSON', WE_LS_SLUG ) + esc_html__( 'Export to JSON', WE_LS_SLUG ) ); } if ( $entry_counts['number-of-entries'] > 5000 ) { - printf( '

%s

', __( 'For performance reasons, the following table shall be restricted to a maximum of 5000 entries. For more data, please view individual user records.' ) ); + printf( '

%s

', esc_html__( 'For performance reasons, the following table shall be restricted to a maximum of 5000 entries. For more data, please view individual user records.' ) ); } // Show meta data? @@ -71,7 +71,7 @@ function ws_ls_admin_page_view_all() { echo sprintf( '  %s', admin_url( 'admin.php?page=ws-ls-data-home&mode=all&show-meta=' ) . ( ( false === $show_meta ) ? 'y' : 'n' ), - ( false === $show_meta ) ? __( 'Include Custom Fields (Slower)', WE_LS_SLUG ) : __( 'Hide Custom Fields (Quicker)', WE_LS_SLUG ) + ( false === $show_meta ) ? esc_html__( 'Include Custom Fields (Slower)', WE_LS_SLUG ) : esc_html__( 'Hide Custom Fields (Quicker)', WE_LS_SLUG ) ); } diff --git a/includes/ajax.php b/includes/ajax.php index 6874b723..d1d66aed 100644 --- a/includes/ajax.php +++ b/includes/ajax.php @@ -173,9 +173,9 @@ function ws_ls_ajax_get_errors(){ $table_id = ws_ls_post_value('table_id'); $columns = [ - [ 'name' => 'timestamp', 'title' => __( 'Date', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'date' ], - [ 'name' => 'module', 'title' => __( 'Module', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'text' ], - [ 'name' => 'message', 'title' => __( 'Message', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'text' ] + [ 'name' => 'timestamp', 'title' => esc_html__( 'Date', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'date' ], + [ 'name' => 'module', 'title' => esc_html__( 'Module', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'text' ], + [ 'name' => 'message', 'title' => esc_html__( 'Message', WE_LS_SLUG ), 'breakpoints'=> '', 'type' => 'text' ] ]; $data = [ diff --git a/includes/components.php b/includes/components.php index d33c1487..97747456 100644 --- a/includes/components.php +++ b/includes/components.php @@ -46,19 +46,19 @@ function ws_ls_uikit_summary_boxes( $arguments, $boxes = [] ) { break; case 'gender': $html .= ws_ls_component_user_setting( [ 'user-id' => $arguments[ 'user-id' ], - 'title' => __( 'Gender', WE_LS_SLUG ) , + 'title' => esc_html__( 'Gender', WE_LS_SLUG ) , 'setting' => 'gender' ]); break; case 'aim': $html .= ws_ls_component_user_setting( [ 'user-id' => $arguments[ 'user-id' ], - 'title' => __( 'Aim', WE_LS_SLUG ) , + 'title' => esc_html__( 'Aim', WE_LS_SLUG ) , 'setting' => 'aim' ]); break; case 'activity-level': $html .= ws_ls_component_user_setting( [ 'user-id' => $arguments[ 'user-id' ], - 'title' => __( 'Activity Level', WE_LS_SLUG ), + 'title' => esc_html__( 'Activity Level', WE_LS_SLUG ), 'setting' => 'activity_level' ]); break; @@ -76,7 +76,7 @@ function ws_ls_uikit_summary_boxes( $arguments, $boxes = [] ) { break; case 'group': $html .= ws_ls_component_user_setting( [ 'user-id' => $arguments[ 'user-id' ], - 'title' => __( 'Group', WE_LS_SLUG ), + 'title' => esc_html__( 'Group', WE_LS_SLUG ), 'setting' => 'group' ]); break; @@ -129,8 +129,8 @@ function ws_ls_uikit_summary_boxes( $arguments, $boxes = [] ) { case 'latest-versus-start': $html .= ws_ls_component_latest_versus_another( [ 'user-id' => $arguments[ 'user-id' ], 'compare-against' => 'start', - 'compare-missing-text' => __( 'Missing data', WE_LS_SLUG ), - 'title' => __( 'Latest vs Start', WE_LS_SLUG ) + 'compare-missing-text' => esc_html__( 'Missing data', WE_LS_SLUG ), + 'title' => esc_html__( 'Latest vs Start', WE_LS_SLUG ) ]); break; case 'bmi': @@ -234,7 +234,7 @@ function ws_ls_component_latest_weight( $args = [] ) { $latest_entry = ws_ls_entry_get_latest( $args ); $text_date = ''; - $text_data = __( 'No data', WE_LS_SLUG ); + $text_data = esc_html__( 'No data', WE_LS_SLUG ); if( false === empty( $latest_entry[ 'kg' ] ) ) { @@ -263,7 +263,7 @@ function ws_ls_component_latest_weight( $args = [] ) { $text_data .= sprintf( ' %s', $class, - __( 'The difference between your latest weight and previous.', WE_LS_SLUG ), + esc_html__( 'The difference between your latest weight and previous.', WE_LS_SLUG ), $difference ); } @@ -280,7 +280,7 @@ function ws_ls_component_latest_weight( $args = [] ) {
', $text_data, $text_date, - __( 'Latest Weight', WE_LS_SLUG ) + esc_html__( 'Latest Weight', WE_LS_SLUG ) ); } @@ -302,7 +302,7 @@ function ws_ls_component_weight_difference_since_previous( $args = [] ) { ] ); if ( true === empty( $text_data ) ) { - $text_data = __( 'No data', WE_LS_SLUG ); + $text_data = esc_html__( 'No data', WE_LS_SLUG ); } return sprintf( '
@@ -314,7 +314,7 @@ function ws_ls_component_weight_difference_since_previous( $args = [] ) {
', $text_data, - __( 'Latest / Previous', WE_LS_SLUG ) + esc_html__( 'Latest / Previous', WE_LS_SLUG ) ); } @@ -337,7 +337,7 @@ function ws_ls_component_number_of_awards( $args = [] ) {
', ws_ls_awards_count( $args[ 'user-id' ] ), - __( 'No. of awards', WE_LS_SLUG ) + esc_html__( 'No. of awards', WE_LS_SLUG ) ); } @@ -350,7 +350,7 @@ function ws_ls_component_latest_award( $args = [] ) { $args = wp_parse_args( $args, [ 'user-id' => get_current_user_id() ] ); $awards = ws_ls_awards_previous_awards( $args[ 'user-id' ], 50, 50, 'timestamp' ); $html_thumbnail = ''; - $html_title = __( 'n/a', WE_LS_SLUG ); + $html_title = esc_html__( 'n/a', WE_LS_SLUG ); if ( false === empty( $awards[0] ) ) { @@ -386,7 +386,7 @@ function ws_ls_component_latest_award( $args = [] ) {
', $html_thumbnail, $html_title, - __( 'Latest Award', WE_LS_SLUG ) + esc_html__( 'Latest Award', WE_LS_SLUG ) ); } @@ -402,7 +402,7 @@ function ws_ls_component_previous_weight( $args = [] ) { $previous_entry = ws_ls_entry_get_previous( $args ); $text_date = ''; - $text_data = __( 'No data', WE_LS_SLUG ); + $text_data = esc_html__( 'No data', WE_LS_SLUG ); if( false === empty( $previous_entry ) ) { @@ -425,7 +425,7 @@ function ws_ls_component_previous_weight( $args = [] ) {
', $text_data, $text_date, - __( 'Previous Weight', WE_LS_SLUG ) + esc_html__( 'Previous Weight', WE_LS_SLUG ) ); } @@ -441,14 +441,14 @@ function ws_ls_component_target_weight( $args = [] ) { $target_weight = ws_ls_target_get( $args[ 'user-id' ] ); $text_date = ''; - $text_data = __( 'Not set', WE_LS_SLUG ); + $text_data = esc_html__( 'Not set', WE_LS_SLUG ); if( false === empty( $target_weight ) ) { $text_data = $target_weight[ 'display' ]; } if ( false === ws_ls_targets_enabled() ) { - $text_data = __( 'Targets not enabled in settings', WE_LS_SLUG ); + $text_data = esc_html__( 'Targets not enabled in settings', WE_LS_SLUG ); } return sprintf( '
@@ -463,7 +463,7 @@ function ws_ls_component_target_weight( $args = [] ) {
', $text_data, $text_date, - __( 'Target Weight', WE_LS_SLUG ), + esc_html__( 'Target Weight', WE_LS_SLUG ), ! ws_ls_targets_enabled() ? 'ws-ls-hide' : '' ); } @@ -476,15 +476,15 @@ function ws_ls_component_target_weight( $args = [] ) { */ function ws_ls_component_user_setting( $args = [] ) { - $args = wp_parse_args( $args, [ 'user-id' => get_current_user_id(), 'setting' => 'height', 'title' => __( 'Height', WE_LS_SLUG ) ] ); + $args = wp_parse_args( $args, [ 'user-id' => get_current_user_id(), 'setting' => 'height', 'title' => esc_html__( 'Height', WE_LS_SLUG ) ] ); if ( 'group' === $args[ 'setting' ] ) { $groups = ws_ls_groups_user( $args[ 'user-id'] ); - $setting = ( false === empty( $groups ) ) ? $groups[ 0 ][ 'name' ] : __( 'Not set', WE_LS_SLUG ); + $setting = ( false === empty( $groups ) ) ? $groups[ 0 ][ 'name' ] : esc_html__( 'Not set', WE_LS_SLUG ); } else { - $setting = ws_ls_display_user_setting( $args[ 'user-id' ], $args[ 'setting' ], __( 'Not set', WE_LS_SLUG ), true ); + $setting = ws_ls_display_user_setting( $args[ 'user-id' ], $args[ 'setting' ], esc_html__( 'Not set', WE_LS_SLUG ), true ); } return sprintf( '
@@ -513,7 +513,7 @@ function ws_ls_component_start_weight( $args = [] ) { $start_weight = ws_ls_entry_get_oldest( $args ); $text_date = ''; - $text_data = __( 'Not set', WE_LS_SLUG ); + $text_data = esc_html__( 'Not set', WE_LS_SLUG ); if( false === empty( $start_weight[ 'display' ] ) ) { $text_data = $start_weight[ 'display' ]; @@ -536,7 +536,7 @@ function ws_ls_component_start_weight( $args = [] ) {
', $text_data, $text_date, - __( 'Starting Weight', WE_LS_SLUG ) + esc_html__( 'Starting Weight', WE_LS_SLUG ) ); } @@ -549,11 +549,11 @@ function ws_ls_component_latest_versus_another( $args = [] ) { $args = wp_parse_args( $args, [ 'user-id' => get_current_user_id(), 'compare-against' => 'target', - 'compare-missing-text' => __( 'No target set', WE_LS_SLUG ), - 'title' => __( 'Latest vs Target', WE_LS_SLUG ) + 'compare-missing-text' => esc_html__( 'No target set', WE_LS_SLUG ), + 'title' => esc_html__( 'Latest vs Target', WE_LS_SLUG ) ] ); $comparison_weight = NULL; - $text_data = __( 'No data', WE_LS_SLUG ); + $text_data = esc_html__( 'No data', WE_LS_SLUG ); $latest_entry = ws_ls_entry_get_latest( $args ); if ( 'target' === $args[ 'compare-against' ] ) { @@ -563,7 +563,7 @@ function ws_ls_component_latest_versus_another( $args = [] ) { } if( true === empty( $latest_entry ) ) { - $text_data = __('No entries', WE_LS_SLUG); + $text_data = esc_html__('No entries', WE_LS_SLUG); } elseif( true === empty( $comparison_weight ) ) { $text_data = $args[ 'compare-missing-text' ]; } elseif ( false === empty( $latest_entry ) ) { @@ -600,7 +600,7 @@ function ws_ls_component_latest_versus_another( $args = [] ) { $text_data .= sprintf( ' %s%%', $class, - __( 'The difference between your latest weight and target.', WE_LS_SLUG ), + esc_html__( 'The difference between your latest weight and target.', WE_LS_SLUG ), $percentage_difference ); } @@ -632,7 +632,7 @@ function ws_ls_component_number_of_entries( $args = [] ) { $text_data = ( false === empty( $counts[ 'number-of-entries' ] ) ) ? (int) $counts[ 'number-of-entries' ] : - __( 'No data', WE_LS_SLUG ); + esc_html__( 'No data', WE_LS_SLUG ); return sprintf( '
@@ -643,7 +643,7 @@ function ws_ls_component_number_of_entries( $args = [] ) {
', $text_data, - __( 'No. entries', WE_LS_SLUG ) + esc_html__( 'No. entries', WE_LS_SLUG ) ); } @@ -655,22 +655,22 @@ function ws_ls_component_number_of_entries( $args = [] ) { */ function ws_ls_component_calories( $args = [] ) { - $args = wp_parse_args( $args, [ 'user-id' => get_current_user_id(), 'progress' => 'lose', 'type' => 'total', 'add-unit' => true, 'error-message' => __( 'No data', WE_LS_SLUG ) ] ); + $args = wp_parse_args( $args, [ 'user-id' => get_current_user_id(), 'progress' => 'lose', 'type' => 'total', 'add-unit' => true, 'error-message' => esc_html__( 'No data', WE_LS_SLUG ) ] ); $text_data = ws_ls_shortcode_harris_benedict( $args ); switch ( $args[ 'progress' ] ) { case 'auto': - $title = __( 'Calories for meeting aim', WE_LS_SLUG ); + $title = esc_html__( 'Calories for meeting aim', WE_LS_SLUG ); break; case 'maintain': - $title = __( 'Calories for maintaining', WE_LS_SLUG ); + $title = esc_html__( 'Calories for maintaining', WE_LS_SLUG ); break; case 'gain': - $title = __( 'Calories for gain', WE_LS_SLUG ); + $title = esc_html__( 'Calories for gain', WE_LS_SLUG ); break; default: - $title = __( 'Calories for loss', WE_LS_SLUG ); + $title = esc_html__( 'Calories for loss', WE_LS_SLUG ); } return sprintf( '
@@ -699,7 +699,7 @@ function ws_ls_component_number_of_weight_entries( $args = [] ) { $text_data = ( false === empty( $counts[ 'number-of-weight-entries' ] ) ) ? (int) $counts[ 'number-of-weight-entries' ] : - __( 'No data', WE_LS_SLUG ); + esc_html__( 'No data', WE_LS_SLUG ); return sprintf( '
@@ -710,7 +710,7 @@ function ws_ls_component_number_of_weight_entries( $args = [] ) {
', $text_data, - __( 'No. weight entries', WE_LS_SLUG ) + esc_html__( 'No. weight entries', WE_LS_SLUG ) ); } @@ -738,8 +738,8 @@ function ws_ls_component_number_of_days_tracking( $args = [] ) {
', ws_ls_round_number( $days ), - __( 'Tracking for', WE_LS_SLUG ), - __( 'days', WE_LS_SLUG ) + esc_html__( 'Tracking for', WE_LS_SLUG ), + esc_html__( 'days', WE_LS_SLUG ) ); } @@ -754,8 +754,8 @@ function ws_ls_component_age_dob( $args = [] ) { $args = wp_parse_args( $args, [ 'user-id' => get_current_user_id() ] ); $dob = ws_ls_get_dob( $args[ 'user-id'] ); - $text_link = __( 'Set DoB', WE_LS_SLUG ); - $age = __( 'DoB missing', WE_LS_SLUG ); + $text_link = esc_html__( 'Set DoB', WE_LS_SLUG ); + $age = esc_html__( 'DoB missing', WE_LS_SLUG ); if( false === empty( $dob ) ) { $text_link = ws_ls_iso_date_into_correct_format( $dob ); @@ -772,7 +772,7 @@ function ws_ls_component_age_dob( $args = [] ) {
', $age, - __( 'Age', WE_LS_SLUG ), + esc_html__( 'Age', WE_LS_SLUG ), $text_link ); } @@ -820,7 +820,7 @@ function ws_ls_component_alert( $args ) { wp_kses_post( $args[ 'message' ] ), true === $args[ 'closable' ] ? 'ykuk-close' : '', ( true === $args[ 'include-login-link' ] ) ? - sprintf( ' .', esc_url( wp_login_url( get_permalink() ) ), __( 'Login' , WE_LS_SLUG ) ) : + sprintf( ' .', esc_url( wp_login_url( get_permalink() ) ), esc_html__( 'Login' , WE_LS_SLUG ) ) : '', $args[ 'notification-id' ], esc_attr( $args[ 'css-classes' ] ) @@ -841,7 +841,7 @@ function ws_ls_component_bmi( $args = [] ) { $status = ( false !== strpos( $text_data, 'Healthy' ) ) ? 'ykuk-label ykuk-label-success' : 'ykuk-label ykuk-label-warning'; if ( true === empty( $text_data ) ) { - $text_data = __( 'Missing data', WE_LS_SLUG ); + $text_data = esc_html__( 'Missing data', WE_LS_SLUG ); $status = 'ykuk-text-bold'; } @@ -850,11 +850,11 @@ function ws_ls_component_bmi( $args = [] ) { $text_link = sprintf ( '
%s - ', __( 'What is BMI?', WE_LS_SLUG ) ); + ', esc_html__( 'What is BMI?', WE_LS_SLUG ) ); $text_link .= ws_ls_component_modal( 'modal-bmi', - __( 'Body Mass Index (BMI)', WE_LS_SLUG ), - __('The BMI (Body Mass Index) is used by the medical profession to quickly determine a person’s weight in regard to their height. From a straight forward calculation the BMI factor can be gained and may be used to determine if a person is underweight, of normal weight, overweight or obese.', WE_LS_SLUG ) + esc_html__( 'Body Mass Index (BMI)', WE_LS_SLUG ), + esc_html__('The BMI (Body Mass Index) is used by the medical profession to quickly determine a person’s weight in regard to their height. From a straight forward calculation the BMI factor can be gained and may be used to determine if a person is underweight, of normal weight, overweight or obese.', WE_LS_SLUG ) ); } @@ -868,7 +868,7 @@ function ws_ls_component_bmi( $args = [] ) {
', $text_data, $text_link, - ( 'start' === $args[ 'bmi-type' ] ) ? __( 'Starting BMI', WE_LS_SLUG ) : __( 'Current BMI', WE_LS_SLUG ), + ( 'start' === $args[ 'bmi-type' ] ) ? esc_html__( 'Starting BMI', WE_LS_SLUG ) : esc_html__( 'Current BMI', WE_LS_SLUG ), $status ); } @@ -894,16 +894,16 @@ function ws_ls_component_bmi_warning_notifications( $args ) { } $html = ''; - $prefix = ( false === empty( $args[ 'kiosk-mode'] ) ) ? __( 'User\'s ', WE_LS_SLUG ) : __( 'Your ', WE_LS_SLUG ); + $prefix = ( false === empty( $args[ 'kiosk-mode'] ) ) ? esc_html__( 'User\'s ', WE_LS_SLUG ) : esc_html__( 'Your ', WE_LS_SLUG ); if ( false === empty( $args[ 'bmi-alert-if-above' ] ) && (float) $bmi > (float) $args[ 'bmi-alert-if-above' ] ) { - $html .= ws_ls_component_alert( [ 'message' => $prefix . sprintf( __( 'BMI is above %s.', WE_LS_SLUG ), $args[ 'bmi-alert-if-above' ] ), 'type' => 'danger' ] ); + $html .= ws_ls_component_alert( [ 'message' => $prefix . sprintf( esc_html__( 'BMI is above %s.', WE_LS_SLUG ), $args[ 'bmi-alert-if-above' ] ), 'type' => 'danger' ] ); } if ( false === empty( $args[ 'bmi-alert-if-below' ] ) && $bmi < (float) $args[ 'bmi-alert-if-below' ] ) { - $html .= ws_ls_component_alert( [ 'message' => $prefix . sprintf( __( 'BMI is below %s.', WE_LS_SLUG ), $args[ 'bmi-alert-if-below' ] ), 'type' => 'danger' ] ); + $html .= ws_ls_component_alert( [ 'message' => $prefix . sprintf( esc_html__( 'BMI is below %s.', WE_LS_SLUG ), $args[ 'bmi-alert-if-below' ] ), 'type' => 'danger' ] ); } return $html; @@ -923,7 +923,7 @@ function ws_ls_component_bmr( $args = [] ) { $text_data = ws_ls_shortcode_bmr( [ 'user-id' => $args[ 'user-id' ], 'bmr-type' => $args[ 'bmr-type' ], 'suppress-errors' => true ] ); if ( true === empty( $text_data ) ) { - $text_data = __( 'Missing data', WE_LS_SLUG ); + $text_data = esc_html__( 'Missing data', WE_LS_SLUG ); } if( true === empty( $args[ 'hide-advanced-narrative' ] ) ) { @@ -931,11 +931,11 @@ function ws_ls_component_bmr( $args = [] ) { $text_link = sprintf ( '
%s - ', __( 'What is BMR?', WE_LS_SLUG ) ); + ', esc_html__( 'What is BMR?', WE_LS_SLUG ) ); $text_link .= ws_ls_component_modal( 'modal-bmr', - __( 'Basal Metabolic Rate (BMR)', WE_LS_SLUG ), - __( 'BMR is short for Basal Metabolic Rate. The Basal Metabolic Rate is the number of calories required to keep your body functioning at rest, also known as your metabolism. We calculate your BMR using formulas provided by www.diabetes.co.uk.', WE_LS_SLUG ) + esc_html__( 'Basal Metabolic Rate (BMR)', WE_LS_SLUG ), + esc_html__( 'BMR is short for Basal Metabolic Rate. The Basal Metabolic Rate is the number of calories required to keep your body functioning at rest, also known as your metabolism. We calculate your BMR using formulas provided by www.diabetes.co.uk.', WE_LS_SLUG ) ); } @@ -947,7 +947,7 @@ function ws_ls_component_bmr( $args = [] ) { %3$s
', - ( 'start' === $args[ 'bmr-type' ] ) ? __( 'Starting BMR', WE_LS_SLUG ) : __( 'Current BMR', WE_LS_SLUG ), + ( 'start' === $args[ 'bmr-type' ] ) ? esc_html__( 'Starting BMR', WE_LS_SLUG ) : esc_html__( 'Current BMR', WE_LS_SLUG ), $text_data, $text_link ); @@ -972,7 +972,7 @@ function ws_ls_component_name_and_email( $args = [] ) { %3$s
', - __( 'Name', WE_LS_SLUG ), + esc_html__( 'Name', WE_LS_SLUG ), esc_html( $name ), esc_html( $user->user_email ) ); @@ -994,7 +994,7 @@ function ws_ls_component_user_id( $args = [] ) { %2$d
', - __( 'User ID', WE_LS_SLUG ), + esc_html__( 'User ID', WE_LS_SLUG ), $args[ 'user-id' ] ); } @@ -1194,7 +1194,7 @@ function ws_ls_component_user_search( $arguments ) { 'disable-main-font' => false, 'disable-not-logged-in' => false, 'preload-max' => 1200, // Preload the user list via Ajax if total user count is less than this. - 'placeholder' => __( 'Search for a user...', WE_LS_SLUG ), + 'placeholder' => esc_html__( 'Search for a user...', WE_LS_SLUG ), 'previous-search' => '', 'querystring-key-user-id' => 'wt-user-id' ]); @@ -1203,7 +1203,7 @@ function ws_ls_component_user_search( $arguments ) { if ( false === is_user_logged_in() ) { return ( false === ws_ls_to_bool( $arguments[ 'disable-not-logged-in' ] ) ) ? - ws_ls_component_alert( [ 'message' => __( 'You need to be logged in to search for users.', WE_LS_SLUG ), 'type' => 'primary', 'closable' => false, 'include=login-link' => true ] ) : + ws_ls_component_alert( [ 'message' => esc_html__( 'You need to be logged in to search for users.', WE_LS_SLUG ), 'type' => 'primary', 'closable' => false, 'include=login-link' => true ] ) : ''; } @@ -1236,7 +1236,7 @@ function ws_ls_component_user_search( $arguments ) { ws_ls_component_id(), esc_url( $reset_link ), ( NULL === ws_ls_querystring_value( $arguments[ 'querystring-key-user-id' ] ) ) ? 'default' : 'secondary', - ( false === $arguments[ 'kiosk-barcode-scanner' ] ) ? __( 'Clear Screen', WE_LS_SLUG ) : __( 'Clear', WE_LS_SLUG ), + ( false === $arguments[ 'kiosk-barcode-scanner' ] ) ? esc_html__( 'Clear Screen', WE_LS_SLUG ) : esc_html__( 'Clear', WE_LS_SLUG ), ( false === $arguments[ 'user-loaded' ] ) ? ' ws-ls-hide' : '', ( false === $arguments[ 'kiosk-barcode-scanner' ] || false === $arguments[ 'kiosk-barcode-scanner-camera' ] ) ? ' ws-ls-hide' : '', ( false === $arguments[ 'kiosk-barcode-scanner' ] || false === $arguments[ 'kiosk-barcode-scanner-lazer' ] ) ? ' ws-ls-hide' : '' @@ -1288,15 +1288,15 @@ function ws_ls_component_group_view_entries( $arguments ) { } $display_text = ( true === ws_ls_to_bool( $arguments[ 'todays-entries-only' ] ) ) ? - __( 'Total weight difference (between previous/latest)', WE_LS_SLUG ) : - __( 'Total weight difference (between start/latest)', WE_LS_SLUG ); + esc_html__( 'Total weight difference (between previous/latest)', WE_LS_SLUG ) : + esc_html__( 'Total weight difference (between start/latest)', WE_LS_SLUG ); - $message = ws_ls_component_alert( [ 'message' => __( 'Total losses', WE_LS_SLUG ) . ': .', + $message = ws_ls_component_alert( [ 'message' => esc_html__( 'Total losses', WE_LS_SLUG ) . ': .', 'css-classes' => 'ykuk-invisible ws-ls-total-losses-count', 'uikit' => $arguments[ 'uikit'] ]); - $message .= ws_ls_component_alert( [ 'message' => __( 'Total gains', WE_LS_SLUG ) . ': .', + $message .= ws_ls_component_alert( [ 'message' => esc_html__( 'Total gains', WE_LS_SLUG ) . ': .', 'css-classes' => 'ykuk-invisible ws-ls-total-gains-count', 'uikit' => $arguments[ 'uikit'] ]); diff --git a/includes/converters.php b/includes/converters.php index 4466f0da..fd6acbe7 100755 --- a/includes/converters.php +++ b/includes/converters.php @@ -115,13 +115,13 @@ function ws_ls_weight_display( $kg, $user_id = NULL, $key = false, $force_admin case 'pounds_only': $weight[ 'pounds' ] = ws_ls_convert_kg_to_lb( $kg ); - $weight[ 'display' ] = sprintf( '%s%s', $weight[ 'pounds' ], __( 'lbs', WE_LS_SLUG ) ); + $weight[ 'display' ] = sprintf( '%s%s', $weight[ 'pounds' ], esc_html__( 'lbs', WE_LS_SLUG ) ); $weight[ 'graph-value' ] = $weight[ 'pounds' ]; $weight[ 'imperial' ] = true; break; case 'kg': - $weight[ 'display' ] = sprintf( '%s%s', ws_ls_round_decimals( $kg ), __( 'kg', WE_LS_SLUG ) ); + $weight[ 'display' ] = sprintf( '%s%s', ws_ls_round_decimals( $kg ), esc_html__( 'kg', WE_LS_SLUG ) ); $weight[ 'graph-value' ] = $weight['kg']; $weight[ 'imperial' ] = false; break; @@ -136,7 +136,7 @@ function ws_ls_weight_display( $kg, $user_id = NULL, $key = false, $force_admin $imperial[ 'stones' ]++; } - $weight[ 'display' ] = sprintf( '%s%s %s%s', $imperial[ 'stones' ],__( 'st' , WE_LS_SLUG), $imperial[ 'pounds' ], __( 'lbs' , WE_LS_SLUG) ); + $weight[ 'display' ] = sprintf( '%s%s %s%s', $imperial[ 'stones' ],esc_html__( 'st' , WE_LS_SLUG), $imperial[ 'pounds' ], esc_html__( 'lbs' , WE_LS_SLUG) ); $weight[ 'graph-value' ] = ( $imperial['stones'] * 14 ) + $imperial['pounds']; $weight[ 'stones' ] = $imperial['stones']; $weight[ 'pounds' ] = $imperial['pounds']; @@ -185,7 +185,7 @@ function ws_ls_format_stones_pound_for_comparison_display( $weight ) { } if ($show_stones) { - $text[] = $weight['stones'] . __( 'st', WE_LS_SLUG ); + $text[] = $weight['stones'] . esc_html__( 'st', WE_LS_SLUG ); } if ( true === is_numeric( $weight['pounds'] ) ) { @@ -197,7 +197,7 @@ function ws_ls_format_stones_pound_for_comparison_display( $weight ) { $weight['pounds'] = abs($weight['pounds']); } - $text[] = $weight['pounds'] . __('lbs', WE_LS_SLUG); + $text[] = $weight['pounds'] . esc_html__('lbs', WE_LS_SLUG); } return implode(' ', $text); diff --git a/includes/core-charting.php b/includes/core-charting.php index e1af5709..72db19bc 100644 --- a/includes/core-charting.php +++ b/includes/core-charting.php @@ -26,7 +26,7 @@ function ws_ls_display_chart( $weight_data, $options = [] ) { 'custom-field-slugs' => '', // If specified, only show the custom fields that are specified 'height' => 250, 'legend-position' => 'top', // 'top' or 'bottom' - 'message-no-data' => __( 'No entries could be found for this user.', WE_LS_SLUG ), + 'message-no-data' => esc_html__( 'No entries could be found for this user.', WE_LS_SLUG ), 'show-gridlines' => ws_ls_option_to_bool( 'ws-ls-grid-lines', 'yes', true ), 'show-weight' => true, 'show-target' => true, @@ -54,7 +54,7 @@ function ws_ls_display_chart( $weight_data, $options = [] ) { false === $chart_config[ 'show-target' ] && false === $chart_config[ 'show-meta-fields' ] ) { - return __( 'Error. You have disabled all data sets from rendering.', WE_LS_SLUG ); + return esc_html__( 'Error. You have disabled all data sets from rendering.', WE_LS_SLUG ); } $chart_config[ 'id' ] = ws_ls_component_id(); @@ -70,7 +70,7 @@ function ws_ls_display_chart( $weight_data, $options = [] ) { $chart_config[ 'meta-fields' ] = WS_LS_IS_PRO ? ws_ls_meta_fields_plottable( $chart_config ) : false; $chart_config[ 'show-meta-fields' ] = ( true === ws_ls_to_bool( $chart_config[ 'show-meta-fields' ] ) && false === empty( $chart_config[ 'meta-fields' ] ) ); - $chart_config[ 'y-axis-unit' ] = ( 'kg' !== ws_ls_setting( 'weight-unit', $chart_config[ 'user-id' ] ) ) ? __( 'lbs', WE_LS_SLUG ) : __( 'kg', WE_LS_SLUG ); + $chart_config[ 'y-axis-unit' ] = ( 'kg' !== ws_ls_setting( 'weight-unit', $chart_config[ 'user-id' ] ) ) ? esc_html__( 'lbs', WE_LS_SLUG ) : esc_html__( 'kg', WE_LS_SLUG ); $chart_config[ 'point-size' ] = ws_ls_option_to_int( 'ws-ls-point-size', 3, true ); $chart_config[ 'line-thickness' ] = 2; $chart_config[ 'target-weight' ] = false; @@ -97,7 +97,7 @@ function ws_ls_display_chart( $weight_data, $options = [] ) { $graph_data['datasets'][ $index_weight ] = [ 'fill' => false, - 'label' => __( 'Weight', WE_LS_SLUG ), + 'label' => esc_html__( 'Weight', WE_LS_SLUG ), 'borderColor' => $chart_config[ 'weight-line-color' ], 'data' => [], 'yAxisID' => AXIS_WEIGHT_AND_TARGET, @@ -146,7 +146,7 @@ function ws_ls_display_chart( $weight_data, $options = [] ) { // If target weights are enabled, then include into javascript data object if ( false === empty( $chart_config[ 'target-weight' ] ) ) { - $graph_data['datasets'][ $index_target ] = [ 'label' => __( 'Target', WE_LS_SLUG ), + $graph_data['datasets'][ $index_target ] = [ 'label' => esc_html__( 'Target', WE_LS_SLUG ), 'borderColor' => $chart_config[ 'target-fill-color' ], 'borderWidth' => $chart_config[ 'line-thickness' ], 'pointRadius' => 0, @@ -280,7 +280,7 @@ function ws_ls_display_chart( $weight_data, $options = [] ) { 'grid' => [ 'drawOnChartArea' => $chart_config[ 'show-gridlines' ] ], 'position' => 'left', 'title' => [ 'display' => true, - 'text' => sprintf( '%s (%s)', __( 'Weight', WE_LS_SLUG ), $chart_config[ 'y-axis-unit' ] ), + 'text' => sprintf( '%s (%s)', esc_html__( 'Weight', WE_LS_SLUG ), $chart_config[ 'y-axis-unit' ] ), 'color' => $chart_config['font-config']['fontColor'], 'font' => [ 'family' => $chart_config[ 'font-config' ][ 'fontFamily' ] ] ], @@ -293,7 +293,7 @@ function ws_ls_display_chart( $weight_data, $options = [] ) { // If we only have one custom field, then use that suffix for the y axis label. if( $count_meta_fields > 1 || true === empty( $y_axis_label ) ) { - $y_axis_label = __( 'Additional Fields', WE_LS_SLUG ); + $y_axis_label = esc_html__( 'Additional Fields', WE_LS_SLUG ); } // Custom fields? diff --git a/includes/core-forms.php b/includes/core-forms.php index 11cc652e..7afd94ea 100644 --- a/includes/core-forms.php +++ b/includes/core-forms.php @@ -51,7 +51,7 @@ function ws_ls_form_weight( $arguments = [] ) { // Did we manage to fetch an entry? if ( true === empty( $arguments[ 'entry' ] ) ) { - return ws_ls_blockquote_error( __( 'The selected entry no longer exists.', WE_LS_SLUG ) ); + return ws_ls_blockquote_error( esc_html__( 'The selected entry no longer exists.', WE_LS_SLUG ) ); } $arguments[ 'hide-button-cancel' ] = false; @@ -109,10 +109,10 @@ function ws_ls_form_weight( $arguments = [] ) { $url .= '#' . $arguments[ 'form-key' ]; $html .= sprintf( '

%1$s: %2$s %4$s

', - __( 'Note', WE_LS_SLUG ), - __( 'Data has previously been entered for this date and will be replaced if this form is submitted.', WE_LS_SLUG ), + esc_html__( 'Note', WE_LS_SLUG ), + esc_html__( 'Data has previously been entered for this date and will be replaced if this form is submitted.', WE_LS_SLUG ), esc_url( $url ), - __( 'Load the existing data.', WE_LS_SLUG ) + esc_html__( 'Load the existing data.', WE_LS_SLUG ) ); } @@ -122,7 +122,7 @@ function ws_ls_form_weight( $arguments = [] ) {

%1$s

    -
    ', __( 'Please correct the following:', WE_LS_SLUG ) ); +
    ', esc_html__( 'Please correct the following:', WE_LS_SLUG ) ); // Weight / Custom fields form? Display date field? if ( true === in_array( $arguments[ 'type' ], [ 'custom-fields', 'weight' ] ) ) { @@ -139,7 +139,7 @@ function ws_ls_form_weight( $arguments = [] ) { $html .= ws_ls_form_field_date( [ 'name' => 'we-ls-date', 'value' => $date, 'placeholder' => $date, - 'title' => __( 'Date', WE_LS_SLUG ), + 'title' => esc_html__( 'Date', WE_LS_SLUG ), 'form-id' => $arguments[ 'form-id' ], 'uikit' => $arguments[ 'uikit' ], 'css-class' => 'we-ls-datepicker' . ( true === $arguments[ 'uikit' ] ? ' ykuk-width-1-1' : '' ) @@ -155,7 +155,7 @@ function ws_ls_form_weight( $arguments = [] ) { if ( false === empty( $target_weight[ 'display' ] ) ) { $html .= sprintf( '

    %1$s %2$s.

    ', - ( false === is_admin() ) ? __( 'Your target weight is', WE_LS_SLUG ) : __( 'The user\'s target weight is currently', WE_LS_SLUG ), + ( false === is_admin() ) ? esc_html__( 'Your target weight is', WE_LS_SLUG ) : esc_html__( 'The user\'s target weight is currently', WE_LS_SLUG ), esc_html( $target_weight[ 'display' ] ) ); @@ -190,7 +190,7 @@ function ws_ls_form_weight( $arguments = [] ) { // Stones field? if ( 'stones_pounds' === $arguments[ 'data-unit' ] ) { $html .= ws_ls_form_field_number( [ 'name' => 'ws-ls-weight-stones', - 'placeholder' => false === empty( $placeholders[ 'stones' ] ) ? $placeholders[ 'stones' ] . __( 'st', WE_LS_SLUG ) : __( 'st', WE_LS_SLUG ), + 'placeholder' => false === empty( $placeholders[ 'stones' ] ) ? $placeholders[ 'stones' ] . esc_html__( 'st', WE_LS_SLUG ) : esc_html__( 'st', WE_LS_SLUG ), 'css-class' => 'ykuk-input ykuk-width-1-2 ykuk-margin', 'uikit' => $arguments[ 'uikit' ], 'value' => ( false === empty( $arguments[ 'entry' ][ 'stones' ] ) ) ? $arguments[ 'entry' ][ 'stones' ] : '' ] ); @@ -199,7 +199,7 @@ function ws_ls_form_weight( $arguments = [] ) { // Pounds? if ( true === in_array( $arguments[ 'data-unit' ], [ 'stones_pounds', 'pounds_only' ] ) ) { $html .= ws_ls_form_field_number( [ 'name' => 'ws-ls-weight-pounds', - 'placeholder' =>( false === empty( $placeholders[ 'pounds' ] ) ) ? $placeholders[ 'pounds' ] . __( 'lb', WE_LS_SLUG ) : __( 'lb', WE_LS_SLUG ), + 'placeholder' =>( false === empty( $placeholders[ 'pounds' ] ) ) ? $placeholders[ 'pounds' ] . esc_html__( 'lb', WE_LS_SLUG ) : esc_html__( 'lb', WE_LS_SLUG ), 'max' => ( 'stones_pounds' === $arguments[ 'data-unit' ] ) ? '13.99' : '5000', 'css-class' => 'ykuk-input ykuk-width-1-2 ykuk-margin', 'uikit' => $arguments[ 'uikit' ], @@ -209,7 +209,7 @@ function ws_ls_form_weight( $arguments = [] ) { // Kg if ( 'kg' === $arguments[ 'data-unit' ] ) { $html .= ws_ls_form_field_number( [ 'name' => 'ws-ls-weight-kg', - 'placeholder' => $placeholders[ 'kg' ] . __( 'kg', WE_LS_SLUG ), + 'placeholder' => $placeholders[ 'kg' ] . esc_html__( 'kg', WE_LS_SLUG ), 'value' => ( false === empty( $arguments[ 'entry' ][ 'kg' ] ) ) ? $arguments[ 'entry' ][ 'kg' ] : '', 'testid' => ( 'target' == $arguments[ 'type' ] ) ? 'ws-form-target' : 'ws-form-weight' ] ); @@ -220,7 +220,7 @@ function ws_ls_form_weight( $arguments = [] ) { false === $arguments[ 'hide-notes' ] ) { $html .= ws_ls_form_field_textarea( [ 'name' => 'we-ls-notes', - 'placeholder' => __( 'Notes', WE_LS_SLUG ), + 'placeholder' => esc_html__( 'Notes', WE_LS_SLUG ), 'value' => ( false === empty( $arguments[ 'entry' ][ 'notes' ] ) ) ? $arguments[ 'entry' ][ 'notes' ] : '' ] ); } @@ -234,7 +234,7 @@ function ws_ls_form_weight( $arguments = [] ) {
    ', ws_ls_form_tab_index_next(), - ( 'target' === $arguments[ 'type' ] ) ? __( 'Set Target', WE_LS_SLUG ) : __( 'Save Entry', WE_LS_SLUG ), + ( 'target' === $arguments[ 'type' ] ) ? esc_html__( 'Set Target', WE_LS_SLUG ) : esc_html__( 'Save Entry', WE_LS_SLUG ), $arguments[ 'form-id' ] ); @@ -245,7 +245,7 @@ function ws_ls_form_weight( $arguments = [] ) { $html .= sprintf(' ', ws_ls_form_tab_index_next(), $arguments[ 'form-id' ], - __( 'Cancel', WE_LS_SLUG ) + esc_html__( 'Cancel', WE_LS_SLUG ) ); } @@ -256,7 +256,7 @@ function ws_ls_form_weight( $arguments = [] ) { false === empty( ws_ls_target_get( $arguments[ 'user-id' ] ) ) ){ $html .= sprintf(' ', ws_ls_form_tab_index_next(), - __( 'Clear Target', WE_LS_SLUG ), + esc_html__( 'Clear Target', WE_LS_SLUG ), $arguments[ 'user-id' ] ); } @@ -292,7 +292,7 @@ function ws_ls_form_init( $arguments = [] ) { if ( false === empty( $save_response ) && false === $arguments[ 'hide-confirmation' ] && $arguments[ 'form-number'] === $save_response['form_number'] ){ - $arguments[ 'html' ] .= ( true === $save_response[ 'error' ] ) ? $save_response[ 'message' ] : ws_ls_display_blockquote( __( 'Your entry has been successfully saved.', WE_LS_SLUG ) ); + $arguments[ 'html' ] .= ( true === $save_response[ 'error' ] ) ? $save_response[ 'message' ] : ws_ls_display_blockquote( esc_html__( 'Your entry has been successfully saved.', WE_LS_SLUG ) ); } // Main title for form @@ -300,15 +300,15 @@ function ws_ls_form_init( $arguments = [] ) { if ( true === empty( $title ) ) { if ( 'target' === $arguments[ 'type' ] ) { - $title = __( 'Target weight', WE_LS_SLUG ); + $title = esc_html__( 'Target weight', WE_LS_SLUG ); } elseif ( 'weight' === $arguments[ 'type' ] ) { if ( false === empty( $arguments[ 'entry' ] ) ) { - $title = __( 'Edit an existing entry', WE_LS_SLUG ); + $title = esc_html__( 'Edit an existing entry', WE_LS_SLUG ); } else { - $title = __( 'Add a new weight entry', WE_LS_SLUG ); + $title = esc_html__( 'Add a new weight entry', WE_LS_SLUG ); } } elseif ( 'custom-fields' === $arguments[ 'type' ] ) { - $title = __( 'Complete the following form', WE_LS_SLUG ); + $title = esc_html__( 'Complete the following form', WE_LS_SLUG ); } } @@ -481,7 +481,7 @@ function ws_ls_form_field_textarea( $arguments = [] ) { $arguments = wp_parse_args( $arguments, [ 'type' => 'date', 'name' => '', 'value' => NULL, - 'placeholder' => __( 'Notes', WE_LS_SLUG ), + 'placeholder' => esc_html__( 'Notes', WE_LS_SLUG ), 'show-label' => false, 'title' => '', 'css-class' => 'we-ls-textarea', @@ -515,7 +515,7 @@ function ws_ls_form_field_textarea( $arguments = [] ) { $arguments[ 'name' ] . ' ' . $arguments[ 'css-class' ], ( false === empty( $arguments[ 'value' ] ) ? esc_textarea( $arguments[ 'value' ] ) : '' ), ( true === $arguments[ 'mandatory' ] ? 'required' : ''), - __( 'Please select a value for'), + esc_html__( 'Please select a value for'), esc_attr( $arguments[ 'title' ] ), ( true === $arguments[ 'disabled' ] ) ? ' disabled="disabled"' : '', esc_attr( $arguments[ 'testid' ] ) @@ -693,7 +693,7 @@ function ws_ls_form_field_select( $arguments ) { esc_attr( $arguments[ 'css-class' ] ), ( true === $arguments[ 'required' ] ) ? ' required="required" ' : '', ( false === empty( $arguments[ 'js-on-change' ] ) ) ? sprintf( ' onchange="%s"', $arguments[ 'js-on-change' ] ) : '', - __( 'Please select a value for'), + esc_html__( 'Please select a value for'), esc_attr( $arguments[ 'label' ] ), $label_id, esc_attr( $arguments[ 'testid'] ) diff --git a/includes/core-tables.php b/includes/core-tables.php index 6f7e623b..d502d053 100644 --- a/includes/core-tables.php +++ b/includes/core-tables.php @@ -30,9 +30,9 @@ function ws_ls_display_table( $user_id, $weight_data ) {
    @@ -328,11 +261,11 @@ function ws_ls_postbox_sidebar_user_information( $user_id ) { - + - + @@ -341,7 +274,7 @@ function ws_ls_postbox_sidebar_user_information( $user_id ) { 1 ) : ?> - + - + - + - + - - + + - + - + - + - + - + - + ', - sprintf( '%s (%s%%)', __( 'Proteins', WE_LS_SLUG ), ws_ls_harris_benedict_setting( 'ws-ls-macro-proteins-' . $key ) ) , + sprintf( '%s (%s%%)', esc_html__( 'Proteins', WE_LS_SLUG ), ws_ls_harris_benedict_setting( 'ws-ls-macro-proteins-' . $key ) ) , ws_ls_macro_round($macros[$key]['total']['protein']), ws_ls_macro_round($macros[$key]['breakfast']['protein']), ws_ls_macro_round($macros[$key]['lunch']['protein']), @@ -187,7 +187,7 @@ function ws_ls_macro_render_table($user_id, $missing_data_text = false, $additio ', - sprintf( '%s (%s%%)', __( 'Carbs', WE_LS_SLUG ), ws_ls_harris_benedict_setting( 'ws-ls-macro-carbs-' . $key ) ) , + sprintf( '%s (%s%%)', esc_html__( 'Carbs', WE_LS_SLUG ), ws_ls_harris_benedict_setting( 'ws-ls-macro-carbs-' . $key ) ) , ws_ls_macro_round($macros[$key]['total']['carbs']), ws_ls_macro_round($macros[$key]['breakfast']['carbs']), ws_ls_macro_round($macros[$key]['lunch']['carbs']), @@ -204,7 +204,7 @@ function ws_ls_macro_render_table($user_id, $missing_data_text = false, $additio ', - sprintf( '%s (%s%%)', __( 'Fats', WE_LS_SLUG ), ws_ls_harris_benedict_setting( 'ws-ls-macro-fats-' . $key ) ) , + sprintf( '%s (%s%%)', esc_html__( 'Fats', WE_LS_SLUG ), ws_ls_harris_benedict_setting( 'ws-ls-macro-fats-' . $key ) ) , ws_ls_macro_round($macros[$key]['total']['fats']), ws_ls_macro_round($macros[$key]['breakfast']['fats']), ws_ls_macro_round($macros[$key]['lunch']['fats']), @@ -235,7 +235,7 @@ function ws_ls_shortcode_macro( $user_defined_arguments ) { } $arguments = shortcode_atts([ - 'error-message' => __('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ), + 'error-message' => esc_html__('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ), 'user-id' => false, 'progress' => 'maintain', // 'maintain', 'lose', 'gain', 'auto' 'nutrient' => 'fats', // 'fats', 'protein', 'carbs' @@ -283,7 +283,7 @@ function ws_ls_shortcode_macro_table($user_defined_arguments) { } $arguments = shortcode_atts([ 'css-class' => '', - 'error-message' => __('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ), + 'error-message' => esc_html__('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ), 'user-id' => false, 'disable-jquery' => false ], $user_defined_arguments ); @@ -324,7 +324,7 @@ function ws_ls_macro_round($value) { */ function ws_ls_get_macro_name( $key ) { - $lookup = [ 'maintain' => __('Maintain', WE_LS_SLUG), 'lose' => __('Lose', WE_LS_SLUG), 'gain' => __('Gain', WE_LS_SLUG) ]; + $lookup = [ 'maintain' => esc_html__('Maintain', WE_LS_SLUG), 'lose' => esc_html__('Lose', WE_LS_SLUG), 'gain' => esc_html__('Gain', WE_LS_SLUG) ]; $lookup = apply_filters( 'wlt-filter-macros-labels', $lookup ); diff --git a/pro-features/plus/messaging/activate.php b/pro-features/plus/messaging/activate.php index 584d97c1..323c9479 100644 --- a/pro-features/plus/messaging/activate.php +++ b/pro-features/plus/messaging/activate.php @@ -57,11 +57,11 @@ function ws_ls_note_email_activate() { // Insert the notification template if ( false === ws_ls_emailer_get('note-added') ) { - $email = sprintf( '

    %s {first-name},

    ', __( 'Hello' , WE_LS_SLUG) ); - $email .= __( '

    A new note has been sent to you from {name}:

    ' , WE_LS_SLUG); - $email .= __( '

    {data}

    ' , WE_LS_SLUG) . PHP_EOL . PHP_EOL; + $email = sprintf( '

    %s {first-name},

    ', esc_html__( 'Hello' , WE_LS_SLUG) ); + $email .= esc_html__( '

    A new note has been sent to you from {name}:

    ' , WE_LS_SLUG); + $email .= esc_html__( '

    {data}

    ' , WE_LS_SLUG) . PHP_EOL . PHP_EOL; - ws_ls_emailer_add( 'note-added', 'Weight Tracker: New note', $email, __( 'Note added' , WE_LS_SLUG ) ); + ws_ls_emailer_add( 'note-added', 'Weight Tracker: New note', $email, esc_html__( 'Note added' , WE_LS_SLUG ) ); } } diff --git a/pro-features/plus/messaging/functions-notes.php b/pro-features/plus/messaging/functions-notes.php index deb6d43c..a5eda667 100644 --- a/pro-features/plus/messaging/functions-notes.php +++ b/pro-features/plus/messaging/functions-notes.php @@ -113,7 +113,7 @@ function ws_ls_notes_render( $note, $echo = true, $uikit = false, $alternate = f ws_ls_iso_datetime_into_correct_format( $note[ 'created' ] ), $note[ 'message_text' ], ws_ls_component_id(), - true === ws_ls_to_bool( $note[ 'visible_to_user' ] ) && true === is_admin() ? __( ' (Visible via [wt-notes])', WE_LS_SLUG ) : '', + true === ws_ls_to_bool( $note[ 'visible_to_user' ] ) && true === is_admin() ? esc_html__( ' (Visible via [wt-notes])', WE_LS_SLUG ) : '', is_admin() ? 'h2' : 'h6', ! is_admin() ? 'ws-ls-hide' : '' ); diff --git a/pro-features/plus/messaging/hooks.php b/pro-features/plus/messaging/hooks.php index 18e0807f..a17d2f06 100644 --- a/pro-features/plus/messaging/hooks.php +++ b/pro-features/plus/messaging/hooks.php @@ -80,7 +80,7 @@ function ws_ls_note_shortcode( $user_defined_arguments ) { $arguments = shortcode_atts( [ 'user-id' => get_current_user_id(), 'paging' => true, 'notes-per-page' => 10, // Return 10 notes by default - 'message-no-data' => __( 'You currently have no notes from the administrator.', WE_LS_SLUG ), + 'message-no-data' => esc_html__( 'You currently have no notes from the administrator.', WE_LS_SLUG ), 'uikit' => false ], $user_defined_arguments ); @@ -106,8 +106,8 @@ function ws_ls_note_shortcode( $user_defined_arguments ) { 'format' => '?tab=messages¬es-page=%#%', 'current' => $page, 'total' => ceil( $stats[ 'notes-count-visible' ] / $arguments[ 'notes-per-page' ] ), - 'prev_text' => __( '« prev', WE_LS_SLUG ), - 'next_text' => __('next »', WE_LS_SLUG ), + 'prev_text' => esc_html__( '« prev', WE_LS_SLUG ), + 'next_text' => esc_html__('next »', WE_LS_SLUG ), ]); } diff --git a/pro-features/plus/meta-fields/activate.php b/pro-features/plus/meta-fields/activate.php index be822884..a46bb8d2 100755 --- a/pro-features/plus/meta-fields/activate.php +++ b/pro-features/plus/meta-fields/activate.php @@ -110,10 +110,10 @@ function ws_ls_meta_fields_load_examples() { false === ws_ls_meta_fields_key_exist( 'cups-of-water-drank-today' ) ) { // Number ws_ls_meta_fields_add([ - 'field_name' => __('Cups of water drank today?', WE_LS_SLUG), - 'abv' => __('Water', WE_LS_SLUG), + 'field_name' => esc_html__('Cups of water drank today?', WE_LS_SLUG), + 'abv' => esc_html__('Water', WE_LS_SLUG), 'field_type' => 0, - 'suffix' => __('cups', WE_LS_SLUG), + 'suffix' => esc_html__('cups', WE_LS_SLUG), 'mandatory' => 2, 'enabled' => 1, 'sort' => 100, @@ -125,10 +125,10 @@ function ws_ls_meta_fields_load_examples() { if ( false === ws_ls_meta_fields_key_exist( 'waist' ) ) { // Number ws_ls_meta_fields_add([ - 'field_name' => __('Waist', WE_LS_SLUG), - 'abv' => __('Waist', WE_LS_SLUG), + 'field_name' => esc_html__('Waist', WE_LS_SLUG), + 'abv' => esc_html__('Waist', WE_LS_SLUG), 'field_type' => 0, - 'suffix' => __('cm', WE_LS_SLUG), + 'suffix' => esc_html__('cm', WE_LS_SLUG), 'mandatory' => 1, 'enabled' => 1, 'sort' => 100, @@ -140,10 +140,10 @@ function ws_ls_meta_fields_load_examples() { if ( false === ws_ls_meta_fields_key_exist( 'leg' ) ) { // Number ws_ls_meta_fields_add([ - 'field_name' => __('Leg', WE_LS_SLUG), - 'abv' => __('Leg', WE_LS_SLUG), + 'field_name' => esc_html__('Leg', WE_LS_SLUG), + 'abv' => esc_html__('Leg', WE_LS_SLUG), 'field_type' => 0, - 'suffix' => __('cm', WE_LS_SLUG), + 'suffix' => esc_html__('cm', WE_LS_SLUG), 'mandatory' => 1, 'enabled' => 1, 'sort' => 100, @@ -155,8 +155,8 @@ function ws_ls_meta_fields_load_examples() { if ( false === ws_ls_meta_fields_key_exist( 'did-you-stick-to-your-diet' ) ) { // Yes / No ws_ls_meta_fields_add([ - 'field_name' => __('Did you stick to your diet?', WE_LS_SLUG), - 'abv' => __('Diet', WE_LS_SLUG), + 'field_name' => esc_html__('Did you stick to your diet?', WE_LS_SLUG), + 'abv' => esc_html__('Diet', WE_LS_SLUG), 'field_type' => 2, 'suffix' => '', 'mandatory' => 1, diff --git a/pro-features/plus/meta-fields/assets/meta-fields.js b/pro-features/plus/meta-fields/assets/meta-fields.js index 3817f1a1..b66efd7b 100644 --- a/pro-features/plus/meta-fields/assets/meta-fields.js +++ b/pro-features/plus/meta-fields/assets/meta-fields.js @@ -1,5 +1,28 @@ jQuery( document ).ready( function ( $ ) { + // ----------------------------------------------------------------------- + // Custom Fields slider + // ----------------------------------------------------------------------- + + $( '.ws-ls-meta-fields-slider' ).each( function () { + + let id = '#' + this.id; + + $( id ).slider({ + min: $( this ).data( 'min' ), + max: $( this ).data( 'max' ), + step: $( this ).data( 'step' ), + value: $( this ).data( 'value' ) + } + ).slider( "pips", { + rest: $( this ).data( 'pips' ) + } + ).on( "slidechange", function(e,ui) { + $( id + "-value" ).val( ui.value ); + }); + + }); + // ----------------------------------------------------------------------- // Custom Fields accumulator // ----------------------------------------------------------------------- diff --git a/pro-features/plus/meta-fields/assets/meta-fields.min.js b/pro-features/plus/meta-fields/assets/meta-fields.min.js index 30259deb..22abf740 100644 --- a/pro-features/plus/meta-fields/assets/meta-fields.min.js +++ b/pro-features/plus/meta-fields/assets/meta-fields.min.js @@ -1 +1 @@ -jQuery(document).ready(function(a){a(".ws-ls-acc-buttons button").click(function(b){b.preventDefault();let c=a(this),d=c.data("parent-id"),e=a("#"+d+" .ws-ls-status-message"),f=c.data("increment");e.removeClass("ws-meta-error").addClass("ws-meta-hide").html(""),!0!==c.data("width-set")&&(c.css("width",c.outerWidth()+"px"),c.data("width-set",!0)),c.html(ws_ls_meta_fields_config["text-saving"]),ws_ls_post("ws_ls_meta_field_accumulator",{increment:f,"meta-field-id":c.data("meta-field-id")},function(b,f){!0===f.error?(e.addClass("ws-meta-error").removeClass("ws-meta-success").html("

    "+ws_ls_meta_fields_config["text-failure"]+"

    "),c.html("")):(a("#"+d+" .ws-ls-acc-value").html(f.value),c.html("")),e.removeClass("ws-meta-hide"),c.delay(1e3).queue(function(a){c.html(" "+c.data("original-text")),a()})})})});function ws_ls_post(a,b,c){b.action=a,b.security=ws_ls_meta_fields_config["ajax-security-nonce"],jQuery.post(ws_ls_meta_fields_config["ajax-url"],b,function(a){c(b,a)})} +jQuery(document).ready(function(a){a(".ws-ls-meta-fields-slider").each(function(){let b="#"+this.id;a(b).slider({min:a(this).data("min"),max:a(this).data("max"),step:a(this).data("step"),value:a(this).data("value")}).slider("pips",{rest:a(this).data("pips")}).on("slidechange",function(c,d){a(b+"-value").val(d.value)})}),a(".ws-ls-acc-buttons button").click(function(b){b.preventDefault();let c=a(this),d=c.data("parent-id"),e=a("#"+d+" .ws-ls-status-message"),f=c.data("increment");e.removeClass("ws-meta-error").addClass("ws-meta-hide").html(""),!0!==c.data("width-set")&&(c.css("width",c.outerWidth()+"px"),c.data("width-set",!0)),c.html(ws_ls_meta_fields_config["text-saving"]),ws_ls_post("ws_ls_meta_field_accumulator",{increment:f,"meta-field-id":c.data("meta-field-id")},function(b,f){!0===f.error?(e.addClass("ws-meta-error").removeClass("ws-meta-success").html("

    "+ws_ls_meta_fields_config["text-failure"]+"

    "),c.html("")):(a("#"+d+" .ws-ls-acc-value").html(f.value),c.html("")),e.removeClass("ws-meta-hide"),c.delay(1e3).queue(function(a){c.html(" "+c.data("original-text")),a()})})})});function ws_ls_post(a,b,c){b.action=a,b.security=ws_ls_meta_fields_config["ajax-security-nonce"],jQuery.post(ws_ls_meta_fields_config["ajax-url"],b,function(a){c(b,a)})} \ No newline at end of file diff --git a/pro-features/plus/meta-fields/db.php b/pro-features/plus/meta-fields/db.php index 0ce305b3..de1d84cd 100755 --- a/pro-features/plus/meta-fields/db.php +++ b/pro-features/plus/meta-fields/db.php @@ -581,7 +581,7 @@ function ws_ls_meta_fields_groups( $include_none = true ) { ws_ls_cache_user_set( 'custom-fields-groups', 'all' , $data ); if ( true === $include_none ) { - $data = array_merge( [ [ 'id' => 0, 'name' => __('None', WE_LS_SLUG ) ] ], $data ); + $data = array_merge( [ [ 'id' => 0, 'name' => esc_html__('None', WE_LS_SLUG ) ] ], $data ); } return $data; diff --git a/pro-features/plus/meta-fields/functions-photos.php b/pro-features/plus/meta-fields/functions-photos.php index 9a4e031b..579c6501 100644 --- a/pro-features/plus/meta-fields/functions-photos.php +++ b/pro-features/plus/meta-fields/functions-photos.php @@ -127,7 +127,7 @@ function ws_ls_meta_fields_photos_process_upload( $field_name, $date_text = NULL $attachment = array( 'post_mime_type' => $mime_type['type'], 'post_title' => ( $user_data ) ? $user_data->user_nicename . ' (' . $date_text . ')' : $date_text, - 'post_content' => ( $user_data ) ? __('The user ', WE_LS_SLUG) . $user_data->user_nicename . ', ' . __('uploaded this photo of them for their entry on the', WE_LS_SLUG) . ' ' . $date_text : '', + 'post_content' => ( $user_data ) ? esc_html__('The user ', WE_LS_SLUG) . $user_data->user_nicename . ', ' . esc_html__('uploaded this photo of them for their entry on the', WE_LS_SLUG) . ' ' . $date_text : '', 'post_status' => 'inherit' ); @@ -319,8 +319,8 @@ function ws_ls_meta_fields_photos_create_example_field() { ws_ls_log_add('meta-field-setup', 'Adding photo field.' ); ws_ls_meta_fields_add([ - 'field_name' => __('Photo', WE_LS_SLUG), - 'abv' => __('Photo', WE_LS_SLUG), + 'field_name' => esc_html__('Photo', WE_LS_SLUG), + 'abv' => esc_html__('Photo', WE_LS_SLUG), 'field_type' => 3, 'suffix' => '', 'mandatory' => 1, @@ -339,8 +339,8 @@ function ws_ls_meta_fields_photos_create_example_field() { function ws_ls_meta_fields_photos_form_display_info() { return sprintf( '

    %3$s: %1$s%2$s

    ', - __('Photos are only visible to you and administrators. ', WE_LS_SLUG), - __('Photos must be under', WE_LS_SLUG) . ' ' . ws_ls_photo_display_max_upload_size() . ' ' . __('or they will silently fail to upload.', WE_LS_SLUG), - __('A note about photos', WE_LS_SLUG) + esc_html__('Photos are only visible to you and administrators. ', WE_LS_SLUG), + esc_html__('Photos must be under', WE_LS_SLUG) . ' ' . ws_ls_photo_display_max_upload_size() . ' ' . esc_html__('or they will silently fail to upload.', WE_LS_SLUG), + esc_html__('A note about photos', WE_LS_SLUG) ); } diff --git a/pro-features/plus/meta-fields/functions-slider.php b/pro-features/plus/meta-fields/functions-slider.php index 7f2d609d..00f86d96 100644 --- a/pro-features/plus/meta-fields/functions-slider.php +++ b/pro-features/plus/meta-fields/functions-slider.php @@ -24,28 +24,9 @@ function ws_ls_meta_fields_form_field_range_slider( $field, $value ) { $html = sprintf( '
    -
    +
    -
    - - ', + ', ws_ls_meta_fields_form_field_generate_id( $field['id'] ), esc_attr( $field['field_name'] ), ws_ls_form_tab_index_next(), @@ -71,5 +52,6 @@ function ws_meta_fields_range_slider_enqueue() { $minified = ws_ls_use_minified(); wp_enqueue_script( 'ws-ls-meta-fields-range-js', plugins_url( '/meta-fields/assets/jquery-ui-slider-pips' . $minified . '.js', __DIR__ ), [ 'jquery', 'jquery-ui-core', 'jquery-ui-slider' ], WE_LS_CURRENT_VERSION, true ); + wp_enqueue_script( 'ws-ls-meta-fields', plugins_url( '/meta-fields/assets/meta-fields' . $minified . '.js', __DIR__ ), [ 'ws-ls-meta-fields-range-js' ], WE_LS_CURRENT_VERSION, true ); wp_enqueue_style( 'ws-ls-meta-fields-range-css', plugins_url( '/meta-fields/assets/jquery-ui-slider-pips' . $minified . '.css', __DIR__ ), [], WE_LS_CURRENT_VERSION ); } diff --git a/pro-features/plus/meta-fields/functions.php b/pro-features/plus/meta-fields/functions.php index b8f1bfca..0c8f3fd4 100755 --- a/pro-features/plus/meta-fields/functions.php +++ b/pro-features/plus/meta-fields/functions.php @@ -32,14 +32,14 @@ function ws_ls_meta_fields_base_url( $args = [] ) { */ function ws_ls_meta_fields_types() { return [ - 0 => __( 'Number', WE_LS_SLUG), - 3 => __( 'Photo', WE_LS_SLUG), - 5 => __( 'Radio buttons', WE_LS_SLUG ), - 4 => __( 'Range slider', WE_LS_SLUG ), - 7 => __( 'Dropdown', WE_LS_SLUG ), - 6 => __( 'Large text', WE_LS_SLUG ), - 1 => __( 'Small text', WE_LS_SLUG ), - 2 => __( 'Yes', WE_LS_SLUG ) . ' / ' . __( 'No', WE_LS_SLUG ) + 0 => esc_html__( 'Number', WE_LS_SLUG), + 3 => esc_html__( 'Photo', WE_LS_SLUG), + 5 => esc_html__( 'Radio buttons', WE_LS_SLUG ), + 4 => esc_html__( 'Range slider', WE_LS_SLUG ), + 7 => esc_html__( 'Dropdown', WE_LS_SLUG ), + 6 => esc_html__( 'Large text', WE_LS_SLUG ), + 1 => esc_html__( 'Small text', WE_LS_SLUG ), + 2 => esc_html__( 'Yes', WE_LS_SLUG ) . ' / ' . esc_html__( 'No', WE_LS_SLUG ) ]; } @@ -228,9 +228,9 @@ function ws_ls_fields_display_field_value_yes_no( $value ) { switch ( (int) $value ) { case 1: - return __('No', WE_LS_SLUG); + return esc_html__('No', WE_LS_SLUG); case 2: - return __('Yes', WE_LS_SLUG); + return esc_html__('Yes', WE_LS_SLUG); default: return ''; @@ -347,7 +347,7 @@ function ws_ls_meta_fields_form_field_text( $field, $value ) { 2 === (int) $field['mandatory'] ? ' required' : '', ws_ls_form_tab_index_next(), ( false === empty( $value ) ) ? esc_attr( $value ) : '', - __('Please enter a value for', WE_LS_SLUG), + esc_html__('Please enter a value for', WE_LS_SLUG), ( false === empty( $field[ 'placeholder' ] ) ) ? esc_attr( $field[ 'placeholder' ] ) : '' ); @@ -399,7 +399,7 @@ function ws_ls_meta_fields_form_field_number( $field, $value ) { 2 === (int) $field['mandatory'] ? ' required' : '', ws_ls_form_tab_index_next(), ( false === empty( $value ) ) ? esc_attr( $value ) : '', - __('Please enter a number for', WE_LS_SLUG), + esc_html__('Please enter a number for', WE_LS_SLUG), ( false === empty( $field[ 'placeholder' ] ) ) ? esc_attr( $field[ 'placeholder' ] ) : '' ); @@ -429,8 +429,8 @@ function ws_ls_meta_fields_form_field_yes_no( $field, $value ) { $html .= sprintf( '', selected( $value, 0, false ) ); } - $html .= sprintf( '', selected( $value, 1, false ), __('No', WE_LS_SLUG) ); - $html .= sprintf( '', selected( $value, 2, false ), __('Yes', WE_LS_SLUG) ); + $html .= sprintf( '', selected( $value, 1, false ), esc_html__('No', WE_LS_SLUG) ); + $html .= sprintf( '', selected( $value, 2, false ), esc_html__('Yes', WE_LS_SLUG) ); $html .= ''; @@ -496,7 +496,7 @@ function ws_ls_meta_fields_form_field_radio_buttons( $field, $value ) { $field = ws_ls_meta_fields_form_prep_options( $field ); if ( true === empty( $field[ 'options-labels' ] ) ) { - $html .= '

    ' . __( 'No labels/values have been specified for this question.', WE_LS_SLUG ) . '

    '; + $html .= '

    ' . esc_html__( 'No labels/values have been specified for this question.', WE_LS_SLUG ) . '

    '; } $first = true; @@ -542,7 +542,7 @@ function ws_ls_meta_fields_form_field_select( $field, $value ) { $field = ws_ls_meta_fields_form_prep_options( $field ); if ( true === empty( $field[ 'options' ] ) ) { - return '

    ' . __( 'No labels/values have been specified for this question.', WE_LS_SLUG ) . '

    '; + return '

    ' . esc_html__( 'No labels/values have been specified for this question.', WE_LS_SLUG ) . '

    '; } return ws_ls_form_field_select([ 'key' => ws_ls_meta_fields_form_field_generate_id( $field['id'] ), @@ -598,10 +598,10 @@ function ws_ls_meta_fields_form_field_photo( $field, $value, $field_id = NULL ) ', esc_attr( $field_id ), ws_ls_form_tab_index_next(), - ( false === empty( $value ) ) ? __('Replace photo', WE_LS_SLUG) : __('Select photo', WE_LS_SLUG), + ( false === empty( $value ) ) ? esc_html__('Replace photo', WE_LS_SLUG) : esc_html__('Select photo', WE_LS_SLUG), 2 === (int) $field['mandatory'] ? 'y' : 'n', true === empty( $value ) && 2 === (int) $field['mandatory'] ? 'required' : '', - __('Please select a photo (png or jpg) for', WE_LS_SLUG), + esc_html__('Please select a photo (png or jpg) for', WE_LS_SLUG), esc_attr( $field['field_name'] ), ws_ls_component_id() ); @@ -634,12 +634,12 @@ function ws_ls_meta_fields_form_field_photo( $field, $value, $field_id = NULL ) ', esc_url( $full_url ), esc_url( $thumbnail[0] ), - __('Existing photo for this date', WE_LS_SLUG), + esc_html__('Existing photo for this date', WE_LS_SLUG), (int) $attachment_id, (int) $thumbnail[1], (int) $thumbnail[2], - __( 'Delete existing photo', WE_LS_SLUG ), - __( 'Existing photo', WE_LS_SLUG ), + esc_html__( 'Delete existing photo', WE_LS_SLUG ), + esc_html__( 'Existing photo', WE_LS_SLUG ), esc_attr( $field_id ), 2 === (int) $field['mandatory'] ? 'y' : 'n' ); diff --git a/pro-features/plus/meta-fields/hooks.php b/pro-features/plus/meta-fields/hooks.php index aea51332..9d614f73 100755 --- a/pro-features/plus/meta-fields/hooks.php +++ b/pro-features/plus/meta-fields/hooks.php @@ -87,13 +87,13 @@ function ws_ls_meta_fields_ajax_list() { $columns = [ [ 'name' => 'id', 'title' => 'ID', 'visible'=> true, 'type' => 'number' ], - [ 'name' => 'field_name', 'title' => __('Field / Question', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'field_key', 'title' => __('Key / Slug', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'field_type', 'title' => __('Type', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'sort', 'title' => __('Display Order', WE_LS_SLUG), 'visible'=> true, 'type' => 'number' ], - [ 'name' => 'group', 'title' => __('Group', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'mandatory', 'title' => __('Mandatory', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'enabled', 'title' => __('Enabled', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ] + [ 'name' => 'field_name', 'title' => esc_html__('Field / Question', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'field_key', 'title' => esc_html__('Key / Slug', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'field_type', 'title' => esc_html__('Type', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'sort', 'title' => esc_html__('Display Order', WE_LS_SLUG), 'visible'=> true, 'type' => 'number' ], + [ 'name' => 'group', 'title' => esc_html__('Group', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'mandatory', 'title' => esc_html__('Mandatory', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'enabled', 'title' => esc_html__('Enabled', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ] ]; $meta_fields = ws_ls_meta_fields(); @@ -132,10 +132,10 @@ function ws_ls_meta_fields_ajax_custom_field_groups_get(){ } $columns = [ - [ 'name' => 'id', 'title' => __('Group ID', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number', 'visible' => false ], - [ 'name' => 'slug', 'title' => __( 'Slug', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ], - [ 'name' => 'name', 'title' => __( 'Name', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ], - [ 'name' => 'count', 'title' => __( 'No. Fields', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number' ], + [ 'name' => 'id', 'title' => esc_html__('Group ID', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number', 'visible' => false ], + [ 'name' => 'slug', 'title' => esc_html__( 'Slug', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ], + [ 'name' => 'name', 'title' => esc_html__( 'Name', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ], + [ 'name' => 'count', 'title' => esc_html__( 'No. Fields', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number' ], ]; $rows = []; diff --git a/pro-features/plus/meta-fields/shortcodes.php b/pro-features/plus/meta-fields/shortcodes.php index 0f43897e..253191a6 100644 --- a/pro-features/plus/meta-fields/shortcodes.php +++ b/pro-features/plus/meta-fields/shortcodes.php @@ -23,9 +23,9 @@ function ws_ls_meta_fields_shortcode_accumulator( $user_defined_arguments ) { 'hide-value' => false, 'hide-login-prompt' => false, 'increment-values' => '-1,-5,-10,1,5,10', // A string of comma delimited integers of allowed increments - 'value-text' => sprintf( '%s {value}.', __( 'So far you have recorded:', WE_LS_SLUG ) ), + 'value-text' => sprintf( '%s {value}.', esc_html__( 'So far you have recorded:', WE_LS_SLUG ) ), 'value-level' => 'p', - 'saved-text' => __( 'Your entry has been saved!', WE_LS_SLUG ) + 'saved-text' => esc_html__( 'Your entry has been saved!', WE_LS_SLUG ) ], $user_defined_arguments ); @@ -33,25 +33,25 @@ function ws_ls_meta_fields_shortcode_accumulator( $user_defined_arguments ) { if ( false === is_user_logged_in() ) { return ( true !== ws_ls_to_bool( $shortcode_arguments[ 'hide-login-prompt' ] ) ) ? '' : - ws_ls_display_blockquote( __( 'You need to be logged in to record your weight.', WE_LS_SLUG ) , '', false, true ); + ws_ls_display_blockquote( esc_html__( 'You need to be logged in to record your weight.', WE_LS_SLUG ) , '', false, true ); } if ( true === empty( $shortcode_arguments[ 'slug' ] ) ) { - return __( 'Please specify a custom field slug e.g. [wt-custom-fields-accumulator slug="cups-of-water-drank-today"].', WE_LS_SLUG ); + return esc_html__( 'Please specify a custom field slug e.g. [wt-custom-fields-accumulator slug="cups-of-water-drank-today"].', WE_LS_SLUG ); } $meta_field = ws_ls_meta_fields_get( [ $shortcode_arguments[ 'slug' ] ] ); if ( 2 !== (int) $meta_field[ 'enabled'] ) { - return __( 'The custom field needs to be enabled.', WE_LS_SLUG ); + return esc_html__( 'The custom field needs to be enabled.', WE_LS_SLUG ); } if ( true === empty( $meta_field ) ) { - return __( 'The custom field could not be found for the given slug.', WE_LS_SLUG ); + return esc_html__( 'The custom field could not be found for the given slug.', WE_LS_SLUG ); } if ( 0 !== (int) $meta_field[ 'field_type' ] ) { - return __( 'This shortcode will only work for numeric custom fields.', WE_LS_SLUG ); + return esc_html__( 'This shortcode will only work for numeric custom fields.', WE_LS_SLUG ); } ws_ls_meta_fields_shortcode_accumulator_enqueue_scripts(); @@ -96,7 +96,7 @@ function ws_ls_meta_fields_shortcode_accumulator( $user_defined_arguments ) { $increments = explode( ',', $shortcode_arguments[ 'increment-values' ] ); if ( empty( $shortcode_arguments['increment-values'] ) || empty( $increments ) ) { - return __( 'Please ensure you have a valid list of increment values e.g. [wt-custom-fields-accumulator increment-values="1,5,10"]', WE_LS_SLUG ); + return esc_html__( 'Please ensure you have a valid list of increment values e.g. [wt-custom-fields-accumulator increment-values="1,5,10"]', WE_LS_SLUG ); } @@ -157,7 +157,7 @@ function ws_ls_meta_fields_shortcode_js() { return [ 'ajax-url' => admin_url( 'admin-ajax.php' ), 'ajax-security-nonce' => wp_create_nonce( 'ws-ls-nonce' ), 'text-saving' => '', - 'text-failure' => __( 'There was an issue saving your entry. Please try again.', WE_LS_SLUG ), + 'text-failure' => esc_html__( 'There was an issue saving your entry. Please try again.', WE_LS_SLUG ), ]; } /** @@ -210,7 +210,7 @@ function ws_ls_meta_fields_shortcode_chart( $user_defined_arguments ) { $user_defined_arguments = shortcode_atts( [ 'bezier' => ws_ls_option_to_bool( 'ws-ls-bezier-curve' ), 'height' => 250, 'ignore-login-status' => false, - 'message-no-data' => __( 'Currently there is no data to display on the chart.', WE_LS_SLUG ), + 'message-no-data' => esc_html__( 'Currently there is no data to display on the chart.', WE_LS_SLUG ), 'max-data-points' => ws_ls_option( 'ws-ls-max-points', '25', true ), 'show-gridlines' => ws_ls_option_to_bool( 'ws-ls-grid-lines' ), 'type' => get_option( 'ws-ls-chart-type', 'line' ), @@ -275,7 +275,7 @@ function ws_ls_meta_fields_shortcode_value_latest( $user_defined_arguments ) { $arguments = shortcode_atts( [ 'slug' => '', 'user-id' => get_current_user_id(), 'which' => 'latest', 'return-as-array' => false ] , $user_defined_arguments ); if ( true === empty( $arguments[ 'slug' ] ) ) { - return __( 'You must specify a slug.', WE_LS_SLUG ); + return esc_html__( 'You must specify a slug.', WE_LS_SLUG ); } $meta_field = [ 'id' => ws_ls_meta_fields_slug_to_id( $arguments[ 'slug' ] ), 'slug' => $arguments[ 'slug' ] ]; @@ -283,7 +283,7 @@ function ws_ls_meta_fields_shortcode_value_latest( $user_defined_arguments ) { $meta_field[ 'id' ] = ws_ls_meta_fields_slug_to_id( $arguments[ 'slug' ] ); if ( true === empty( $meta_field[ 'id' ] ) ) { - return __( 'The slug you specified does not exist.', WE_LS_SLUG ); + return esc_html__( 'The slug you specified does not exist.', WE_LS_SLUG ); } $arguments[ 'key' ] = $meta_field[ 'id' ]; @@ -343,13 +343,13 @@ function ws_ls_meta_fields_shortcode_value_count( $user_defined_arguments ) { $arguments = shortcode_atts( [ 'slug' => '', 'user-id' => get_current_user_id() ] , $user_defined_arguments ); if ( true === empty( $arguments[ 'slug' ] ) ) { - return __( 'You must specify a slug.', WE_LS_SLUG ); + return esc_html__( 'You must specify a slug.', WE_LS_SLUG ); } $meta_field_id = ws_ls_meta_fields_slug_to_id( $arguments[ 'slug' ] ); if ( true === empty( $meta_field_id ) ) { - return __( 'The slug you specified does not exist.', WE_LS_SLUG ); + return esc_html__( 'The slug you specified does not exist.', WE_LS_SLUG ); } $arguments[ 'key' ] = $meta_field_id; diff --git a/pro-features/plus/photos-gallery.php b/pro-features/plus/photos-gallery.php index 129299d1..39cbc947 100644 --- a/pro-features/plus/photos-gallery.php +++ b/pro-features/plus/photos-gallery.php @@ -32,7 +32,7 @@ function ws_ls_photos_shortcode_gallery($user_defined_arguments) { } $arguments = shortcode_atts([ 'error-message' => ( false === empty( $user_defined_arguments[ 'source' ] ) ) ? - __('No awards.', WE_LS_SLUG ) : __('It doesn\'t look you\'ve uploaded any photos.', WE_LS_SLUG ), + esc_html__('No awards.', WE_LS_SLUG ) : esc_html__('It doesn\'t look you\'ve uploaded any photos.', WE_LS_SLUG ), 'user-id' => get_current_user_id(), 'mode' => 'default', // Gallery type: carousel, default or compact 'height' => 800, // Height of slider if compact or default theme diff --git a/pro-features/plus/photos.php b/pro-features/plus/photos.php index ba350e26..b1d4d8c3 100755 --- a/pro-features/plus/photos.php +++ b/pro-features/plus/photos.php @@ -58,7 +58,7 @@ function ws_ls_photos_shortcode_oldest($user_defined_arguments) { $user_defined_arguments = []; } - $user_defined_arguments['error-message'] = (true === empty($user_defined_arguments['error-message'])) ? __('No photos were found.', WE_LS_SLUG ) : $user_defined_arguments['error-message']; + $user_defined_arguments['error-message'] = (true === empty($user_defined_arguments['error-message'])) ? esc_html__('No photos were found.', WE_LS_SLUG ) : $user_defined_arguments['error-message']; $user_defined_arguments['recent'] = false; return ws_ls_photos_shortcode_core($user_defined_arguments); @@ -75,7 +75,7 @@ function ws_ls_photos_shortcode_core($user_defined_arguments) { $arguments = shortcode_atts([ 'css-class' => '', - 'error-message' => __('No recent photo found.', WE_LS_SLUG ), + 'error-message' => esc_html__('No recent photo found.', WE_LS_SLUG ), 'height' => 200, 'hide-date' => false, 'user-id' => get_current_user_id(), @@ -443,10 +443,10 @@ function ws_ls_photos_attachment_fields_to_edit( $form_fields, $post ) { $already_hidden = (bool) get_post_meta($post->ID, 'ws-ls-hide-image', true); $form_fields['ws_ls_hide_image'] = array( - 'label' => __( 'Don\'t show to public?', WE_LS_SLUG ), + 'label' => esc_html__( 'Don\'t show to public?', WE_LS_SLUG ), 'input' => 'html', 'html' => '', - 'helps' => __( 'If the user has uploaded this as part of their Weight Tracker progress, they probably don\'t want it viewable on a public attachment page!', WE_LS_SLUG ), + 'helps' => esc_html__( 'If the user has uploaded this as part of their Weight Tracker progress, they probably don\'t want it viewable on a public attachment page!', WE_LS_SLUG ), ); return $form_fields; diff --git a/pro-features/plus/shortcode.calculator.php b/pro-features/plus/shortcode.calculator.php index b6200557..9be4f6af 100644 --- a/pro-features/plus/shortcode.calculator.php +++ b/pro-features/plus/shortcode.calculator.php @@ -24,10 +24,10 @@ function ws_ls_shortcode_calculator( $user_defined_arguments ) { 'results-show-calories' => true, // Show calories on results page 'results-show-macros' => true, // Show macros on results page 'results-show-form' => true, // If true, always show the form when rendering results - 'text-bmi' => __( 'Your BMI is:', WE_LS_SLUG ), - 'text-bmr' => __( 'Your BMR is:', WE_LS_SLUG ), - 'text-calories' => __( 'The following table illustrates your recommended calorie intake:', WE_LS_SLUG ), - 'text-macros' => __( 'The following table illustrates your recommended macronutrient intake:', WE_LS_SLUG ), + 'text-bmi' => esc_html__( 'Your BMI is:', WE_LS_SLUG ), + 'text-bmr' => esc_html__( 'Your BMR is:', WE_LS_SLUG ), + 'text-calories' => esc_html__( 'The following table illustrates your recommended calorie intake:', WE_LS_SLUG ), + 'text-macros' => esc_html__( 'The following table illustrates your recommended macronutrient intake:', WE_LS_SLUG ), ], $user_defined_arguments ); // Enqueue front end scripts if needed (mainly for datepicker) @@ -134,7 +134,7 @@ function ws_ls_shortcode_calculator( $user_defined_arguments ) {

    %1$s

      ', - __( 'Please ensure you have completed all of the fields.', WE_LS_SLUG ) ); + esc_html__( 'Please ensure you have completed all of the fields.', WE_LS_SLUG ) ); } //------------------------------------------------------- @@ -143,19 +143,19 @@ function ws_ls_shortcode_calculator( $user_defined_arguments ) { $data_unit = ws_ls_setting( 'weight-unit', $user_id ); - $html_output .= sprintf( '', __( 'Current weight', WE_LS_SLUG ) ); + $html_output .= sprintf( '', esc_html__( 'Current weight', WE_LS_SLUG ) ); // Stones field? if ( 'stones_pounds' === $data_unit ) { $html_output .= ws_ls_form_field_number( [ 'name' => 'ws-ls-weight-stones', - 'placeholder' => __( 'st', WE_LS_SLUG ), + 'placeholder' => esc_html__( 'st', WE_LS_SLUG ), 'value' => $entry[ 'ws-ls-weight-stones' ] ]); } // Pounds? if ( true === in_array( $data_unit, [ 'stones_pounds', 'pounds_only' ] ) ) { $html_output .= ws_ls_form_field_number( [ 'name' => 'ws-ls-weight-pounds', - 'placeholder' => __( 'lb', WE_LS_SLUG ), + 'placeholder' => esc_html__( 'lb', WE_LS_SLUG ), 'max' => ( 'stones_pounds' === $data_unit ) ? '13.99' : '5000', 'value' => $entry[ 'ws-ls-weight-pounds' ] ] ); } @@ -163,7 +163,7 @@ function ws_ls_shortcode_calculator( $user_defined_arguments ) { // Kg if ( 'kg' === $data_unit ) { $html_output .= ws_ls_form_field_number( [ 'name' => 'ws-ls-weight-kg', - 'placeholder' => __( 'kg', WE_LS_SLUG ), + 'placeholder' => esc_html__( 'kg', WE_LS_SLUG ), 'value' => $entry[ 'ws-ls-weight-kg' ] ]); } @@ -171,21 +171,21 @@ function ws_ls_shortcode_calculator( $user_defined_arguments ) { // Height //------------------------------------------------------- - $html_output .= ws_ls_form_field_select( [ 'key' => 'ws-ls-height', 'label' => __( 'Your height:', WE_LS_SLUG ), 'values' => ws_ls_heights(), 'empty-option' => true, 'include-div' => true, + $html_output .= ws_ls_form_field_select( [ 'key' => 'ws-ls-height', 'label' => esc_html__( 'Your height:', WE_LS_SLUG ), 'values' => ws_ls_heights(), 'empty-option' => true, 'include-div' => true, 'selected' => ( false === empty( $entry[ 'ws-ls-height' ] ) ) ? $entry[ 'ws-ls-height' ] : '', 'css-class' => 'ws-ls-aboutyou-field' ] ); //------------------------------------------------------- // Gender //------------------------------------------------------- - $html_output .= ws_ls_form_field_select( [ 'key' => 'ws-ls-gender', 'required' => true, 'label' => __( 'Your Gender:', WE_LS_SLUG ), 'values' => ws_ls_genders(), 'include-div' => true, + $html_output .= ws_ls_form_field_select( [ 'key' => 'ws-ls-gender', 'required' => true, 'label' => esc_html__( 'Your Gender:', WE_LS_SLUG ), 'values' => ws_ls_genders(), 'include-div' => true, 'selected' => ( false === empty( $entry[ 'ws-ls-gender' ] ) ) ? $entry[ 'ws-ls-gender' ] : '', 'css-class' => 'ws-ls-aboutyou-field' ] ); //------------------------------------------------------- // Activity Level //------------------------------------------------------- - $html_output .= ws_ls_form_field_select( [ 'key' => 'ws-ls-activity-level', 'required' => true, 'label' => __( 'Your Activity Level:', WE_LS_SLUG ), 'values' => ws_ls_activity_levels(), 'include-div' => true, + $html_output .= ws_ls_form_field_select( [ 'key' => 'ws-ls-activity-level', 'required' => true, 'label' => esc_html__( 'Your Activity Level:', WE_LS_SLUG ), 'values' => ws_ls_activity_levels(), 'include-div' => true, 'selected' => ( false === empty( $entry[ 'ws-ls-activity-level' ] ) ) ? $entry[ 'ws-ls-activity-level' ] : '', 'css-class' => 'ws-ls-aboutyou-field' ] ); //------------------------------------------------------- @@ -195,7 +195,7 @@ function ws_ls_shortcode_calculator( $user_defined_arguments ) { $html_output .= ws_ls_form_field_date( [ 'name' => 'ws-ls-dob', 'id' => 'ws-ls-dob', 'include-div' => true, - 'title' => __( 'Your Date of Birth:', WE_LS_SLUG ), + 'title' => esc_html__( 'Your Date of Birth:', WE_LS_SLUG ), 'value' => ( false === empty( $entry[ 'ws-ls-dob' ] ) ) ? $entry[ 'ws-ls-dob' ] : '', 'css-class' => 'we-ls-datepicker ws-ls-dob-field ws-ls-aboutyou-field', 'show-label' => true ] ); @@ -209,7 +209,7 @@ function ws_ls_shortcode_calculator( $user_defined_arguments ) { $html_output .= sprintf('', ws_ls_form_tab_index_next(), - __( 'Calculate', WE_LS_SLUG ) + esc_html__( 'Calculate', WE_LS_SLUG ) ); $html_output .= ''; diff --git a/pro-features/plus/shortcode.wlt.php b/pro-features/plus/shortcode.wlt.php index d8138a3c..a57f2093 100755 --- a/pro-features/plus/shortcode.wlt.php +++ b/pro-features/plus/shortcode.wlt.php @@ -20,14 +20,14 @@ function ws_ls_shortcode_wlt_display_photos_tab( $user_id = null ) { if ( $user_id ) { - $html .= sprintf('

      %s

      ', __( 'Photos', WE_LS_SLUG ) ); + $html .= sprintf('

      %s

      ', esc_html__( 'Photos', WE_LS_SLUG ) ); $photo_count = ws_ls_photos_db_count_photos( $user_id, true ); if ( $photo_count > 0 ) { $html .= sprintf('

      %s %s %s:

      ', - __('You have uploaded', WE_LS_SLUG), + esc_html__('You have uploaded', WE_LS_SLUG), $photo_count, _n( 'photo', 'photos', $photo_count, WE_LS_SLUG ) ); @@ -35,7 +35,7 @@ function ws_ls_shortcode_wlt_display_photos_tab( $user_id = null ) { $html .= ws_ls_photos_shortcode_gallery( ['user-id' => $user_id] ); } else { - $html .= '

      ' . __('It looks like you haven\'t uploaded any photos yet', WE_LS_SLUG) . '.

      '; + $html .= '

      ' . esc_html__('It looks like you haven\'t uploaded any photos yet', WE_LS_SLUG) . '.

      '; } } @@ -63,42 +63,42 @@ function ws_ls_shortcode_wlt_display_advanced_tab( $arguments ) { if ( $user_id ) { // BMI - $html .= sprintf('

      %s

      ', __( 'BMI (Body Mass Index)', WE_LS_SLUG ) ); + $html .= sprintf('

      %s

      ', esc_html__( 'BMI (Body Mass Index)', WE_LS_SLUG ) ); if ( ws_ls_shortcode_if_value_exist( $user_id, [ 'weight', 'height' ] ) ) { if ( true === $include_narrative ) { $html .= sprintf( '

      %s

      ', - __('The BMI (Body Mass Index) is used by the medical profession to quickly determine a person’s weight in regard to their height. From a straight forward calculation the BMI factor can be gained and may be used to determine if a person is underweight, of normal weight, overweight or obese.', WE_LS_SLUG ) ); + esc_html__('The BMI (Body Mass Index) is used by the medical profession to quickly determine a person’s weight in regard to their height. From a straight forward calculation the BMI factor can be gained and may be used to determine if a person is underweight, of normal weight, overweight or obese.', WE_LS_SLUG ) ); } $html .= sprintf('

      %s: %s

      ', - __('Your current BMI is', WE_LS_SLUG), + esc_html__('Your current BMI is', WE_LS_SLUG), ws_ls_shortcode_bmi( [ 'user-id' => $user_id, 'display' => 'both' ] ) ); } else { - $html .= sprintf( '

      %s

      ', __( 'Before we can calculate your BMI, we need your current weight and height.', WE_LS_SLUG ) ); + $html .= sprintf( '

      %s

      ', esc_html__( 'Before we can calculate your BMI, we need your current weight and height.', WE_LS_SLUG ) ); } $got_bmr = ws_ls_shortcode_if_value_exist( $user_id, 'bmr' ); - $bmr_missing_text = sprintf( '

      %s

      ', __( 'To allow us to calculate this, we need your latest weight, date of birth, height and gender.', WE_LS_SLUG ) ); + $bmr_missing_text = sprintf( '

      %s

      ', esc_html__( 'To allow us to calculate this, we need your latest weight, date of birth, height and gender.', WE_LS_SLUG ) ); // BMR - $html .= sprintf('

      %s

      ', __( 'BMR (Basal Metabolic Rate)', WE_LS_SLUG ) ); + $html .= sprintf('

      %s

      ', esc_html__( 'BMR (Basal Metabolic Rate)', WE_LS_SLUG ) ); if ( true === $got_bmr ) { if ( true === $include_narrative ) { - $html .= sprintf( '

      %s

      ', __( 'BMR is short for Basal Metabolic Rate. The Basal Metabolic Rate is the number of calories required to keep your body functioning at rest, also known as your metabolism. We calculate your BMR using formulas provided by www.diabetes.co.uk.', WE_LS_SLUG ) ); + $html .= sprintf( '

      %s

      ', esc_html__( 'BMR is short for Basal Metabolic Rate. The Basal Metabolic Rate is the number of calories required to keep your body functioning at rest, also known as your metabolism. We calculate your BMR using formulas provided by www.diabetes.co.uk.', WE_LS_SLUG ) ); } $html .= sprintf('

      %s: %s

      ', - __('Your current BMR is', WE_LS_SLUG), + esc_html__('Your current BMR is', WE_LS_SLUG), ws_ls_shortcode_bmr(['user-id' => $user_id]) ); } else { @@ -106,12 +106,12 @@ function ws_ls_shortcode_wlt_display_advanced_tab( $arguments ) { } // Calories - $html .= sprintf( '

      %s

      ', __('Suggested Calorie Intake', WE_LS_SLUG ) ); + $html .= sprintf( '

      %s

      ', esc_html__('Suggested Calorie Intake', WE_LS_SLUG ) ); if ( true === $got_bmr ) { if ( true === $include_narrative ) { - $html .= sprintf( '

      %s

      ', __('Once we know your BMR (the number of calories to keep you functioning at rest), we can go on to give you suggestions on how to spread your calorie intake across the day. Firstly we split the figures into daily calorie intake to maintain weight and daily calorie intake to lose weight. Daily calorie intake to lose weight is calculated based on NHS advice – they suggest to lose 1 – 2lbs a week you should subtract 600 calories from your BMR. The two daily figures can be further broken down by recommending how to split calorie intake across the day i.e. breakfast, lunch, dinner and snacks.', WE_LS_SLUG ) ); + $html .= sprintf( '

      %s

      ', esc_html__('Once we know your BMR (the number of calories to keep you functioning at rest), we can go on to give you suggestions on how to spread your calorie intake across the day. Firstly we split the figures into daily calorie intake to maintain weight and daily calorie intake to lose weight. Daily calorie intake to lose weight is calculated based on NHS advice – they suggest to lose 1 – 2lbs a week you should subtract 600 calories from your BMR. The two daily figures can be further broken down by recommending how to split calorie intake across the day i.e. breakfast, lunch, dinner and snacks.', WE_LS_SLUG ) ); } $html .= sprintf('
      @@ -125,13 +125,13 @@ function ws_ls_shortcode_wlt_display_advanced_tab( $arguments ) { } // Macro N - $html .= sprintf('

      %s

      ', __('Macronutrients', WE_LS_SLUG ) ); + $html .= sprintf('

      %s

      ', esc_html__('Macronutrients', WE_LS_SLUG ) ); if ( true === $got_bmr ) { if ( true === $include_narrative ) { - $html .= sprintf('

      %s

      ', __( 'With calories calculated, the we can recommend how those calories should be split into Fats, Carbohydrates and Proteins.' , WE_LS_SLUG ) ); + $html .= sprintf('

      %s

      ', esc_html__( 'With calories calculated, the we can recommend how those calories should be split into Fats, Carbohydrates and Proteins.' , WE_LS_SLUG ) ); } diff --git a/pro-features/plus/unitegallery/js/unitegallery.js b/pro-features/plus/unitegallery/js/unitegallery.js new file mode 100644 index 00000000..962d842e --- /dev/null +++ b/pro-features/plus/unitegallery/js/unitegallery.js @@ -0,0 +1,25135 @@ +// Unite Gallery, Version: 1.7.45, released 27 Feb 2017 + + + +/** + * write something to debug line + */ +function debugLine(html,addRandom, addHtml){ + + if(html === true) + html = "true"; + + if(html === false) + html = "false"; + + var output = html; + + if(typeof html == "object"){ + output = ""; + for(name in html){ + var value = html[name]; + output += " " + name + ": " + value; + } + } + + if(addRandom == true && !addHtml) + output += " " + Math.random(); + + if(addHtml == true){ + var objLine = jQuery("#debug_line"); + objLine.width(200); + + if(objLine.height() >= 500) + objLine.html(""); + + var currentHtml = objLine.html(); + output = currentHtml + "
      --------------
      " + output; + } + + jQuery("#debug_line").show().html(output); + +} + +/** + * + * debug side some object + */ +function debugSide(obj){ + + var html = ""; + for(name in obj){ + var value = obj[name]; + html += name+" : " + value + "
      "; + } + + jQuery("#debug_side").show().html(html); + +} + + +/** + * output some string to console + */ +function trace(str){ + + if(typeof console != "undefined") + console.log(str); + +} + + + + +/** -------------- UgFunctions class ---------------------*/ + +function UGFunctions(){ + + var g_browserPrefix = null; + var t = this; + var g_temp = { + starTime:0, + arrThemes:[], + isTouchDevice:-1, + isRgbaSupported: -1, + timeCache:{}, + dataCache:{}, + lastEventType:"", //for validate touchstart click + lastEventTime:0, + lastTouchStartElement:null, + touchThreshold:700, + handle: null //interval handle + }; + + this.debugVar = ""; + + this.z__________FULL_SCREEN___________ = function(){} + + + + /** + * move to full screen mode + * fullscreen ID - the ID of current fullscreen + */ + this.toFullscreen = function(element, fullscreenID) { + + if(element.requestFullscreen) { + element.requestFullscreen(); + } else if(element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } else if(element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } else if(element.msRequestFullscreen) { + element.msRequestFullscreen(); + } else{ + return(false); + } + + return(true); + } + + + /** + * exit full screen mode + * return if operation success (or if fullscreen mode supported) + */ + this.exitFullscreen = function() { + if(t.isFullScreen() == false) + return(false); + + if(document.exitFullscreen) { + document.exitFullscreen(); + + } else if(document.cancelFullScreen) { + document.cancelFullScreen(); + + } else if(document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + + } else if(document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + + } else if(document.msExitFullscreen) { + document.msExitFullscreen(); + + }else{ + return(false); + } + + return(true); + } + + /** + * cross browser attach even function + */ + function addEvent(evnt, elem, func) { + if (elem.addEventListener) // W3C DOM + elem.addEventListener(evnt,func,false); + else if (elem.attachEvent) { // IE DOM + elem.attachEvent("on"+evnt, func); + } + else { // No much to do + elem[evnt] = func; + } + } + + + /** + * add fullscreen event to some function + */ + this.addFullScreenChangeEvent = function(func){ + + if(document["webkitCancelFullScreen"]) + addEvent("webkitfullscreenchange",document,func); + else if(document["msExitFullscreen"]) + addEvent("MSFullscreenChange",document,func); + else if(document["mozCancelFullScreen"]) + addEvent("mozfullscreenchange",document,func); + else + addEvent("fullscreenchange",document,func); + } + + + /** + * destroy the full screen change event + */ + this.destroyFullScreenChangeEvent = function(){ + + jQuery(document).unbind("fullscreenChange"); + jQuery(document).unbind("mozfullscreenchange"); + jQuery(document).unbind("webkitfullscreenchange"); + jQuery(document).unbind("MSFullscreenChange"); + } + + + /** + * get the fullscreen element + */ + this.getFullScreenElement = function(){ + + var fullscreenElement = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement; + + return(fullscreenElement); + } + + /** + * return if fullscreen enabled + */ + this.isFullScreen = function(){ + + var isFullScreen = document.fullscreen || document.mozFullScreen || document.webkitIsFullScreen || document.msFullscreenElement; + + if(!isFullScreen) + isFullScreen = false; + else + isFullScreen = true; + + return(isFullScreen); + } + + + + this.z__________GET_PROPS___________ = function(){} + + /** + * get browser prefix, can be empty if not detected. + */ + this.getBrowserPrefix = function(){ + + if(g_browserPrefix !== null) + return g_browserPrefix; + + var arrayOfPrefixes = ['webkit','Moz','ms','O']; + + var div = document.createElement("div"); + + for(var index in arrayOfPrefixes){ + var prefix = arrayOfPrefixes[index]; + + if(prefix+"Transform" in div.style){ + prefix = prefix.toLowerCase(); + g_browserPrefix = prefix; + return(prefix); + } + } + + g_browserPrefix = ""; + return ""; + } + + /** + * get image inside parent data by image (find parent and size) + * scaleMode: fit, down, fill, fitvert + */ + this.getImageInsideParentDataByImage = function(objImage, scaleMode, objPadding){ + + var objParent = objImage.parent(); + + var objOrgSize = t.getImageOriginalSize(objImage); + + var objData = t.getImageInsideParentData(objParent, objOrgSize.width, objOrgSize.height, scaleMode, objPadding); + + return(objData); + } + + + /** + * get data of image inside parent + * scaleMode: fit, down, fill, fitvert + */ + this.getImageInsideParentData = function(objParent, originalWidth, originalHeight, scaleMode, objPadding, maxWidth, maxHeight){ + + if(!objPadding) + var objPadding = {}; + + var objOutput = {}; + + if(typeof maxWidth === "undefined") + var maxWidth = objParent.width(); + + if(typeof maxHeight === "undefined") + var maxHeight = objParent.height(); + + if(objPadding.padding_left) + maxWidth -= objPadding.padding_left; + + if(objPadding.padding_right) + maxWidth -= objPadding.padding_right; + + if(objPadding.padding_top) + maxHeight -= objPadding.padding_top; + + if(objPadding.padding_bottom) + maxHeight -= objPadding.padding_bottom; + + var imageWidth = null; + var imageHeight = "100%"; + var imageTop = null; + var imageLeft = null; + var style = "display:block;margin:0px auto;"; + + if(originalWidth > 0 && originalHeight > 0){ + + //get image size and position + + if(scaleMode == "down" && originalWidth < maxWidth && originalHeight < maxHeight){ + + imageHeight = originalHeight; + imageWidth = originalWidth; + imageLeft = (maxWidth - imageWidth) / 2; + imageTop = (maxHeight - imageHeight) / 2; + + }else if(scaleMode == "fill"){ + var ratio = originalWidth / originalHeight; + + imageHeight = maxHeight; + imageWidth = imageHeight * ratio; + + if(imageWidth < maxWidth){ + imageWidth = maxWidth; + imageHeight = imageWidth / ratio; + + //center y position + imageLeft = 0; + imageTop = Math.round((imageHeight - maxHeight) / 2 * -1); + }else{ //center x position + imageTop = 0; + imageLeft = Math.round((imageWidth - maxWidth) / 2 * -1); + } + + } + else{ //fit to borders + var ratio = originalWidth / originalHeight; + imageHeight = maxHeight; + imageWidth = imageHeight * ratio; + imageTop = 0; + imageLeft = (maxWidth - imageWidth) / 2; + + if(scaleMode != "fitvert" && imageWidth > maxWidth){ + imageWidth = maxWidth; + imageHeight = imageWidth / ratio; + imageLeft = 0; + imageTop = (maxHeight - imageHeight) / 2; + } + + } + + imageWidth = Math.floor(imageWidth); + imageHeight = Math.floor(imageHeight); + + imageTop = Math.floor(imageTop); + imageLeft = Math.floor(imageLeft); + + style="position:absolute;"; + } + + //set padding + if(objPadding.padding_top) + imageTop += objPadding.padding_top; + + if(objPadding.padding_left) + imageLeft += objPadding.padding_left; + + objOutput.imageWidth = imageWidth; + objOutput.imageHeight = imageHeight; + objOutput.imageTop = imageTop; + objOutput.imageLeft = imageLeft; + objOutput.imageRight = imageLeft + imageWidth; + if(imageTop == 0 || imageHeight == "100%") + objOutput.imageBottom = null; + else + objOutput.imageBottom = imageTop + imageHeight; + + objOutput.style = style; + + return(objOutput); + } + + + /** + * get element center position inside parent + * even if the object bigger than the parent + */ + this.getElementCenterPosition = function(element, objPadding){ + + var parent = element.parent(); + var objSize = t.getElementSize(element); + var objSizeParent = t.getElementSize(parent); + + var parentWidth = objSizeParent.width; + var parentHeight = objSizeParent.height; + + if(objPadding && objPadding.padding_top !== undefined) + parentHeight -= objPadding.padding_top; + + if(objPadding && objPadding.padding_bottom !== undefined) + parentHeight -= objPadding.padding_bottom; + + if(objPadding && objPadding.padding_left !== undefined) + parentWidth -= objPadding.padding_left; + + if(objPadding && objPadding.padding_right !== undefined) + parentWidth -= objPadding.padding_right; + + + var output = {}; + output.left = Math.round((parentWidth - objSize.width) / 2); + output.top = Math.round((parentHeight - objSize.height) / 2); + + if(objPadding && objPadding.padding_top !== undefined) + output.top += objPadding.padding_top; + + if(objPadding && objPadding.padding_left !== undefined) + output.left += objPadding.padding_left; + + + return(output); + } + + + /** + * get the center of the element + * includeParent - including left / right related to the parent + */ + this.getElementCenterPoint = function(element, includeParent){ + + if(!includeParent) + var includeParent = false; + + var objSize = t.getElementSize(element); + var output = {}; + + output.x = objSize.width / 2; + output.y = objSize.height / 2; + + if(includeParent == true){ + output.x += objSize.left; + output.y += objSize.top; + } + + output.x = Math.round(output.x); + output.y = Math.round(output.y); + + return(output); + } + + + /** + * + * get mouse position from the event + * optimised to every device + */ + this.getMousePosition = function(event, element){ + + var output = { + pageX: event.pageX, + pageY: event.pageY, + clientX: event.clientX, + clientY: event.clientY + }; + + if(event.originalEvent && event.originalEvent.touches && event.originalEvent.touches.length > 0){ + output.pageX = event.originalEvent.touches[0].pageX; + output.pageY = event.originalEvent.touches[0].pageY; + output.clientX = event.originalEvent.touches[0].clientX; + output.clientY = event.originalEvent.touches[0].clientY; + } + + /** + * get element's mouse position + */ + if(element){ + var elementPos = element.offset(); + output.mouseX = output.pageX - elementPos.left; + output.mouseY = output.pageY - elementPos.top; + } + + return(output); + } + + /** + * get mouse element related point from page related point + */ + this.getMouseElementPoint = function(point, element){ + + //rename the input and output + var newPoint = {x: point.pageX, y: point.pageY}; + + var elementPoint = t.getElementLocalPoint(newPoint, element); + + return(elementPoint); + } + + + /** + * get element local point from global point position + */ + this.getElementLocalPoint = function(point, element){ + + var elementPoint = {}; + var elementPos = element.offset(); + + elementPoint.x = Math.round(point.x - elementPos.left); + elementPoint.y = Math.round(point.y - elementPos.top); + + return(elementPoint); + } + + + /** + * get image oritinal size + * if originalWidth, originalHeight is set, just return them. + */ + this.getImageOriginalSize = function(objImage, originalWidth, originalHeight){ + + if(typeof originalWidth != "undefined" && typeof originalHeight != "undefined") + return({width:originalWidth, height:originalHeight}); + + var htmlImage = objImage[0]; + + if(typeof htmlImage == "undefined") + throw new Error("getImageOriginalSize error - Image not found"); + + var output = {}; + + if(typeof htmlImage.naturalWidth == "undefined"){ + + //check from cache + if(typeof objImage.data("naturalWidth") == "number"){ + var output = {}; + output.width = objImage.data("naturalWidth"); + output.height = objImage.data("naturalHeight"); + return(output); + } + + //load new image + var newImg = new Image(); + newImg.src = htmlImage.src; + + if (newImg.complete) { + output.width = newImg.width; + output.height = newImg.height; + + //caching + objImage.data("naturalWidth", output.width); + objImage.data("naturalHeight", output.height); + return(output); + + } + + return({width:0,height:0}); + + }else{ + + output.width = htmlImage.naturalWidth; + output.height = htmlImage.naturalHeight; + + return(output); + } + + } + + + /** + * get current image ratio from original size + */ + this.getimageRatio = function(objImage){ + + var originalSize = t.getImageOriginalSize(objImage); + var size = t.getElementSize(objImage); + var ratio = size.width / originalSize.width; + + return(ratio); + } + + /** + * tells if the image fit the parent (smaller then the parent) + */ + this.isImageFitParent = function(objImage){ + var objParent = objImage.parent(); + var sizeImage = t.getElementSize(objImage); + var sizeParent = t.getElementSize(objParent); + + if(sizeImage.width <= sizeParent.width && sizeImage.height <= sizeParent.height) + return(true); + + return(false); + } + + /** + * get size and position of some object + */ + this.getElementSize = function(element){ + + if(element === undefined){ + throw new Error("Can't get size, empty element"); + } + + var obj = element.position(); + + obj.height = element.outerHeight(); + obj.width = element.outerWidth(); + + obj.left = Math.round(obj.left); + obj.top = Math.round(obj.top); + + obj.right = obj.left + obj.width; + obj.bottom = obj.top + obj.height; + + return(obj); + } + + + + /** + * return true if the element is bigger then it's parent + */ + this.isElementBiggerThenParent = function(element){ + + var objParent = element.parent(); + var objSizeElement = t.getElementSize(element); + var objSizeParent = t.getElementSize(objParent); + + if(objSizeElement.width > objSizeParent.width || objSizeElement.height > objSizeParent.height) + return(true); + + return(false); + } + + + /** + * tells if the mouse point inside image + * the mouse point is related to image pos + */ + this.isPointInsideElement = function(point, objSize){ + + var isMouseXInside = (point.x >= 0 && point.x < objSize.width); + if(isMouseXInside == false) + return(false); + + var isMouseYInside = (point.y >= 0 && point.y < objSize.height); + if(isMouseYInside == false) + return(false); + + return(true); + } + + + /** + * get element relative position according the parent + * if the left / top is offset text (left , center, right / top, middle , bottom) + * the element can be number size as well + */ + this.getElementRelativePos = function(element, pos, offset, objParent){ + + if(!objParent) + var objParent = element.parent(); + + if(typeof element == "number"){ + var elementSize = { + width: element, + height: element + }; + }else + var elementSize = t.getElementSize(element); + + var parentSize = t.getElementSize(objParent); + + + switch(pos){ + case "top": + case "left": + pos = 0; + if(offset) + pos += offset; + break; + case "center": + pos = Math.round((parentSize.width - elementSize.width) / 2); + if(offset) + pos += offset; + + break; + case "right": + pos = parentSize.width - elementSize.width; + if(offset) + pos -= offset; + break; + case "middle": + pos = Math.round((parentSize.height - elementSize.height) / 2); + if(offset) + pos += offset; + break; + case "bottom": + pos = parentSize.height - elementSize.height; + if(offset) + pos -= offset; + break; + } + + return(pos); + } + + + + this.z_________SET_ELEMENT_PROPS_______ = function(){} + + + /** + * + * zoom image inside parent + * the mouse point is page offset position, can be null + * return true if zoomed and false if not zoomed + */ + this.zoomImageInsideParent = function(objImage, zoomIn, step, point, scaleMode, maxZoomRatio, objPadding){ + + if(!step) + var step = 1.2; + + if(!scaleMode) + var scaleMode = "fit"; + + var zoomRatio = step; + + var objParent = objImage.parent(); + + var objSize = t.getElementSize(objImage); + var objOriginalSize = t.getImageOriginalSize(objImage); + + + var isMouseInside = false; + var newHeight,newWidth, panX = 0, panY = 0, newX, newY,panOrientX = 0, panOrientY = 0; + + if(!point){ + isMouseInside = false; + }else{ + var pointImg = t.getMouseElementPoint(point, objImage); + isMouseInside = t.isPointInsideElement(pointImg, objSize); + + //if mouse point outside image, set orient to image center + panOrientX = pointImg.x; + panOrientY = pointImg.y; + } + + if(isMouseInside == false){ + var imgCenterPoint = t.getElementCenterPoint(objImage); + panOrientX = imgCenterPoint.x; + panOrientY = imgCenterPoint.y; + } + + //zoom: + if(zoomIn == true){ //zoom in + + newHeight = objSize.height * zoomRatio; + newWidth = objSize.width * zoomRatio; + + if(panOrientX != 0) + panX = -(panOrientX * zoomRatio - panOrientX); + + if(panOrientY != 0) + panY = -(panOrientY * zoomRatio - panOrientY); + + + }else{ //zoom out + + newHeight = objSize.height / zoomRatio; + newWidth = objSize.width / zoomRatio; + + var objScaleData = t.getImageInsideParentData(objParent, objOriginalSize.width, objOriginalSize.height, scaleMode, objPadding); + + //go back to original size + if(newWidth < objScaleData.imageWidth){ + + t.scaleImageFitParent(objImage, objOriginalSize.width, objOriginalSize.height, scaleMode, objPadding); + return(true); + } + + if(isMouseInside == true){ + if(panOrientX != 0) + panX = -(panOrientX / zoomRatio - panOrientX); + + if(panOrientY != 0) + panY = -(panOrientY / zoomRatio - panOrientY); + } + + } + + //check max zoom ratio, ix exeeded, abort + if(maxZoomRatio){ + var expectedZoomRatio = 1; + if(objOriginalSize.width != 0) + expectedZoomRatio = newWidth / objOriginalSize.width; + + if(expectedZoomRatio > maxZoomRatio) + return(false); + } + + //resize the element + t.setElementSize(objImage, newWidth, newHeight); + + //set position: + + //if zoom out and mouse point not inside image, + //get the image to center + if(zoomIn == false && isMouseInside == false){ + var posCenter = t.getElementCenterPosition(objImage); + newX = posCenter.left; + newY = posCenter.top; + }else{ + + newX = objSize.left + panX; + newY = objSize.top + panY; + } + + t.placeElement(objImage, newX, newY); + + return(true); + } + + + + /** + * place some element to some position + * if the left / top is offset text (left , center, right / top, middle , bottom) + * then put it in parent by the offset. + */ + this.placeElement = function(element, left, top, offsetLeft, offsetTop, objParent){ + + + if(jQuery.isNumeric(left) == false || jQuery.isNumeric(top) == false){ + + if(!objParent) + var objParent = element.parent(); + + var elementSize = t.getElementSize(element); + var parentSize = t.getElementSize(objParent); + } + + //select left position + if(jQuery.isNumeric(left) == false){ + + switch(left){ + case "left": + left = 0; + if(offsetLeft) + left += offsetLeft; + break; + case "center": + left = Math.round((parentSize.width - elementSize.width) / 2); + if(offsetLeft) + left += offsetLeft; + break; + case "right": + left = parentSize.width - elementSize.width; + if(offsetLeft) + left -= offsetLeft; + break; + } + } + + //select top position + if(jQuery.isNumeric(top) == false){ + + switch(top){ + case "top": + top = 0; + if(offsetTop) + top += offsetTop; + break; + case "middle": + case "center": + top = Math.round((parentSize.height - elementSize.height) / 2); + if(offsetTop) + top += offsetTop; + break; + case "bottom": + top = parentSize.height - elementSize.height; + if(offsetTop) + top -= offsetTop; + break; + } + + } + + + var objCss = { + "position":"absolute", + "margin":"0px" + }; + + if(left !== null) + objCss.left = left; + + if(top !== null) + objCss.top = top; + + element.css(objCss); + } + + + /** + * place element inside parent center. + * the element should be absolute position + */ + this.placeElementInParentCenter = function(element){ + + t.placeElement(element, "center", "middle"); + } + + + /** + * set element size and position + */ + this.setElementSizeAndPosition = function(element,left,top,width,height){ + + var objCss = { + "width":width+"px", + "height":height+"px", + "left":left+"px", + "top":top+"px", + "position":"absolute", + "margin":"0px" + } + + element.css(objCss); + } + + /** + * set widht and height of the element + */ + this.setElementSize = function(element, width, height){ + + var objCss = { + "width":width+"px" + } + + if(height !== null && typeof height != "undefined") + objCss["height"] = height+"px" + + element.css(objCss); + } + + + /** + * clone element size and position + */ + this.cloneElementSizeAndPos = function(objSource, objTarget, isOuter, offsetX, offsetY){ + + var objSize = objSource.position(); + + if(objSize == undefined){ + throw new Error("Can't get size, empty element"); + } + + if(isOuter === true){ + objSize.height = objSource.outerHeight(); + objSize.width = objSource.outerWidth(); + }else{ + objSize.height = objSource.height(); + objSize.width = objSource.width(); + } + + objSize.left = Math.round(objSize.left); + objSize.top = Math.round(objSize.top); + + if(offsetX) + objSize.left += offsetX; + + if(offsetY) + objSize.top += offsetY; + + t.setElementSizeAndPosition(objTarget, objSize.left, objSize.top, objSize.width, objSize.height); + } + + + /** + * place image inside parent, scale it by the options + * and scale it to fit the parent. + * scaleMode: fit, down, fill + */ + this.placeImageInsideParent = function(urlImage, objParent, originalWidth, originalHeight, scaleMode, objPadding){ + var obj = t.getImageInsideParentData(objParent, originalWidth, originalHeight, scaleMode, objPadding); + + //set html image: + var htmlImage = " realMax) + value = realMax; + } + + return(value); + } + + + /** + * + * get "real" setting from normalized setting + */ + this.getNormalizedValue = function(realMin, realMax, settingMin, settingMax, realValue){ + + var ratio = (realValue - realMin) / (realMax - realMin); + realValue = realMin + (settingMax - settingMin) * ratio; + + return(realValue); + } + + + /** + * get distance between 2 points + */ + this.getDistance = function(x1,y1,x2,y2) { + + var distance = Math.round(Math.sqrt(Math.abs(((x2-x1)*(x2-x1)) + ((y2-y1)*(y2-y1))))); + + return distance; + } + + + /** + * get center point of the 2 points + */ + this.getMiddlePoint = function(x1,y1,x2,y2){ + var output = {} + output.x = x1 + Math.round((x2 - x1) / 2); + output.y = y1 + Math.round((y2 - y1) / 2); + + return(output); + } + + + /** + * get number of items in space width gap + * even item sizes + * by lowest + */ + this.getNumItemsInSpace = function(spaceSize, itemsSize, gapSize){ + var numItems = Math.floor((spaceSize + gapSize) / (itemsSize + gapSize)); + return(numItems); + } + + /** + * get number of items in space width gap + * even item sizes + * by Math.round + */ + this.getNumItemsInSpaceRound = function(spaceSize, itemsSize, gapSize){ + var numItems = Math.round((spaceSize + gapSize) / (itemsSize + gapSize)); + return(numItems); + } + + /** + * get space (width in carousel for example) by num items, item size, and gap size + */ + this.getSpaceByNumItems = function(numItems, itemSize, gapSize){ + var space = numItems * itemSize + (numItems-1) * gapSize; + return(space); + } + + + /** + * get item size by space and gap + */ + this.getItemSizeInSpace = function(spaceSize, numItems, gapSize){ + var itemSize = Math.floor((spaceSize - (numItems-1) * gapSize) / numItems); + + return(itemSize); + } + + + /** + * get column x pos with even column sizes, start from 0 + */ + this.getColX = function(col, colWidth, colGap){ + + var posx = col * (colWidth + colGap); + + return posx; + } + + + /** + * get column number by index + */ + this.getColByIndex = function(numCols, index){ + var col = index % numCols; + return(col); + } + + + /** + * get col and row by index + */ + this.getColRowByIndex = function(index, numCols){ + + var row = Math.floor(index / numCols); + var col = Math.floor(index % numCols); + + return({col:col,row:row}); + } + + + /** + * get index by row, col, numcols + */ + this.getIndexByRowCol = function(row, col, numCols){ + + if(row < 0) + return(-1); + + if(col < 0) + return(-1); + + var index = row * numCols + col; + return(index); + } + + /** + * get previous row item in the same column + */ + this.getPrevRowSameColIndex = function(index, numCols){ + var obj = t.getColRowByIndex(index, numCols); + var prevIndex = t.getIndexByRowCol(obj.row-1, obj.col, numCols); + return(prevIndex); + } + + /** + * get next row item in the same column + */ + this.getNextRowSameColIndex = function(index, numCols){ + var obj = t.getColRowByIndex(index, numCols); + var nextIndex = t.getIndexByRowCol(obj.row+1, obj.col, numCols); + return(nextIndex); + } + + + this.z_________DATA_FUNCTIONS_______ = function(){} + + /** + * set data value + */ + this.setGlobalData = function(key, value){ + + jQuery.data(document.body, key, value); + + } + + /** + * get global data + */ + this.getGlobalData = function(key){ + + var value = jQuery.data(document.body, key); + + return(value); + } + + this.z_________EVENT_DATA_FUNCTIONS_______ = function(){} + + + /** + * handle scroll top, return if scroll mode or not + */ + this.handleScrollTop = function(storedEventID){ + + if(t.isTouchDevice() == false) + return(null); + + var objData = t.getStoredEventData(storedEventID); + + var horPass = 15; + var vertPass = 15; + + //check if need to set some movement + if(objData.scrollDir === null){ + + if(Math.abs(objData.diffMouseX) > horPass) + objData.scrollDir = "hor"; + else + if(Math.abs(objData.diffMouseY) > vertPass && Math.abs(objData.diffMouseY) > Math.abs(objData.diffMouseX) ){ + objData.scrollDir = "vert"; + objData.scrollStartY = objData.lastMouseClientY; + objData.scrollOrigin = jQuery(document).scrollTop(); + + g_temp.dataCache[storedEventID].scrollStartY = objData.lastMouseClientY; + g_temp.dataCache[storedEventID].scrollOrigin = objData.scrollOrigin; + } + + //update scrollDir + g_temp.dataCache[storedEventID].scrollDir = objData.scrollDir; + } + + if(objData.scrollDir !== "vert") + return(objData.scrollDir); + + + var currentScroll = jQuery(document).scrollTop(); + + var scrollPos = objData.scrollOrigin - (objData.lastMouseClientY - objData.scrollStartY); + + if(scrollPos >= 0) + jQuery(document).scrollTop(scrollPos); + + return(objData.scrollDir); + } + + + /** + * return true / false if was vertical scrolling + */ + this.wasVerticalScroll = function(storedEventID){ + var objData = t.getStoredEventData(storedEventID); + + if(objData.scrollDir === "vert") + return(true); + + return(false); + } + + + /** + * store event data + */ + this.storeEventData = function(event, id, addData){ + + var mousePos = t.getMousePosition(event); + var time = jQuery.now(); + + var obj = { + startTime: time, + lastTime: time, + startMouseX: mousePos.pageX, + startMouseY: mousePos.pageY, + lastMouseX: mousePos.pageX, + lastMouseY: mousePos.pageY, + + startMouseClientY: mousePos.clientY, + lastMouseClientY: mousePos.clientY, + + scrollTop: jQuery(document).scrollTop(), + scrollDir: null + }; + + if(addData) + obj = jQuery.extend(obj, addData); + + g_temp.dataCache[id] = obj; + + } + + + /** + * update event data with last position + */ + this.updateStoredEventData = function(event, id, addData){ + + if(!g_temp.dataCache[id]) + throw new Error("updateEventData error: must have stored cache object"); + + var obj = g_temp.dataCache[id]; + + var mousePos = t.getMousePosition(event); + obj.lastTime = jQuery.now(); + + if(mousePos.pageX !== undefined){ + obj.lastMouseX = mousePos.pageX; + obj.lastMouseY = mousePos.pageY; + obj.lastMouseClientY = mousePos.clientY; + } + + if(addData) + obj = jQuery.extend(obj, addData); + + g_temp.dataCache[id] = obj; + } + + /** + * get stored event data + */ + this.getStoredEventData = function(id, isVertical){ + if(!g_temp.dataCache[id]) + throw new Error("updateEventData error: must have stored cache object"); + + var obj = g_temp.dataCache[id]; + + obj.diffMouseX = obj.lastMouseX - obj.startMouseX; + obj.diffMouseY = obj.lastMouseY - obj.startMouseY; + + obj.diffMouseClientY = obj.lastMouseClientY - obj.startMouseClientY; + + obj.diffTime = obj.lastTime - obj.startTime; + + //get mouse position according orientation + if(isVertical === true){ + obj.startMousePos = obj.lastMouseY; + obj.lastMousePos = obj.lastMouseY; + obj.diffMousePos = obj.diffMouseY; + }else{ + obj.startMousePos = obj.lastMouseX; + obj.lastMousePos = obj.lastMouseX; + obj.diffMousePos = obj.diffMouseX; + } + + return(obj); + } + + /** + * return if click event approved according the done motion + */ + this.isApproveStoredEventClick = function(id, isVertical){ + + if(!g_temp.dataCache[id]) + return(true); + + var objData = t.getStoredEventData(id, isVertical); + + var passedDistanceAbs = Math.abs(objData.diffMousePos); + + if(objData.diffTime > 400) + return(false); + + if(passedDistanceAbs > 30) + return(false); + + return(true); + + } + + + /** + * clear stored event data + */ + this.clearStoredEventData = function(id){ + g_temp.dataCache[id] = null; + } + + this.z_________CHECK_SUPPORT_FUNCTIONS_______ = function(){} + + + + /** + * is canvas exists in the browser + */ + this.isCanvasExists = function(){ + + var canvas = jQuery(' ')[0]; + + if(typeof canvas.getContext == "function") + return(true); + + return(false); + } + + /** + * tells if vertical scrollbar exists + */ + this.isScrollbarExists = function(){ + var hasScrollbar = window.innerWidth > document.documentElement.clientWidth; + return(hasScrollbar); + } + + /** + * check if this device are touch enabled + */ + this.isTouchDevice = function(){ + + //get from cache + if(g_temp.isTouchDevice !== -1) + return(g_temp.isTouchDevice); + + try{ + document.createEvent("TouchEvent"); + g_temp.isTouchDevice = true; + } + catch(e){ + g_temp.isTouchDevice = false; + } + + return(g_temp.isTouchDevice); + } + + + + /** + * check if + */ + this.isRgbaSupported = function(){ + + if(g_temp.isRgbaSupported !== -1) + return(g_temp.isRgbaSupported); + + var scriptElement = document.getElementsByTagName('script')[0]; + var prevColor = scriptElement.style.color; + try { + scriptElement.style.color = 'rgba(1,5,13,0.44)'; + } catch(e) {} + var result = scriptElement.style.color != prevColor; + scriptElement.style.color = prevColor; + + g_temp.isRgbaSupported = result; + + return result; + } + + this.z_________GENERAL_FUNCTIONS_______ = function(){} + + + /** + * check if current jquery version is more then minimal version + * version can be "1.8.0" for example + */ + this.checkMinJqueryVersion = function(version){ + + var arrCurrent = jQuery.fn.jquery.split('.'); + var arrMin = version.split('.'); + + for (var i=0, len=arrCurrent.length; i numCurrent) + return(false); + + if(numCurrent > numMin) + return(true); + } + + + //if all equal then all ok + return true; + } + + + /** + * get css size parameter, like width. if % given, leave it, if number without px - add px. + */ + this.getCssSizeParam = function(sizeParam){ + if(jQuery.isNumeric(sizeParam)) + return(sizeParam + "px"); + + return(sizeParam); + } + + /** + * convert hex color to rgb color + */ + this.convertHexToRGB = function(hexInput, opacity){ + + var hex = hexInput.replace('#',''); + if(hex === hexInput) + return(hexInput); + + r = parseInt(hex.substring(0,2), 16); + g = parseInt(hex.substring(2,4), 16); + b = parseInt(hex.substring(4,6), 16); + result = 'rgba('+r+','+g+','+b+','+opacity+')'; + return result; + } + + /** + * get timestamp to string + */ + this.timestampToString = function(stamp){ + + var d = new Date(stamp); + var str = d.getDate() + "/" + d.getMonth(); + str += " " + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + ":" + d.getMilliseconds(); + + return(str); + } + + /** + * get touches array (if exists) from the event + */ + this.getArrTouches = function(event){ + + var arrTouches = []; + + if(event.originalEvent && event.originalEvent.touches && event.originalEvent.touches.length > 0){ + arrTouches = event.originalEvent.touches; + } + + return(arrTouches); + } + + /** + * extract touch positions from arrTouches + */ + this.getArrTouchPositions = function(arrTouches){ + + var arrOutput = []; + + for(var i=0;i g_temp.touchThreshold) + return(true); + + objElement.trigger("tap"); + }); + + } + + + } + + + /** + * load javascript dynamically + */ + this.loadJs = function(url, addProtocol){ + + if(addProtocol === true) + url = location.protocol + "//" + url; + + var tag = document.createElement('script'); + tag.src = url; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + } + + /** + * load css dymanically + */ + this.loadCss = function(url, addProtocol){ + if(addProtocol === true) + url = location.protocol + "//" + url; + + var tag=document.createElement("link"); + tag.setAttribute("rel", "stylesheet"); + tag.setAttribute("type", "text/css"); + tag.setAttribute("href", url); + + document.getElementsByTagName("head")[0].appendChild(tag); + } + + /** + * add event listener with old browsers fallback + */ + this.addEvent = function(elem, event, func ) { + + if (typeof (elem.addEventListener) != "undefined") { + elem.addEventListener(event, func, false); + } else if (elem.attachEvent) { + elem.attachEvent('on' + event, func); + } + + } + + + + + /** + * fire event where all images are loaded + */ + this.checkImagesLoaded = function(objImages, onComplete, onProgress){ + + var arrImages = []; + var counter = 0; + var numImages = objImages.length; + + //if no images - exit + if(numImages == 0 && onComplete){ + onComplete(); + return(false); + } + + //nested function + function checkComplete(image, isError){ + counter++; + + if(typeof onProgress == "function"){ + + setTimeout(function(){ + onProgress(image, isError); + }); + } + + if(counter == numImages && typeof onComplete == "function"){ + setTimeout(function(){ + onComplete(); + }); + } + + } + + + //start a little later + setTimeout(function(){ + + //start the function + for(var index=0;index < numImages; index++){ + var image = objImages[index]; + + //arrImages.push(jQuery(image)); + if(image.naturalWidth !== undefined && image.naturalWidth !== 0){ + checkComplete(objImages[index], false); + } + else{ + var proxyImage = jQuery(''); + proxyImage.data("index", index); + + proxyImage.on("load", function(){ + var index = jQuery(this).data("index"); + checkComplete(objImages[index], false); + }); + proxyImage.on("error", function(){ + var index = jQuery(this).data("index"); + checkComplete(objImages[index], true); + }); + proxyImage.attr("src", image.src); + } + } + + }); + + + } + + + /** + * run the function when the element size will be not 0 + */ + this.waitForWidth = function(element, func){ + var width = element.width(); + if(width != 0){ + func(); + return(false); + } + + g_temp.handle = setInterval(function(){ + width = element.width(); + if(width != 0){ + clearInterval(g_temp.handle); + func(); + } + + }, 300); + + } + + /** + * shuffle (randomise) array + */ + this.arrayShuffle = function(arr){ + + if(typeof arr != "object") + return(arr); + + for(var j, x, i = arr.length; i; j = parseInt(Math.random() * i), x = arr[--i], arr[i] = arr[j], arr[j] = x); + return arr; + } + + + /** + * get object length + */ + this.getObjectLength = function(object){ + var num = 0; + for(var item in object) + num++; + return num; + } + + /** + * normalize the percent, return always between 0 and 1 + */ + this.normalizePercent = function(percent){ + + if(percent < 0) + percent = 0; + + if(percent > 1) + percent = 1; + + return(percent); + } + + + /** + * strip tags from string + */ + this.stripTags = function(html){ + + var text = html.replace(/(<([^>]+)>)/ig,""); + + return(text); + } + + + /** + * escape double slash + */ + this.escapeDoubleSlash = function(str){ + + return str.replace('"','\"'); + } + + + /** + * html entitles + */ + this.htmlentitles = function(html){ + var text = jQuery('
      ').text(html).html(); + return(text); + } + + + this.z_________END_GENERAL_FUNCTIONS_______ = function(){} + +} + + + +var g_ugFunctions = new UGFunctions(); + + +/** -------------- END UgFunctions class ---------------------*/ + + + +/** -------------- MouseWheel ---------------------*/ + +!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?module.exports=e:e(jQuery)}(function(e){function t(t){var s=t||window.event,a=h.call(arguments,1),u=0,r=0,d=0,f=0;if(t=e.event.fix(s),t.type="mousewheel","detail"in s&&(d=-1*s.detail),"wheelDelta"in s&&(d=s.wheelDelta),"wheelDeltaY"in s&&(d=s.wheelDeltaY),"wheelDeltaX"in s&&(r=-1*s.wheelDeltaX),"axis"in s&&s.axis===s.HORIZONTAL_AXIS&&(r=-1*d,d=0),u=0===d?r:d,"deltaY"in s&&(d=-1*s.deltaY,u=d),"deltaX"in s&&(r=s.deltaX,0===d&&(u=-1*r)),0!==d||0!==r){if(1===s.deltaMode){var c=e.data(this,"mousewheel-line-height");u*=c,d*=c,r*=c}else if(2===s.deltaMode){var m=e.data(this,"mousewheel-page-height");u*=m,d*=m,r*=m}return f=Math.max(Math.abs(d),Math.abs(r)),(!l||l>f)&&(l=f,i(s,f)&&(l/=40)),i(s,f)&&(u/=40,r/=40,d/=40),u=Math[u>=1?"floor":"ceil"](u/l),r=Math[r>=1?"floor":"ceil"](r/l),d=Math[d>=1?"floor":"ceil"](d/l),t.deltaX=r,t.deltaY=d,t.deltaFactor=l,t.deltaMode=0,a.unshift(t,u,r,d),o&&clearTimeout(o),o=setTimeout(n,200),(e.event.dispatch||e.event.handle).apply(this,a)}}function n(){l=null}function i(e,t){return r.settings.adjustOldDeltas&&"mousewheel"===e.type&&t%120===0}var o,l,s=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],a="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],h=Array.prototype.slice;if(e.event.fixHooks)for(var u=s.length;u;)e.event.fixHooks[s[--u]]=e.event.mouseHooks;var r=e.event.special.mousewheel={version:"3.1.9",setup:function(){if(this.addEventListener)for(var n=a.length;n;)this.addEventListener(a[--n],t,!1);else this.onmousewheel=t;e.data(this,"mousewheel-line-height",r.getLineHeight(this)),e.data(this,"mousewheel-page-height",r.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var e=a.length;e;)this.removeEventListener(a[--e],t,!1);else this.onmousewheel=null},getLineHeight:function(t){return parseInt(e(t)["offsetParent"in e.fn?"offsetParent":"parent"]().css("fontSize"),10)},getPageHeight:function(t){return e(t).height()},settings:{adjustOldDeltas:!0}};e.fn.extend({mousewheel:function(e){return e?this.bind("mousewheel",e):this.trigger("mousewheel")},unmousewheel:function(e){return this.unbind("mousewheel",e)}})}); + +/** -------------- EASING FUNCTIONS ---------------------*/ + +(function(factory){if(typeof define==="function"&&define.amd){define(["jquery"],function($){return factory($)})}else if(typeof module==="object"&&typeof module.exports==="object"){exports=factory(require("jquery"))}else{factory(jQuery)}})(function($){$.easing["jswing"]=$.easing["swing"];var pow=Math.pow,sqrt=Math.sqrt,sin=Math.sin,cos=Math.cos,PI=Math.PI,c1=1.70158,c2=c1*1.525,c3=c1+1,c4=2*PI/3,c5=2*PI/4.5;function bounceOut(x){var n1=7.5625,d1=2.75;if(x<1/d1){return n1*x*x}else if(x<2/d1){return n1*(x-=1.5/d1)*x+.75}else if(x<2.5/d1){return n1*(x-=2.25/d1)*x+.9375}else{return n1*(x-=2.625/d1)*x+.984375}}$.extend($.easing,{def:"easeOutQuad",swing:function(x){return $.easing[$.easing.def](x)},easeInQuad:function(x){return x*x},easeOutQuad:function(x){return 1-(1-x)*(1-x)},easeInOutQuad:function(x){return x<.5?2*x*x:1-pow(-2*x+2,2)/2},easeInCubic:function(x){return x*x*x},easeOutCubic:function(x){return 1-pow(1-x,3)},easeInOutCubic:function(x){return x<.5?4*x*x*x:1-pow(-2*x+2,3)/2},easeInQuart:function(x){return x*x*x*x},easeOutQuart:function(x){return 1-pow(1-x,4)},easeInOutQuart:function(x){return x<.5?8*x*x*x*x:1-pow(-2*x+2,4)/2},easeInQuint:function(x){return x*x*x*x*x},easeOutQuint:function(x){return 1-pow(1-x,5)},easeInOutQuint:function(x){return x<.5?16*x*x*x*x*x:1-pow(-2*x+2,5)/2},easeInSine:function(x){return 1-cos(x*PI/2)},easeOutSine:function(x){return sin(x*PI/2)},easeInOutSine:function(x){return-(cos(PI*x)-1)/2},easeInExpo:function(x){return x===0?0:pow(2,10*x-10)},easeOutExpo:function(x){return x===1?1:1-pow(2,-10*x)},easeInOutExpo:function(x){return x===0?0:x===1?1:x<.5?pow(2,20*x-10)/2:(2-pow(2,-20*x+10))/2},easeInCirc:function(x){return 1-sqrt(1-pow(x,2))},easeOutCirc:function(x){return sqrt(1-pow(x-1,2))},easeInOutCirc:function(x){return x<.5?(1-sqrt(1-pow(2*x,2)))/2:(sqrt(1-pow(-2*x+2,2))+1)/2},easeInElastic:function(x){return x===0?0:x===1?1:-pow(2,10*x-10)*sin((x*10-10.75)*c4)},easeOutElastic:function(x){return x===0?0:x===1?1:pow(2,-10*x)*sin((x*10-.75)*c4)+1},easeInOutElastic:function(x){return x===0?0:x===1?1:x<.5?-(pow(2,20*x-10)*sin((20*x-11.125)*c5))/2:pow(2,-20*x+10)*sin((20*x-11.125)*c5)/2+1},easeInBack:function(x){return c3*x*x*x-c1*x*x},easeOutBack:function(x){return 1+c3*pow(x-1,3)+c1*pow(x-1,2)},easeInOutBack:function(x){return x<.5?pow(2*x,2)*((c2+1)*2*x-c2)/2:(pow(2*x-2,2)*((c2+1)*(x*2-2)+c2)+2)/2},easeInBounce:function(x){return 1-bounceOut(1-x)},easeOutBounce:bounceOut,easeInOutBounce:function(x){return x<.5?(1-bounceOut(1-2*x))/2:(1+bounceOut(2*x-1))/2}})}); + +/** -------------- JQuery Color Animations ---------------------*/ + +!function(r,n){ + if(typeof r.cssHooks == "undefined") //error protection + return(false); + function t(r,n,t){var e=f[n.type]||{};return null==r?t||!n.def?null:n.def:(r=e.floor?~~r:parseFloat(r),isNaN(r)?n.def:e.mod?(r+e.mod)%e.mod:0>r?0:e.max6*t?r+(n-r)*t*6:1>2*t?n:2>3*t?r+(n-r)*(2/3-t)*6:r}var a,s="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",i=/^([\-+])=\s*(\d+\.?\d*)/,u=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(r){return[r[1],r[2],r[3],r[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(r){return[2.55*r[1],2.55*r[2],2.55*r[3],r[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(r){return[parseInt(r[1],16),parseInt(r[2],16),parseInt(r[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(r){return[parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16),parseInt(r[3]+r[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(r){return[r[1],r[2]/100,r[3]/100,r[4]]}}],l=r.Color=function(n,t,e,o){return new r.Color.fn.parse(n,t,e,o)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},f={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},p=l.support={},d=r("

      ")[0],h=r.each;d.style.cssText="background-color:rgba(1,1,1,.5)",p.rgba=d.style.backgroundColor.indexOf("rgba")>-1,h(c,function(r,n){n.cache="_"+r,n.props.alpha={idx:3,type:"percent",def:1}}),l.fn=r.extend(l.prototype,{parse:function(o,s,i,u){if(o===n)return this._rgba=[null,null,null,null],this;(o.jquery||o.nodeType)&&(o=r(o).css(s),s=n);var f=this,p=r.type(o),d=this._rgba=[];return s!==n&&(o=[o,s,i,u],p="array"),"string"===p?this.parse(e(o)||a._default):"array"===p?(h(c.rgba.props,function(r,n){d[n.idx]=t(o[n.idx],n)}),this):"object"===p?(o instanceof l?h(c,function(r,n){o[n.cache]&&(f[n.cache]=o[n.cache].slice())}):h(c,function(n,e){var a=e.cache;h(e.props,function(r,n){if(!f[a]&&e.to){if("alpha"===r||null==o[r])return;f[a]=e.to(f._rgba)}f[a][n.idx]=t(o[r],n,!0)}),f[a]&&r.inArray(null,f[a].slice(0,3))<0&&(f[a][3]=1,e.from&&(f._rgba=e.from(f[a])))}),this):void 0},is:function(r){var n=l(r),t=!0,e=this;return h(c,function(r,o){var a,s=n[o.cache];return s&&(a=e[o.cache]||o.to&&o.to(e._rgba)||[],h(o.props,function(r,n){return null!=s[n.idx]?t=s[n.idx]===a[n.idx]:void 0})),t}),t},_space:function(){var r=[],n=this;return h(c,function(t,e){n[e.cache]&&r.push(t)}),r.pop()},transition:function(r,n){var e=l(r),o=e._space(),a=c[o],s=0===this.alpha()?l("transparent"):this,i=s[a.cache]||a.to(s._rgba),u=i.slice();return e=e[a.cache],h(a.props,function(r,o){var a=o.idx,s=i[a],l=e[a],c=f[o.type]||{};null!==l&&(null===s?u[a]=l:(c.mod&&(l-s>c.mod/2?s+=c.mod:s-l>c.mod/2&&(s-=c.mod)),u[a]=t((l-s)*n+s,o)))}),this[o](u)},blend:function(n){if(1===this._rgba[3])return this;var t=this._rgba.slice(),e=t.pop(),o=l(n)._rgba;return l(r.map(t,function(r,n){return(1-e)*o[n]+e*r}))},toRgbaString:function(){var n="rgba(",t=r.map(this._rgba,function(r,n){return null==r?n>2?1:0:r});return 1===t[3]&&(t.pop(),n="rgb("),n+t.join()+")"},toHslaString:function(){var n="hsla(",t=r.map(this.hsla(),function(r,n){return null==r&&(r=n>2?1:0),n&&3>n&&(r=Math.round(100*r)+"%"),r});return 1===t[3]&&(t.pop(),n="hsl("),n+t.join()+")"},toHexString:function(n){var t=this._rgba.slice(),e=t.pop();return n&&t.push(~~(255*e)),"#"+r.map(t,function(r){return r=(r||0).toString(16),1===r.length?"0"+r:r}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(r){if(null==r[0]||null==r[1]||null==r[2])return[null,null,null,r[3]];var n,t,e=r[0]/255,o=r[1]/255,a=r[2]/255,s=r[3],i=Math.max(e,o,a),u=Math.min(e,o,a),l=i-u,c=i+u,f=.5*c;return n=u===i?0:e===i?60*(o-a)/l+360:o===i?60*(a-e)/l+120:60*(e-o)/l+240,t=0===l?0:.5>=f?l/c:l/(2-c),[Math.round(n)%360,t,f,null==s?1:s]},c.hsla.from=function(r){if(null==r[0]||null==r[1]||null==r[2])return[null,null,null,r[3]];var n=r[0]/360,t=r[1],e=r[2],a=r[3],s=.5>=e?e*(1+t):e+t-e*t,i=2*e-s;return[Math.round(255*o(i,s,n+1/3)),Math.round(255*o(i,s,n)),Math.round(255*o(i,s,n-1/3)),a]},h(c,function(e,o){var a=o.props,s=o.cache,u=o.to,c=o.from;l.fn[e]=function(e){if(u&&!this[s]&&(this[s]=u(this._rgba)),e===n)return this[s].slice();var o,i=r.type(e),f="array"===i||"object"===i?e:arguments,p=this[s].slice();return h(a,function(r,n){var e=f["object"===i?r:n.idx];null==e&&(e=p[n.idx]),p[n.idx]=t(e,n)}),c?(o=l(c(p)),o[s]=p,o):l(p)},h(a,function(n,t){l.fn[n]||(l.fn[n]=function(o){var a,s=r.type(o),u="alpha"===n?this._hsla?"hsla":"rgba":e,l=this[u](),c=l[t.idx];return"undefined"===s?c:("function"===s&&(o=o.call(this,c),s=r.type(o)),null==o&&t.empty?this:("string"===s&&(a=i.exec(o),a&&(o=c+parseFloat(a[2])*("+"===a[1]?1:-1))),l[t.idx]=o,this[u](l)))})})}),l.hook=function(n){var t=n.split(" ");h(t,function(n,t){r.cssHooks[t]={set:function(n,o){var a,s,i="";if("transparent"!==o&&("string"!==r.type(o)||(a=e(o)))){if(o=l(a||o),!p.rgba&&1!==o._rgba[3]){for(s="backgroundColor"===t?n.parentNode:n;(""===i||"transparent"===i)&&s&&s.style;)try{i=r.css(s,"backgroundColor"),s=s.parentNode}catch(u){}o=o.blend(i&&"transparent"!==i?i:"_default")}o=o.toRgbaString()}try{n.style[t]=o}catch(u){}}},r.fx.step[t]=function(n){n.colorInit||(n.start=l(n.elem,t),n.end=l(n.end),n.colorInit=!0),r.cssHooks[t].set(n.elem,n.start.transition(n.end,n.pos))}})},l.hook(s),r.cssHooks.borderColor={expand:function(r){var n={};return h(["Top","Right","Bottom","Left"],function(t,e){n["border"+e+"Color"]=r}),n}},a=r.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(jQuery); + + +/** -------------- SOME GENERAL FUNCTIONS ---------------------*/ + +/** + * ismouseover function - check if the mouse over some object + */ +!function(t){function e(){try{var i=this===document?t(this):t(this).contents();}catch(error){return(false);}i.mousemove(function(e){t.mlp={x:e.pageX,y:e.pageY}}),i.find("iframe").on("load",e)}t.mlp={x:0,y:0},t(e),t.fn.ismouseover=function(){var e=!1;return this.eq(0).each(function(){var i=t(this).is("iframe")?t(this).contents().find("body"):t(this),n=i.offset();e=n.left<=t.mlp.x&&n.left+i.outerWidth()>t.mlp.x&&n.top<=t.mlp.y&&n.top+i.outerHeight()>t.mlp.y}),e}}(jQuery); + + + +function UGThumbsGeneral(){ + + var t = this, g_objThis = jQuery(t); + + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objects, g_objWrapper; + var g_arrItems, g_objStrip, g_objParent; + var g_functions = new UGFunctions(); + var g_strip; + var outer_options; + + this.type = { + GET_THUMBS_ALL: "all", + GET_THUMBS_RATIO: "ratio", + GET_THUMBS_NO_RATIO: "no_ratio", + GET_THUMBS_NEW:"new" + }; + + this.events = { + SETOVERSTYLE: "thumbmouseover", + SETNORMALSTYLE: "thumbmouseout", + SETSELECTEDSTYLE: "thumbsetselected", + + PLACEIMAGE: "thumbplaceimage", + AFTERPLACEIMAGE: "thumb_after_place_image", + IMAGELOADERROR: "thumbimageloaderror", + THUMB_IMAGE_LOADED: "thumb_image_loaded" + }; + + var g_options = { + thumb_width:88, //thumb width + thumb_height:50, //thumb height + thumb_fixed_size: true, //true,false - fixed/dynamic thumbnail width + thumb_resize_by: "height", //set resize by width or height of the image in case of non fixed size, + + thumb_border_effect:true, //true, false - specify if the thumb has border + thumb_border_width: 0, //thumb border width + thumb_border_color: "#000000", //thumb border color + thumb_over_border_width: 0, //thumb border width in mouseover state + thumb_over_border_color: "#d9d9d9", //thumb border color in mouseover state + thumb_selected_border_width: 1, //thumb width in selected state + thumb_selected_border_color: "#d9d9d9", //thumb border color in selected state + + thumb_round_corners_radius:0, //thumb border radius + + thumb_color_overlay_effect: true, //true,false - thumb color overlay effect, release the overlay on mouseover and selected states + thumb_overlay_color: "#000000", //thumb overlay color + thumb_overlay_opacity: 0.4, //thumb overlay color opacity + thumb_overlay_reverse:false, //true,false - reverse the overlay, will be shown on selected state only + + thumb_image_overlay_effect: false, //true,false - images orverlay effect on normal state only + thumb_image_overlay_type: "bw", //bw , blur, sepia - the type of image effect overlay, black and white, sepia and blur. + + thumb_transition_duration: 200, //thumb effect transition duration + thumb_transition_easing: "easeOutQuad", //thumb effect transition easing + + thumb_show_loader: true, //show thumb loader while loading the thumb + thumb_loader_type: "dark", //dark, light - thumb loader type + + thumb_wrapper_as_link: false, //set thumb as link + thumb_link_newpage: false //set the link to open newpage + } + + var g_temp = { + touchEnabled: false, + num_thumbs_checking:0, + customThumbs:false, + funcSetCustomThumbHtml:null, + isEffectBorder: false, + isEffectOverlay: false, + isEffectImage: false, + colorOverlayOpacity: 1, + thumbInnerReduce: 0, + allowOnResize: true, //allow onresize event + classNewThumb: "ug-new-thumb" + }; + + + var g_serviceParams = { //service variables + timeout_thumb_check:100, + thumb_max_check_times:600, //60 seconds + eventSizeChange: "thumb_size_change" + }; + + /** + * init the thumbs object + */ + this.init = function(gallery, customOptions){ + g_objects = gallery.getObjects(); + g_gallery = gallery; + g_objGallery = jQuery(gallery); + g_objWrapper = g_objects.g_objWrapper; + g_arrItems = g_objects.g_arrItems; + + g_options = jQuery.extend(g_options, customOptions); + + //set effects vars: + g_temp.isEffectBorder = g_options.thumb_border_effect; + g_temp.isEffectOverlay = g_options.thumb_color_overlay_effect; + g_temp.isEffectImage = g_options.thumb_image_overlay_effect; + + } + + this._____________EXTERNAL_SETTERS__________ = function(){}; + + + /** + * append html from item + */ + function appendHtmlThumbFromItem(itemIndex, imageEffectClass){ + + var objItem = g_arrItems[itemIndex]; + + var classAddition = ""; + if(g_temp.customThumbs == false) + classAddition = " ug-thumb-generated"; + + var zIndex = objItem.index + 1; + var thumbStyle = "style='z-index:"+zIndex+";'"; + + var htmlThumb = "

      "; + + if(g_options.thumb_wrapper_as_link == true){ + var urlLink = objItem.link; + if(objItem.link == "") + urlLink = "javascript:void(0)"; + + var linkTarget = ""; + if(g_options.thumb_link_newpage == true && objItem.link) + linkTarget = " target='_blank'"; + + var htmlThumb = ""; + } + + var objThumbWrapper = jQuery(htmlThumb); + + var objImage = objItem.objThumbImage; + + if(g_temp.customThumbs == false){ + + if(g_options.thumb_show_loader == true && objImage){ + + var loaderClass = "ug-thumb-loader-dark"; + if(g_options.thumb_loader_type == "bright") + loaderClass = "ug-thumb-loader-bright"; + + objThumbWrapper.append("
      "); + objThumbWrapper.append(""); + } + + if(objImage){ + objImage.addClass("ug-thumb-image"); + + //if image overlay effects active, clone the image, and set the effects class on it + if(g_options.thumb_image_overlay_effect == true){ + var objImageOverlay = objImage.clone().appendTo(objThumbWrapper); + + objImageOverlay.addClass("ug-thumb-image-overlay " + imageEffectClass).removeClass("ug-thumb-image"); + objImageOverlay.fadeTo(0,0); + objItem.objImageOverlay = objImageOverlay; + } + + objThumbWrapper.append(objImage); + } + + }//end if not custom thumb + + if(g_temp.isEffectBorder) + objThumbWrapper.append("
      "); + + if(g_temp.isEffectOverlay) + objThumbWrapper.append("
      "); + + g_objParent.append(objThumbWrapper); + + //only custom thumbs function + if(g_temp.customThumbs){ + + g_temp.funcSetCustomThumbHtml(objThumbWrapper, objItem); + + } + + //add thumb wrapper object to items array + g_arrItems[itemIndex].objThumbWrapper = objThumbWrapper; + + return(objThumbWrapper); + } + + + /** + * append the thumbs html to some parent + */ + this.setHtmlThumbs = function(objParent, isAppend){ + + g_objParent = objParent; + + //set image effect class + if(g_temp.isEffectImage == true){ + var imageEffectClass = getImageEffectsClass(); + } + + if(isAppend !== true){ //set all thumbs + var numItems = g_gallery.getNumItems(); + + //append thumbs to strip + for(var i=0;i= objThumbs.length || index < 0) + throw new Error("Wrong thumb index"); + + var objThumb = jQuery(objThumbs[index]); + + return(objThumb); + } + + + /** + * get all thumbs jquery object + */ + this.getThumbs = function(mode){ + + var thumbClass = ".ug-thumb-wrapper"; + var classRatio = ".ug-thumb-ratio-set"; + + switch(mode){ + default: + case t.type.GET_THUMBS_ALL: + var objThumbs = g_objParent.children(thumbClass) + break; + case t.type.GET_THUMBS_NO_RATIO: + var objThumbs = g_objParent.children(thumbClass).not(classRatio); + break; + case t.type.GET_THUMBS_RATIO: + var objThumbs = g_objParent.children(thumbClass + classRatio); + break; + case t.type.GET_THUMBS_NEW: + var objThumbs = g_objParent.children("."+g_temp.classNewThumb); + break; + } + + return(objThumbs); + } + + + /** + * get item by thumb object + */ + this.getItemByThumb = function(objThumb){ + + var index = objThumb.data("index"); + + if(index === undefined) + index = objThumb.index(); + + var arrItem = g_arrItems[index]; + return(arrItem); + } + + + /** + * is thumb loaded + */ + this.isThumbLoaded = function(objThumb){ + + var objItem = t.getItemByThumb(objThumb); + + return(objItem.isLoaded); + } + + + /** + * get global thumb size + */ + this.getGlobalThumbSize = function(){ + + var objSize = { + width:g_options.thumb_width, + height: g_options.thumb_height + }; + + return(objSize); + } + + + this._____________EXTERNAL_OTHERS__________ = function(){}; + + + /** + * init events + */ + this.initEvents = function(){ + + var selectorThumb = ".ug-thumb-wrapper"; + + //one time event + if(g_temp.allowOnResize == true) + g_objWrapper.on(g_serviceParams.eventSizeChange, onThumbSizeChange); + + //on image loaded event - for setting the image sizes + g_objThis.on(t.events.THUMB_IMAGE_LOADED, onThumbImageLoaded); + + //thumbs events + g_objParent.on("touchstart",selectorThumb,function(){ + g_temp.touchEnabled = true; + g_objParent.off("mouseenter").off("mouseleave"); + }); + + g_objParent.on("mouseenter",selectorThumb,function(event){ + var objThumb = jQuery(this); + onMouseOver(objThumb); + }); + + g_objParent.on("mouseleave",selectorThumb,function(event){ + var objThumb = jQuery(this); + onMouseOut(objThumb); + }); + + + } + + + /** + * destroy the thumb element + */ + this.destroy = function(){ + + var selectorThumb = ".ug-thumb-wrapper"; + + g_objParent.off("touchstart",selectorThumb); + g_objWrapper.off(g_serviceParams.eventSizeChange); + g_objParent.off("mouseenter",selectorThumb); + g_objParent.off("mouseleave",selectorThumb); + g_objThis.off(t.events.THUMB_IMAGE_LOADED); + } + + + /** + * preload thumbs images and put them into the thumbnails + */ + this.loadThumbsImages = function(){ + + var objImages = g_objParent.find(".ug-thumb-image"); + g_functions.checkImagesLoaded(objImages, null, function(objImage,isError){ + + if(isError == false){ + onImageLoaded(objImage, true); + } + else{ + var objItem = jQuery(objImage).parent(); + setItemThumbLoadedError(objItem); + } + }); + } + + + /** + * trigger image loaded event + */ + this.triggerImageLoadedEvent = function(objThumb, objImage){ + + g_objThis.trigger(t.events.THUMB_IMAGE_LOADED, [objThumb, objImage]); + + } + + + /** + * hide thumbs + */ + this.hideThumbs = function(){ + + g_objParent.find(".ug-thumb-wrapper").hide(); + + } + +} + +/** + * thumbs class + * addon to strip gallery + */ +function UGThumbsStrip(){ + + var t = this; + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objects, g_objWrapper; + var g_arrItems, g_objStrip, g_objStripInner; + var g_aviaControl, g_touchThumbsControl, g_functions = new UGFunctions(); + var g_isVertical = false, g_thumbs = new UGThumbsGeneral(); + var g_functions = new UGFunctions(); + + var g_options = { + strip_vertical_type: false, + strip_thumbs_align: "left", //left, center, right, top, middle, bottom - the align of the thumbs when they smaller then the strip size. + strip_space_between_thumbs:6, //space between thumbs + strip_thumb_touch_sensetivity:15, //from 1-100, 1 - most sensetive, 100 - most unsensetive + strip_scroll_to_thumb_duration:500, //duration of scrolling to thumb + strip_scroll_to_thumb_easing:"easeOutCubic", //easing of scrolling to thumb animation + strip_control_avia:true, //avia control - move the strip according strip moseover position + strip_control_touch:true, //touch control - move the strip by dragging it + strip_padding_top: 0, //add some space from the top + strip_padding_bottom: 0, //add some space from the bottom + strip_padding_left: 0, //add some space from left + strip_padding_right: 0 //add some space from right + } + + var g_temp = { + isRunOnce:false, + is_placed:false, + isNotFixedThumbs: false, + handle: null + }; + + var g_sizes = { + stripSize:0, //set after position thumbs + stripActiveSize:0, //strip size without the padding + stripInnerSize:0, + thumbSize:0, + thumbSecondSize:0 //size of the height and width of the strip + } + + this.events = { //events variables + STRIP_MOVE:"stripmove", + INNER_SIZE_CHANGE:"size_change" + } + + //the defaults for vertical align + var g_defaultsVertical = { + strip_thumbs_align: "top", + thumb_resize_by: "width" + } + + + /** + * set the thumbs strip html + */ + this.setHtml = function(objParent){ + + if(!objParent){ + var objParent = g_objWrapper; + if(g_options.parent_container != null) + objParent = g_options.parent_container; + } + + objParent.append("
      "); + g_objStrip = objParent.children(".ug-thumbs-strip"); + + g_objStripInner = g_objStrip.children(".ug-thumbs-strip-inner"); + + //put the thumbs to inner strip + g_thumbs.setHtmlThumbs(g_objStripInner); + + //hide thumbs on not fixed mode + if(g_temp.isNotFixedThumbs == true) + g_thumbs.hideThumbs(); + + } + + + function ___________GENERAL___________(){}; + + + /** + * init the strip + */ + function initStrip(gallery, customOptions){ + + g_objects = gallery.getObjects(); + g_gallery = gallery; + + g_gallery.attachThumbsPanel("strip", t); + + g_objGallery = jQuery(gallery); + g_objWrapper = g_objects.g_objWrapper; + g_arrItems = g_objects.g_arrItems; + + g_options = jQuery.extend(g_options, customOptions); + g_isVertical = g_options.strip_vertical_type; + + //set vertical defaults + if(g_isVertical == true){ + g_options = jQuery.extend(g_options, g_defaultsVertical); + g_options = jQuery.extend(g_options, customOptions); + + customOptions.thumb_resize_by = "width"; + } + + g_thumbs.init(gallery, customOptions); + + onAfterSetOptions(); + } + + + /** + * run this funcion after set options. + */ + function onAfterSetOptions(){ + + var thumbsOptions = g_thumbs.getOptions(); + + g_temp.isNotFixedThumbs = (thumbsOptions.thumb_fixed_size === false); + g_isVertical = g_options.strip_vertical_type; + } + + + /** + * run the strip + */ + function runStrip(){ + + g_thumbs.setHtmlProperties(); + + initSizeParams(); + + fixSize(); + + positionThumbs(); + + //run only once + if(g_temp.isRunOnce == false){ + + //init thumbs strip touch + if(g_options.strip_control_touch == true){ + g_touchThumbsControl = new UGTouchThumbsControl(); + g_touchThumbsControl.init(t); + } + + //init thumbs strip avia control + if(g_options.strip_control_avia == true){ + g_aviaControl = new UGAviaControl(); + g_aviaControl.init(t); + } + + checkControlsEnableDisable(); + + g_thumbs.loadThumbsImages(); + + initEvents(); + } + + + g_temp.isRunOnce = true; + + } + + + /** + * store strip size and strip active size in vars + * do after all strip size change + */ + function storeStripSize(size){ + + g_sizes.stripSize = size; + + if(g_isVertical == false) + g_sizes.stripActiveSize = g_sizes.stripSize - g_options.strip_padding_left - g_options.strip_padding_right; + else + g_sizes.stripActiveSize = g_sizes.stripSize - g_options.strip_padding_top - g_options.strip_padding_bottom; + + if(g_sizes.stripActiveSize < 0) + g_sizes.stripActiveSize = 0; + + } + + + /** + * init some size parameters, before size init and after position thumbs + */ + function initSizeParams(){ + + //set thumb outer size: + var arrThumbs = g_objStripInner.children(".ug-thumb-wrapper"); + var firstThumb = jQuery(arrThumbs[0]); + var thumbsRealWidth = firstThumb.outerWidth(); + var thumbsRealHeight = firstThumb.outerHeight(); + var thumbs_options = g_thumbs.getOptions(); + + if(g_isVertical == false){ //horizontal + + g_sizes.thumbSize = thumbsRealWidth; + + if(thumbs_options.thumb_fixed_size == true){ + g_sizes.thumbSecondSize = thumbsRealHeight; + } else { + g_sizes.thumbSecondSize = thumbs_options.thumb_height; + } + + storeStripSize(g_objStrip.width()); + g_sizes.stripInnerSize = g_objStripInner.width(); + + }else{ //vertical + g_sizes.thumbSize = thumbsRealHeight; + + if(thumbs_options.thumb_fixed_size == true){ + g_sizes.thumbSecondSize = thumbsRealWidth; + } else { + g_sizes.thumbSecondSize = thumbs_options.thumb_width; + } + + storeStripSize(g_objStrip.height()); + + g_sizes.stripInnerSize = g_objStripInner.height(); + } + + + } + + + + + /** + * set size of inner strip according the orientation + */ + function setInnerStripSize(innerSize){ + + if(g_isVertical == false) + g_objStripInner.width(innerSize); + else + g_objStripInner.height(innerSize); + + g_sizes.stripInnerSize = innerSize; + + checkControlsEnableDisable(); + + jQuery(t).trigger(t.events.INNER_SIZE_CHANGE); + } + + + /** + * position thumbnails in the thumbs panel + */ + function positionThumbs(){ + + var arrThumbs = g_objStripInner.children(".ug-thumb-wrapper"); + + var posx = 0; + var posy = 0; + + if(g_isVertical == false) + posy = g_options.strip_padding_top; + + for (i = 0; i < arrThumbs.length; i++) { + + var objThumb = jQuery(arrThumbs[i]); + + //skip from placing if not loaded yet on non fixed mode + if(g_temp.isNotFixedThumbs == true){ + objItem = g_thumbs.getItemByThumb(objThumb); + if(objItem.isLoaded == false) + continue; + + //the thumb is hidden if not placed + objThumb.show(); + } + + g_functions.placeElement(objThumb, posx, posy); + + if(g_isVertical == false) + posx += objThumb.outerWidth() + g_options.strip_space_between_thumbs; + else + posy += objThumb.outerHeight() + g_options.strip_space_between_thumbs; + } + + //set strip size, width or height + if(g_isVertical == false) + var innerStripSize = posx - g_options.strip_space_between_thumbs; + else + var innerStripSize = posy - g_options.strip_space_between_thumbs; + + setInnerStripSize(innerStripSize); + } + + + + /** + * fix strip and inner size + */ + function fixSize(){ + + if(g_isVertical == false){ //fix horizontal + + var height = g_sizes.thumbSecondSize; + + var objCssStrip = {}; + objCssStrip["height"] = height+"px"; + + //set inner strip params + var objCssInner = {}; + objCssInner["height"] = height+"px"; + + }else{ //fix vertical + + var width = g_sizes.thumbSecondSize; + + var objCssStrip = {}; + objCssStrip["width"] = width+"px"; + + //set inner strip params + var objCssInner = {}; + objCssInner["width"] = width+"px"; + + } + + g_objStrip.css(objCssStrip); + g_objStripInner.css(objCssInner); + } + + + + /** + * scroll by some number + * . + */ + function scrollBy(scrollStep){ + + var innerPos = t.getInnerStripPos(); + var finalPos = innerPos + scrollStep; + + finalPos = t.fixInnerStripLimits(finalPos); + + t.positionInnerStrip(finalPos, true); + } + + + /** + * scroll to thumb from min. (left or top) position + */ + function scrollToThumbMin(objThumb){ + + var objThumbPos = getThumbPos(objThumb); + + var scrollPos = objThumbPos.min * -1; + scrollPos = t.fixInnerStripLimits(scrollPos); + + t.positionInnerStrip(scrollPos, true); + } + + + /** + * scroll to thumb from max. (right or bottom) position + */ + function scrollToThumbMax(objThumb){ + + var objThumbPos = getThumbPos(objThumb); + + var scrollPos = objThumbPos.max * -1 + g_sizes.stripSize; + scrollPos = t.fixInnerStripLimits(scrollPos); + + t.positionInnerStrip(scrollPos, true); + } + + + /** + * scroll to some thumbnail + */ + function scrollToThumb(objThumb){ + + if(isStripMovingEnabled() == false) + return(false); + + var objBounds = getThumbsInsideBounds(); + + var objThumbPos = getThumbPos(objThumb); + + if(objThumbPos.min < objBounds.minPosThumbs){ + + var prevThumb = objThumb.prev(); + if(prevThumb.length) + scrollToThumbMin(prevThumb); + else + scrollToThumbMin(objThumb); + + }else if(objThumbPos.max > objBounds.maxPosThumbs){ + + var nextThumb = objThumb.next(); + if(nextThumb.length) + scrollToThumbMax(nextThumb); + else + scrollToThumbMax(objThumb); + + } + + } + + /** + * scroll to selected thumb + */ + function scrollToSelectedThumb(){ + + var selectedItem = g_gallery.getSelectedItem(); + if(selectedItem == null) + return(true); + + var objThumb = selectedItem.objThumbWrapper; + if(objThumb) + scrollToThumb(objThumb); + + } + + + + /** + * check that the inner strip off the limits position, and reposition it if there is a need + */ + function checkAndRepositionInnerStrip(){ + if(isStripMovingEnabled() == false) + return(false); + + var pos = t.getInnerStripPos(); + + var posFixed = t.fixInnerStripLimits(pos); + + if(pos != posFixed) + t.positionInnerStrip(posFixed, true); + } + + + /** + * enable / disable controls according inner width (move enabled). + */ + function checkControlsEnableDisable(){ + + var isMovingEndbled = isStripMovingEnabled(); + + if(isMovingEndbled == true){ + + if(g_aviaControl) + g_aviaControl.enable(); + + if(g_touchThumbsControl) + g_touchThumbsControl.enable(); + + }else{ + + if(g_aviaControl) + g_aviaControl.disable(); + + if(g_touchThumbsControl) + g_touchThumbsControl.disable(); + + } + + } + + /** + * align inner strip according the options + */ + function alignInnerStrip(){ + + if(isStripMovingEnabled()) + return(false); + + if(g_isVertical == false) + g_functions.placeElement(g_objStripInner, g_options.strip_thumbs_align, 0); + else + g_functions.placeElement(g_objStripInner, 0, g_options.strip_thumbs_align); + + } + + + function ___________EVENTS___________(){}; + + /** + * on thumb click event. Select the thumb + */ + function onThumbClick(objThumb){ + + //cancel click event only if passed significant movement + if(t.isTouchMotionActive()){ + + var isSignificantPassed = g_touchThumbsControl.isSignificantPassed(); + if(isSignificantPassed == true) + return(true); + } + + //run select item operation + var objItem = g_thumbs.getItemByThumb(objThumb); + + g_gallery.selectItem(objItem); + } + + + /** + * on some thumb placed, run the resize, but with time passed + */ + function onThumbPlaced(){ + + clearTimeout(g_temp.handle); + + g_temp.handle = setTimeout(function(){ + + positionThumbs(); + + },50); + + + } + + /** + * on item change + */ + function onItemChange(){ + + var objItem = g_gallery.getSelectedItem(); + g_thumbs.setThumbSelected(objItem.objThumbWrapper); + scrollToThumb(objItem.objThumbWrapper); + } + + + /** + * init panel events + */ + function initEvents(){ + + g_thumbs.initEvents(); + + var objThumbs = g_objStrip.find(".ug-thumb-wrapper"); + + objThumbs.on("click touchend", function(event){ + + var clickedThumb = jQuery(this); + onThumbClick(clickedThumb); + }); + + //on item change, make the thumb selected + g_objGallery.on(g_gallery.events.ITEM_CHANGE, onItemChange); + + + //position thumbs after each load on non fixed mode + if(g_temp.isNotFixedThumbs){ + + jQuery(g_thumbs).on(g_thumbs.events.AFTERPLACEIMAGE, onThumbPlaced); + + } + + } + + + /** + * destroy the strip panel events + */ + this.destroy = function(){ + + var objThumbs = g_objStrip.find(".ug-thumb-wrapper"); + + objThumbs.off("click"); + objThumbs.off("touchend"); + g_objGallery.off(g_gallery.events.ITEM_CHANGE); + + jQuery(g_thumbs).off(g_thumbs.events.AFTERPLACEIMAGE); + + if(g_touchThumbsControl) + g_touchThumbsControl.destroy(); + + if(g_aviaControl) + g_aviaControl.destroy(); + + g_thumbs.destroy(); + } + + + function ____________GETTERS___________(){}; + + + /** + * check if the inner width is more then strip width + */ + function isStripMovingEnabled(){ + + if(g_sizes.stripInnerSize > g_sizes.stripActiveSize) + return(true); + else + return(false); + + } + + + /** + * get bounds, if the thumb not in them, it need to be scrolled + * minPosThumbs, maxPosThumbs - the min and max position that the thumbs should be located to be visible + */ + function getThumbsInsideBounds(){ + var obj = {}; + var innerPos = t.getInnerStripPos(); + + //the 1 is gap that avoid exact bounds + obj.minPosThumbs = innerPos * -1 + 1; + obj.maxPosThumbs = innerPos * -1 + g_sizes.stripSize - 1; + + return(obj); + } + + + /** + * get thumb position according the orientation in the inner strip + */ + function getThumbPos(objThumb){ + + var objReturn = {}; + + var objPos = objThumb.position(); + + if(g_isVertical == false){ + objReturn.min = objPos.left; + objReturn.max = objPos.left + g_sizes.thumbSize; + }else{ + objReturn.min = objPos.top; + objReturn.max = objPos.top + g_sizes.thumbSize; + } + + + return(objReturn); + } + + + + + this.________EXTERNAL_GENERAL___________ = function(){}; + + /** + * init function for avia controls + */ + this.init = function(gallery, customOptions){ + + initStrip(gallery, customOptions); + } + + + /** + * set html and properties + */ + this.run = function(){ + runStrip(); + } + + + + + /** + * position inner strip on some pos according the orientation + */ + this.positionInnerStrip = function(pos, isAnimate){ + + if(isAnimate === undefined) + var isAnimate = false; + + if(g_isVertical == false) + var objPosition = {"left": pos + "px"}; + else + var objPosition = {"top": pos + "px"}; + + if(isAnimate == false){ //normal position + + g_objStripInner.css(objPosition); + t.triggerStripMoveEvent(); + } + else{ //position with animation + + t.triggerStripMoveEvent(); + + g_objStripInner.stop(true).animate(objPosition ,{ + duration: g_options.strip_scroll_to_thumb_duration, + easing: g_options.strip_scroll_to_thumb_easing, + queue: false, + progress:function(){t.triggerStripMoveEvent()}, + always: function(){t.triggerStripMoveEvent()} + }); + + } + + } + + /** + * trigger event - on strip move + */ + this.triggerStripMoveEvent = function(){ + + //trigger onstripmove event + jQuery(t).trigger(t.events.STRIP_MOVE); + + } + + + + /** + * return true if the touch animation or dragging is active + */ + this.isTouchMotionActive = function(){ + if(!g_touchThumbsControl) + return(false); + + var isActive = g_touchThumbsControl.isTouchActive(); + + return(isActive); + } + + + /** + * check if thmb item visible, means inside the visible part of the inner strip + */ + this.isItemThumbVisible = function(objItem){ + + var objThumb = objItem.objThumbWrapper; + var thumbPos = objThumb.position(); + + var posMin = t.getInnerStripPos() * -1; + + if(g_isVertical == false){ + var posMax = posMin + g_sizes.stripSize; + var thumbPosMin = thumbPos.left; + var thumbPosMax = thumbPos.left + objThumb.width(); + }else{ + var posMax = posMin + g_sizes.stripSize; + var thumbPosMin = thumbPos.top; + var thumbPosMax = thumbPos.top + objThumb.height(); + } + + var isVisible = false; + if(thumbPosMax >= posMin && thumbPosMin <= posMax) + isVisible = true; + + return(isVisible); + } + + /** + * get inner strip position according the orientation + */ + this.getInnerStripPos = function(){ + + if(g_isVertical == false) + return g_objStripInner.position().left; + else + return g_objStripInner.position().top; + } + + + /** + * get inner strip limits + */ + this.getInnerStripLimits = function(){ + + var output = {}; + + if(g_isVertical == false) + output.maxPos = g_options.strip_padding_left; + else + output.maxPos = g_options.strip_padding_top; + + //debugLine(g_sizes.stripActiveSize); + + output.minPos = -(g_sizes.stripInnerSize - g_sizes.stripActiveSize); + + return(output); + } + + + /** + * fix inner position by check boundaries limit + */ + this.fixInnerStripLimits = function(distPos){ + + var minPos; + + var objLimits = t.getInnerStripLimits(); + + if(distPos > objLimits.maxPos) + distPos = objLimits.maxPos; + + if(distPos < objLimits.minPos) + distPos = objLimits.minPos; + + return(distPos); + } + + + + /** + * scroll left or down + */ + this.scrollForeward = function(){ + scrollBy(-g_sizes.stripSize); + } + + + /** + * scroll left or down + */ + this.scrollBack = function(){ + + scrollBy(g_sizes.stripSize); + } + + + this.________EXTERNAL_SETTERS___________ = function(){}; + + + /** + * set the options of the strip + */ + this.setOptions = function(objOptions){ + + g_options = jQuery.extend(g_options, objOptions); + + g_thumbs.setOptions(objOptions); + + onAfterSetOptions(); + } + + + /** + * set size of the strip + * the height size is set automatically from options + */ + this.setSizeVertical = function(height){ + + if(g_isVertical == false){ + throw new Error("setSizeVertical error, the strip size is not vertical"); + return(false); + } + + var width = g_sizes.thumbSecondSize; + + var objCssStrip = {}; + objCssStrip["width"] = width+"px"; + objCssStrip["height"] = height+"px"; + + g_objStrip.css(objCssStrip); + + storeStripSize(height); + + //set inner strip params + var objCssInner = {}; + objCssInner["width"] = width+"px"; + objCssInner["left"] = "0px"; + objCssInner["top"] = "0px"; + + g_objStripInner.css(objCssInner); + + g_temp.is_placed = true; + + checkControlsEnableDisable(); + } + + + /** + * set size of the strip + * the height size is set automatically from options + */ + this.setSizeHorizontal = function(width){ + + if(g_isVertical == true){ + throw new Error("setSizeHorizontal error, the strip size is not horizontal"); + return(false); + } + + var height = g_sizes.thumbSecondSize + g_options.strip_padding_top + g_options.strip_padding_bottom; + + var objCssStrip = {}; + objCssStrip["width"] = width+"px"; + objCssStrip["height"] = height+"px"; + + g_objStrip.css(objCssStrip); + + storeStripSize(width); + + var innerLeft = g_options.strip_padding_left; + + //set inner strip params + var objCssInner = {}; + objCssInner["height"] = height+"px"; + objCssInner["left"] = innerLeft + "px"; + objCssInner["top"] = "0px"; + + g_objStripInner.css(objCssInner); + + g_temp.is_placed = true; + + checkControlsEnableDisable(); + } + + + /** + * set position of the strip + */ + this.setPosition = function(left, top, offsetLeft, offsetTop){ + g_functions.placeElement(g_objStrip, left, top, offsetLeft, offsetTop); + } + + + /** + * resize the panel according the orientation + */ + this.resize = function(newSize){ + + if(g_isVertical == false){ + + g_objStrip.width(newSize); + g_sizes.stripActiveSize = newSize - g_options.strip_padding_left - g_options.strip_padding_right; + + }else{ + g_objStrip.height(newSize); + g_sizes.stripActiveSize = newSize - g_options.strip_padding_top - g_options.strip_padding_bottom; + } + + storeStripSize(newSize); + + checkControlsEnableDisable(); + + checkAndRepositionInnerStrip(); + + alignInnerStrip(); + + scrollToSelectedThumb(); + } + + + /** + * set the thumb unselected state + */ + this.setThumbUnselected = function(objThumbWrapper){ + + g_thumbs.setThumbUnselected(objThumbWrapper); + + } + + + /** + * set custom thumbs + */ + this.setCustomThumbs = function(funcSetHtml){ + + g_thumbs.setCustomThumbs(funcSetHtml); + + } + + + + + this.________EXTERNAL_GETTERS___________ = function(){}; + + /** + * get objects + */ + this.getObjects = function(){ + + var thumbsOptions = g_thumbs.getOptions(); + var commonOpitions = jQuery.extend(g_options, thumbsOptions); + + var obj = { + g_gallery: g_gallery, + g_objGallery: g_objGallery, + g_objWrapper:g_objWrapper, + g_arrItems:g_arrItems, + g_objStrip : g_objStrip, + g_objStripInner : g_objStripInner, + g_aviaControl:g_aviaControl, + g_touchThumbsControl:g_touchThumbsControl, + isVertical: g_isVertical, + g_options: commonOpitions, + g_thumbs: g_thumbs + }; + + return(obj); + } + + + /** + * get thumbs onject + */ + this.getObjThumbs = function(){ + + return(g_thumbs); + } + + + /** + * get selected thumb + */ + this.getSelectedThumb = function(){ + + var selectedIndex = g_gallery.getSelectedItemIndex(); + if(selectedIndex == -1) + return(null); + + return g_thumbs.getThumbByIndex(selectedIndex); + } + + + /** + * get strip size and position object + */ + this.getSizeAndPosition = function(){ + + var obj = g_functions.getElementSize(g_objStrip); + + return(obj); + } + + /** + * get thumbs strip height + */ + this.getHeight = function(){ + + var stripHeight = g_objStrip.outerHeight(); + + return(stripHeight) + } + + + /** + * get thumbs strip width + */ + this.getWidth = function(){ + + var stripWidth = g_objStrip.outerWidth(); + + return(stripWidth); + } + + + + /** + * get all stored sizes object + */ + this.getSizes = function(){ + + return(g_sizes); + } + + + /** + * return if vertical orientation or not + */ + this.isVertical = function(){ + return(g_isVertical); + } + + + /** + * return if the strip is placed or not + */ + this.isPlaced = function(){ + + return(g_temp.is_placed); + } + + /** + * return if the strip moving enabled or not + */ + this.isMoveEnabled = function(){ + var isEnabled = isStripMovingEnabled(); + return(isEnabled); + } + +} + + +/** + * touch thumbs control class + * addon to strip gallery + */ +function UGTouchThumbsControl(){ + + var g_parent, g_gallery, g_objGallery, g_objects; + var g_objStrip, g_objStripInner, g_options, g_isVertical; + var g_functions = new UGFunctions(); + + //service variables + var g_serviceParams = { + touch_portion_time:200, //the time in ms that the potion is counts for continue touch movement + thumb_touch_slowFactor:0, //set from user + minDeltaTime: 70, //don't alow portion less then the minTime + minPath: 10, //if less then this path, dont' continue motion + limitsBreakAddition: 30, //the limits break addition for second return animation + returnAnimateSpeed: 500, //the speed of return animation + animationEasing: "easeOutCubic", //animation easing function + returnAnimationEasing: "easeOutCubic" //return animation easing function + }; + + + var g_temp = { //temp variables + touch_active:false, + loop_active:false, + mousePos:0, + innerPos:0, + startPos:0, + startTime:0, + lastTime:0, + buttonReleaseTime:0, + lastPos:0, + lastPortionPos:0, + lastDeltaTime:0, + lastDeltaPos:0, + speed:0, + handle:"", + touchEnabled: false, + isControlEnabled: true + }; + + + /** + * enable the control + */ + this.enable = function(){ + g_temp.isControlEnabled = true; + } + + + /** + * disable the control + */ + this.disable = function(){ + g_temp.isControlEnabled = false; + } + + /** + * init function for avia controls + */ + this.init = function(objStrip){ + + g_parent = objStrip; + g_objects = objStrip.getObjects(); + + g_gallery = g_objects.g_gallery; + g_objGallery = g_objects.g_objGallery; //jquery object + + g_objStrip = g_objects.g_objStrip; + g_objStripInner = g_objects.g_objStripInner; + g_options = g_objects.g_options; + g_isVertical = g_objects.isVertical; + + setServiceParams(); + + initEvents(); + } + + /** + * get action related variables + */ + function getActionVars(){ + + var currentTime = jQuery.now(); + + var obj = {}; + obj.passedTime = g_temp.lastTime - g_temp.startTime; + obj.lastActiveTime = currentTime - g_temp.buttonReleaseTime; + obj.passedDistance = g_temp.lastPos - g_temp.startPos; + obj.passedDistanceAbs = Math.abs(obj.passedDistance); + + return(obj); + } + + /** + * return if passed some significant movement + */ + this.isSignificantPassed = function(){ + var objVars = getActionVars(); + if(objVars.passedTime > 300) + return(true); + + if(objVars.passedDistanceAbs > 30) + return(true); + + return(false); + } + + + /** + * return true if the touch dragging or animate motion is active + */ + this.isTouchActive = function(){ + + if(g_temp.touch_active == true) + return(true); + + //check if still animating + if(g_objStripInner.is(":animated") == true) + return(true); + + //check if just ended, the touch active continue for a few moments. + var objVars = getActionVars(); + if(objVars.lastActiveTime < 50) + return(true); + + return(false); + } + + /** + * set service parameters from user parameters + */ + function setServiceParams(){ + + //set slow factor by sensetivity of touch motion + g_serviceParams.thumb_touch_slowFactor = g_functions.normalizeSetting(0.00005, 0.01, 1, 100, g_options.strip_thumb_touch_sensetivity, true); + + //debugLine("slowfactor "+ g_serviceParams.thumb_touch_slowFactor); + } + + + /** + * get mouse position based on orientation + */ + function getMouseOrientPosition(event){ + + if(g_isVertical == false) + return(g_functions.getMousePosition(event).pageX); + else + return(g_functions.getMousePosition(event).pageY); + } + + /** + * position the strip according the touch drag diff + */ + function handleStripDrag(mousePos){ + var diff = g_temp.mousePos - mousePos; + var distPos = g_temp.innerPos - diff; + + //make harder to drag the limits + var objLimits = g_parent.getInnerStripLimits(); + + if(distPos > objLimits.maxPos){ + var path = distPos - objLimits.maxPos; + distPos = objLimits.maxPos + path/3; + } + + if(distPos < objLimits.minPos){ + var path = objLimits.minPos - distPos; + distPos = objLimits.minPos - path/3; + } + + g_parent.positionInnerStrip(distPos); + } + + + /** + * store initial touch values + */ + function storeInitTouchValues(pagePos){ + var currentInnerPos = g_parent.getInnerStripPos(); + + //remember current mouse position and inner strip position + g_temp.mousePos = pagePos; + g_temp.innerPos = currentInnerPos; + g_temp.lastPortionPos = currentInnerPos; + g_temp.lastDeltaTime = 0; + g_temp.lastDeltaPos = 0; + + //init position and time related variables + g_temp.startTime = jQuery.now(); + g_temp.startPos = g_temp.innerPos; + + g_temp.lastTime = g_temp.startTime; + g_temp.lastPos = g_temp.startPos; + g_temp.speed = 0; + } + + + /** + * store touch portion data + */ + function storeTouchPortionData(){ + + //calc speed + var currentTime = jQuery.now(); + var deltaTime = currentTime - g_temp.lastTime; + + if(deltaTime >= g_serviceParams.touch_portion_time){ + g_temp.lastDeltaTime = currentTime - g_temp.lastTime; + if(g_temp.lastDeltaTime > g_serviceParams.touch_portion_time) + g_temp.lastDeltaTime = g_serviceParams.touch_portion_time; + + g_temp.lastDeltaPos = g_temp.lastPos - g_temp.lastPortionPos; + + g_temp.lastPortionPos = g_temp.lastPos; + g_temp.lastTime = currentTime; + + } + + } + + + /** + * continue touch motion - touch motion ending. + */ + function continueTouchMotion(){ + + var slowFactor = g_serviceParams.thumb_touch_slowFactor; + + //don't alow portion less then the minTime + var minDeltaTime = g_serviceParams.minDeltaTime; + + //if less then this path, dont' continue motion + var minPath = g_serviceParams.minPath; + + var currentInnerPos = g_parent.getInnerStripPos(); + + var currentTime = jQuery.now(); + var deltaTime = currentTime - g_temp.lastTime; + var deltaPos = currentInnerPos - g_temp.lastPortionPos; + + //if time too fast, take last portion values + if(deltaTime < minDeltaTime && g_temp.lastDeltaTime > 0){ + deltaTime = g_temp.lastDeltaTime; + deltaPos = g_temp.lastDeltaPos + deltaPos; + } + + //fix delta time + if(deltaTime < minDeltaTime) + deltaTime = minDeltaTime; + + var dir = (deltaPos > 0)?1:-1; + + var speed = 0; + if(deltaTime > 0) + speed = deltaPos / deltaTime; + + var path = (speed * speed) / (slowFactor * 2) * dir; + + //disable path for very slow motions + if(Math.abs(path) <= minPath) + path = 0; + + + var innerStripPos = g_parent.getInnerStripPos(); + var newPos = innerStripPos + path; + + var correctPos = g_parent.fixInnerStripLimits(newPos); + var objLimits = g_parent.getInnerStripLimits(); + + //check the off the limits and return (second animation) + var limitsBreakAddition = g_serviceParams.limitsBreakAddition; + var doQueue = false; + var returnPos = correctPos; + + //fix the first animation position (off the limits) + if(newPos > objLimits.maxPos){ + doQueue = true; + correctPos = limitsBreakAddition; + if(newPos < limitsBreakAddition) + correctPos = newPos; + } + + if(newPos < objLimits.minPos){ + doQueue = true; + var maxStopPos = objLimits.minPos - limitsBreakAddition; + correctPos = maxStopPos; + if(newPos > maxStopPos) + correctPos = newPos; + } + + var correctPath = correctPos - innerStripPos; + + //set animation speed + var animateSpeed = Math.abs(Math.round(speed / slowFactor)); + + //fix animation speed according the paths difference + if(path != 0) + animateSpeed = animateSpeed * correctPath / path; + + + //Do first animation + if(innerStripPos != correctPos){ + + var animateProps = {"left":correctPos+"px"}; + if(g_isVertical == true) + animateProps = {"top":correctPos+"px"}; + + g_objStripInner.animate(animateProps ,{ + duration: animateSpeed, + easing: g_serviceParams.animationEasing, + queue: true, + progress:onAnimateProgress + }); + + } + + + //do second animation if off limits + if(doQueue == true){ + var returnAnimateSpeed = g_serviceParams.returnAnimateSpeed; + + var returnAnimateProps = {"left":returnPos+"px"}; + if(g_isVertical == true) + returnAnimateProps = {"top":returnPos+"px"}; + + + g_objStripInner.animate(returnAnimateProps,{ + duration: returnAnimateSpeed, + easing: g_serviceParams.returnAnimationEasing, + queue: true, + progress:onAnimateProgress + }); + } + + } + + /** + * on animate progress event. store position and trigger event to gallery + */ + function onAnimateProgress(){ + g_temp.lastPos = g_parent.getInnerStripPos(); + g_parent.triggerStripMoveEvent(); + } + + /** + * start loop while touching strip + */ + function startStripTouchLoop(){ + + if(g_temp.loop_active == true) + return(true); + + g_temp.loop_active = true; + g_temp.handle = setInterval(storeTouchPortionData,10); + } + + + /** + * end loop when not touching + */ + function endStripTouchLoop(event){ + + if(g_temp.loop_active == false) + return(true); + + if(event){ + var pagePos = getMouseOrientPosition(event); + continueTouchMotion(pagePos); + } + + g_temp.loop_active = false; + g_temp.handle = clearInterval(g_temp.handle); + } + + + /** + * on tuch end event + */ + function onTouchEnd(event){ + + if(g_temp.isControlEnabled == false) + return(true); + + g_temp.buttonReleaseTime = jQuery.now(); + + if(g_temp.touch_active == false){ + endStripTouchLoop(event); + return(true); + } + + event.preventDefault(); + + g_temp.touch_active = false; + + endStripTouchLoop(event); + + g_objStrip.removeClass("ug-dragging"); + + } + + + /** + * on touch start - start the motion + * @param event + */ + function onTouchStart(event){ + + if(g_temp.isControlEnabled == false) + return(true); + + event.preventDefault(); + + g_temp.touch_active = true; //don't move this up + + var pagePos = getMouseOrientPosition(event); + + //stop inner animation if exist + g_objStripInner.stop(true); + + //store initial touch values + storeInitTouchValues(pagePos); + startStripTouchLoop(); + + g_objStrip.addClass("ug-dragging"); + } + + + /** + * on touch move event, move the strip + */ + function onTouchMove(event){ + + if(g_temp.isControlEnabled == false) + return(true); + + if(g_temp.touch_active == false) + return(true); + + event.preventDefault(); + + //detect moving without button press + if(event.buttons == 0){ + g_temp.touch_active = false; + + endStripTouchLoop(event); + return(true); + } + + //store current position + var pagePos = getMouseOrientPosition(event); + g_temp.lastPos = g_parent.getInnerStripPos(); + + handleStripDrag(pagePos); + + storeTouchPortionData(); + + } + + + /** + * init strip touch events + */ + function initEvents(){ + + //strip mouse down - drag start + g_objStrip.bind("mousedown touchstart",onTouchStart); + + + //on body mouse up - drag end + jQuery(window).add("body").bind("mouseup touchend",onTouchEnd); + + //on body move + jQuery("body").bind("mousemove touchmove", onTouchMove); + + } + + + /** + * destroy the touch events + */ + this.destroy = function(){ + g_objStrip.unbind("mousedown"); + g_objStrip.unbind("touchstart"); + jQuery(window).add("body").unbind("mouseup").unbind("touchend"); + jQuery("body").unbind("mousemove").unbind("touchmove"); + } +} + + +/** -------------- Panel Base Functions ---------------------*/ + +function UGPanelsBase(){ + + var g_temp, g_panel, g_objPanel, g_options, g_objThis; + var g_gallery = new UniteGalleryMain(); + var t = this, g_objHandle, g_objGallery; + var g_functions = new UGFunctions(); + + + /** + * init the base panel + */ + this.init = function(gallery, g_tempArg, g_panelArg, options, g_objThisArg){ + g_temp = g_tempArg; + g_panel = g_panelArg; + g_gallery = gallery; + g_options = options; + g_objThis = g_objThisArg; + + g_objGallery = jQuery(g_gallery); + } + + /** + * set common panels html + */ + this.setHtml = function(g_objPanelArg){ + + g_objPanel = g_objPanelArg; + + if(g_temp.panelType == "strip") + var enable_handle = g_options.strippanel_enable_handle; + else + var enable_handle = g_options.gridpanel_enable_handle; + + // add handle + if (enable_handle == true) { + g_objHandle = new UGPanelHandle(); + g_objHandle.init(g_panel, g_objPanel, g_options, g_temp.panelType, g_gallery); + g_objHandle.setHtml(); + } + + + //set disabled at start if exists + if(g_temp.isDisabledAtStart === true){ + var html = "
      "; + g_objPanel.append(html); + + setTimeout(function(){ + g_objPanel.children(".ug-overlay-disabled").hide(); + }, g_temp.disabledAtStartTimeout); + + } + + } + + + /** + * place common elements + */ + this.placeElements = function(){ + + // place handle + if (g_objHandle) + g_objHandle.placeHandle(); + } + + + /** + * init common events + */ + this.initEvents = function(){ + + // set handle events + if (g_objHandle){ + g_objHandle.initEvents(); + + //set on slider action events + g_objGallery.on(g_gallery.events.SLIDER_ACTION_START, function(){ + g_objHandle.hideHandle(); + }); + + g_objGallery.on(g_gallery.events.SLIDER_ACTION_END, function(){ + g_objHandle.showHandle(); + }); + + } + + } + + /** + * destroy the panel events + */ + this.destroy = function(){ + + if(g_objHandle){ + g_objHandle.destroy(); + g_objGallery.off(g_gallery.events.SLIDER_ACTION_START); + g_objGallery.off(g_gallery.events.SLIDER_ACTION_END); + } + + } + + /** + * place panel with some animation + */ + function placePanelAnimation(panelDest, functionOnComplete) { + + switch (g_temp.orientation) { + case "right": // vertical + case "left": + var objCss = { + left : panelDest + "px" + }; + break; + case "top": + case "bottom": + var objCss = { + top : panelDest + "px" + }; + break; + } + + g_objPanel.stop(true).animate(objCss, { + duration : 300, + easing : "easeInOutQuad", + queue : false, + complete : function() { + if (functionOnComplete) + functionOnComplete(); + } + }); + + } + + + /** + * place the panel without animation + * + * @param panelDest + */ + function placePanelNoAnimation(panelDest) { + + switch (g_temp.orientation) { + case "right": // vertical + case "left": + g_functions.placeElement(g_objPanel, panelDest, null); + break; + case "top": + case "bottom": + g_functions.placeElement(g_objPanel, null, panelDest); + break; + } + } + + /** + * event on panel slide finish + */ + function onPanelSlideFinish() { + + g_objThis.trigger(g_panel.events.FINISH_MOVE); + + } + + + /** + * open the panel + */ + this.openPanel = function(noAnimation) { + + if (!noAnimation) + var noAnimation = false; + + if (g_objPanel.is(":animated")) + return (false); + + if (g_temp.isClosed == false) + return (false); + + g_temp.isClosed = false; + + g_objThis.trigger(g_panel.events.OPEN_PANEL); + + if (noAnimation === false) + placePanelAnimation(g_temp.originalPos, onPanelSlideFinish); + else { + + placePanelNoAnimation(g_temp.originalPos); + onPanelSlideFinish(); + } + + } + + + /** + * close the panel (slide in) + */ + this.closePanel = function(noAnimation) { + + if (!noAnimation) + var noAnimation = false; + + if (g_objPanel.is(":animated")) + return (false); + + if (g_temp.isClosed == true) + return (false); + + var panelDest = t.getClosedPanelDest(); + + g_temp.isClosed = true; + + g_objThis.trigger(g_panel.events.CLOSE_PANEL); + + if (noAnimation === false) + placePanelAnimation(panelDest, onPanelSlideFinish); + else { + placePanelNoAnimation(panelDest); + onPanelSlideFinish(); + } + + } + + /** + * set the panel that it's in closed state, and set original pos for opening + * later + */ + this.setClosedState = function(originalPos) { + + g_temp.originalPos = originalPos; + g_objThis.trigger(g_panel.events.CLOSE_PANEL); + + g_temp.isClosed = true; + } + + /** + * set the panel opened state + */ + this.setOpenedState = function(originalPos) { + g_objThis.trigger(g_panel.events.OPEN_PANEL); + + g_temp.isClosed = false; + } + + /** + * get closed panel destanation + */ + this.getClosedPanelDest = function() { + + var objPanelSize = g_functions.getElementSize(g_objPanel), panelDest; + + switch (g_temp.orientation) { + case "left": + g_temp.originalPos = objPanelSize.left; + panelDest = -g_temp.panelWidth; + break; + case "right": + g_temp.originalPos = objPanelSize.left; + var gallerySize = g_gallery.getSize(); + panelDest = gallerySize.width; + break; + case "top": + g_temp.originalPos = objPanelSize.top; + panelDest = -g_temp.panelHeight; + break; + case "bottom": + g_temp.originalPos = objPanelSize.top; + var gallerySize = g_gallery.getSize(); + panelDest = gallerySize.height; + break; + } + + return (panelDest); + } + + + /** + * tells if the panel is closed + */ + this.isPanelClosed = function() { + + return (g_temp.isClosed); + } + + + /** + * set the panel disabled at start, called after init before setHtml + * it's enabled again after timeout end + */ + this.setDisabledAtStart = function(timeout){ + + if(timeout <= 0) + return(false); + + g_temp.isDisabledAtStart = true; + g_temp.disabledAtStartTimeout = timeout; + + } + + +} + + +/** -------------- Panel Handle object ---------------------*/ + +function UGPanelHandle(){ + + var t = this, g_objPanel, g_panel, g_objHandleTip, g_panelOptions = {}; + var g_functions = new UGFunctions(); + + var g_options = { + panel_handle_align: "top", //top, middle, bottom , left, right, center - close handle tip align on the handle bar according panel orientation + panel_handle_offset: 0, //offset of handle bar according the valign + panel_handle_skin: 0 //skin of the handle, if empty inherit from gallery skin + }; + + + /** + * init the handle + */ + this.init = function(panel, objPanel, panelOptions, panelType, gallery){ + g_panel = panel; + g_objPanel = objPanel; + + //set needed options + switch(panelType){ + case "grid": + g_options.panel_handle_align = panelOptions.gridpanel_handle_align; + g_options.panel_handle_offset = panelOptions.gridpanel_handle_offset; + g_options.panel_handle_skin = panelOptions.gridpanel_handle_skin; + break; + case "strip": + g_options.panel_handle_align = panelOptions.strippanel_handle_align; + g_options.panel_handle_offset = panelOptions.strippanel_handle_offset; + g_options.panel_handle_skin = panelOptions.strippanel_handle_skin; + break; + default: + throw new Error("Panel handle error: wrong panel type: " + panelType); + break; + } + + //set arrows skin: + var galleryOptions = gallery.getOptions(); + var globalSkin = galleryOptions.gallery_skin; + if(g_options.panel_handle_skin == "") + g_options.panel_handle_skin = globalSkin; + + + } + + + /** + * set handle html + */ + this.setHtml = function(){ + + var orientation = g_panel.getOrientation(); + + var classTip = "ug-panel-handle-tip"; + + switch(orientation){ + case "right": + classTip += " ug-handle-tip-left"; + break; + case "left": + classTip += " ug-handle-tip-right"; + break; + case "bottom": + classTip += " ug-handle-tip-top"; + break; + case "top": + classTip += " ug-handle-tip-bottom"; + break; + } + + g_objPanel.append("
      "); + g_objHandleTip = g_objPanel.children(".ug-panel-handle-tip"); + } + + + /** + * remove hover state of the tip + */ + function removeHoverState(){ + + g_objHandleTip.removeClass("ug-button-hover"); + } + + /** + * add closed state class + */ + function setClosedState(){ + + g_objHandleTip.addClass("ug-button-closed"); + } + + /** + * add closed state class + */ + function removeClosedState(){ + g_objHandleTip.removeClass("ug-button-closed"); + } + + /** + * on handle click, close or open panel + */ + function onHandleClick(event){ + + event.stopPropagation(); + event.stopImmediatePropagation(); + + if(g_functions.validateClickTouchstartEvent(event.type) == false) + return(true); + + if(g_panel.isPanelClosed()) + g_panel.openPanel(); + else + g_panel.closePanel(); + } + + /** + * init events + */ + this.initEvents = function(){ + g_functions.addClassOnHover(g_objHandleTip); + g_objHandleTip.bind("click touchstart", onHandleClick); + + //on panel open + jQuery(g_panel).on(g_panel.events.OPEN_PANEL, function(){ + removeHoverState(); + removeClosedState(); + }); + + //one panel close + jQuery(g_panel).on(g_panel.events.CLOSE_PANEL, function(){ + removeHoverState(); + setClosedState(); + }); + + } + + /** + * destroy the handle panel events + */ + this.destroy = function(){ + g_functions.destroyButton(g_objHandleTip); + jQuery(g_panel).off(g_panel.events.OPEN_PANEL); + jQuery(g_panel).off(g_panel.events.CLOSE_PANEL); + } + + + + /** + * check and fix align option, set write direction + */ + function checkAndFixAlign(){ + var orientation = g_panel.getOrientation(); + + switch(orientation){ + case "right": + case "left": + if(g_options.panel_handle_align != "top" && g_options.panel_handle_align != "bottom") + g_options.panel_handle_align = "top"; + break; + case "bottom": + if(g_options.panel_handle_align != "left" && g_options.panel_handle_align != "right") + g_options.panel_handle_align = "left"; + break; + case "top": + if(g_options.panel_handle_align != "left" && g_options.panel_handle_align != "right") + g_options.panel_handle_align = "right"; + break; + } + + } + + + /** + * place the panel + */ + this.placeHandle = function(){ + var handleSize = g_functions.getElementSize(g_objHandleTip); + + checkAndFixAlign(); + + var orientation = g_panel.getOrientation(); + + switch(orientation){ + case "left": + g_functions.placeElement(g_objHandleTip, "right", g_options.panel_handle_align, -handleSize.width); + break; + case "right": + g_functions.placeElement(g_objHandleTip, -handleSize.width, g_options.panel_handle_align, 0 ,g_options.panel_handle_offset); + break; + case "top": + g_functions.placeElement(g_objHandleTip, g_options.panel_handle_align, "bottom", g_options.panel_handle_offset, -handleSize.height); + break; + case "bottom": + g_functions.placeElement(g_objHandleTip, g_options.panel_handle_align, "top", g_options.panel_handle_offset, -handleSize.height); + break; + default: + throw new Error("Wrong panel orientation: " + orientation); + break; + } + + } + + /** + * hide the handle + */ + this.hideHandle = function(){ + + if(g_objHandleTip.is(":visible") == true) + g_objHandleTip.hide(); + + } + + /** + * show the handle + */ + this.showHandle = function(){ + + if(g_objHandleTip.is(":visible") == false) + g_objHandleTip.show(); + + } + + +} + +/** + * grid panel class addon to grid gallery + */ +function UGStripPanel() { + + var t = this, g_objThis = jQuery(this); + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objWrapper, g_objPanel; + var g_functions = new UGFunctions(), g_objStrip = new UGThumbsStrip(), g_panelBase = new UGPanelsBase(); + var g_objButtonNext, g_objButtonPrev; + + this.events = { + FINISH_MOVE : "gridpanel_move_finish", // called after close or open panel (slide finish). + OPEN_PANEL : "open_panel", // called before opening the panel. + CLOSE_PANEL : "close_panel" // called before closing the panel. + }; + + var g_options = { + + strippanel_vertical_type : false, // true, false - specify if the panel is vertical or horizonatal type + + strippanel_padding_top : 8, // space from top of the panel + strippanel_padding_bottom : 8, // space from bottom of the panel + + strippanel_padding_left : 0, // space from left of the panel + strippanel_padding_right : 0, // space from right of the panel + + strippanel_enable_buttons : true, // enable buttons from the sides of the panel + strippanel_buttons_skin : "", // skin of the buttons, if empty inherit from gallery skin + + strippanel_padding_buttons : 2, // padding between the buttons and the panel + + strippanel_buttons_role : "scroll_strip", // scroll_strip, advance_item - the role of the side buttons + + strippanel_enable_handle : true, // enable grid handle + strippanel_handle_align : "top", // top, middle, bottom , left, right, center - close handle tip align on the handle bar according panel orientation + strippanel_handle_offset : 0, // offset of handle bar according the valign + + strippanel_handle_skin : "", // skin of the handle, if empty inherit from gallery skin + + strippanel_background_color : "" // background color of the strip wrapper, if not set, it will be taken from the css + + }; + + var g_defaults_vertical = { + strip_vertical_type : true, + strippanel_padding_left : 8, + strippanel_padding_right : 8, + strippanel_padding_top : 0, + strippanel_padding_bottom : 0 + }; + + var g_defaults_no_buttons = { + strippanel_padding_left : 8, + strippanel_padding_right : 8, + strippanel_padding_top : 8, + strippanel_padding_bottom : 8 + }; + + var g_temp = { + panelType: "strip", + panelWidth : 0, + panelHeight : 0, + isEventsInited : false, + isClosed : false, + orientation : null, + originalPos : null, + isFirstRun : true + }; + + /** + * init the panel + */ + function initPanel(gallery, customOptions) { + g_gallery = gallery; + + g_objGallery = jQuery(g_gallery); + + g_options = jQuery.extend(g_options, customOptions); + + var repeatCustomOptions = false; + + if (g_options.strippanel_vertical_type == true) { + g_options = jQuery.extend(g_options, g_defaults_vertical); + repeatCustomOptions = true; + } + + if (g_options.strippanel_enable_buttons == false) { + g_options = jQuery.extend(g_options, g_defaults_no_buttons); + repeatCustomOptions = true; + } + + if (repeatCustomOptions == true) + g_options = jQuery.extend(g_options, customOptions); // do the + + + // set arrows skin: + var galleryOptions = g_gallery.getOptions(); + var globalSkin = galleryOptions.gallery_skin; + if (g_options.strippanel_buttons_skin == "") + g_options.strippanel_buttons_skin = globalSkin; + + g_objWrapper = g_gallery.getElement(); + + //init the base panel object: + g_panelBase.init(g_gallery, g_temp, t, g_options, g_objThis); + + //init thumbs strip + g_objStrip = new UGThumbsStrip(); + g_objStrip.init(g_gallery, g_options); + + } + + + /** + * validate panel before run + */ + function validatePanelBeforeRun() { + + if (g_options.strippanel_vertical_type == false) { // horizontal + // validate + if (g_temp.panelWidth == 0) { + throw new Error( + "Strip panel error: The width not set, please set width"); + return (false); + } + + } else { // vertical validate + + if (g_temp.panelHeight == 0) { + throw new Error( + "Strip panel error: The height not set, please set height"); + return (false); + } + + } + + // validate orientation + if (g_temp.orientation == null) { + throw new Error( + "Wrong orientation, please set panel orientation before run"); + return (false); + } + + return (true); + } + + /** + * run the panel + */ + function runPanel() { + + // validation + if (g_temp.isFirstRun == true && validatePanelBeforeRun() == false) + return (false); + + g_objStrip.run(); + + setElementsSize(); + placeElements(); + + initEvents(); + + g_temp.isFirstRun = false; + + checkSideButtons(); + } + + /** + * set panel html + */ + function setPanelHtml(parentContainer) { + + if (!parentContainer) + var parentContainer = g_objWrapper; + + // add panel wrapper + parentContainer.append("
      "); + + g_objPanel = parentContainer.children('.ug-strip-panel'); + + // add arrows: + if (g_options.strippanel_enable_buttons == true) { + + var arrowPrevClass = "ug-strip-arrow-left"; + var arrowNextClass = "ug-strip-arrow-right"; + if (g_options.strippanel_vertical_type == true) { + arrowPrevClass = "ug-strip-arrow-up"; + arrowNextClass = "ug-strip-arrow-down"; + } + + g_objPanel.append("
      "); + g_objPanel.append("
      "); + + } + + g_panelBase.setHtml(g_objPanel); + + g_objStrip.setHtml(g_objPanel); + + if (g_options.strippanel_enable_buttons == true) { + g_objButtonPrev = g_objPanel.children("." + arrowPrevClass); + g_objButtonNext = g_objPanel.children("." + arrowNextClass); + } + + setHtmlProperties(); + } + + /** + * set html properties according the options + */ + function setHtmlProperties() { + + // set panel background color + if (g_options.strippanel_background_color != "") + g_objPanel.css("background-color", + g_options.strippanel_background_color); + + } + + /** + * set elements size horizontal type + */ + function setElementsSize_hor() { + + // get strip height + var stripHeight = g_objStrip.getHeight(); + var panelWidth = g_temp.panelWidth; + + // set buttons height + if (g_objButtonNext) { + g_objButtonPrev.height(stripHeight); + g_objButtonNext.height(stripHeight); + + // arrange buttons tip + var prevTip = g_objButtonPrev.children(".ug-strip-arrow-tip"); + g_functions.placeElement(prevTip, "center", "middle"); + + var nextTip = g_objButtonNext.children(".ug-strip-arrow-tip"); + g_functions.placeElement(nextTip, "center", "middle"); + } + + // set panel height + var panelHeight = stripHeight + g_options.strippanel_padding_top + + g_options.strippanel_padding_bottom; + + // set panel size + g_objPanel.width(panelWidth); + g_objPanel.height(panelHeight); + + g_temp.panelHeight = panelHeight; + + // set strip size + var stripWidth = panelWidth - g_options.strippanel_padding_left - g_options.strippanel_padding_right; + + if (g_objButtonNext) { + var buttonWidth = g_objButtonNext.outerWidth(); + stripWidth = stripWidth - buttonWidth * 2 - g_options.strippanel_padding_buttons * 2; + } + + g_objStrip.resize(stripWidth); + } + + /** + * set elements size vertical type + */ + function setElementsSize_vert() { + + // get strip width + var stripWidth = g_objStrip.getWidth(); + var panelHeight = g_temp.panelHeight; + + // set buttons height + if (g_objButtonNext) { + g_objButtonPrev.width(stripWidth); + g_objButtonNext.width(stripWidth); + + // arrange buttons tip + var prevTip = g_objButtonPrev.children(".ug-strip-arrow-tip"); + g_functions.placeElement(prevTip, "center", "middle"); + + var nextTip = g_objButtonNext.children(".ug-strip-arrow-tip"); + g_functions.placeElement(nextTip, "center", "middle"); + } + + // set panel width + var panelWidth = stripWidth + g_options.strippanel_padding_left + + g_options.strippanel_padding_right; + + // set panel size + g_objPanel.width(panelWidth); + g_objPanel.height(panelHeight); + + g_temp.panelWidth = panelWidth; + + // set strip size + var stripHeight = panelHeight - g_options.strippanel_padding_top + - g_options.strippanel_padding_bottom; + + if (g_objButtonNext) { + var buttonHeight = g_objButtonNext.outerHeight(); + stripHeight = stripHeight - buttonHeight * 2 + - g_options.strippanel_padding_buttons * 2; + } + + g_objStrip.resize(stripHeight); + } + + /** + * set elements size and place the elements + */ + function setElementsSize() { + + if (g_options.strippanel_vertical_type == false) + setElementsSize_hor(); + else + setElementsSize_vert(); + } + + /** + * place elements horizontally + */ + function placeElements_hor() { + + // place buttons + if (g_objButtonNext) { + g_functions.placeElement(g_objButtonPrev, "left", "top", + g_options.strippanel_padding_left, + g_options.strippanel_padding_top); + g_functions.placeElement(g_objButtonNext, "right", "top", + g_options.strippanel_padding_right, + g_options.strippanel_padding_top); + } + + var stripX = g_options.strippanel_padding_left; + if (g_objButtonNext) + stripX += g_objButtonNext.outerWidth() + + g_options.strippanel_padding_buttons; + + g_objStrip.setPosition(stripX, g_options.strippanel_padding_top); + + } + + /** + * place elements vertically + */ + function placeElements_vert() { + + // place buttons + if (g_objButtonNext) { + g_functions.placeElement(g_objButtonPrev, "left", "top", + g_options.strippanel_padding_left, + g_options.strippanel_padding_top); + g_functions.placeElement(g_objButtonNext, "left", "bottom", + g_options.strippanel_padding_left, + g_options.strippanel_padding_bottom); + } + + var stripY = g_options.strippanel_padding_top; + if (g_objButtonNext) + stripY += g_objButtonNext.outerHeight() + + g_options.strippanel_padding_buttons; + + g_objStrip.setPosition(g_options.strippanel_padding_left, stripY); + } + + /** + * place elements + */ + function placeElements() { + + if (g_options.strippanel_vertical_type == false) + placeElements_hor(); + else + placeElements_vert(); + + g_panelBase.placeElements(); + + } + + + + function __________EVENTS___________() { + } + ; + + /** + * on next button click + */ + function onNextButtonClick(objButton) { + + if (g_functions.isButtonDisabled(objButton)) + return (true); + + if (g_options.strippanel_buttons_role == "advance_item") + g_gallery.nextItem(); + else + g_objStrip.scrollForeward(); + } + + /** + * on previous button click + */ + function onPrevButtonClick(objButton) { + + if (g_functions.isButtonDisabled(objButton)) + return (true); + + if (g_options.strippanel_buttons_role == "advance_item") + g_gallery.prevItem(); + else + g_objStrip.scrollBack(); + } + + /** + * check buttons if they need to be disabled or not + */ + function checkSideButtons() { + + if (!g_objButtonNext) + return (true); + + // if the strip not movable - disable both buttons + if (g_objStrip.isMoveEnabled() == false) { + g_functions.disableButton(g_objButtonPrev); + g_functions.disableButton(g_objButtonNext); + return (true); + } + + // check the limits + var limits = g_objStrip.getInnerStripLimits(); + var pos = g_objStrip.getInnerStripPos(); + + if (pos >= limits.maxPos) { + g_functions.disableButton(g_objButtonPrev); + } else { + g_functions.enableButton(g_objButtonPrev); + } + + if (pos <= limits.minPos) + g_functions.disableButton(g_objButtonNext); + else + g_functions.enableButton(g_objButtonNext); + + } + + /** + * on strip move event + */ + function onStripMove() { + checkSideButtons(); + } + + /** + * on item change event, disable or enable buttons according the images + * position + */ + function onItemChange() { + + if (g_gallery.isLastItem()) + g_functions.disableButton(g_objButtonNext); + else + g_functions.enableButton(g_objButtonNext); + + if (g_gallery.isFirstItem()) + g_functions.disableButton(g_objButtonPrev); + else + g_functions.enableButton(g_objButtonPrev); + + } + + + /** + * init panel events + */ + function initEvents() { + + if (g_temp.isEventsInited == true) + return (false); + + g_temp.isEventsInited = true; + + // buttons events + if (g_objButtonNext) { + + // add hove class + g_functions.addClassOnHover(g_objButtonNext, "ug-button-hover"); + g_functions.addClassOnHover(g_objButtonPrev, "ug-button-hover"); + + // add click events + g_functions.setButtonOnClick(g_objButtonPrev, onPrevButtonClick); + g_functions.setButtonOnClick(g_objButtonNext, onNextButtonClick); + + // add disable / enable buttons on strip move event + if (g_options.strippanel_buttons_role != "advance_item") { + + jQuery(g_objStrip).on(g_objStrip.events.STRIP_MOVE, onStripMove); + + jQuery(g_objStrip).on(g_objStrip.events.INNER_SIZE_CHANGE, checkSideButtons); + + g_objGallery.on(g_gallery.events.SIZE_CHANGE, checkSideButtons); + + } else { + var galleryOptions = g_gallery.getOptions(); + if (galleryOptions.gallery_carousel == false) + jQuery(g_gallery).on(g_gallery.events.ITEM_CHANGE, onItemChange); + } + + } + + g_panelBase.initEvents(); + } + + /** + * destroy the strip panel events + */ + this.destroy = function(){ + + if(g_objButtonNext){ + g_functions.destroyButton(g_objButtonNext); + g_functions.destroyButton(g_objButtonPrev); + jQuery(g_objStrip).off(g_objStrip.events.STRIP_MOVE); + jQuery(g_gallery).off(g_gallery.events.ITEM_CHANGE); + jQuery(g_gallery).off(g_gallery.events.SIZE_CHANGE); + } + + g_panelBase.destroy(); + g_objStrip.destroy(); + } + + + /** + * get panel orientation + */ + this.getOrientation = function() { + + return (g_temp.orientation); + } + + /** + * set panel orientation (left, right, top, bottom) + */ + this.setOrientation = function(orientation) { + + g_temp.orientation = orientation; + } + + + /** + * init the panel + */ + this.init = function(gallery, customOptions) { + initPanel(gallery, customOptions); + } + + /** + * run the panel + */ + this.run = function() { + runPanel(); + } + + /** + * place panel html + */ + this.setHtml = function(parentContainer) { + setPanelHtml(parentContainer); + } + + /** + * get the panel element + */ + this.getElement = function() { + return (g_objPanel); + } + + /** + * get panel size object + */ + this.getSize = function() { + + var objSize = g_functions.getElementSize(g_objPanel); + + return (objSize); + } + + /** + * set panel width (for horizonal type) + */ + this.setWidth = function(width) { + + g_temp.panelWidth = width; + + } + + /** + * set panel height (for vertical type) + */ + this.setHeight = function(height) { + + g_temp.panelHeight = height; + + } + + /** + * resize the panel + */ + this.resize = function(newWidth) { + t.setWidth(newWidth); + setElementsSize(); + placeElements(); + } + + this.__________Functions_From_Base_____ = function() {} + + /** + * tells if the panel is closed + */ + this.isPanelClosed = function() { + return (g_panelBase.isPanelClosed()); + } + + /** + * get closed panel destanation + */ + this.getClosedPanelDest = function() { + return g_panelBase.getClosedPanelDest(); + } + + /** + * open the panel + */ + this.openPanel = function(noAnimation) { + g_panelBase.openPanel(noAnimation); + } + + + /** + * close the panel (slide in) + */ + this.closePanel = function(noAnimation) { + g_panelBase.closePanel(noAnimation); + } + + /** + * set the panel opened state + */ + this.setOpenedState = function(originalPos) { + g_panelBase.setOpenedState(originalPos); + } + + /** + * set the panel that it's in closed state, and set original pos for opening later + */ + this.setClosedState = function(originalPos) { + g_panelBase.setClosedState(originalPos); + } + + /** + * set custom thumbs of the strip + */ + this.setCustomThumbs = function(funcSetHtml){ + + g_objStrip.setCustomThumbs(funcSetHtml); + + } + + /** + * set panel disabled at start + */ + this.setDisabledAtStart = function(timeout){ + + g_panelBase.setDisabledAtStart(timeout); + + } + +} + + +/** + * grid panel class + * addon to grid gallery + */ +function UGGridPanel(){ + + var t = this, g_objThis = jQuery(this); + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objWrapper, g_objPanel; + var g_functions = new UGFunctions(); + var g_objGrid = new UGThumbsGrid(); + var g_panelBase = new UGPanelsBase(); + var g_objArrowNext, g_objArrowPrev; + + this.events = { + FINISH_MOVE: "gridpanel_move_finish", //called after close or open panel (slide finish). + OPEN_PANEL: "open_panel", //called before opening the panel. + CLOSE_PANEL: "close_panel" //called before closing the panel. + }; + + var g_options = { + gridpanel_vertical_scroll: true, //vertical or horizontal grid scroll and arrows + gridpanel_grid_align: "middle", //top , middle , bottom, left, center, right - the align of the grid panel in the gallery + gridpanel_padding_border_top: 10, //padding between the top border of the panel + gridpanel_padding_border_bottom: 4, //padding between the bottom border of the panel + gridpanel_padding_border_left: 10, //padding between the left border of the panel + gridpanel_padding_border_right: 10, //padding between the right border of the panel + + gridpanel_arrows_skin: "", //skin of the arrows, if empty inherit from gallery skin + gridpanel_arrows_align_vert: "middle", //borders, grid, middle - vertical align of arrows, to the top and bottom botders, to the grid, or in the middle space. + gridpanel_arrows_padding_vert: 4, //padding between the arrows and the grid, in case of middle align, it will be minimal padding + gridpanel_arrows_align_hor: "center", //borders, grid, center - horizontal align of arrows, to the left and right botders, to the grid, or in the center space. + gridpanel_arrows_padding_hor: 10, //in case of horizontal type only, minimal size from the grid in case of "borders" and size from the grid in case of "grid" + + gridpanel_space_between_arrows: 20, //space between arrows on horizontal grids only + gridpanel_arrows_always_on: false, //always show arrows even if the grid is one pane only + + gridpanel_enable_handle: true, //enable grid handle + gridpanel_handle_align: "top", //top, middle, bottom , left, right, center - close handle tip align on the handle bar according panel orientation + gridpanel_handle_offset: 0, //offset of handle bar according the valign + gridpanel_handle_skin: "", //skin of the handle, if empty inherit from gallery skin + + gridpanel_background_color:"" //background color of the grid wrapper, if not set, it will be taken from the css + }; + + + //default options for vertical scroll + var g_defaultsVertical = { + gridpanel_grid_align: "middle", //top , middle , bottom + gridpanel_padding_border_top: 2, //padding between the top border of the panel + gridpanel_padding_border_bottom: 2 //padding between the bottom border of the panel + }; + + //default options for horizontal type panel + var g_defaultsHorType = { + gridpanel_grid_align: "center" //left, center, right + }; + + var g_temp = { + panelType: "grid", + isHorType: false, + arrowsVisible: false, + panelHeight: 0, + panelWidth: 0, + originalPosX:null, + isEventsInited: false, + isClosed: false, + orientation: null + }; + + + /** + * init the grid panel + */ + function initGridPanel(gallery, customOptions){ + + g_gallery = gallery; + + validateOrientation(); + + //set defaults and custom options + if(customOptions && customOptions.vertical_scroll){ + g_options.gridpanel_vertical_scroll = customOptions.vertical_scroll; + } + + g_options = jQuery.extend(g_options, customOptions); + + //set defautls for horizontal panel type + if(g_temp.isHorType == true){ + + g_options = jQuery.extend(g_options, g_defaultsHorType); + g_options = jQuery.extend(g_options, customOptions); + + }else if(g_options.gridpanel_vertical_scroll == true){ + + //set defaults for vertical scroll + g_options = jQuery.extend(g_options, g_defaultsVertical); + g_options = jQuery.extend(g_options, customOptions); + g_options.grid_panes_direction = "bottom"; + } + + //set arrows skin: + var galleryOptions = g_gallery.getOptions(); + var globalSkin = galleryOptions.gallery_skin; + if(g_options.gridpanel_arrows_skin == "") + g_options.gridpanel_arrows_skin = globalSkin; + + + //get the gallery wrapper + var objects = gallery.getObjects(); + g_objWrapper = objects.g_objWrapper; + + //init the base panel object: + g_panelBase.init(g_gallery, g_temp, t, g_options, g_objThis); + + //init the grid + g_objGrid = new UGThumbsGrid(); + g_objGrid.init(g_gallery, g_options); + + } + + + /** + * validate the orientation if exists + */ + function validateOrientation(){ + + if(g_temp.orientation == null) + throw new Error("Wrong orientation, please set panel orientation before run"); + + } + + /** + * run the rid panel + */ + function runPanel(){ + + //validate orientation + validateOrientation(); + + processOptions(); + + g_objGrid.run(); + + setArrows(); + setPanelSize(); + placeElements(); + + initEvents(); + } + + + /** + * set html of the grid panel + */ + function setHtmlPanel(){ + + //add panel wrapper + g_objWrapper.append("
      "); + + g_objPanel = g_objWrapper.children('.ug-grid-panel'); + + //add arrows: + if(g_temp.isHorType){ + + g_objPanel.append("
      "); + g_objPanel.append("
      "); + + g_objArrowPrev = g_objPanel.children(".grid-arrow-left-hortype"); + g_objArrowNext = g_objPanel.children(".grid-arrow-right-hortype"); + } + else if(g_options.gridpanel_vertical_scroll == false){ //horizonatl arrows + g_objPanel.append("
      "); + g_objPanel.append("
      "); + + g_objArrowPrev = g_objPanel.children(".grid-arrow-left"); + g_objArrowNext = g_objPanel.children(".grid-arrow-right"); + + }else{ //vertical arrows + g_objPanel.append("
      "); + g_objPanel.append("
      "); + + g_objArrowPrev = g_objPanel.children(".grid-arrow-up"); + g_objArrowNext = g_objPanel.children(".grid-arrow-down"); + } + + g_panelBase.setHtml(g_objPanel); + + //hide the arrows + g_objArrowPrev.fadeTo(0,0); + g_objArrowNext.fadeTo(0,0); + + g_objGrid.setHtml(g_objPanel); + + setHtmlProperties(); + } + + + /** + * set html properties according the options + */ + function setHtmlProperties(){ + + //set panel background color + if(g_options.gridpanel_background_color != "") + g_objPanel.css("background-color",g_options.gridpanel_background_color); + } + + + /** + * process and fix certain options, avoid arrows and validate options + */ + function processOptions(){ + + if(g_options.gridpanel_grid_align == "center") + g_options.gridpanel_grid_align = "middle"; + } + + + /** + * place panel with some animation + */ + function placePanelAnimation(panelX, functionOnComplete){ + + var objCss = {left: panelX + "px"}; + + g_objPanel.stop(true).animate(objCss ,{ + duration: 300, + easing: "easeInOutQuad", + queue: false, + complete: function(){ + if(functionOnComplete) + functionOnComplete(); + } + }); + + } + + + + /** + * get max height of the grid according the arrows size + */ + function getGridMaxHeight(){ + + //check space taken without arrows for one pane grids + var spaceTaken = g_options.gridpanel_padding_border_top + g_options.gridpanel_padding_border_bottom; + var maxGridHeight = g_temp.panelHeight - spaceTaken; + + if(g_options.gridpanel_arrows_always_on == false){ + var numPanes = g_objGrid.getNumPanesEstimationByHeight(maxGridHeight); + if(numPanes == 1) + return(maxGridHeight); + } + + //count the size with arrows + var arrowsSize = g_functions.getElementSize(g_objArrowNext); + var arrowsHeight = arrowsSize.height; + + var spaceTaken = arrowsHeight + g_options.gridpanel_arrows_padding_vert; + + if(g_options.gridpanel_vertical_scroll == true) //in case of 2 arrows multiply by 2 + spaceTaken *= 2; + + spaceTaken += g_options.gridpanel_padding_border_top + g_options.gridpanel_padding_border_bottom; + + maxGridHeight = g_temp.panelHeight - spaceTaken; + + return(maxGridHeight); + } + + + /** + * get grid maximum width + */ + function getGridMaxWidth(){ + + //check space taken without arrows for one pane grids + var spaceTaken = g_options.gridpanel_padding_border_left + g_options.gridpanel_padding_border_right; + + var maxGridWidth = g_temp.panelWidth - spaceTaken; + + if(g_options.gridpanel_arrows_always_on == false){ + var numPanes = g_objGrid.getNumPanesEstimationByWidth(maxGridWidth); + + if(numPanes == 1) + return(maxGridWidth); + } + + //count the size with arrows + var arrowsSize = g_functions.getElementSize(g_objArrowNext); + var arrowsWidth = arrowsSize.width; + + spaceTaken += (arrowsWidth + g_options.gridpanel_arrows_padding_hor) * 2; + + maxGridWidth = g_temp.panelWidth - spaceTaken; + + return(maxGridWidth); + } + + + /** + * enable / disable arrows according the grid + */ + function setArrows(){ + + var showArrows = false; + if(g_options.gridpanel_arrows_always_on == true){ + showArrows = true; + } + else{ + var numPanes = g_objGrid.getNumPanes(); + if(numPanes > 1) + showArrows = true; + } + + if(showArrows == true){ //show arrows + + g_objArrowNext.show().fadeTo(0,1); + g_objArrowPrev.show().fadeTo(0,1); + g_temp.arrowsVisible = true; + + }else{ //hide arrows + + g_objArrowNext.hide(); + g_objArrowPrev.hide(); + g_temp.arrowsVisible = false; + + } + + } + + + /** + * set panel size by the given height and grid width + */ + function setPanelSize(){ + var gridSize = g_objGrid.getSize(); + + //set panel size + if(g_temp.isHorType == true) + g_temp.panelHeight = gridSize.height + g_options.gridpanel_padding_border_top + g_options.gridpanel_padding_border_bottom; + else + g_temp.panelWidth = gridSize.width + g_options.gridpanel_padding_border_left + g_options.gridpanel_padding_border_right; + + g_functions.setElementSize(g_objPanel, g_temp.panelWidth, g_temp.panelHeight); + + } + + + /** + * place the panel without animation + * @param panelDest + */ + function placePanelNoAnimation(panelDest){ + + switch(g_temp.orientation){ + case "right": //vertical + case "left": + g_functions.placeElement(g_objPanel, panelDest, null); + break; + } + + } + + + + function __________EVENTS___________(){}; + + + + /** + * event on panel slide finish + */ + function onPanelSlideFinish(){ + + g_objThis.trigger(t.events.FINISH_MOVE); + + } + + + /** + * init panel events + */ + function initEvents(){ + + if(g_temp.isEventsInited == true) + return(false); + + g_temp.isEventsInited = true; + + if(g_objArrowPrev){ + g_functions.addClassOnHover(g_objArrowPrev); + g_objGrid.attachPrevPaneButton(g_objArrowPrev); + } + + + if(g_objArrowNext){ + g_functions.addClassOnHover(g_objArrowNext); + g_objGrid.attachNextPaneButton(g_objArrowNext); + } + + g_panelBase.initEvents(); + + } + + + /** + * destroy the events + */ + this.destroy = function(){ + + if(g_objArrowPrev) + g_functions.destroyButton(g_objArrowPrev); + + if(g_objArrowNext) + g_functions.destroyButton(g_objArrowNext); + + g_panelBase.destroy(); + + g_objGrid.destroy(); + } + + + function ______PLACE_ELEMENTS___________(){}; + + + /** + * get padding left of the grid + */ + function getGridPaddingLeft(){ + + var gridPanelLeft = g_options.gridpanel_padding_border_left; + + return(gridPanelLeft); + } + + + /** + * place elements vertical - grid only + */ + function placeElements_noarrows(){ + + //place grid + var gridY = g_options.gridpanel_grid_align, gridPaddingY = 0; + + switch(gridY){ + case "top": + gridPaddingY = g_options.gridpanel_padding_border_top; + + break; + case "bottom": + gridPaddingY = g_options.gridpanel_padding_border_bottom; + break; + } + + var gridPanelLeft = getGridPaddingLeft(); + + var gridElement = g_objGrid.getElement(); + g_functions.placeElement(gridElement, gridPanelLeft, gridY, 0 , gridPaddingY); + + } + + + /** + * place elements vertical - with arrows + */ + function placeElementsVert_arrows(){ + + //place grid + var gridY, prevArrowY, nextArrowY, nextArrowPaddingY; + var objArrowSize = g_functions.getElementSize(g_objArrowPrev); + var objGridSize = g_objGrid.getSize(); + + + switch(g_options.gridpanel_grid_align){ + default: + case "top": + gridY = g_options.gridpanel_padding_border_top + objArrowSize.height + g_options.gridpanel_arrows_padding_vert; + break; + case "middle": + gridY = "middle"; + break; + case "bottom": + gridY = g_temp.panelHeight - objGridSize.height - objArrowSize.height - g_options.gridpanel_padding_border_bottom - g_options.gridpanel_arrows_padding_vert; + break; + } + + //place the grid + var gridPanelLeft = getGridPaddingLeft(); + + var gridElement = g_objGrid.getElement(); + g_functions.placeElement(gridElement, gridPanelLeft, gridY); + + var objGridSize = g_objGrid.getSize(); + + //place arrows + switch(g_options.gridpanel_arrows_align_vert){ + default: + case "center": + case "middle": + prevArrowY = (objGridSize.top - objArrowSize.height) / 2; + nextArrowY = objGridSize.bottom + (g_temp.panelHeight - objGridSize.bottom - objArrowSize.height) / 2; + nextArrowPaddingY = 0; + break; + case "grid": + prevArrowY = objGridSize.top - objArrowSize.height - g_options.gridpanel_arrows_padding_vert_vert + nextArrowY = objGridSize.bottom + g_options.gridpanel_arrows_padding_vert; + nextArrowPaddingY = 0; + break; + case "border": + case "borders": + prevArrowY = g_options.gridpanel_padding_border_top; + nextArrowY = "bottom"; + nextArrowPaddingY = g_options.gridpanel_padding_border_bottom; + break; + } + + g_functions.placeElement(g_objArrowPrev, "center", prevArrowY); + + g_functions.placeElement(g_objArrowNext, "center", nextArrowY, 0, nextArrowPaddingY); + } + + + /** + * place elements vertical + */ + function placeElementsVert(){ + + if(g_temp.arrowsVisible == true) + placeElementsVert_arrows(); + else + placeElements_noarrows(); + } + + + /** + * place elements horizontal with arrows + */ + function placeElementsHor_arrows(){ + + var arrowsY, prevArrowPadding, arrowsPaddingY, nextArrowPadding; + var objArrowSize = g_functions.getElementSize(g_objArrowPrev); + var objGridSize = g_objGrid.getSize(); + + //place grid + var gridY = g_options.gridpanel_padding_border_top; + + switch(g_options.gridpanel_grid_align){ + case "middle": + + switch(g_options.gridpanel_arrows_align_vert){ + default: + var elementsHeight = objGridSize.height + g_options.gridpanel_arrows_padding_vert + objArrowSize.height; + gridY = (g_temp.panelHeight - elementsHeight) / 2; + break; + case "border": + case "borders": + var remainHeight = g_temp.panelHeight - objArrowSize.height - g_options.gridpanel_padding_border_bottom; + gridY = (remainHeight - objGridSize.height) / 2; + break; + } + + break; + case "bottom": + var elementsHeight = objGridSize.height + objArrowSize.height + g_options.gridpanel_arrows_padding_vert; + gridY = g_temp.panelHeight - elementsHeight - g_options.gridpanel_padding_border_bottom; + break; + } + + var gridElement = g_objGrid.getElement(); + var gridPanelLeft = getGridPaddingLeft(); + + g_functions.placeElement(gridElement, gridPanelLeft, gridY); + + var objGridSize = g_objGrid.getSize(); + + switch(g_options.gridpanel_arrows_align_vert){ + default: + case "center": + case "middle": + arrowsY = objGridSize.bottom + (g_temp.panelHeight - objGridSize.bottom - objArrowSize.height) / 2; + arrowsPaddingY = 0; + break; + case "grid": + arrowsY = objGridSize.bottom + g_options.gridpanel_arrows_padding_vert; + arrowsPaddingY = 0; + break; + case "border": + case "borders": + arrowsY = "bottom"; + arrowsPaddingY = g_options.gridpanel_padding_border_bottom; + break; + } + + prevArrowPadding = -objArrowSize.width/2 - g_options.gridpanel_space_between_arrows / 2; + + g_functions.placeElement(g_objArrowPrev, "center", arrowsY, prevArrowPadding, arrowsPaddingY); + + //place next arrow + var nextArrowPadding = Math.abs(prevArrowPadding); //make positive + + g_functions.placeElement(g_objArrowNext, "center", arrowsY, nextArrowPadding, arrowsPaddingY); + + } + + + /** + * place elements horizonatal + */ + function placeElementsHor(){ + + if(g_temp.arrowsVisible == true) + placeElementsHor_arrows(); + else + placeElements_noarrows(); + + } + + + /** + * place elements horizontal type with arrows + */ + function placeElementsHorType_arrows(){ + + //place grid + var gridX, prevArrowX, nextArrowX, arrowsY; + var objArrowSize = g_functions.getElementSize(g_objArrowPrev); + var objGridSize = g_objGrid.getSize(); + + switch(g_options.gridpanel_grid_align){ + default: + case "left": + gridX = g_options.gridpanel_padding_border_left + g_options.gridpanel_arrows_padding_hor + objArrowSize.width; + break; + case "middle": + case "center": + gridX = "center"; + break; + case "right": + gridX = g_temp.panelWidth - objGridSize.width - objArrowSize.width - g_options.gridpanel_padding_border_right - g_options.gridpanel_arrows_padding_hor; + break; + } + + //place the grid + var gridElement = g_objGrid.getElement(); + g_functions.placeElement(gridElement, gridX, g_options.gridpanel_padding_border_top); + objGridSize = g_objGrid.getSize(); + + //place arrows, count Y + switch(g_options.gridpanel_arrows_align_vert){ + default: + case "center": + case "middle": + arrowsY = (objGridSize.height - objArrowSize.height) / 2 + objGridSize.top; + break; + case "top": + arrowsY = g_options.gridpanel_padding_border_top + g_options.gridpanel_arrows_padding_vert; + break; + case "bottom": + arrowsY = g_temp.panelHeight - g_options.gridpanel_padding_border_bottom - g_options.gridpanel_arrows_padding_vert - objArrowSize.height; + break; + } + + //get arrows X + switch(g_options.gridpanel_arrows_align_hor){ + default: + case "borders": + prevArrowX = g_options.gridpanel_padding_border_left; + nextArrowX = g_temp.panelWidth - g_options.gridpanel_padding_border_right - objArrowSize.width; + break; + case "grid": + prevArrowX = objGridSize.left - g_options.gridpanel_arrows_padding_hor - objArrowSize.width; + nextArrowX = objGridSize.right + g_options.gridpanel_arrows_padding_hor; + break; + case "center": + prevArrowX = (objGridSize.left - objArrowSize.width) / 2; + nextArrowX = objGridSize.right + (g_temp.panelWidth - objGridSize.right - objArrowSize.width) / 2; + break; + } + + g_functions.placeElement(g_objArrowPrev, prevArrowX, arrowsY); + g_functions.placeElement(g_objArrowNext, nextArrowX, arrowsY); + } + + + /** + * place elements horizontal type without arrows + */ + function placeElementHorType_noarrows(){ + + var gridX; + var objGridSize = g_objGrid.getSize(); + + switch(g_options.gridpanel_grid_align){ + default: + case "left": + gridX = g_options.gridpanel_padding_border_left; + break; + case "middle": + case "center": + gridX = "center"; + break; + case "right": + gridX = g_temp.panelWidth - objGridSize.width - g_options.gridpanel_padding_border_right; + break; + } + + //place the grid + var gridElement = g_objGrid.getElement(); + g_functions.placeElement(gridElement, gridX, g_options.gridpanel_padding_border_top); + } + + + /** + * place elements when the grid in horizontal position + */ + function placeElementsHorType(){ + + if(g_temp.arrowsVisible == true) + placeElementsHorType_arrows(); + else + placeElementHorType_noarrows(); + + } + + + /** + * place the arrows + */ + function placeElements(){ + + if(g_temp.isHorType == false){ + + if(g_options.gridpanel_vertical_scroll == true) + placeElementsVert(); + else + placeElementsHor(); + + }else{ + placeElementsHorType(); + } + + g_panelBase.placeElements(); + } + + + /** + * get panel orientation + */ + this.getOrientation = function(){ + + return(g_temp.orientation); + } + + + /** + * set panel orientation (left, right, top, bottom) + */ + this.setOrientation = function(orientation){ + + g_temp.orientation = orientation; + + //set isHorType temp variable for ease of use + switch(orientation){ + case "right": + case "left": + g_temp.isHorType = false; + break; + case "top": + case "bottom": + g_temp.isHorType = true; + break; + default: + throw new Error("Wrong grid panel orientation: " + orientation); + break; + } + + } + + /** + * set panel height + */ + this.setHeight = function(height){ + + if(g_temp.isHorType == true) + throw new Error("setHeight is not appliable to this orientatio ("+g_temp.orientation+"). Please use setWidth"); + + g_temp.panelHeight = height; + var gridMaxHeight = getGridMaxHeight(); + + g_objGrid.setMaxHeight(gridMaxHeight); + } + + + /** + * set panel width + */ + this.setWidth = function(width){ + + if(g_temp.isHorType == false) + throw new Error("setWidth is not appliable to this orientatio ("+g_temp.orientation+"). Please use setHeight"); + + g_temp.panelWidth = width; + + var gridMaxWidth = getGridMaxWidth(); + + g_objGrid.setMaxWidth(gridMaxWidth); + } + + + /** + * init the panel + */ + this.init = function(gallery, customOptions){ + + initGridPanel(gallery, customOptions); + } + + /** + * place panel html + */ + this.setHtml = function(){ + setHtmlPanel(); + } + + + /** + * run the panel + */ + this.run = function(){ + + runPanel(); + } + + + /** + * get the panel element + */ + this.getElement = function(){ + return(g_objPanel); + } + + + /** + * get panel size object + */ + this.getSize = function(){ + + var objSize = g_functions.getElementSize(g_objPanel); + + return(objSize); + } + + this.__________Functions_From_Base_____ = function() {} + + /** + * tells if the panel is closed + */ + this.isPanelClosed = function() { + return (g_panelBase.isPanelClosed()); + } + + /** + * get closed panel destanation + */ + this.getClosedPanelDest = function() { + return g_panelBase.getClosedPanelDest(); + } + + /** + * open the panel + */ + this.openPanel = function(noAnimation) { + g_panelBase.openPanel(noAnimation); + } + + + /** + * close the panel (slide in) + */ + this.closePanel = function(noAnimation) { + g_panelBase.closePanel(noAnimation); + } + + /** + * set the panel opened state + */ + this.setOpenedState = function(originalPos) { + g_panelBase.setOpenedState(originalPos); + } + + /** + * set the panel that it's in closed state, and set original pos for opening later + */ + this.setClosedState = function(originalPos) { + g_panelBase.setClosedState(originalPos); + } + + + /** + * set panel disabled at start + */ + this.setDisabledAtStart = function(timeout){ + + g_panelBase.setDisabledAtStart(timeout); + + } + + +} + +/** + * thumbs class + * addon to strip gallery + */ +function UGThumbsGrid(){ + + var t = this, g_objThis = jQuery(this); + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objects, g_objWrapper; + var g_functions = new UGFunctions(), g_arrItems, g_objGrid, g_objInner; + var g_thumbs = new UGThumbsGeneral(), g_tilesDesign = new UGTileDesign(); + + var g_options = { + grid_panes_direction: "left", //where panes will move -> left, bottom + grid_num_cols: 2, //number of grid columns + grid_min_cols: 2, //minimum number of columns (for horizontal type) - the tile size is dynamic + grid_num_rows: 2, //number of grid rows (for horizontal type) + grid_space_between_cols: 10, //space between columns + grid_space_between_rows: 10, //space between rows + grid_space_between_mobile: 10, //space between rows and cols mobile + grid_transition_duration: 300, //transition of the panes change duration + grid_transition_easing: "easeInOutQuad", //transition of the panes change easing function + grid_carousel: false, //next pane goes to first when last + grid_padding: 0, //set padding to the grid + grid_vertical_scroll_ondrag: false //scroll the gallery on vertical drag + }; + + this.events = { + PANE_CHANGE: "pane_change" + }; + + var g_temp = { + eventSizeChange: "thumb_size_change", + isHorizontal: false, + isMaxHeight:false, //set if the height that set is max height. In that case need a height correction + isMaxWidth:false, //set if the height that set is max height. In that case need a height correction + gridHeight: 0, + gridWidth: 0, + innerWidth: 0, + innerHeight:0, + numPanes:0, + arrPanes:0, + numThumbs:0, + currentPane:0, + numThumbsInPane:0, + isNavigationVertical:false, + touchActive: false, + startScrollPos:0, + isFirstTimeRun:true, + isTilesMode: false, + storedEventID: "thumbsgrid", + tileMaxWidth:null, + tileMaxHeight:null, + spaceBetweenCols: null, + spaceBetweenRows: null + }; + + + function __________GENERAL_________(){}; + + /** + * init the gallery + */ + function init(gallery, customOptions, isTilesMode){ + + g_objects = gallery.getObjects(); + g_gallery = gallery; + + g_gallery.attachThumbsPanel("grid", t); + + g_objGallery = jQuery(gallery); + g_objWrapper = g_objects.g_objWrapper; + g_arrItems = g_objects.g_arrItems; + + if(isTilesMode === true) + g_temp.isTilesMode = true; + + g_temp.numThumbs = g_arrItems.length; + + setOptions(customOptions); + + if(g_temp.isTilesMode == true){ + + g_tilesDesign.setFixedMode(); + g_tilesDesign.setApproveClickFunction(isApproveTileClick); + g_tilesDesign.init(gallery, g_options); + + var options_td = g_tilesDesign.getOptions(); + g_temp.tileMaxHeight = options_td.tile_height; + g_temp.tileMaxWidth = options_td.tile_width; + + g_thumbs = g_tilesDesign.getObjThumbs(); + }else{ + + //disable the dynamic size in thumbs + customOptions.thumb_fixed_size = true; + + g_thumbs.init(gallery, customOptions); + } + + } + + + /** + * set the grid panel html + */ + function setHtml(parentContainer){ + + var objParent = g_objWrapper; + + if(parentContainer) + objParent = parentContainer; + + objParent.append("
      "); + g_objGrid = objParent.children(".ug-thumbs-grid"); + g_objInner = g_objGrid.children(".ug-thumbs-grid-inner"); + + //put the thumbs to inner strip + + if(g_temp.isTilesMode == true) + g_tilesDesign.setHtml(g_objInner); + else + g_thumbs.setHtmlThumbs(g_objInner); + + } + + + /** + * validate before running the grid + */ + function validateBeforeRun(){ + + if(g_temp.isHorizontal == false){ //vertical type + if(g_temp.gridHeight == 0) + throw new Error("You must set height before run."); + }else{ + if(g_temp.gridWidth == 0) + throw new Error("You must set width before run."); + } + + } + + + /** + * run the gallery after init and set html + */ + function run(){ + + var selectedItem = g_gallery.getSelectedItem(); + + validateBeforeRun(); + + if(g_temp.isFirstTimeRun == true){ + + initEvents(); + + if(g_temp.isTilesMode == true){ + + initGridDynamicSize(); + initSizeParams(); + g_tilesDesign.run(); + + }else{ + g_thumbs.setHtmlProperties(); + initSizeParams(); + g_thumbs.loadThumbsImages(); + } + + }else{ + + if(g_temp.isTilesMode == true){ + + //check if dynamic size changed. If do, run the thumbs again + var isChanged = initGridDynamicSize(); + + if(isChanged == true){ + initSizeParams(); + g_tilesDesign.run(); + } + } + + } + + positionThumbs(); + + if(g_temp.isFirstTimeRun == true && g_temp.isTilesMode){ + + var objTiles = g_thumbs.getThumbs(); + + //fire size change event + objTiles.each(function(index, tile){ + + g_objWrapper.trigger(g_temp.eventSizeChange, jQuery(tile)); + }); + + objTiles.fadeTo(0,1); + } + + if(selectedItem != null) + scrollToThumb(selectedItem.index); + + //trigger pane change event on the start + g_objThis.trigger(t.events.PANE_CHANGE, g_temp.currentPane); + + g_temp.isFirstTimeRun = false; + } + + /** + * get thumb size object + */ + function getThumbsSize(){ + if(g_temp.isTilesMode == true) + var objThumbSize = g_tilesDesign.getGlobalTileSize(); + else + var objThumbSize = g_thumbs.getGlobalThumbSize(); + + return(objThumbSize); + } + + + /** + * init grid dynamic size (tiles mode) + */ + function initGridDynamicSize(){ + + if(g_temp.isTilesMode == false) + throw new Error("Dynamic size can be set only in tiles mode"); + + var isChanged = false; + var isMobile = g_gallery.isMobileMode(); + + //--- set space between cols and rows + + var spaceOld = g_temp.spaceBetweenCols; + + if(isMobile == true){ + g_temp.spaceBetweenCols = g_options.grid_space_between_mobile; + g_temp.spaceBetweenRows = g_options.grid_space_between_mobile; + }else{ + g_temp.spaceBetweenCols = g_options.grid_space_between_cols; + g_temp.spaceBetweenRows = g_options.grid_space_between_rows; + } + + if(g_temp.spaceBetweenCols != spaceOld) + isChanged = true; + + //set tile size + + var lastThumbSize = getThumbsSize(); + var lastThumbWidth = lastThumbSize.width; + + var tileWidth = g_temp.tileMaxWidth; + var numCols = g_functions.getNumItemsInSpace(g_temp.gridWidth, g_temp.tileMaxWidth, g_temp.spaceBetweenCols); + + if(numCols < g_options.grid_min_cols){ + tileWidth = g_functions.getItemSizeInSpace(g_temp.gridWidth, g_options.grid_min_cols, g_temp.spaceBetweenCols); + } + + g_tilesDesign.setTileSizeOptions(tileWidth); + + if(tileWidth != lastThumbWidth) + isChanged = true; + + + return(isChanged); + } + + + /** + * init grid size horizontal + * get height param + */ + function initSizeParamsHor(){ + + var objThumbSize = getThumbsSize(); + + var thumbsHeight = objThumbSize.height; + + //set grid size + var gridWidth = g_temp.gridWidth; + var gridHeight = g_options.grid_num_rows * thumbsHeight + (g_options.grid_num_rows-1) * g_temp.spaceBetweenRows + g_options.grid_padding*2; + + g_temp.gridHeight = gridHeight; + + g_functions.setElementSize(g_objGrid, gridWidth, gridHeight); + + //set inner size (as grid size, will be corrected after placing thumbs + g_functions.setElementSize(g_objInner, gridWidth, gridHeight); + + //set initial inner size params + g_temp.innerWidth = gridWidth; + g_temp.innerHeight = gridHeight; + } + + + /** + * init size params vertical + */ + function initSizeParamsVert(){ + + var objThumbSize = getThumbsSize(); + + var thumbsWidth = objThumbSize.width; + + //set grid size + var gridWidth = g_options.grid_num_cols * thumbsWidth + (g_options.grid_num_cols-1) * g_temp.spaceBetweenCols + g_options.grid_padding*2; + var gridHeight = g_temp.gridHeight; + + g_temp.gridWidth = gridWidth; + + g_functions.setElementSize(g_objGrid, gridWidth, gridHeight); + + //set inner size (as grid size, will be corrected after placing thumbs + g_functions.setElementSize(g_objInner, gridWidth, gridHeight); + + //set initial inner size params + g_temp.innerWidth = gridWidth; + g_temp.innerHeight = gridHeight; + + } + + /** + * init grid size + */ + function initSizeParams(){ + + if(g_temp.isHorizontal == false) + initSizeParamsVert(); + else + initSizeParamsHor(); + + } + + + + /** + * goto pane by index + */ + function scrollToThumb(thumbIndex){ + + var paneIndex = getPaneIndexByThumbIndex(thumbIndex); + if(paneIndex == -1) + return(false); + + t.gotoPane(paneIndex, "scroll"); + + } + + + /** + * set the options of the strip + */ + function setOptions(objOptions){ + + g_options = jQuery.extend(g_options, objOptions); + + g_thumbs.setOptions(objOptions); + + //set vertical or horizon + g_temp.isNavigationVertical = (g_options.grid_panes_direction == "top" || g_options.grid_panes_direction == "bottom") + + g_temp.spaceBetweenCols = g_options.grid_space_between_cols; + g_temp.spaceBetweenRows = g_options.grid_space_between_rows; + + } + + + /** + * position the thumbs and init panes horizontally + */ + function positionThumb_hor(){ + + var arrThumbs = g_objInner.children(".ug-thumb-wrapper"); + + var posx = 0; + var posy = 0; + var counter = 0; + var baseX = 0; + var maxx = 0, maxy = 0; + g_temp.innerWidth = 0; + g_temp.numPanes = 1; + g_temp.arrPanes = []; + g_temp.numThumbsInPane = 0; + + //set first pane position + g_temp.arrPanes.push(baseX); + + var numThumbs = arrThumbs.length; + + for(i=0;i < numThumbs; i++){ + var objThumb = jQuery(arrThumbs[i]); + g_functions.placeElement(objThumb, posx, posy); + + var thumbWidth = objThumb.outerWidth(); + var thumbHeight = objThumb.outerHeight(); + + //count maxx + if(posx > maxx) + maxx = posx; + + //count maxy + var endY = posy + thumbHeight; + if(endY > maxy) + maxy = endY; + + //count maxx end + var endX = maxx + thumbWidth; + if(endX > g_temp.innerWidth) + g_temp.innerWidth = endX; + + posx += thumbWidth + g_temp.spaceBetweenCols; + + //next row + counter++; + if(counter >= g_options.grid_num_cols){ + posy += thumbHeight + g_temp.spaceBetweenRows; + posx = baseX; + counter = 0; + } + + //count number thumbs in pane + if(g_temp.numPanes == 1) + g_temp.numThumbsInPane++; + + //prepare next pane + if((posy + thumbHeight) > g_temp.gridHeight){ + posy = 0; + baseX = g_temp.innerWidth + g_temp.spaceBetweenCols; + posx = baseX; + counter = 0; + + //correct max height size (do it once only) + if(g_temp.isMaxHeight == true && g_temp.numPanes == 1){ + g_temp.gridHeight = maxy; + g_objGrid.height(g_temp.gridHeight); + } + + //save next pane props (if exists) + if(i < (numThumbs - 1)){ + g_temp.numPanes++; + + //set next pane position + g_temp.arrPanes.push(baseX); + + } + } + } + + + //set inner strip width and height + g_objInner.width(g_temp.innerWidth); + + //set grid height + if(g_temp.isMaxHeight == true && g_temp.numPanes == 1){ + g_temp.gridHeight = maxy; + g_objGrid.height(maxy); + } + + } + + + /** + * position the thumbs and init panes vertically + */ + function positionThumb_vert(){ + var arrThumbs = g_objInner.children(".ug-thumb-wrapper"); + + var posx = 0; + var posy = 0; + var maxy = 0; + var counter = 0; + var baseX = 0; + var paneStartY = 0; + + g_temp.innerWidth = 0; + g_temp.numPanes = 1; + g_temp.arrPanes = []; + g_temp.numThumbsInPane = 0; + + //set first pane position + g_temp.arrPanes.push(baseX); + + var numThumbs = arrThumbs.length; + + for(i=0;i < numThumbs; i++){ + var objThumb = jQuery(arrThumbs[i]); + g_functions.placeElement(objThumb, posx, posy); + + var thumbWidth = objThumb.outerWidth(); + var thumbHeight = objThumb.outerHeight(); + + posx += thumbWidth + g_temp.spaceBetweenCols; + + var endy = (posy + thumbHeight); + if(endy > maxy) + maxy = endy; + + //next row + counter++; + if(counter >= g_options.grid_num_cols){ + posy += thumbHeight + g_temp.spaceBetweenRows; + posx = baseX; + counter = 0; + } + + //count number thumbs in pane + if(g_temp.numPanes == 1) + g_temp.numThumbsInPane++; + + //prepare next pane + endy = (posy + thumbHeight); + var paneMaxY = paneStartY + g_temp.gridHeight; + + //advance next pane + if(endy > paneMaxY){ + + //correct max height size (do it once only) + if(g_temp.isMaxHeight == true && g_temp.numPanes == 1){ + g_temp.gridHeight = maxy; + g_objGrid.height(g_temp.gridHeight); + paneMaxY = g_temp.gridHeight; + } + + posy = paneMaxY + g_temp.spaceBetweenRows; + paneStartY = posy; + baseX = 0; + posx = baseX; + counter = 0; + + //save next pane props (if exists) + if(i < (numThumbs - 1)){ + g_temp.numPanes++; + + //set next pane position + g_temp.arrPanes.push(posy); + + } + } + + }//for + + //set inner height + g_objInner.height(maxy); + g_temp.innerHeight = maxy; + + //set grid height + if(g_temp.isMaxHeight == true && g_temp.numPanes == 1){ + g_temp.gridHeight = maxy; + g_objGrid.height(maxy); + } + + } + + + /** + * position the thumbs horizontal type + */ + function positionThumb_hortype(){ + + var arrThumbs = g_objInner.children(".ug-thumb-wrapper"); + + var baseX = g_options.grid_padding; + var baseY = g_options.grid_padding; + var posy = baseY; + var posx = baseX; + var maxx = 0, maxy = 0, paneMaxY = 0, gridMaxY = 0; + var rowsCounter = 0; + + g_temp.innerWidth = 0; + g_temp.numPanes = 1; + g_temp.arrPanes = []; + g_temp.numThumbsInPane = 0; + + //set first pane position + g_temp.arrPanes.push(baseX-g_options.grid_padding); + + var numThumbs = arrThumbs.length; + + for(i=0;i < numThumbs; i++){ + var objThumb = jQuery(arrThumbs[i]); + + var thumbWidth = objThumb.outerWidth(); + var thumbHeight = objThumb.outerHeight(); + + //check end of the size, start a new row + if((posx - baseX + thumbWidth) > g_temp.gridWidth){ + rowsCounter++; + posy = 0; + + if(rowsCounter >= g_options.grid_num_rows){ + + //change to a new pane + rowsCounter = 0; + baseX = posx; + posy = baseY; + paneMaxY = 0; + + //change grid width to max width + if(g_temp.numPanes == 1){ + g_temp.gridWidth = maxx+g_options.grid_padding; + g_objGrid.width(g_temp.gridWidth); + + g_temp.gridHeight = gridMaxY + g_options.grid_padding; + g_objGrid.height(g_temp.gridHeight); + + } + + g_temp.numPanes++; + g_temp.arrPanes.push(baseX-g_options.grid_padding); + + }else{ //start new line in existing pane + posx = baseX; + posy = paneMaxY + g_temp.spaceBetweenRows; + } + } + + //place the thumb + g_functions.placeElement(objThumb, posx, posy); + + //count maxx + var endX = posx + thumbWidth; + if(endX > maxx) + maxx = endX; + + //count maxy + var endY = posy + thumbHeight; + + if(endY > paneMaxY) //pane height + paneMaxY = endY; + + if(endY > gridMaxY) //total height + gridMaxY = endY; + + if(endY > maxy) + maxy = endY; + + //count maxx end + var endX = maxx + thumbWidth; + if(endX > g_temp.innerWidth) + g_temp.innerWidth = endX; + + posx += thumbWidth + g_temp.spaceBetweenCols; + + //count number thumbs in pane + if(g_temp.numPanes == 1) + g_temp.numThumbsInPane++; + + + }//end for + + //set inner strip width and height + g_temp.innerWidth = maxx + g_options.grid_padding; + g_temp.innerHeight = gridMaxY + g_options.grid_padding; + + g_objInner.width(g_temp.innerWidth); + g_objInner.height(g_temp.innerHeight); + + + //set grid height + if(g_temp.numPanes == 1){ + g_temp.gridWidth = maxx + g_options.grid_padding; + g_temp.gridHeight = gridMaxY + g_options.grid_padding; + + g_objGrid.width(g_temp.gridWidth); + g_objGrid.height(g_temp.gridHeight); + + } + + + } + + + /** + * position the thumbs and init panes related and width related vars + */ + function positionThumbs(){ + + if(g_temp.isHorizontal == false){ //position vertical type + + if(g_temp.isNavigationVertical) + positionThumb_vert(); + else + positionThumb_hor(); + + }else{ + positionThumb_hortype(); + } + + } + + + /** + * validate thumb index + */ + function validateThumbIndex(thumbIndex){ + + if(thumbIndex < 0 || thumbIndex >= g_temp.numThumbs){ + throw new Error("Thumb not exists: " + thumbIndex); + return(false); + } + + return(true); + } + + + /** + * + * validate that the pane index exists + */ + function validatePaneIndex(paneIndex){ + + if(paneIndex >= g_temp.numPanes || paneIndex < 0){ + throw new Error("Pane " + index + " doesn't exists."); + return(false); + } + + return(true); + } + + /** + * validate inner position + */ + function validateInnerPos(pos){ + + var absPos = Math.abs(pos); + + if(g_temp.isNavigationVertical == false){ + + if(absPos < 0 || absPos >= g_temp.innerWidth){ + throw new Error("wrong inner x position: " + pos); + return(false); + } + + }else{ + + if(absPos < 0 || absPos >= g_temp.innerHeight){ + throw new Error("wrong inner y position: " + pos); + return(false); + } + + } + + return(true); + } + + + + + /** + * + * set inner strip position + */ + function setInnerPos(pos){ + + var objCss = getInnerPosObj(pos); + if(objCss == false) + return(false); + + g_objInner.css(objCss); + } + + + /** + * animate inner to some position + */ + function animateInnerTo(pos){ + + var objCss = getInnerPosObj(pos); + if(objCss == false) + return(false); + + g_objInner.stop(true).animate(objCss ,{ + duration: g_options.grid_transition_duration, + easing: g_options.grid_transition_easing, + queue: false + }); + + } + + /** + * animate back to current pane + */ + function animateToCurrentPane(){ + + var innerPos = -g_temp.arrPanes[g_temp.currentPane]; + animateInnerTo(innerPos); + } + + + + function __________GETTERS_________(){}; + + /** + * get inner object size according the orientation + */ + function getInnerSize(){ + + if(g_temp.isNavigationVertical == true) + return(g_temp.innerHeight); + else + return(g_temp.innerWidth); + } + + + /** + * get pane width or height according the orientation + */ + function getPaneSize(){ + + if(g_temp.isNavigationVertical == true) + return(g_temp.gridHeight); + else + return(g_temp.gridWidth); + } + + + /** + * get object of iner position move + */ + function getInnerPosObj(pos){ + + var obj = {}; + if(g_temp.isNavigationVertical == true) + obj.top = pos + "px"; + else + obj.left = pos + "px"; + + return(obj); + } + + + /** + * get mouse position according the orientation + */ + function getMousePos(event){ + + var mousePos = g_functions.getMousePosition(event); + + if(g_temp.isNavigationVertical == true) + return(mousePos.pageY); + else + return(mousePos.pageX); + + } + + + /** + * get inner position according the orientation + */ + function getInnerPos(){ + + var objSize = g_functions.getElementSize(g_objInner); + + if(g_temp.isNavigationVertical == true) + return(objSize.top); + else + return(objSize.left); + + } + + /** + * get pane by thumb index + */ + function getPaneIndexByThumbIndex(thumbIndex){ + + //validate thumb index + if(validateThumbIndex(thumbIndex) == false) + return(-1); + + var numPane = Math.floor(thumbIndex / g_temp.numThumbsInPane); + + return(numPane); + } + + /** + * get position of some pane + */ + function getPanePosition(index){ + + var pos = g_temp.arrPanes[index]; + return(pos); + } + + + /** + * return if passed some significant movement, for thumb click + */ + function isSignificantPassed(){ + + if(g_temp.numPanes == 1) + return(false); + + var objData = g_functions.getStoredEventData(g_temp.storedEventID); + + var passedTime = objData.diffTime; + + var currentInnerPos = getInnerPos(); + var passedDistanceAbs = Math.abs(currentInnerPos - objData.startInnerPos); + + if(passedDistanceAbs > 30) + return(true); + + if(passedDistanceAbs > 5 && passedTime > 300) + return(true); + + return(false); + } + + + + /** + * check if the movement that was held is valid for pane change + */ + function isMovementValidForChange(){ + + var objData = g_functions.getStoredEventData(g_temp.storedEventID); + + //check position, if more then half, move + var currentInnerPos = getInnerPos(); + diffPos = Math.abs(objData.startInnerPos - currentInnerPos); + + var paneSize = getPaneSize(); + var breakSize = Math.round(paneSize * 3 / 8); + + if(diffPos >= breakSize) + return(true); + + if(objData.diffTime < 300 && diffPos > 25) + return(true); + + return(false); + } + + + /** + * return if passed some significant movement + */ + function isApproveTileClick(){ + + if(g_temp.numPanes == 1) + return(true); + + var isApprove = g_functions.isApproveStoredEventClick(g_temp.storedEventID, g_temp.isNavigationVertical); + + return(isApprove); + } + + + function __________EVENTS_______(){}; + + + /** + * on thumb click event + */ + function onThumbClick(event){ + + //event.preventDefault(); + if(isSignificantPassed() == true) + return(true); + + //run select item operation + var objThumb = jQuery(this); + var objItem = g_thumbs.getItemByThumb(objThumb); + + g_gallery.selectItem(objItem); + } + + + /** + * on touch start + */ + function onTouchStart(event){ + + if(g_temp.numPanes == 1) + return(true); + + if(g_temp.touchActive == true) + return(true); + + if(g_temp.isTilesMode == false) + event.preventDefault(); + + g_temp.touchActive = true; + + var addData = { + startInnerPos: getInnerPos() + }; + + g_functions.storeEventData(event, g_temp.storedEventID, addData); + + } + + + /** + * handle scroll top, return if scroll mode or not + */ + function handleScrollTop(){ + + if(g_options.grid_vertical_scroll_ondrag == false) + return(false); + + if(g_temp.isNavigationVertical == true) + return(false); + + var scrollDir = g_functions.handleScrollTop(g_temp.storedEventID); + + if(scrollDir === "vert") + return(true); + + return(false); + } + + + /** + * on touch move + */ + function onTouchMove(event){ + + if(g_temp.touchActive == false) + return(true); + + event.preventDefault(); + + g_functions.updateStoredEventData(event, g_temp.storedEventID); + + var objData = g_functions.getStoredEventData(g_temp.storedEventID, g_temp.isNavigationVertical); + + //check if was vertical scroll + var isScroll = handleScrollTop(); + if(isScroll) + return(true); + + + var diff = objData.diffMousePos; + var innerPos = objData.startInnerPos + diff; + var direction = (diff > 0) ? "prev":"next"; + var lastPaneSize = g_temp.arrPanes[g_temp.numPanes-1]; + + //slow down when off limits + if(g_options.grid_carousel == false && innerPos > 0 && direction == "prev"){ + innerPos = innerPos / 3; + } + + //debugLine({lastSize:lastPaneSize,innerPos: innerPos}); + + if(g_options.grid_carousel == false && innerPos < -lastPaneSize && direction == "next"){ + innerPos = objData.startInnerPos + diff / 3; + } + + setInnerPos(innerPos); + + } + + + /** + * on touch end + * change panes or return to current pane + */ + function onTouchEnd(event){ + + if(g_temp.touchActive == false) + return(true); + + g_functions.updateStoredEventData(event, g_temp.storedEventID); + var objData = g_functions.getStoredEventData(g_temp.storedEventID, g_temp.isNavigationVertical); + + //event.preventDefault(); + g_temp.touchActive = false; + + if(isMovementValidForChange() == false){ + animateToCurrentPane(); + return(true); + } + + //move pane or return back + var innerPos = getInnerPos(); + var diff = innerPos - objData.startInnerPos; + var direction = (diff > 0) ? "prev":"next"; + + if(direction == "next"){ + + if(g_options.grid_carousel == false && t.isLastPane()) + animateToCurrentPane(); + else + t.nextPane(); + } + else{ + + if(g_options.grid_carousel == false && t.isFirstPane()){ + animateToCurrentPane(); + } + else + t.prevPane(); + } + + } + + + /** + * on item change + */ + function onItemChange(){ + + var objItem = g_gallery.getSelectedItem(); + g_thumbs.setThumbSelected(objItem.objThumbWrapper); + + scrollToThumb(objItem.index); + + } + + + /** + * init panel events + */ + function initEvents(){ + + if(g_temp.isTilesMode == false){ + + g_thumbs.initEvents(); + var objThumbs = g_objGrid.find(".ug-thumb-wrapper"); + objThumbs.on("click touchend",onThumbClick); + + g_objGallery.on(g_gallery.events.ITEM_CHANGE, onItemChange); + + }else{ + g_tilesDesign.initEvents(); + } + + //touch drag events + + //slider mouse down - drag start + g_objGrid.bind("mousedown touchstart",onTouchStart); + + //on body move + jQuery("body").bind("mousemove touchmove",onTouchMove); + + //on body mouse up - drag end + jQuery(window).add("body").bind("mouseup touchend", onTouchEnd); + + } + + + /** + * destroy the events + */ + this.destroy = function(){ + + if(g_temp.isTilesMode == false){ + + var objThumbs = g_objGrid.find(".ug-thumb-wrapper"); + objThumbs.off("click"); + objThumbs.off("touchend"); + g_objGallery.on(g_gallery.events.ITEM_CHANGE); + g_thumbs.destroy(); + + }else{ + g_tilesDesign.destroy(); + } + + g_objGrid.unbind("mousedown"); + g_objGrid.unbind("touchstart"); + jQuery("body").unbind("mousemove"); + jQuery("body").unbind("touchmove"); + + jQuery(window).add("body").unbind("touchend"); + jQuery(window).add("body").unbind("mouseup"); + + g_objThis.off(t.events.PANE_CHANGE); + + } + + + + this.__________EXTERNAL_GENERAL_________ = function(){}; + + /** + * set the thumb unselected state + */ + this.setThumbUnselected = function(objThumbWrapper){ + + g_thumbs.setThumbUnselected(objThumbWrapper); + + } + + /** + * check if thmb item visible, means inside the visible part of the inner strip + */ + this.isItemThumbVisible = function(objItem){ + var itemIndex = objItem.index; + var paneIndex = getPaneIndexByThumbIndex(itemIndex); + + if(paneIndex == g_temp.currentPane) + return(true); + + return(false); + } + + + this.__________EXTERNAL_API_________ = function(){}; + + /** + * get estimation of number of panes by the height of the grid. + */ + this.getNumPanesEstimationByHeight = function(gridHeight){ + + if(g_temp.isTilesMode == true){ + + var thumbHeight = g_options.tile_height; + + }else{ + var thumbsOptions = g_thumbs.getOptions(); + var thumbHeight = thumbsOptions.thumb_height; + } + + var numThumbs = g_thumbs.getNumThumbs(); + var numRows = Math.ceil(numThumbs / g_options.grid_num_cols); + + var totalHeight = numRows * thumbHeight + (numRows-1) * g_temp.spaceBetweenRows; + + var numPanes = Math.ceil(totalHeight / gridHeight); + + return(numPanes); + } + + /** + * get estimation of number of panes by the width of the grid. + */ + this.getNumPanesEstimationByWidth = function(gridWidth){ + + if(g_temp.isTilesMode){ + var thumbWidth = g_options.tile_width; + }else{ + var thumbsOptions = g_thumbs.getOptions(); + var thumbWidth = thumbsOptions.thumb_width; + } + + var numThumbs = g_thumbs.getNumThumbs(); + var numCols = Math.ceil(numThumbs / g_options.grid_num_rows); + + var totalWidth = numCols * thumbWidth + (numCols-1) * g_temp.spaceBetweenCols; + + var numPanes = Math.ceil(totalWidth / gridWidth); + + return(numPanes); + } + + + /** + * get height estimation by width, works only in tiles mode + */ + this.getHeightEstimationByWidth = function(width){ + + if(g_temp.isTilesMode == false) + throw new Error("This function works only with tiles mode"); + + var numThumbs = g_thumbs.getNumThumbs(); + var numCols = g_functions.getNumItemsInSpace(width, g_options.tile_width, g_temp.spaceBetweenCols); + var numRows = Math.ceil(numThumbs / numCols); + + if(numRows > g_options.grid_num_rows) + numRows = g_options.grid_num_rows; + + var gridHeight = g_functions.getSpaceByNumItems(numRows, g_options.tile_height, g_temp.spaceBetweenRows); + gridHeight += g_options.grid_padding * 2; + + return(gridHeight); + } + + /** + * get the grid element + */ + this.getElement = function(){ + return(g_objGrid); + } + + /** + * get element size and position + */ + this.getSize = function(){ + + var objSize = g_functions.getElementSize(g_objGrid); + return(objSize); + + } + + /** + * get number of panes + */ + this.getNumPanes = function(){ + + return(g_temp.numPanes); + } + + /** + * get if the current pane is first + */ + this.isFirstPane = function(){ + + if(g_temp.currentPane == 0) + return(true); + + return(false); + } + + + /** + * get if the current pane is last + */ + this.isLastPane = function(){ + + if(g_temp.currentPane == (g_temp.numPanes -1) ) + return(true); + + return(false); + } + + + /** + * get pane number, and num panes + */ + this.getPaneInfo = function(){ + + var obj = { + pane: g_temp.currentPane, + total: g_temp.numPanes + }; + + return(obj); + } + + + /** + * get current pane + */ + this.getPane = function(){ + + return(g_temp.currentPane); + } + + + /** + * set grid width (horizontal type) + */ + this.setWidth = function(gridWidth){ + g_temp.gridWidth = gridWidth; + g_temp.isHorizontal = true; + } + + /** + * set max width, the width will be corrected by the number of items + * set vertical type + */ + this.setMaxWidth = function(maxWidth){ + g_temp.gridWidth = maxWidth; + g_temp.isMaxWidth = true; + g_temp.isHorizontal = true; + } + + + /** + * set grid height (vertical type) + */ + this.setHeight = function(gridHeight){ + g_temp.gridHeight = gridHeight; + g_temp.isHorizontal = false; + + } + + /** + * set max height, the height will be corrected by the number of items + * set the vertical type + */ + this.setMaxHeight = function(maxHeight){ + g_temp.gridHeight = maxHeight; + g_temp.isMaxHeight = true; + g_temp.isHorizontal = false; + } + + + /** + * goto some pane + * force skip current pane checks + */ + this.gotoPane = function(index, fromWhere){ + + if(validatePaneIndex(index) == false) + return(false); + + if(index == g_temp.currentPane) + return(false); + + var innerPos = -g_temp.arrPanes[index]; + + g_temp.currentPane = index; + animateInnerTo(innerPos); + + //trigger pane change event + g_objThis.trigger(t.events.PANE_CHANGE, index); + } + + + /** + * foreward to the next pane + */ + this.nextPane = function(){ + + var nextPaneIndex = g_temp.currentPane+1; + + if(nextPaneIndex >= g_temp.numPanes){ + + if(g_options.grid_carousel == false) + return(true); + + nextPaneIndex = 0; + } + + t.gotoPane(nextPaneIndex, "next"); + } + + + /** + * foreward to the next pane + */ + this.prevPane = function(){ + + var prevPaneIndex = g_temp.currentPane-1; + if(prevPaneIndex < 0){ + prevPaneIndex = g_temp.numPanes-1; + + if(g_options.grid_carousel == false) + return(false); + } + + t.gotoPane(prevPaneIndex, "prev"); + } + + + /** + * set next pane button + */ + this.attachNextPaneButton = function(objButton){ + + g_functions.setButtonOnClick(objButton, t.nextPane); + + if(g_options.grid_carousel == true) + return(true); + + if(t.isLastPane()) + objButton.addClass("ug-button-disabled"); + + //set disabled button class if first pane + g_objThis.on(t.events.PANE_CHANGE, function(){ + + if(t.isLastPane()) + objButton.addClass("ug-button-disabled"); + else + objButton.removeClass("ug-button-disabled"); + + }); + + } + + + /** + * set prev pane button + */ + this.attachPrevPaneButton = function(objButton){ + + g_functions.setButtonOnClick(objButton, t.prevPane); + + if(g_options.grid_carousel == true) + return(true); + + if(t.isFirstPane()) + objButton.addClass("ug-button-disabled"); + + //set disabled button class if first pane + g_objThis.on(t.events.PANE_CHANGE, function(){ + + if(t.isFirstPane()) + objButton.addClass("ug-button-disabled"); + else + objButton.removeClass("ug-button-disabled"); + + }); + + } + + + /** + * attach bullets object + */ + this.attachBullets = function(objBullets){ + + objBullets.setActive(g_temp.currentPane); + + jQuery(objBullets).on(objBullets.events.BULLET_CLICK, function(data, numBullet){ + t.gotoPane(numBullet, "theme"); + objBullets.setActive(numBullet); + }); + + jQuery(t).on(t.events.PANE_CHANGE, function(data, numPane){ + objBullets.setActive(numPane); + }); + + } + + + /** + * get tile design object + */ + this.getObjTileDesign = function(){ + return g_tilesDesign; + } + + + /** + * init function + */ + this.init = function(gallery, customOptions, isTilesMode){ + + init(gallery, customOptions, isTilesMode); + } + + + + /** + * set html and properties + */ + this.run = function(){ + run(); + } + + + /** + * set html + */ + this.setHtml = function(parentContainer){ + + setHtml(parentContainer); + } + +} + + + +/** + * tiles class + */ +function UGTiles(){ + + var t = this, g_objThis = jQuery(this); + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objWrapper, g_objParent; + var g_functions = new UGFunctions(), g_arrItems, g_objTileDesign = new UGTileDesign(); + var g_thumbs = new UGThumbsGeneral(), g_vars = {}; + var g_arrNestedGridRow, g_arrNestedItems; + + + var g_options = { + tiles_type: "columns", //columns / justified - tiles layout type + tiles_col_width: 250, //column width - exact or base according the settings + tiles_align:"center", //align of the tiles in the space + tiles_exact_width: false, //exact width of column - disables the min and max columns + tiles_space_between_cols: 3, //space between images + tiles_space_between_cols_mobile: 3, //space between cols for mobile type + tiles_include_padding: true, //include padding at the sides of the columns, equal to current space between cols + tiles_min_columns: 2, //min columns + tiles_max_columns: 0, //max columns (0 for unlimited) + tiles_keep_order: false, //keep order - slower algorytm + tiles_set_initial_height: true, //set some estimated height before images show + + tiles_justified_row_height: 150, //base row height of the justified type + tiles_justified_space_between: 3, //space between the tiles justified type + + tiles_nested_optimal_tile_width: 250, // tiles optimal width + tiles_nested_col_width: null, // nested tiles column width + tiles_nested_debug: false, + + tiles_enable_transition: true //enable transition when screen width change + }; + + this.events = { + THUMB_SIZE_CHANGE: "thumb_size_change", + TILES_FIRST_PLACED: "tiles_first_placed", //only in case of justified + ALL_TILES_LOADED: "all_tiles_loaded" + }; + + var g_temp = { + isFirstTimeRun:true, //if run once + handle:null, //interval handle + isTransActive: false, //is transition active + isTransInited: false, //if the transition function is set + isFirstPlaced: true, //is first time placed + isAllLoaded: false + }; + + var g_nestedWork = { + colWidth: null, + nestedOptimalCols: 5, + gridY: 0, + maxColumns: 0, //maxColumns + columnsValueToEnableHeightResize: 3, //columns Value To Enable Height Resize + resizeLeftRightToColumn: true, + currentItem: 0, + currentGap: null, + optimalTileWidth: null, + maxGridY:0 + } + + + function __________GENERAL_________(){}; + + + /** + * init the gallery + */ + function init(gallery, customOptions){ + + g_objects = gallery.getObjects(); + g_gallery = gallery; + g_objGallery = jQuery(gallery); + g_objWrapper = g_objects.g_objWrapper; + g_arrItems = g_objects.g_arrItems; + + g_options = jQuery.extend(g_options, customOptions); + + modifyOptions(); + + g_objTileDesign.init(gallery, g_options); + + g_thumbs = g_objTileDesign.getObjThumbs(); + + } + + + /** + * modify options + */ + function modifyOptions(){ + + if(g_options.tiles_min_columns < 1) + g_options.tiles_min_columns = 1; + + //protection of max columns, can't be more then min columns + if(g_options.tiles_max_columns != 0 && g_options.tiles_max_columns < g_options.tiles_min_columns){ + g_options.tiles_max_columns = g_options.tiles_min_columns; + } + + } + + + /** + * set the grid panel html + */ + function setHtml(objParent){ + + if(!objParent){ + if(g_objParent) + objParent = g_objParent; + else + var objParent = g_objWrapper; + } + + g_objParent = objParent; + + var tilesType = g_options.tiles_type; + objParent.addClass("ug-tiletype-"+tilesType); + + g_objTileDesign.setHtml(objParent); + + objParent.children(".ug-thumb-wrapper").hide(); + } + + + /** + * set class that enables transition + */ + function setTransition(){ + + //set the tiles in resting mode, to activate their own transitions + g_objParent.addClass("ug-tiles-rest-mode"); + + g_temp.isTransInited = true; + + //add css tansition + if(g_options.tiles_enable_transition == true){ + g_objParent.addClass("ug-tiles-transit"); + + //add image overlay transition + var optionsTile = g_objTileDesign.getOptions(); + + if(optionsTile.tile_enable_image_effect == true && optionsTile.tile_image_effect_reverse == false) + g_objParent.addClass("ug-tiles-transit-overlays"); + + g_temp.isTransActive = true; + } + + } + + + /** + * get parent width + */ + function getParentWidth(){ + return g_functions.getElementSize(g_objParent).width; + } + + + /** + * do some actions before transition + */ + function doBeforeTransition(){ + + if(g_temp.isTransInited == false) + return(false); + + g_objParent.addClass("ug-tiles-transition-active"); + g_objParent.removeClass("ug-tiles-rest-mode"); + + //prepare for transition + if(g_temp.isTransActive == false) + return(false); + + g_objTileDesign.disableEvents(); + } + + + /** + * set after transition classes + */ + function doAfterTransition_setClasses(){ + + if(g_temp.isTransInited == false) + return(false); + + g_objParent.removeClass("ug-tiles-transition-active"); + g_objParent.addClass("ug-tiles-rest-mode"); + } + + + /** + * do some actions after transition + */ + function doAfterTransition(){ + + + if(g_temp.isTransActive == true){ + + //trigger size change after transition + setTimeout(function(){ + + g_objTileDesign.enableEvents(); + g_objTileDesign.triggerSizeChangeEventAllTiles(); + + doAfterTransition_setClasses(); + + + }, 800); + + //control size change + if(g_temp.handle) + clearTimeout(g_temp.handle); + + g_temp.handle = setTimeout(function(){ + + doAfterTransition_setClasses(); + + g_objTileDesign.triggerSizeChangeEventAllTiles(); + g_temp.handle = null; + + }, 2000); + + + }else{ + + g_objTileDesign.triggerSizeChangeEventAllTiles(); + + doAfterTransition_setClasses(); + + } + + } + + + function __________COLUMN_TYPE_RELATED_________(){}; + + /** + * count width by number of columns + */ + function fillTilesVars_countWidthByCols(){ + + g_vars.colWidth = (g_vars.availWidth - g_vars.colGap * (g_vars.numCols-1)) / g_vars.numCols; + g_vars.colWidth = Math.floor(g_vars.colWidth); + + g_vars.totalWidth = g_functions.getSpaceByNumItems(g_vars.numCols, g_vars.colWidth, g_vars.colGap); + + } + + + + /** + * fill common tiles vars + */ + function fillTilesVars(){ + + g_vars.colWidth = g_options.tiles_col_width; + g_vars.minCols = g_options.tiles_min_columns; + g_vars.maxCols = g_options.tiles_max_columns; + + if(g_gallery.isMobileMode() == false){ + g_vars.colGap = g_options.tiles_space_between_cols; + } else { + g_vars.colGap = g_options.tiles_space_between_cols_mobile; + } + + //set gallery width + g_vars.galleryWidth = getParentWidth(); + + g_vars.availWidth = g_vars.galleryWidth; + + if(g_options.tiles_include_padding == true) + g_vars.availWidth = g_vars.galleryWidth - g_vars.colGap*2; + + //set the column number by exact width + if(g_options.tiles_exact_width == true){ + + g_vars.numCols = g_functions.getNumItemsInSpace(g_vars.availWidth, g_vars.colWidth, g_vars.colGap); + + if(g_vars.maxCols > 0 && g_vars.numCols > g_vars.maxCols) + g_vars.numCols = g_vars.maxCols; + + //if less then min cols count width by cols + if(g_vars.numCols < g_vars.minCols){ + g_vars.numCols = g_vars.minCols; + + fillTilesVars_countWidthByCols(); + + }else{ + g_vars.totalWidth = g_vars.numCols * (g_vars.colWidth + g_vars.colGap) - g_vars.colGap; + } + + } else { + + //set dynamic column number + + var numCols = g_functions.getNumItemsInSpaceRound(g_vars.availWidth, g_vars.colWidth, g_vars.colGap); + + if(numCols < g_vars.minCols) + numCols = g_vars.minCols; + else + if(g_vars.maxCols != 0 && numCols > g_vars.maxCols) + numCols = g_vars.maxCols; + + g_vars.numCols = numCols; + + fillTilesVars_countWidthByCols(); + + } + + switch(g_options.tiles_align){ + case "center": + default: + //add x to center point + g_vars.addX = Math.round( (g_vars.galleryWidth - g_vars.totalWidth) / 2 ); + break; + case "left": + g_vars.addX = 0; + break; + case "right": + g_vars.addX = g_vars.galleryWidth - g_vars.totalWidth; + break; + } + + //get posx array (constact to all columns) + g_vars.arrPosx = []; + for(col = 0; col < g_vars.numCols; col++){ + var colX = g_functions.getColX(col, g_vars.colWidth, g_vars.colGap); + g_vars.arrPosx[col] = colX + g_vars.addX; + } + + } + + + /** + * init col heights + */ + function initColHeights(){ + + g_vars.maxColHeight = 0; + + //empty heights array + g_vars.colHeights = [0]; + + } + + + + /** + * get column with minimal height + */ + function getTilesMinCol(){ + var numCol = 0; + + var minHeight = 999999999; + + for(col = 0; col < g_vars.numCols; col++){ + + if(g_vars.colHeights[col] == undefined || g_vars.colHeights[col] == 0) + return col; + + if(g_vars.colHeights[col] < minHeight){ + numCol = col; + minHeight = g_vars.colHeights[col]; + } + + } + + return(numCol); + } + + + /** + * place tile as it loads + */ + function placeTile(objTile, toShow, setGalleryHeight, numCol){ + + if(numCol === null || typeof numCol == "undefined") + var numCol = getTilesMinCol(); + + //set posy + var posy = 0; + if(g_vars.colHeights[numCol] !== undefined) + posy = g_vars.colHeights[numCol]; + + var itemHeight = g_objTileDesign.getTileHeightByWidth(g_vars.colWidth, objTile); + + if(itemHeight === null){ //for custom html tile + if(g_options.tiles_enable_transition == true) + throw new Error("Can't know tile height, please turn off transition"); + + var itemSize = g_functions.getElementSize(objTile); + itemHeight = itemSize.height; + } + + var posx = g_vars.arrPosx[numCol]; + + g_functions.placeElement(objTile, posx, posy); + + var realHeight = posy + itemHeight; + + g_vars.colHeights[numCol] = realHeight + g_vars.colGap; + + //set max height + if(g_vars.maxColHeight < realHeight) + g_vars.maxColHeight = realHeight; + + if(toShow == true) + objTile.show().fadeTo(0,1); + + if(setGalleryHeight == true){ + g_objParent.height(g_vars.maxColHeight); + } + + } + + + /** + * place the tiles + */ + function placeTiles(toShow){ + + if(!toShow) + toShow = false; + + fillTilesVars(); + initColHeights(); + + var objThumbs = g_thumbs.getThumbs(g_thumbs.type.GET_THUMBS_RATIO); + + //do some operation before the transition + doBeforeTransition(); + + //resize all thumbs + g_objTileDesign.resizeAllTiles(g_vars.colWidth, g_objTileDesign.resizemode.VISIBLE_ELEMENTS, objThumbs); + + //place elements + for(var index = 0; index < objThumbs.length; index++){ + var objTile = jQuery(objThumbs[index]); + var col = undefined; + if(g_options.tiles_keep_order == true) + col = g_functions.getColByIndex(g_vars.numCols, index); + + placeTile(objTile, toShow, false, col); + } + + //bring back the state after transition + doAfterTransition(); + + //set gallery height, according the transition + var galleryHeight = g_objParent.height(); + + if(g_temp.isTransActive == true && galleryHeight > g_vars.maxColHeight) + setTimeout(function(){ + g_objParent.height(g_vars.maxColHeight); + },700); + else + g_objParent.height(g_vars.maxColHeight); + } + + + /** + * check if alowed to place ordered tile + */ + function isOrderedTilePlaceAlowed(objTile){ + + var index = objTile.index(); + + //don't allow double put items + var currentItem = g_gallery.getItem(index); + if(currentItem.ordered_placed === true) + return(false); + + + var prevIndex = g_functions.getPrevRowSameColIndex(index, g_vars.numCols); + + //put first item in the column + if(prevIndex < 0) + return(true); + + //check if previous tile in column is placed + var objPrevItem = g_gallery.getItem(prevIndex); + if(objPrevItem.ordered_placed === true) + return(true); + + return(false); + } + + + /** + * place ordered tile + */ + function placeOrderedTile(objTile, isForce){ + + if(isForce !== true){ + + var isAlowed = isOrderedTilePlaceAlowed(objTile); + + if(isAlowed == false) + return(false); + } + + var index = objTile.index(); + + var col = g_functions.getColByIndex(g_vars.numCols, index); + + var objItem = g_gallery.getItem(index); + + g_objTileDesign.resizeTile(objTile, g_vars.colWidth); + + placeTile(objTile, true, true, col); + + objItem.ordered_placed = true; + + //check by recursion and place next items in column + var numItems = g_gallery.getNumItems(); + var nextIndex = g_functions.getNextRowSameColIndex(index, g_vars.numCols); + if(nextIndex >= numItems) + return(false); + + var nextTile = g_thumbs.getThumbByIndex(nextIndex); + var nextItem = g_gallery.getItem(nextIndex); + + var isLoaded = g_thumbs.isThumbLoaded(nextTile); + + if(g_thumbs.isThumbLoaded(nextTile) && !nextItem.ordered_placed) + placeOrderedTile(nextTile, true); + } + + + /** + * on single image load + */ + function onSingleImageLoad(objImage, isError){ + + if(isError == true) + return(false); + + objImage = jQuery(objImage); + var objTile = jQuery(objImage).parent(); + + g_thumbs.triggerImageLoadedEvent(objTile, objImage); + + if(g_options.tiles_keep_order == true){ + + placeOrderedTile(objTile); + + }else{ + + g_objTileDesign.resizeTile(objTile, g_vars.colWidth); + placeTile(objTile, true, true); + } + + } + + + /** + * run columns type - place tiles that are not loaded yet + */ + function runColumnsType(){ + + //get thumbs only when the ratio not set + var objThumbs = g_thumbs.getThumbs(g_thumbs.type.GET_THUMBS_NO_RATIO); + + if(!objThumbs || objThumbs.length == 0) + return(false); + + g_temp.isAllLoaded = false; + + if(g_temp.isFirstPlaced == true){ + + fillTilesVars(); + initColHeights(); + + var diffWidth = Math.abs(g_vars.galleryWidth - g_vars.totalWidth); + + //set initial height of the parent by estimation + if(g_options.tiles_set_initial_height == true && g_functions.isScrollbarExists() == false && diffWidth < 25){ + + var numThumbs = objThumbs.length; + var numRows = Math.ceil(objThumbs.length / g_vars.numCols); + var estimateHeight = numRows * g_options.tiles_col_width * 0.75; + + g_objParent.height(estimateHeight); + fillTilesVars(); + } + + } + + + objThumbs.fadeTo(0,0); + var objImages = objThumbs.find("img.ug-thumb-image"); + + var initNumCols = g_vars.numCols; + var initWidth = g_vars.galleryWidth; + + //on place the tile as it loads. After all tiles loaded,check position again. + g_functions.checkImagesLoaded(objImages, function(){ + + fillTilesVars(); + + if(initNumCols != g_vars.numCols || initWidth != g_vars.galleryWidth){ + placeTiles(false); + } + + setTransition(); + g_objThis.trigger(t.events.ALL_TILES_LOADED); + + } ,function(objImage, isError){ + + if(g_temp.isFirstPlaced == true) + g_gallery.triggerEvent(t.events.TILES_FIRST_PLACED); //set to false + + onSingleImageLoad(objImage, isError); + + }); + + + } + + + function __________JUSTIFIED_TYPE_RELATED_________(){}; + + /** + * ------------ JUSTIFIED TYPE RELATED FUNCTIONS ---------------- + */ + + function getJustifiedData(){ + + var galleryWidth = getParentWidth(); + + var objTiles = g_thumbs.getThumbs(true); + var rowHeightOpt = g_options.tiles_justified_row_height; + var arrWidths = []; + var totalWidth = 0; + var gap = g_options.tiles_justified_space_between; + var numTiles = objTiles.length; + + //get arr widths and total width + jQuery.each(objTiles, function(index, objTile){ + objTile = jQuery(objTile); + + var objItem = g_thumbs.getItemByThumb(objTile); + + var tileWidth = objItem.thumbWidth; + var tileHeight = objItem.thumbHeight; + + if (tileHeight !== rowHeightOpt) + tileWidth = Math.floor(objItem.thumbRatioByWidth * rowHeightOpt); + + arrWidths[index] = tileWidth; + + totalWidth += tileWidth; + }); + + + var numRows = Math.ceil(totalWidth / galleryWidth); + + if(numRows > numTiles) + numRows = numTiles; + + var finalRowWidth = totalWidth / numRows; + + //fill rows array, break tiles to rows + var arrRows = [], eachRowWidth = 0; + var rowsWidths = [], rowsTiles = [], row = []; + var progressWidth = 0, numRow = 0; + + jQuery.each(objTiles, function(index, objTile){ + var tileWidth = arrWidths[index]; + + if( (progressWidth + tileWidth / 2) > (numRow+1) * finalRowWidth){ + + rowsWidths[arrRows.length] = eachRowWidth; + arrRows.push(row); + row = []; + eachRowWidth = 0; + numRow++; + } + + progressWidth += tileWidth; + eachRowWidth += tileWidth; + + row.push(objTile); + }); + + rowsWidths[arrRows.length] = eachRowWidth; + arrRows.push(row); + + + //set heights and position images: + var arrRowWidths = []; + var arrRowHeights = []; + var totalHeight = 0; + + jQuery.each(arrRows, function(index, row){ + + var numTiles = row.length; + var rowWidth = rowsWidths[index]; + + var gapWidth = (row.length-1) * gap; + + var ratio = (galleryWidth - gapWidth) / rowWidth; + var rowHeight = Math.round(rowHeightOpt * ratio); + + //count total height + totalHeight += rowHeight; + if(index > 0) + totalHeight += gap; + + arrRowHeights.push(rowHeight); + + //ratio between 2 heights for fixing image width: + var ratioHeights = rowHeight / rowHeightOpt; + + //set tiles sizes: + var arrRowTileWidths = []; + var actualRowWidth = gapWidth; + + jQuery.each(row, function(indexInRow, tile){ + var objTile = jQuery(tile); + var tileIndex = objTile.index(); + var tileWidth = arrWidths[tileIndex]; + var newWidth = Math.round(tileWidth * ratioHeights); + + arrRowTileWidths[indexInRow] = newWidth; + actualRowWidth += newWidth; + }); + + //fix images widths by adding or reducing 1 pixel + var diff = actualRowWidth - galleryWidth; + + var newTotal = 0; + jQuery.each(arrRowTileWidths, function(indexInRow, width){ + + if(diff == 0) + return(false); + + if(diff < 0){ + arrRowTileWidths[indexInRow] = width + 1; + diff++; + }else{ + arrRowTileWidths[indexInRow] = width - 1; + diff--; + } + + //if at last item diff stays, add all diff + if(indexInRow == (arrRowTileWidths.length-1) && diff != 0) + arrRowTileWidths[indexInRow] -= diff; + }); + + arrRowWidths[index] = arrRowTileWidths; + }); + + + var objData = { + arrRows: arrRows, + arrRowWidths: arrRowWidths, + arrRowHeights: arrRowHeights, + gap: gap, + totalHeight: totalHeight + }; + + return(objData); + } + + + /** + * put justified images + */ + function placeJustified(toShow){ + + if(!toShow) + var toShow = false; + + var parentWidth = getParentWidth(); + + var objData = getJustifiedData(); + + //if the width changed after height change (because of scrollbar), recalculate + g_objParent.height(objData.totalHeight); + + var parentWidthAfter = getParentWidth(); + if(parentWidthAfter != parentWidth) + objData = getJustifiedData(); + + doBeforeTransition(); + + var posy = 0; + var totalWidth = 0; //just count total widht for check / print + jQuery.each(objData.arrRows, function(index, row){ + + var arrRowTileWidths = objData.arrRowWidths[index]; + var rowHeight = objData.arrRowHeights[index]; + + //resize and place tiles + var posx = 0; + jQuery.each(row, function(indexInRow, tile){ + + var objTile = jQuery(tile); + var tileHeight = rowHeight; + var tileWidth = arrRowTileWidths[indexInRow]; + + g_objTileDesign.resizeTile(objTile, tileWidth, tileHeight, g_objTileDesign.resizemode.VISIBLE_ELEMENTS); + g_functions.placeElement(objTile, posx, posy); + + posx += tileWidth; + + if(posx > totalWidth) + totalWidth = posx; + + posx += objData.gap; + + if(toShow == true) + jQuery(tile).show(); + + }); + + posy += (rowHeight + objData.gap); + + }); + + doAfterTransition(); + + } + + + + /** + * run justified type gallery + */ + function runJustifiedType(){ + + var objImages = jQuery(g_objWrapper).find("img.ug-thumb-image"); + var objTiles = g_thumbs.getThumbs(); + + g_temp.isAllLoaded = false; + + objTiles.fadeTo(0,0); + + g_functions.checkImagesLoaded(objImages, function(){ + + setTimeout(function(){ + placeJustified(true); + objTiles.fadeTo(0,1); + g_gallery.triggerEvent(t.events.TILES_FIRST_PLACED); + setTransition(); + + g_objThis.trigger(t.events.ALL_TILES_LOADED); + + }); + + }, function(objImage, isError){ + + objImage = jQuery(objImage); + var objTile = jQuery(objImage).parent(); + g_thumbs.triggerImageLoadedEvent(objTile, objImage); + + }); + + } + + + + + function __________NESTED_TYPE_RELATED_________() { }; + + + /** + * ------------ NESTED TYPE RELATED FUNCTIONS ---------------- + */ + function runNestedType() { + + var objImages = jQuery(g_objWrapper).find("img.ug-thumb-image"); + var objTiles = g_thumbs.getThumbs(); + + g_temp.isAllLoaded = false; + + objTiles.fadeTo(0, 0); + + g_functions.checkImagesLoaded(objImages, function () { + + if(g_gallery.isMobileMode() == true){ + placeTiles(true); + } + else + placeNestedImages(true); + + g_gallery.triggerEvent(t.events.TILES_FIRST_PLACED); + setTransition(); + g_objThis.trigger(t.events.ALL_TILES_LOADED); + + }, function (objImage, isError) { + + objImage = jQuery(objImage); + var objTile = jQuery(objImage).parent(); + g_thumbs.triggerImageLoadedEvent(objTile, objImage); + + }); + + } + + + /** + * fill nested vars + */ + function fillNestedVars(){ + + var galleryWidth = getParentWidth(); + g_nestedWork.galleryWidth = galleryWidth; + + g_arrNestedGridRow = {}; + g_nestedWork.colWidth = g_options.tiles_nested_col_width; + g_nestedWork.optimalTileWidth = g_options.tiles_nested_optimal_tile_width; + + g_nestedWork.currentGap = g_options.tiles_space_between_cols; + + if(g_gallery.isMobileMode() == true) + g_nestedWork.currentGap = g_options.tiles_space_between_cols_mobile; + + if(g_nestedWork.colWidth == null){ + g_nestedWork.colWidth = Math.floor(g_nestedWork.optimalTileWidth/g_nestedWork.nestedOptimalCols); + } else if (g_nestedWork.optimalTileWidth > g_nestedWork.colWidth) { + g_nestedWork.nestedOptimalCols = Math.ceil(g_nestedWork.optimalTileWidth / g_nestedWork.colWidth); + } else { + g_nestedWork.nestedOptimalCols = 1; + } + + g_nestedWork.maxColumns = g_functions.getNumItemsInSpace(galleryWidth, g_nestedWork.colWidth, g_nestedWork.currentGap); + + //fix col width - justify tiles + g_nestedWork.colWidth = g_functions.getItemSizeInSpace(galleryWidth, g_nestedWork.maxColumns, g_nestedWork.currentGap); + + g_nestedWork.gridY = 0; + g_arrNestedItems = [] + + var objTiles = g_thumbs.getThumbs(true); + objTiles.each(function(){ + var objTile = jQuery(this); + var sizes = setNestedSize(objTile); + g_arrNestedItems.push(sizes); + }); + + if (g_nestedWork.optimalTileWidth > g_nestedWork.colWidth) { + g_nestedWork.nestedOptimalCols = Math.ceil(g_nestedWork.optimalTileWidth / g_nestedWork.colWidth); + } else { + g_nestedWork.nestedOptimalCols = 1; + } + + g_nestedWork.totalWidth = g_nestedWork.maxColumns*(g_nestedWork.colWidth+g_nestedWork.currentGap)-g_nestedWork.currentGap; + + switch(g_options.tiles_align){ + case "center": + default: + //add x to center point + g_nestedWork.addX = Math.round( (g_nestedWork.galleryWidth - g_nestedWork.totalWidth) / 2 ); + break; + case "left": + g_nestedWork.addX = 0; + break; + case "right": + g_nestedWork.addX = g_nestedWork.galleryWidth - g_nestedWork.totalWidth; + break; + } + + + g_nestedWork.maxGridY = 0; + } + + + /** + * place Nested type images + */ + function placeNestedImages(toShow){ + + var parentWidth = getParentWidth(); + + fillNestedVars(); + placeNestedImagesCycle(); + + var totalHeight = g_nestedWork.maxGridY * (g_nestedWork.colWidth + g_nestedWork.currentGap) - g_nestedWork.currentGap; + + //if the width changed after height change (because of scrollbar), recalculate + g_objParent.height(totalHeight); + + var parentWidthAfter = getParentWidth(); + + if(parentWidthAfter != parentWidth){ + fillNestedVars(); + placeNestedImagesCycle(); + } + + if(g_options.tiles_nested_debug == false) + drawNestedImages(toShow); + + } + + + /** + * set Nested size + */ + function setNestedSize(objTile){ + + var dimWidth, dimHeight; + var output = {}; + + var colWidth = g_nestedWork.colWidth; + var gapWidth = g_nestedWork.currentGap; + + var objImageSize = g_objTileDesign.getTileImageSize(objTile); + var index = objTile.index(); + + var maxDim = Math.ceil(getPresettedRandomByWidth(index)*(g_nestedWork.nestedOptimalCols*1/3) + g_nestedWork.nestedOptimalCols * 2/3); + + var imgWidth = objImageSize.width; + var imgHeight = objImageSize.height; + + var ratio = imgWidth/imgHeight; + + if(imgWidth>imgHeight){ + dimWidth = maxDim; + dimHeight = Math.round(dimWidth/ratio); + if(dimHeight == 0){ + dimHeight = 1; + } + } else { + dimHeight = maxDim; + dimWidth = Math.round(dimHeight*ratio); + if(dimWidth == 0){ + dimWidth = 1; + } + } + + output.dimWidth = dimWidth; + output.dimHeight = dimHeight; + output.width = dimWidth * colWidth + gapWidth*(dimWidth-1); + output.height = dimHeight * colWidth + gapWidth*(dimHeight-1); + output.imgWidth = imgWidth; + output.imgHeight = imgHeight; + output.left = 0; + output.top = 0; + return output; + } + + + /** + * get presetted random [0,1] from int + */ + function getPresettedRandomByWidth(index){ + return Math.abs(Math.sin(Math.abs(Math.sin(index)*1000))); + } + + + /** + * place nested images debug + */ + function placeNestedImagesDebug(toShow, placeOne){ + + if(placeOne == false){ + for(var i = g_nestedWork.currentItem; i=4){ + if(isGridImageAligned(Math.floor(totalFree/2), column) == true){ + resizeToNewWidth(tileID, Math.floor(totalFree/2)+1); + } else { + resizeToNewWidth(tileID, Math.floor(totalFree/2)); + } + } else { + resizeToNewWidth(objImage, totalFree); + } + } else { + if(isGridImageAligned(currentWidth, column) == true){ + switch(currentWidth>=optimalWidth){ + case true: + resizeToNewWidth(tileID, currentWidth-1); + break + case false: + resizeToNewWidth(tileID, currentWidth+1); + break + + } + } + } + + //Height stretching if needed + sizes = jQuery.extend(true, {}, g_arrNestedItems[tileID]); + var columnInfo = getGridColumnHeight(tileID, sizes.dimWidth, column); // [columnHeight, imagesIDs] + + if(g_nestedWork.columnsValueToEnableHeightResize <= columnInfo[0] && g_nestedWork.maxColumns>=2*g_nestedWork.nestedOptimalCols){ + + var sideHelper = getGridImageVerticalDifference(column, sizes); + var columnSizes = resizeToNewHeight(tileID, sideHelper.newHeight, true); + g_arrNestedItems[tileID].dimHeight = columnSizes.dimHeight; + var columnResizes = redistributeColumnItems(columnInfo, columnSizes.dimWidth, column); + var columnCrosshairs = getColumnCrosshairsCount(columnResizes); + var disableColumnResizes = false; + + if(columnCrosshairs >= 2){ + disableColumnResizes = true; + } + + if(sideHelper.newHeight>=sizes.dimHeight){ + sizes = resizeToNewHeight(tileID, sideHelper.newHeight, true); + } + var sideResizes = getSideResizeInfo(sideHelper.idToResize, sideHelper.newHeight, sizes.dimHeight); + sizes.top = g_nestedWork.gridY; + sizes.left = column; + sideResizes.push({"tileID": tileID, "sizes": sizes}); + + var sideResizesVal = calcResizeRatio(sideResizes); + var columnResizesVal = calcResizeRatio(columnResizes); + + if(sideResizesValsizes.left+sizes.dimWidth){ + topItem = g_arrNestedGridRow[sizes.top+sizes.dimHeight-1][sizes.left+sizes.dimWidth]; + bottomItem = g_arrNestedGridRow[sizes.top+sizes.dimHeight][sizes.left+sizes.dimWidth]; + } + if(topItem != bottomItem){ + crosshairsCountR++; + } + } + + for(var i = 0; i=0){ + topItem = g_arrNestedGridRow[sizes.top+sizes.dimHeight-1][sizes.left-1]; + bottomItem = g_arrNestedGridRow[sizes.top+sizes.dimHeight][sizes.left-1]; + } + if(topItem != bottomItem){ + crosshairsCountL++; + } + } + return Math.max(crosshairsCountL, crosshairsCountR); + } + + /** + * get size resize info + */ + function getSideResizeInfo(idToResize, newHeight, dimHeight){ + + var currentTile = g_arrNestedItems[idToResize]; + var tileHeight = currentTile.dimHeight; + var tileWidth = currentTile.dimWidth; + var tileLeft = currentTile.left; + var tileTop = currentTile.top; + var tileDimTop = parseInt(tileTop / (g_nestedWork.colWidth + g_nestedWork.currentGap)); + var tileDimLeft = parseInt(tileLeft / (g_nestedWork.colWidth + g_nestedWork.currentGap)); + var newSideHeight = tileHeight - newHeight + dimHeight; + + var sideSizes = resizeToNewHeight(idToResize, newSideHeight, true); + var output = []; + output.push({"tileID": idToResize, "sizes": sideSizes}); + return output; + } + + /** + * apply resizes to fix column + */ + function applyResizes(resizeTilesAndSizes){ + + for(var i = 0; i=0; i--){ + var tileID = objTiles[i][0]; + var newHeight; + if(i != 0) { + newHeight = Math.max(Math.round(columnHeight*1/3),Math.floor(objTiles[i][1] * (columnHeight / originalHeight))); + tempHeight = tempHeight - newHeight; + sizes = resizeToNewHeight(tileID, newHeight, true); + sizes.left = cordX; + sizes.top = cordY; + output.push({"tileID": tileID, "sizes": sizes}); + cordY += sizes.dimHeight; + } else { + newHeight = tempHeight; + sizes = resizeToNewHeight(tileID, newHeight, true); + sizes.left = cordX; + sizes.top = cordY; + output.push({"tileID": tileID, "sizes": sizes}); + } + } + return output; + } + + /** + * Calculate num of objects in current column and return they are ID's + */ + function getGridColumnHeight(tileID, dimWidth, column){ + var tempY = g_nestedWork.gridY-1; + var curImage = 0; + var prevImage = 0; + var columnHeight = 1; + var imagesIDs = []; + var toReturn = []; + imagesIDs.push(tileID); + if(tempY>=0){ + prevImage = 0; + while(tempY>=0){ + curImage = g_arrNestedGridRow[tempY][column]; + if( (typeof g_arrNestedGridRow[tempY][column-1] === 'undefined' || g_arrNestedGridRow[tempY][column-1] != g_arrNestedGridRow[tempY][column]) + &&(typeof g_arrNestedGridRow[tempY][column+dimWidth] === 'undefined' || g_arrNestedGridRow[tempY][column+dimWidth-1] != g_arrNestedGridRow[tempY][column+dimWidth]) + &&(g_arrNestedGridRow[tempY][column]==g_arrNestedGridRow[tempY][column+dimWidth-1])){ + if(prevImage != curImage){ + columnHeight++; + imagesIDs.push(curImage); + } + } else { + toReturn.push(columnHeight); + toReturn.push(imagesIDs); + return toReturn; + } + tempY--; + prevImage = curImage; + } + toReturn.push(columnHeight); + toReturn.push(imagesIDs); + return toReturn; + } + return [0, []]; + } + + /** + * get new height based on left and right difference + */ + function getGridImageVerticalDifference(column, sizes){ + var newHeightR = 0; + var newHeightL = 0; + var dimWidth = sizes.dimWidth; + var dimHeight = sizes.dimHeight; + var leftID = 0; + var rightID = 0; + var array = jQuery.map(g_arrNestedGridRow, function(value, index) { + return [value]; + }); + + if(typeof array[g_nestedWork.gridY] === 'undefined' || typeof array[g_nestedWork.gridY][column-1]=== 'undefined'){ + newHeightL = 0; + } else { + var tempY=0; + while(true){ + if(typeof g_arrNestedGridRow[g_nestedWork.gridY+tempY] !== 'undefined' && g_arrNestedGridRow[g_nestedWork.gridY+tempY][column-1] != -1 ){ + leftID = g_arrNestedGridRow[g_nestedWork.gridY+tempY][column-2]; + tempY++; + newHeightL++; + } else { + break; + } + } + } + + if(typeof array[g_nestedWork.gridY] === 'undefined' || typeof array[g_nestedWork.gridY][column+dimWidth]=== 'undefined'){ + newHeightR = 0; + } else { + tempY=0; + while(true){ + if(typeof g_arrNestedGridRow[g_nestedWork.gridY+tempY] !== 'undefined' && g_arrNestedGridRow[g_nestedWork.gridY+tempY][column+dimWidth] != -1 ){ + rightID = g_arrNestedGridRow[g_nestedWork.gridY+tempY][column+dimWidth+1]; + tempY++; + newHeightR++; + } else { + break; + } + } + } + + var newHeight = 0; + var idToResize = 0; + if(Math.abs(dimHeight - newHeightL) < Math.abs(dimHeight - newHeightR) && newHeightL != 0) { + newHeight = newHeightL; + idToResize = leftID; + } else if (newHeightR !=0) { + newHeight = newHeightR; + idToResize = rightID; + } else { + newHeight = dimHeight; //malo li + } + + var output = {"newHeight": newHeight, "idToResize": idToResize}; + + return output; + } + + /** + * resize to new Dim width + */ + function resizeToNewWidth(tileID, newDimWidth, toReturn) { + if(!toReturn){ + toReturn = false; + } + + var colWidth = g_nestedWork.colWidth; + var gapWidth = g_nestedWork.currentGap; + var currentTile = g_arrNestedItems[tileID]; + var imgWidth = currentTile.imgWidth; + var imgHeight = currentTile.imgHeight; + var ratio = imgWidth / imgHeight; + dimWidth = newDimWidth; + dimHeight = Math.round(dimWidth / ratio); + if(toReturn == true){ + var sizes = jQuery.extend(true, {}, currentTile); + sizes.dimWidth = dimWidth; + sizes.dimHeight = dimHeight; + sizes.width = dimWidth * colWidth + gapWidth * (dimWidth - 1); + sizes.height = dimHeight * colWidth + gapWidth * (dimHeight - 1); + return sizes; + } + currentTile.dimWidth = dimWidth; + currentTile.dimHeight = dimHeight; + currentTile.width = dimWidth * colWidth + gapWidth * (dimWidth - 1); + currentTile.height = dimHeight * colWidth + gapWidth * (dimHeight - 1); + } + + /** + * resize to new height without width changing + */ + function resizeToNewHeight(tileID, newDimHeight, toReturn) { + + if(!toReturn){ + toReturn = false; + } + + var currentTile = g_arrNestedItems[tileID]; + var dimWidth = currentTile.dimWidth; + var colWidth = g_nestedWork.colWidth; + var gapWidth = g_nestedWork.currentGap; + + if(toReturn == true){ + var sizes = jQuery.extend(true, {}, currentTile); + + sizes.dimHeight = newDimHeight; + sizes.width = dimWidth * colWidth + gapWidth * (dimWidth - 1); + sizes.height = newDimHeight * colWidth + gapWidth * (newDimHeight - 1); + + return sizes; + } + + currentTile.dimHeight = newDimHeight; + currentTile.width = dimWidth * colWidth + gapWidth * (dimWidth - 1); + currentTile.height = newDimHeight * colWidth + gapWidth * (newDimHeight - 1); + } + + /** + * calc resize koef + */ + function calcResizeRatio(objTilesAndSizes){ + var tempResizes = 0; + var tempNum = 0; + + for(var i = 0; i g_nestedWork.maxColHeight){ + g_nestedWork.maxColHeight = newY + sizes.height; + }; + + if(toShow == true){ + objTile.fadeTo(0, 1); + } + + } + + + function __________COMMON_AND_EVENTS_______(){}; + + + /** + * on resize event + */ + function onResize(){ + + if(g_temp.isFirstTimeRun == true) + return(true); + + if(g_temp.isAllLoaded == false) + return(false); + + switch(g_options.tiles_type){ + case "columns": + placeTiles(false); + break; + case "justified": + placeJustified(false); + break; + case "nested": + + var isMobileMode = g_gallery.isMobileMode(); + if(isMobileMode == true){ + placeTiles(false); + } + else + placeNestedImages(false); + + break; + } + + } + + + /** + * init panel events + */ + function initEvents(){ + + g_objThis.on(t.events.ALL_TILES_LOADED, function(){ + + g_temp.isAllLoaded = true; + + }); + + g_objGallery.on(g_gallery.events.SIZE_CHANGE, onResize); + + g_objGallery.on(t.events.TILES_FIRST_PLACED,function(){ + g_temp.isFirstPlaced = false; + }); + + g_objTileDesign.initEvents(); + + } + + + /** + * run the gallery after init and set html + */ + function run(){ + + //show tiles + g_objWrapper.children(".ug-tile").show(); + + if(g_temp.isFirstTimeRun == true){ + initEvents(); + } + + g_objTileDesign.run(); + + switch(g_options.tiles_type){ + default: + case "columns": + runColumnsType(); + break; + case "justified": + runJustifiedType(); + break; + case "nested": + runNestedType(); + break; + } + + + g_temp.isFirstTimeRun = false; + } + + + /** + * destroy the events + */ + this.destroy = function(){ + + g_objGallery.off(g_gallery.events.SIZE_CHANGE); + g_objTileDesign.destroy(); + g_objGallery.off(t.events.TILES_FIRST_PLACED); + + } + + + + /** + * init function for avia controls + */ + this.init = function(gallery, customOptions){ + + init(gallery, customOptions); + } + + + /** + * set html + */ + this.setHtml = function(objParent){ + setHtml(objParent); + } + + /** + * get tile design object + */ + this.getObjTileDesign = function(){ + return(g_objTileDesign); + } + + /** + * set html and properties + */ + this.run = function(){ + run(); + } + + + /** + * run the new items + */ + this.runNewItems = function(){ + + if(!g_objParent) + throw new Error("Can't run new items - parent not set"); + + g_objTileDesign.setHtml(g_objParent, true); + + g_objTileDesign.run(true); + + + switch(g_options.tiles_type){ + case "columns": + + runColumnsType(); + + break; + default: + case "justified": + case "nested": + throw new Error("Tiles type: "+g_options.tiles_type+" not support load more yet"); + break; + } + + } + +} + + + +/** + * tiles design class + */ +function UGTileDesign(){ + + var t = this, g_objThis = jQuery(this); + var g_gallery = new UniteGalleryMain(), g_objGallery; + var g_functions = new UGFunctions(), g_objParentWrapper, g_objWrapper; + var g_thumbs = new UGThumbsGeneral(), g_items; + + this.resizemode = { //modes constants for resize tile + FULL: "full", + WRAPPER_ONLY: "wrapper_only", + VISIBLE_ELEMENTS: "visible_elements" + }; + + this.sizeby = { //sizeby option constants + GLOBAL_RATIO: "global_ratio", + TILE_RATIO: "tile_ratio", + IMAGE_RATIO: "image_ratio", + CUSTOM: "custom" + }; + + this.events = { + TILE_CLICK: "tile_click" + }; + + var g_options = { + + tile_width: 250, //in case of fixed size: tile width + tile_height: 200, //in case of fixed size: tile height + tile_size_by:t.sizeby.IMAGE_RATIO, //image ratio, tile ratio , global_ratio - decide by what parameter resize the tile + tile_visible_before_image:false, //tile visible before image load + + tile_enable_background:true, //enable backgruond of the tile + tile_background_color: "#F0F0F0", //tile background color + + tile_enable_border:false, //enable border of the tile + tile_border_width:3, //tile border width + tile_border_color:"#F0F0F0", //tile border color + tile_border_radius:0, //tile border radius (applied to border only, not to outline) + + tile_enable_outline: false, //enable outline of the tile (works only together with the border) + tile_outline_color: "#8B8B8B", //tile outline color + + tile_enable_shadow:false, //enable shadow of the tile + tile_shadow_h:1, //position of horizontal shadow + tile_shadow_v:1, //position of vertical shadow + tile_shadow_blur:3, //shadow blur + tile_shadow_spread:2, //shadow spread + tile_shadow_color:"#8B8B8B", //shadow color + + tile_enable_action: true, //enable tile action on click like lightbox + tile_as_link: false, //act the tile as link, no lightbox will appear + tile_link_newpage: true, //open the tile link in new page + + tile_enable_overlay: true, //enable tile color overlay (on mouseover) + tile_overlay_opacity: 0.4, //tile overlay opacity + tile_overlay_color: "#000000", //tile overlay color + + tile_enable_icons: true, //enable icons in mouseover mode + tile_show_link_icon: false, //show link icon (if the tile has a link). In case of tile_as_link this option not enabled + tile_videoplay_icon_always_on: 'never', //'always', 'never', 'mobile_only', 'desktop_only' always show video play icon + tile_space_between_icons: 26, //initial space between icons, (on small tiles it may change) + + tile_enable_image_effect:false, //enable tile image effect + tile_image_effect_type: "bw", //bw, blur, sepia - tile effect type + tile_image_effect_reverse: false, //reverce the image, set only on mouseover state + + tile_enable_textpanel: false, //enable textpanel + tile_textpanel_source: "title", //title,desc,desc_title,title_and_desc. source of the textpanel. desc_title - if description empty, put title + tile_textpanel_always_on: false, //textpanel always visible - for inside type + tile_textpanel_appear_type: "slide", //slide, fade - appear type of the textpanel on mouseover + tile_textpanel_position:"inside_bottom", //inside_bottom, inside_top, inside_center, top, bottom the position of the textpanel + tile_textpanel_offset:0 //vertical offset of the textpanel + }; + + + var g_defaults = { + thumb_color_overlay_effect: true, + thumb_overlay_reverse: true, + thumb_image_overlay_effect: false, + tile_textpanel_enable_description: false, + tile_textpanel_bg_opacity: 0.6, + tile_textpanel_padding_top:8, + tile_textpanel_padding_bottom: 8 + }; + + var g_temp = { + ratioByHeight:0, + ratioByWidth:0, + eventSizeChange: "thumb_size_change", + funcCustomTileHtml: null, + funcCustomPositionElements: null, + funcParentApproveClick: null, + isSaparateIcons: false, + tileInnerReduce: 0, //how much reduce from the tile inner elements from border mostly + isTextpanelOutside: false, //is the textpanel is out of tile image border + hasImageContainer:false, + isVideoplayIconAlwaysOn:false, + isTextPanelHidden:false + }; + + + /** + * init the tile object + */ + function init(gallery, customOptions){ + + g_gallery = gallery; + + g_objGallery = jQuery(gallery); + + var objects = g_gallery.getObjects(); + g_objWrapper = objects.g_objWrapper; + + g_items = g_gallery.getArrItems(); + + g_options = jQuery.extend(g_options, g_defaults); + + g_options = jQuery.extend(g_options, customOptions); + + modifyOptions(); + + g_thumbs.init(gallery, g_options); + + var objCustomOptions = {allow_onresize:false}; + + var customThumbsAdd = ["overlay"]; + + if(g_temp.funcCustomTileHtml) + customThumbsAdd = []; + + g_thumbs.setCustomThumbs(setHtmlThumb, customThumbsAdd, objCustomOptions); + + //get thumb default options too: + var thumbOptions = g_thumbs.getOptions(); + g_options = jQuery.extend(g_options, thumbOptions); + + //set ratios of fixed mode + g_temp.ratioByWidth = g_options.tile_width / g_options.tile_height; + g_temp.ratioByHeight = g_options.tile_height / g_options.tile_width; + + + //set if tile has image container + if(g_options.tile_size_by == t.sizeby.GLOBAL_RATIO && g_temp.isTextpanelOutside) + g_temp.hasImageContainer = true; + + } + + + /** + * set thumb and textpanel options according tile options + */ + function modifyOptions(){ + + //set overlay related options + if(g_options.tile_enable_overlay == true){ + + g_options.thumb_overlay_opacity = g_options.tile_overlay_opacity; + g_options.thumb_overlay_color = g_options.tile_overlay_color; + + }else if(g_options.tile_enable_icons == false){ //if nothing on overlay - turn it off + g_options.thumb_color_overlay_effect = false; + }else{ //if icons enabled - make it transparent + g_options.thumb_overlay_opacity = 0; + } + + //set item as link + if(g_options.tile_as_link){ + g_options.thumb_wrapper_as_link = true; + g_options.thumb_link_newpage = g_options.tile_link_newpage; + } + + //outline cannot appear without border + if(g_options.tile_enable_outline == true && g_options.tile_enable_border == false) + g_options.tile_enable_outline = false; + + //set inner reduce value - in case of the border + g_temp.tileInnerReduce = 0; + if(g_options.tile_enable_border){ + g_temp.tileInnerReduce = g_options.tile_border_width * 2; + g_thumbs.setThumbInnerReduce(g_temp.tileInnerReduce); + } + + //check if saparate icons + g_temp.isSaparateIcons = !g_functions.isRgbaSupported(); + + //set if the textpanel is enabled and outside + if(g_options.tile_enable_textpanel == true){ + + //optimize for touch device + switch(g_options.tile_textpanel_position){ + case "top": + g_options.tile_textpanel_align = "top"; + case "bottom": + g_temp.isTextpanelOutside = true; + g_options.tile_textpanel_always_on = true; + g_options.tile_textpanel_offset = 0; + break; + case "inside_top": + g_options.tile_textpanel_align = "top"; + break; + case "middle": + g_options.tile_textpanel_align = "middle"; + g_options.tile_textpanel_appear_type = "fade"; + break; + } + + //if text panel oppearing with the overlay, icons should be saparated + if(g_options.tile_textpanel_always_on == false) + g_temp.isSaparateIcons = true; + + } + + + //if the textpanel offset is not from the border, it's always fade. + if(g_options.tile_textpanel_offset != 0){ + g_options.tile_textpanel_appear_type = "fade"; + g_options.tile_textpanel_margin = g_options.tile_textpanel_offset; + } + + //enable description if needed + if(g_options.tile_textpanel_source == "title_and_desc"){ + g_options.tile_textpanel_enable_description = true; + g_options.tile_textpanel_desc_style_as_title = true; + } + + } + + + /** + * set options before render + */ + function modifyOptionsBeforeRender(){ + + var isMobile = g_gallery.isMobileMode(); + + //set text panel show / hide + + g_temp.isTextPanelHidden = false; + if(isMobile == true && g_options.tile_textpanel_always_on == false) + g_temp.isTextPanelHidden = true; + + + //set video icon always on true / false + + g_temp.isVideoplayIconAlwaysOn = g_options.tile_videoplay_icon_always_on; + + switch(g_options.tile_videoplay_icon_always_on){ + case "always": + g_temp.isVideoplayIconAlwaysOn = true; + break; + case "never": + g_temp.isVideoplayIconAlwaysOn = false; + break; + case "mobile_only": + g_temp.isVideoplayIconAlwaysOn = (isMobile == true)?true:false; + break; + case "desktop_only": + g_temp.isVideoplayIconAlwaysOn = (isMobile == false)?true:false; + break; + } + + + } + + + /** + * set thumb html + */ + function setHtmlThumb(objThumbWrapper, objItem){ + + objThumbWrapper.addClass("ug-tile"); + + if(g_temp.funcCustomTileHtml){ + g_temp.funcCustomTileHtml(objThumbWrapper, objItem); + return(false); + } + + var html = ""; + + //add image container + if(g_temp.hasImageContainer == true){ + html += "
      "; + } + + //add thumb image: + var classImage = "ug-thumb-image"; + + if(g_options.tile_enable_image_effect == false || g_options.tile_image_effect_reverse == true) + classImage += " ug-trans-enabled"; + + var imageAlt = g_functions.stripTags(objItem.title); + imageAlt = g_functions.htmlentitles(imageAlt); + + html += ""+imageAlt+""; + + if(g_temp.hasImageContainer == true){ + html += "
      "; + } + + objThumbWrapper.append(html); + + + if(g_options.tile_size_by == t.sizeby.GLOBAL_RATIO){ + objThumbWrapper.fadeTo(0,0); //turn on in thumbsGeneral + } + + //---- set thumb styles ---- + + //set border: + var objCss = {}; + + if(g_options.tile_enable_background == true){ + objCss["background-color"] = g_options.tile_background_color; + } + + if(g_options.tile_enable_border == true){ + objCss["border-width"] = g_options.tile_border_width+"px"; + objCss["border-style"] = "solid"; + objCss["border-color"] = g_options.tile_border_color; + + if(g_options.tile_border_radius) + objCss["border-radius"] = g_options.tile_border_radius+"px"; + } + + //set outline: + if(g_options.tile_enable_outline == true){ + objCss["outline"] = "1px solid " + g_options.tile_outline_color; + } + + //set shadow + if(g_options.tile_enable_shadow == true){ + var htmlShadow = g_options.tile_shadow_h+"px "; + htmlShadow += g_options.tile_shadow_v+"px "; + htmlShadow += g_options.tile_shadow_blur+"px "; + htmlShadow += g_options.tile_shadow_spread+"px "; + htmlShadow += g_options.tile_shadow_color; + + objCss["box-shadow"] = htmlShadow; + } + + objThumbWrapper.css(objCss); + + + //----- add icons + + var htmlAdd = ""; + + if(g_options.tile_enable_icons){ + + //add zoom icon + if(g_options.tile_as_link == false && g_options.tile_enable_action == true){ + var iconPlayClass = "ug-button-play ug-icon-zoom"; + if(objItem.type != "image") + iconPlayClass = "ug-button-play ug-icon-play"; + + htmlAdd += ""; + } + + //add link icon + if(objItem.link && g_options.tile_show_link_icon == true || g_options.tile_as_link == true){ + + if(g_options.tile_as_link == false){ + var linkTarget = ""; + if(g_options.tile_link_newpage == true) + linkTarget = " target='_blank'"; + + htmlAdd += ""; + }else{ + htmlAdd += ""; + } + + } + + var toSaparateIcon = g_temp.isSaparateIcons; + if(toSaparateIcon == false && objItem.type != "image" && g_temp.isVideoplayIconAlwaysOn == true) + toSaparateIcon = true; + + if(toSaparateIcon) //put the icons on the thumb + var objOverlay = objThumbWrapper; + else + var objOverlay = objThumbWrapper.children(".ug-thumb-overlay"); + + objOverlay.append(htmlAdd); + + var objButtonZoom = objOverlay.children("." + iconPlayClass); + + if(objButtonZoom.length == 0) + objButtonZoom = null; + else + objButtonZoom.hide(); + + var objButtonLink = objOverlay.children(".ug-icon-link"); + + if(objButtonLink.length == 0) + objButtonLink = null; + else + objButtonLink.hide(); + + //if only zoom icon, make the tile clickable for lightbox open + if(!objButtonLink && g_options.tile_enable_action == true) + objThumbWrapper.addClass("ug-tile-clickable"); + + } //if icons enabled + else{ //if the icons don't enabled, set the tile clickable + + if(g_options.tile_enable_action == true) + objThumbWrapper.addClass("ug-tile-clickable"); + + } + + //add image overlay + if(g_options.tile_enable_image_effect == true){ + + var imageEffectClassAdd = ""; + if(g_options.tile_image_effect_reverse == false) + imageEffectClassAdd = " ug-trans-enabled"; + + var imageOverlayHtml = "
      "; + var imageEffectClass = " ug-"+g_options.tile_image_effect_type+"-effect"; + + imageOverlayHtml += ""+objItem.title+""; + imageOverlayHtml += "
      "; + + objThumbWrapper.append(imageOverlayHtml); + + //hide the image overlay if reversed + if(g_options.tile_image_effect_reverse == true){ + objThumbWrapper.children(".ug-tile-image-overlay").fadeTo(0,0); + } + + } + + + //add text panel + if(g_options.tile_enable_textpanel == true){ + + var objTextPanel = new UGTextPanel(); + objTextPanel.init(g_gallery, g_options, "tile"); + + //set transition class + var textpanelAddClass = ""; + if(g_options.tile_textpanel_always_on == true || g_temp.isTextpanelOutside == true) + textpanelAddClass = "ug-trans-enabled"; + + objTextPanel.appendHTML(objThumbWrapper, textpanelAddClass); + + var panelTitle = objItem.title; + var panelDesc = ""; + + switch(g_options.tile_textpanel_source){ + case "desc": + case "description": + panelTitle = objItem.description; + break; + case "desc_title": + if(objItem.description != "") + panelTitle = objItem.description; + break; + case "title_and_desc": + panelTitle = objItem.title; + panelDesc = objItem.description; + break; + } + + objTextPanel.setTextPlain(panelTitle, panelDesc); + + if(g_options.tile_textpanel_always_on == false) + objTextPanel.getElement().fadeTo(0,0); + + objThumbWrapper.data("objTextPanel", objTextPanel); + + //if textpanel always on, it has to be under the overlay + if(g_options.tile_textpanel_always_on == true){ + var textPanelElement = getTextPanelElement(objThumbWrapper); + textPanelElement.css("z-index",2); + } + + //if text panel is outside, clone textpanel + if(g_temp.isTextpanelOutside == true){ + + var htmlClone = "
      "; + objThumbWrapper.append(htmlClone); + var objCloneWrapper = objThumbWrapper.children(".ug-tile-cloneswrapper"); + + var objTextPanelClone = new UGTextPanel(); + objTextPanelClone.init(g_gallery, g_options, "tile"); + objTextPanelClone.appendHTML(objCloneWrapper); + objTextPanelClone.setTextPlain(panelTitle, panelDesc); + objThumbWrapper.data("objTextPanelClone", objTextPanelClone); + } + + } + + //add additional html + if(objItem.addHtml !== null) + objThumbWrapper.append(objItem.addHtml); + + } + + + /** + * load tile image, place the image on load + */ + this.loadTileImage = function(objTile){ + + var objImage = t.getTileImage(objTile); + + g_functions.checkImagesLoaded(objImage, null, function(objImage,isError){ + onPlaceImage(null, objTile, jQuery(objImage)); + }); + + } + + function _________________GETTERS________________(){}; + + + + /** + * get image overlay + */ + function getTileOverlayImage(objTile){ + var objOverlayImage = objTile.children(".ug-tile-image-overlay"); + return(objOverlayImage); + } + + /** + * get tile color overlay + */ + function getTileOverlay(objTile){ + var objOverlay = objTile.children(".ug-thumb-overlay"); + return(objOverlay); + } + + + /** + * get image container + */ + function getTileImageContainer(objTile){ + if(g_temp.hasImageContainer == false) + return(null); + + var objImageContainer = objTile.children(".ug-image-container"); + + return(objImageContainer); + } + + + /** + * get image effect + */ + function getTileImageEffect(objTile){ + var objImageEffect = objTile.find(".ug-tile-image-overlay img"); + return(objImageEffect); + } + + + /** + * get text panel + */ + function getTextPanel(objTile){ + var objTextPanel = objTile.data("objTextPanel"); + + return(objTextPanel); + } + + + /** + * get cloned text panel + */ + function getTextPanelClone(objTile){ + var objTextPanelClone = objTile.data("objTextPanelClone"); + + return(objTextPanelClone); + + } + + + /** + * get text panel element from the tile + */ + function getTextPanelElement(objTile){ + var objTextPanel = objTile.children(".ug-textpanel"); + + return(objTextPanel); + } + + + /** + * get text panel element cloned + */ + function getTextPanelCloneElement(objTile){ + var objTextPanel = objTile.find(".ug-tile-cloneswrapper .ug-textpanel"); + + if(objTextPanel.length == 0) + throw new Error("text panel cloned element not found"); + + return(objTextPanel); + + } + + + /** + * get text panel height + */ + function getTextPanelHeight(objTile){ + + if(g_temp.isTextpanelOutside == true) + var objTextPanel = getTextPanelCloneElement(objTile); + else + var objTextPanel = getTextPanelElement(objTile); + + + if(!objTextPanel) + return(0); + + var objSize = g_functions.getElementSize(objTextPanel); + return(objSize.height); + } + + + /** + * get button link + */ + function getButtonLink(objTile){ + var objButton = objTile.find(".ug-icon-link"); + if(objButton.length == 0) + return(null); + + return objButton; + } + + + /** + * get tile ratio + */ + function getTileRatio(objTile){ + + //global ratio + var ratio = g_temp.ratioByHeight; + + switch(g_options.tile_size_by){ + default: //global ratio + ratio = g_temp.ratioByHeight; + break; + case t.sizeby.IMAGE_RATIO: + + if(!objTile) + throw new Error("tile should be given for tile ratio"); + + var item = t.getItemByTile(objTile); + + if(typeof item.thumbRatioByHeight != "undefined"){ + + if(item.thumbRatioByHeight == 0){ + trace(item); + throw new Error("the item ratio not inited yet"); + } + + ratio = item.thumbRatioByHeight; + } + + break; + case t.sizeby.CUSTOM: + return null; + break; + } + + return(ratio); + } + + + /** + * get button zoom + */ + function getButtonZoom(objTile){ + var objButton = objTile.find(".ug-button-play"); + + if(objButton.length == 0) + return(null); + + return objButton; + } + + + /** + * tells if the tile is over style + */ + function isOverStyle(objTile){ + + if(objTile.hasClass("ug-thumb-over")) + return(true); + + return(false); + } + + + /** + * check if the tile is clickable + */ + function isTileClickable(objTile){ + + return objTile.hasClass("ug-tile-clickable"); + } + + + /** + * return if the items icon always on + */ + function isItemIconAlwaysOn(objItem){ + + if(g_options.tile_enable_icons == true && g_temp.isVideoplayIconAlwaysOn == true && objItem.type != "image") + return(true); + + return(false); + } + + + function _________________SETTERS________________(){}; + + + /** + * position tile images elements + * width, height - tile width height + */ + function positionElements_images(objTile, width, height, visibleOnly){ + + var objImageOverlay = getTileOverlayImage(objTile); + var objThumbImage = t.getTileImage(objTile); + var objImageEffect = getTileImageEffect(objTile); + + //reduce borders + width -= g_temp.tileInnerReduce; + height -= g_temp.tileInnerReduce; + + var imagePosy = null; + + //reduce textpanel height + if(g_temp.isTextpanelOutside == true){ + + var textHeight = getTextPanelHeight(objTile); + height -= textHeight; + + if(g_options.tile_textpanel_position == "top"){ + imagePosy = textHeight; + } + + /** + * if has image container + */ + if(g_temp.hasImageContainer == true){ + var objImageContainer = getTileImageContainer(objTile); + g_functions.setElementSize(objImageContainer, width, height); + + if(imagePosy !== null) + g_functions.placeElement(objImageContainer, 0, imagePosy); + } + + } + + //scale image + if(g_options.tile_enable_image_effect == false){ + + g_functions.scaleImageCoverParent(objThumbImage, width, height); + + if(g_temp.hasImageContainer == false && imagePosy !== null) + g_functions.placeElement(objThumbImage, 0, imagePosy); + + }else{ //width the effect + + //set what to resize + var dontResize = "nothing"; + if(visibleOnly === true && g_temp.isTextpanelOutside == false){ + if(g_options.tile_image_effect_reverse == true){ + dontResize = "effect"; + }else{ + dontResize = "image"; + } + } + + //resize image effect + if(dontResize != "effect"){ + g_functions.setElementSize(objImageOverlay, width, height); + + if(imagePosy !== null) + g_functions.placeElement(objImageOverlay, 0, imagePosy); + + g_functions.scaleImageCoverParent(objImageEffect, width, height); + } + + + //resize image + if(dontResize != "image"){ + + if(g_temp.hasImageContainer == true){ + g_functions.scaleImageCoverParent(objThumbImage, width, height); + }else{ + + //if can't clone, resize + if(dontResize == "effect"){ + g_functions.scaleImageCoverParent(objThumbImage, width, height); + if(imagePosy !== null) + g_functions.placeElement(objThumbImage, 0, imagePosy); + } + else + g_functions.cloneElementSizeAndPos(objImageEffect, objThumbImage, false, null, imagePosy); + + } + + } + + + + } + + } + + + /** + * position text panel + * panelType - default or clone + */ + function positionElements_textpanel(objTile, panelType, tileWidth, tileHeight){ + + var panelWidth = null; + if(tileWidth) + panelWidth = tileWidth - g_temp.tileInnerReduce; + + if(tileHeight) + tileHeight -= g_temp.tileInnerReduce; + + if(panelType == "clone"){ + var objTextPanelClone = getTextPanelClone(objTile); + objTextPanelClone.refresh(true, true, panelWidth); + var objItem = t.getItemByTile(objTile); + objItem.textPanelCloneSizeSet = true; + + return(false); + } + + var objTextPanel = getTextPanel(objTile); + + if(!objTextPanel) + return(false); + + var panelHeight = null; + + //set panel height also + if(g_temp.isTextpanelOutside == true) + panelHeight = getTextPanelHeight(objTile); + + objTextPanel.refresh(false, true, panelWidth, panelHeight); + + var isPosition = (g_options.tile_textpanel_always_on == true || g_options.tile_textpanel_appear_type == "fade"); + + if(isPosition){ + + if(g_temp.isTextpanelOutside == true && tileHeight && g_options.tile_textpanel_position == "bottom"){ + + var posy = tileHeight - panelHeight; + objTextPanel.positionPanel(posy); + }else + objTextPanel.positionPanel(); + } + + } + + + /** + * position the elements + */ + function positionElements(objTile){ + + var objItem = t.getItemByTile(objTile); + var objButtonZoom = getButtonZoom(objTile); + var objButtonLink = getButtonLink(objTile); + var sizeTile = g_functions.getElementSize(objTile); + + positionElements_images(objTile, sizeTile.width, sizeTile.height); + + //position text panel: + if(g_options.tile_enable_textpanel == true) + positionElements_textpanel(objTile, "regular", sizeTile.width, sizeTile.height); + + + //position overlay: + var overlayWidth = sizeTile.width - g_temp.tileInnerReduce; + var overlayHeight = sizeTile.height - g_temp.tileInnerReduce; + var overlayY = 0; + if(g_temp.isTextpanelOutside == true){ + var textHeight = getTextPanelHeight(objTile); + overlayHeight -= textHeight; + if(g_options.tile_textpanel_position == "top") + overlayY = textHeight; + } + + var objOverlay = getTileOverlay(objTile); + g_functions.setElementSizeAndPosition(objOverlay, 0, overlayY, overlayWidth, overlayHeight); + + //set vertical gap for icons + if(objButtonZoom || objButtonLink){ + + var gapVert = 0; + if( g_options.tile_enable_textpanel == true && g_temp.isTextPanelHidden == false && g_temp.isTextpanelOutside == false){ + var objTextPanelElement = getTextPanelElement(objTile); + var texPanelSize = g_functions.getElementSize(objTextPanelElement); + if(texPanelSize.height > 0) + gapVert = Math.floor((texPanelSize.height / 2) * -1); + } + + } + + if(objButtonZoom && objButtonLink){ + var sizeZoom = g_functions.getElementSize(objButtonZoom); + var sizeLink = g_functions.getElementSize(objButtonLink); + var spaceBetween = g_options.tile_space_between_icons; + + var buttonsWidth = sizeZoom.width + spaceBetween + sizeLink.width; + var buttonsX = Math.floor((sizeTile.width - buttonsWidth) / 2); + + //trace("X: "+buttonsX+" "+"space: " + spaceBetween); + + //if space more then padding, calc even space. + if(buttonsX < spaceBetween){ + spaceBetween = Math.floor((sizeTile.width - sizeZoom.width - sizeLink.width) / 3); + buttonsWidth = sizeZoom.width + spaceBetween + sizeLink.width; + buttonsX = Math.floor((sizeTile.width - buttonsWidth) / 2); + } + + g_functions.placeElement(objButtonZoom, buttonsX, "middle", 0 ,gapVert); + g_functions.placeElement(objButtonLink, buttonsX + sizeZoom.width + spaceBetween, "middle", 0, gapVert); + + }else{ + + if(objButtonZoom) + g_functions.placeElement(objButtonZoom, "center", "middle", 0, gapVert); + + if(objButtonLink) + g_functions.placeElement(objButtonLink, "center", "middle", 0, gapVert); + + } + + if(objButtonZoom) + objButtonZoom.show(); + + if(objButtonLink) + objButtonLink.show(); + } + + + /** + * set tiles htmls + */ + this.setHtml = function(objParent, isAppend){ + g_objParentWrapper = objParent; + + if(isAppend !== true) + modifyOptionsBeforeRender(); + + g_thumbs.setHtmlThumbs(objParent, isAppend); + } + + + + /** + * set the overlay effect + */ + function setImageOverlayEffect(objTile, isActive){ + + var objItem = t.getItemByTile(objTile); + var objOverlayImage = getTileOverlayImage(objTile); + + var animationDuration = g_options.thumb_transition_duration; + + if(g_options.tile_image_effect_reverse == false){ + + var objThumbImage = t.getTileImage(objTile); + + if(isActive){ + objThumbImage.fadeTo(0,1); + objOverlayImage.stop(true).fadeTo(animationDuration, 0); + } + else + objOverlayImage.stop(true).fadeTo(animationDuration, 1); + + }else{ + + if(isActive){ + objOverlayImage.stop(true).fadeTo(animationDuration, 1); + } + else{ + objOverlayImage.stop(true).fadeTo(animationDuration, 0); + } + } + + } + + + /** + * set textpanel effect + */ + function setTextpanelEffect(objTile, isActive){ + + var animationDuration = g_options.thumb_transition_duration; + + var objTextPanel = getTextPanelElement(objTile); + if(!objTextPanel) + return(true); + + if(g_options.tile_textpanel_appear_type == "slide"){ + + var panelSize = g_functions.getElementSize(objTextPanel); + if(panelSize.width == 0) + return(false); + + var startPos = -panelSize.height; + var endPos = 0; + var startClass = {}, endClass = {}; + + var posName = "bottom"; + if(g_options.tile_textpanel_position == "inside_top") + posName = "top"; + + startClass[posName] = startPos+"px"; + endClass[posName] = endPos+"px"; + + if(isActive == true){ + + objTextPanel.fadeTo(0,1); + + if(objTextPanel.is(":animated") == false) + objTextPanel.css(startClass); + + endClass["opacity"] = 1; + + objTextPanel.stop(true).animate(endClass, animationDuration); + + }else{ + + objTextPanel.stop(true).animate(startClass, animationDuration); + + } + + }else{ //fade effect + + if(isActive == true){ + objTextPanel.stop(true).fadeTo(animationDuration, 1); + }else{ + objTextPanel.stop(true).fadeTo(animationDuration, 0); + } + + } + + } + + + /** + * set thumb border effect + */ + function setIconsEffect(objTile, isActive, noAnimation){ + + var animationDuration = g_options.thumb_transition_duration; + if(noAnimation && noAnimation === true) + animationDuration = 0; + + var g_objIconZoom = getButtonZoom(objTile); + var g_objIconLink = getButtonLink(objTile); + var opacity = isActive?1:0; + + if(g_objIconZoom) + g_objIconZoom.stop(true).fadeTo(animationDuration, opacity); + + if(g_objIconLink) + g_objIconLink.stop(true).fadeTo(animationDuration, opacity); + + } + + + + /** + * set tile over style + */ + function setOverStyle(data, objTile){ + + objTile = jQuery(objTile); + + if(g_options.tile_enable_image_effect) + setImageOverlayEffect(objTile, true); + + if(g_options.tile_enable_textpanel == true && g_options.tile_textpanel_always_on == false && g_temp.isTextPanelHidden == false) + setTextpanelEffect(objTile, true); + + //show/hide icons - if saparate (if not, they are part of the overlay) + //if the type is video and icon always on - the icon should stay + if(g_temp.isSaparateIcons && g_options.tile_enable_icons == true){ + var isSet = (g_options.thumb_overlay_reverse == true); + + var objItem = t.getItemByTile(objTile); + if(isItemIconAlwaysOn(objItem) == false) + setIconsEffect(objTile, isSet, false); + + } + + } + + + /** + * set normal style + */ + function setNormalStyle(data, objTile){ + + objTile = jQuery(objTile); + + if(g_options.tile_enable_image_effect) + setImageOverlayEffect(objTile, false); + + if(g_options.tile_enable_textpanel == true && g_options.tile_textpanel_always_on == false) + setTextpanelEffect(objTile, false); + + //show/hide icons - if saparate (if not, they are part of the overlay) + if(g_temp.isSaparateIcons == true && g_options.tile_enable_icons == true){ + + var isSet = (g_options.thumb_overlay_reverse == true)?false:true; + + var objItem = t.getItemByTile(objTile); + if(isItemIconAlwaysOn(objItem) == false) + setIconsEffect(objTile, isSet, false); + else{ //make icon always appear + setIconsEffect(objTile, true, true); + } + + } + + } + + + /** + * set all tiles normal style + */ + function setAllTilesNormalStyle(objTileExcept){ + + var objTiles = g_thumbs.getThumbs().not(objTileExcept); + objTiles.each(function(index, objTile){ + g_thumbs.setThumbNormalStyle(jQuery(objTile)); + }); + + } + + + function _________________EVENTS________________(){}; + + + /** + * on tile size change, place elements + */ + function onSizeChange(data, objTile, forcePosition){ + + objTile = jQuery(objTile); + + //position elements only if the image loaded (placed) + if(g_options.tile_visible_before_image == true && objTile.data("image_placed") !== true && forcePosition !== true) + return(true); + + positionElements(objTile); + + g_thumbs.setThumbNormalStyle(objTile); + } + + + /** + * on place image event after images loaded + */ + function onPlaceImage(data, objTile, objImage){ + + positionElements(objTile); + objImage.fadeTo(0,1); + + objTile.data("image_placed", true); + } + + + /** + * on tile click on mobile devices on normal state + * set the tile over state + */ + function onMobileClick(objTile){ + + if(isTileClickable(objTile) == true){ + g_objThis.trigger(t.events.TILE_CLICK, objTile); + return(true); + } + + if(isOverStyle(objTile) == false){ + setAllTilesNormalStyle(objTile); + g_thumbs.setThumbOverStyle(objTile); + } + + } + + + /** + * on tile click event + */ + function onTileClick(event){ + + var objTile = jQuery(this); + + var tagname = objTile.prop("tagName").toLowerCase(); + var isApproved = true; + if(g_temp.funcParentApproveClick && g_temp.funcParentApproveClick() == false) + isApproved = false; + + if(tagname == "a"){ + + if(isApproved == false) + event.preventDefault(); + + }else{ //in case of div + + if(isOverStyle(objTile) == false){ //mobile click version + + if(isApproved == true) + onMobileClick(objTile); + + }else{ + if(isTileClickable(objTile) == false) + return(true); + + if(isApproved == true) + g_objThis.trigger(t.events.TILE_CLICK, objTile); + } + + + } + + } + + + /** + * click on zoom button (as tile click) + */ + function onZoomButtonClick(event){ + + event.stopPropagation(); + + var objTile = jQuery(this).parents(".ug-tile"); + + var isApproved = true; + + if(g_temp.funcParentApproveClick && g_temp.funcParentApproveClick() == false) + isApproved = false; + + if(isOverStyle(objTile) == false){ + onMobileClick(objTile); + return(true); + } + + if(isApproved == true){ + g_objThis.trigger(t.events.TILE_CLICK, objTile); + return(false); + } + + } + + + /** + * on link icon click + */ + function onLinkButtonClick(event){ + var objTile = jQuery(this).parents(".ug-tile"); + + if(g_temp.funcParentApproveClick && g_temp.funcParentApproveClick() == false) + event.preventDefault(); + + //allow click only from over style + if(isOverStyle(objTile) == false && g_options.tile_as_link == false){ + event.preventDefault(); + onMobileClick(objTile); + } + + } + + + /** + * init events + */ + this.initEvents = function(){ + + g_thumbs.initEvents(); + + //connect the over and normal style of the regular thumbs + jQuery(g_thumbs).on(g_thumbs.events.SETOVERSTYLE, setOverStyle); + jQuery(g_thumbs).on(g_thumbs.events.SETNORMALSTYLE, setNormalStyle); + jQuery(g_thumbs).on(g_thumbs.events.PLACEIMAGE, onPlaceImage); + + g_objWrapper.on(g_temp.eventSizeChange, onSizeChange); + + g_objParentWrapper.on("click", ".ug-tile", onTileClick); + + g_objParentWrapper.on("click", ".ug-tile .ug-button-play", onZoomButtonClick); + + g_objParentWrapper.on("click", ".ug-tile .ug-icon-link", onLinkButtonClick); + } + + + /** + * destroy the element events + */ + this.destroy = function(){ + + g_objParentWrapper.off("click", ".ug-tile"); + g_objParentWrapper.off("click", ".ug-tile .ug-button-play"); + g_objParentWrapper.off("click", ".ug-tile .ug-icon-link"); + + jQuery(g_thumbs).off(g_thumbs.events.SETOVERSTYLE); + jQuery(g_thumbs).off(g_thumbs.events.SETNORMALSTYLE); + jQuery(g_thumbs).off(g_thumbs.events.PLACEIMAGE); + g_objWrapper.off(g_temp.eventSizeChange); + + if(g_options.tile_enable_textpanel == true){ + var objThumbs = g_thumbs.getThumbs(); + jQuery.each(objThumbs, function(index, thumb){ + var textPanel = getTextPanel(jQuery(thumb)); + if(textPanel) + textPanel.destroy(); + }); + } + + g_thumbs.destroy(); + + } + + + /** + * external init + */ + this.init = function(gallery, g_thumbs, customOptions){ + + init(gallery, g_thumbs, customOptions); + } + + /** + * set fixed mode + */ + this.setFixedMode = function(){ + + g_options.tile_size_by = t.sizeby.GLOBAL_RATIO; + g_options.tile_visible_before_image = true; + } + + + /** + * set parent approve click function + */ + this.setApproveClickFunction = function(funcApprove){ + g_temp.funcParentApproveClick = funcApprove; + } + + + + /** + * resize tile. If no size given, resize to original size + * the resize mode taken from resize modes constants, default is full + */ + this.resizeTile = function(objTile, newWidth, newHeight, resizeMode){ + + //if textpanel outside - refresh the textpanel first + if(g_temp.isTextpanelOutside == true) + positionElements_textpanel(objTile, "clone", newWidth); + + if(!newWidth){ + + var newWidth = g_options.tile_width; + var newHeight = g_options.tile_height; + + }else{ //only height is missing + if(!newHeight){ + + var newHeight = t.getTileHeightByWidth(newWidth, objTile); + } + } + + g_functions.setElementSize(objTile, newWidth, newHeight); + + switch(resizeMode){ + default: + case t.resizemode.FULL: + t.triggerSizeChangeEvent(objTile, true); + break; + case t.resizemode.WRAPPER_ONLY: + return(true); + break; + case t.resizemode.VISIBLE_ELEMENTS: + + if(g_temp.funcCustomTileHtml){ + t.triggerSizeChangeEvent(objTile, true); + return(true); + } + + //resize images + positionElements_images(objTile, newWidth, newHeight, true); + + //resize text panel, if visible + if(g_options.tile_enable_textpanel == true && g_options.tile_textpanel_always_on == true && newWidth){ + positionElements_textpanel(objTile, "regular", newWidth, newHeight); + } + + break; + } + + } + + + /** + * resize all tiles + */ + this.resizeAllTiles = function(newWidth, resizeMode, objTiles){ + + modifyOptionsBeforeRender(); + + var newHeight = null; + + if(g_options.tile_size_by == t.sizeby.GLOBAL_RATIO) + newHeight = t.getTileHeightByWidth(newWidth); + + if(!objTiles) + var objTiles = g_thumbs.getThumbs(); + + objTiles.each(function(index, objTile){ + t.resizeTile(jQuery(objTile), newWidth, newHeight, resizeMode); + }); + + } + + + /** + * trigger size change events + * the force is only for fixed size mode + */ + this.triggerSizeChangeEvent = function(objTile, isForce){ + + if(!objTile) + return(false); + + if(!isForce) + var isForce = false; + + g_objWrapper.trigger(g_temp.eventSizeChange, [objTile, isForce]); + + } + + + /** + * trigger size change event to all tiles + * the force is only for fixed mode + */ + this.triggerSizeChangeEventAllTiles = function(isForce){ + + var objThumbs = g_thumbs.getThumbs(); + + objThumbs.each(function(){ + var objTile = jQuery(this); + + t.triggerSizeChangeEvent(objTile, isForce); + + }); + + } + + + + + + /** + * disable all events + */ + this.disableEvents = function(){ + var objThumbs = g_thumbs.getThumbs(); + objThumbs.css("pointer-events", "none"); + } + + + /** + * enable all events + */ + this.enableEvents = function(){ + var objThumbs = g_thumbs.getThumbs(); + objThumbs.css("pointer-events", "auto"); + } + + + /** + * set new options + */ + this.setOptions = function(newOptions){ + g_options = jQuery.extend(g_options, newOptions); + g_thumbs.setOptions(newOptions); + } + + + /** + * set new tile size, this function will not resize, and keep ratio + */ + this.setTileSizeOptions = function(newTileWidth){ + + if(g_options.tile_size_by !== t.sizeby.GLOBAL_RATIO) + throw new Error("setNewTileOptions works with global ration only"); + + g_options.tile_width = newTileWidth; + g_options.tile_height = Math.floor(newTileWidth * g_temp.ratioByHeight); + + + } + + + /** + * set custom tile html function + */ + this.setCustomFunctions = function(funcCustomHtml, funcPositionElements){ + g_temp.funcCustomTileHtml = funcCustomHtml; + g_temp.funcCustomPositionElements = funcPositionElements; + } + + + /** + * run the tile design + */ + this.run = function(newOnly){ + + //resize all tiles + var getMode = g_thumbs.type.GET_THUMBS_ALL; + if(newOnly === true) + getMode = g_thumbs.type.GET_THUMBS_NEW; + + var objThumbs = g_thumbs.getThumbs(getMode); + + if(g_options.tile_size_by == t.sizeby.GLOBAL_RATIO){ + t.resizeAllTiles(g_options.tile_width, t.resizemode.WRAPPER_ONLY, objThumbs); + } + + //hide original image if image effect active + if(g_options.tile_enable_image_effect == true && g_options.tile_image_effect_reverse == false) + objThumbs.children(".ug-thumb-image").fadeTo(0,0); + + g_thumbs.setHtmlProperties(objThumbs); + + if(g_options.tile_visible_before_image == true){ + + //if textpanel outside - refresh the textpanel first + objThumbs.children(".ug-thumb-image").fadeTo(0,0); + g_thumbs.loadThumbsImages(); + } + + } + + + this._____________EXTERNAL_GETTERS____________=function(){}; + + + /** + * get thumbs general option + */ + this.getObjThumbs = function(){ + return g_thumbs; + } + + /** + * get options + */ + this.getOptions = function(){ + return g_options; + } + + /** + * get tile image + */ + this.getTileImage = function(objTile){ + var objImage = objTile.find("img.ug-thumb-image"); + return(objImage); + } + + + /** + * get item from tile + */ + this.getItemByTile = function(objTile){ + return g_thumbs.getItemByThumb(objTile); + } + + + /** + * get tile height by width + */ + this.getTileHeightByWidth = function(newWidth, objTile){ + + var ratio = getTileRatio(objTile); + + if(ratio === null) + return(null); + + var height = Math.floor( (newWidth - g_temp.tileInnerReduce) * ratio) + g_temp.tileInnerReduce; + + if(objTile && g_temp.isTextpanelOutside == true && g_options.tile_size_by == t.sizeby.IMAGE_RATIO) + height += getTextPanelHeight(objTile); + + return(height); + } + + + /** + * get tile original size + */ + this.getTileImageSize = function(objTile){ + var objItem = t.getItemByTile(objTile); + if(!objItem.thumbWidth || !objItem.thumbHeight) + throw new Error("Can't get image size - image not inited."); + + var objSize = { + width: objItem.thumbWidth, + height: objItem.thumbHeight + }; + + return(objSize); + } + + + /** + * get tile size + */ + this.getGlobalTileSize = function(){ + + if(g_options.tile_size_by != t.sizeby.GLOBAL_RATIO) + throw new Error("The size has to be global ratio"); + + var objSize = { + width: g_options.tile_width, + height: g_options.tile_height + }; + + return(objSize); + } + + +} + +/** + * avia control class + * addon to strip gallery + */ +function UGAviaControl(){ + + var g_parent, g_gallery, g_objects, g_objStrip, g_objStripInner, g_options; + var g_isVertical; + + var g_temp = { + touchEnabled:false, //variable that tells if touch event was before move event + isMouseInsideStrip: false, + strip_finalPos:0, + handle_timeout:"", + isStripMoving:false, + isControlEnabled: true + }; + + + /** + * enable the control + */ + this.enable = function(){ + g_temp.isControlEnabled = true; + } + + /** + * disable the control + */ + this.disable = function(){ + g_temp.isControlEnabled = false; + } + + /** + * init function for avia controls + */ + this.init = function(objParent){ + g_parent = objParent; + + g_objects = objParent.getObjects(); + + g_gallery = g_objects.g_gallery; + + g_objStrip = g_objects.g_objStrip; + g_objStripInner = g_objects.g_objStripInner; + g_options = g_objects.g_options; + g_isVertical = g_objects.isVertical; + + initEvents(); + } + + /** + * get mouse position from event according the orientation + */ + function getMousePos(event){ + + if(g_isVertical == false) + return(event.pageX); + + return(event.pageY); + } + + /** + * handle avia strip control event on body mouse move + */ + function initEvents(event){ + + //make sure that the avia control will not work on touch devices + jQuery("body").on("touchstart", function(event){ + + if(g_temp.isControlEnabled == false) + return(true); + + g_temp.touchEnabled = true; + + }); + + //on body move + jQuery("body").mousemove(function(event){ + + if(g_temp.isControlEnabled == false) + return(true); + + //protection for touch devices, disable the avia events + if(g_temp.touchEnabled == true){ + jQuery("body").off("mousemove"); + return(true); + } + + g_temp.isMouseInsideStrip = g_objStrip.ismouseover(); + var strip_touch_active = g_parent.isTouchMotionActive(); + + if(g_temp.isMouseInsideStrip == true && strip_touch_active == false){ + + var mousePos = getMousePos(event); + + moveStripToMousePosition(mousePos); + }else{ + stopStripMovingLoop(); + } + + }); + + } + + + /** + * destroy the avia control events + */ + this.destroy = function(){ + + jQuery("body").off("touchstart"); + jQuery("body").off("mousemove"); + + } + + + /** + * get inner y position according mouse y position on the strip + */ + function getInnerPosY(mouseY){ + + var innerOffsetTop = g_options.strip_padding_top; + var innerOffsetBottom = g_options.strip_padding_bottom; + + var stripHeight = g_objStrip.height(); + var innerHeight = g_objStripInner.height(); + + //if all thumbs visible, no need to move + if(stripHeight > innerHeight) + return(null); + + //find y position inside the strip + var stripOffset = g_objStrip.offset(); + var offsetY = stripOffset.top; + var posy = mouseY - offsetY - innerOffsetTop; + if(posy < 0) + return(null); + + //set measure line parameters + var mlineStart = g_options.thumb_height; + var mlineEnd = stripHeight - g_options.thumb_height; + var mLineSize = mlineEnd - mlineStart; + + //fix position borders on the measure line + if(posy < mlineStart) + posy = mlineStart; + + if(posy > mlineEnd) + posy = mlineEnd; + + //count the ratio of the position on the measure line + var ratio = (posy - mlineStart) / mLineSize; + var innerPosY = (innerHeight - stripHeight) * ratio; + innerPosY = Math.round(innerPosY) * -1 + innerOffsetTop; + + return(innerPosY); + } + + + /** + * get inner x position according mouse x position on the strip + */ + function getInnerPosX(mouseX){ + + var innerOffsetLeft = g_options.strip_padding_left; + var innerOffsetRight = g_options.strip_padding_right; + + var stripWidth = g_objStrip.width() - innerOffsetLeft - innerOffsetRight; + var innerWidth = g_objStripInner.width(); + + //if all thumbs visible, no need to move + if(stripWidth > innerWidth) + return(null); + + var stripOffset = g_objStrip.offset(); + var offsetX = stripOffset.left; + var posx = mouseX - offsetX - innerOffsetLeft; + + //set measure line parameters + var mlineStart = g_options.thumb_width; + var mlineEnd = stripWidth - g_options.thumb_width; + var mLineSize = mlineEnd - mlineStart; + + //fix position borders on the measure line + if(posx < mlineStart) + posx = mlineStart; + + if(posx > mlineEnd) + posx = mlineEnd; + + //count the ratio of the position on the measure line + var ratio = (posx - mlineStart) / mLineSize; + var innerPosX = (innerWidth - stripWidth) * ratio; + innerPosX = Math.round(innerPosX) * -1 + innerOffsetLeft; + + + return(innerPosX); + } + + + /** + * move strip stap to final position + */ + function moveStripStep(){ + + if(g_temp.is_strip_moving == false){ + return(false); + } + + var innerPos = g_parent.getInnerStripPos(); + + if(Math.floor(innerPos) == Math.floor(g_temp.strip_finalPos)){ + stopStripMovingLoop(); + } + + //calc step + var diff = Math.abs(g_temp.strip_finalPos - innerPos); + + var dpos; + if(diff < 1){ + dpos = diff; + } + else{ + + dpos = diff / 4; + if(dpos > 0 && dpos < 1) + dpos = 1; + } + + if(g_temp.strip_finalPos < innerPos) + dpos = dpos * -1; + + var newPos = innerPos + dpos; + + g_parent.positionInnerStrip(newPos); + + } + + + /** + * start loop of strip moving + */ + function startStripMovingLoop(){ + + if(g_temp.isStripMoving == true) + return(false); + + g_temp.isStripMoving = true; + g_temp.handle_timeout = setInterval(moveStripStep,10); + } + + /** + * stop loop of strip moving + */ + function stopStripMovingLoop(){ + + if(g_temp.isStripMoving == false) + return(false); + + g_temp.isStripMoving = false; + g_temp.handle_timeout = clearInterval(g_temp.handle_timeout); + } + + /** + * get inner position according the orientation + * taken by the mouse position + */ + function getInnerPos(mousePos){ + + if(g_isVertical == false) + return getInnerPosX(mousePos); + else + return getInnerPosY(mousePos); + + } + + + /** + * move the strip to mouse position on it + * mousex - mouse position relative to the document + */ + function moveStripToMousePosition(mousePos){ + + var innerPos = getInnerPos(mousePos); + + if(innerPos === null) + return(false); + + g_temp.is_strip_moving = true; + g_temp.strip_finalPos = innerPos; + + startStripMovingLoop(); + } + +} + +/** + * slider class + * addon to strip gallery + */ +function UGSlider(){ + + var t = this, g_objThis = jQuery(t); + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objWrapper, g_objThumbs; + //the video arrows is independent arrows that is not sitting on the controls + var g_objSlider, g_objInner, g_objSlide1, g_objSlide2, g_objSlide3, g_objArrowLeft, g_objArrowRight; + var g_objTouchSlider, g_objZoomSlider, g_objZoomPanel, g_objButtonPlay = null, g_objButtonFullscreen = null, g_objBullets = null; + var g_objVideoPlayer = new UGVideoPlayer(), g_optionsPrefix; + var g_functions = new UGFunctions(), g_objProgress = null, g_objTextPanel = null; + + + this.events = { + ITEM_CHANGED: "item_changed", //on item changed + BEFORE_SWITCH_SLIDES: "before_switch", //before slides switching + BEFORE_RETURN: "before_return", //before return from pan or from zoom + AFTER_RETURN: "after_return", //after return from pan or from zoom + ZOOM_START: "slider_zoom_start", //on zoom start + ZOOM_END: "slider_zoom_end", //on zoom move + ZOOMING: "slider_zooming", //on zooming + ZOOM_CHANGE: "slider_zoom_change", //on slide image image zoom change + START_DRAG: "start_drag", //on slider drag start + AFTER_DRAG_CHANGE: "after_drag_change", //after finish drag chagne transition + ACTION_START: "action_start", //on slide action start + ACTION_END: "action_end", //on slide action stop + CLICK: "slider_click", //on click event + TRANSITION_START:"slider_transition_start", //before transition start event + TRANSITION_END:"slider_transition_end", //on transition end event + AFTER_PUT_IMAGE: "after_put_image", //after put slide image + IMAGE_MOUSEENTER: "slider_image_mouseenter", //on slide image mouseonter + IMAGE_MOUSELEAVE: "slider_image_mouseleave", //on slide image mouseleave + CURRENTSLIDE_LOAD_START: "slider_current_loadstart", //on current slide load image start + CURRENTSLIDE_LOAD_END: "slider_current_loadend" //on finish load current slide image + } + + var g_options = { + slider_scale_mode: "fill", //fit: scale down and up the image to always fit the slider + //down: scale down only, smaller images will be shown, don't enlarge images (scale up) + //fill: fill the entire slider space by scaling, cropping and centering the image + //fitvert: make the image always fill the vertical slider area + slider_scale_mode_media: "fill", //fill, fit, down, fitvert - scale mode on media items + slider_scale_mode_fullscreen: "down", //fill, fit, down, fitvert - scale mode on fullscreen. + + slider_item_padding_top: 0, //padding top of the slider item + slider_item_padding_bottom: 0, //padding bottom of the slider item + slider_item_padding_left: 0, //padding left of the slider item + slider_item_padding_right: 0, //padding right of the slider item + + slider_background_color: "", //slider background color, set if you want to change default + slider_background_opacity: 1, //slider background opacity + + slider_image_padding_top: 0, //padding top of the image inside the item + slider_image_padding_bottom: 0, //padding bottom of the image inside the item + slider_image_padding_left: 0, //padding left of the image inside the item + slider_image_padding_right: 0, //padding right of the image inside the item + + slider_image_border: false, //enable border around the image + slider_image_border_width: 10, //image border width + slider_image_border_color: "#ffffff", //image border color + slider_image_border_radius: 0, //image border radius + slider_image_border_maxratio: 0.35, //max ratio that the border can take from the image + slider_image_shadow: false, //enable border shadow the image + + slider_video_constantsize: false, //constant video size mode for video items + slider_video_constantsize_scalemode: "fit", //fit,down: constant video size scale mode + slider_video_constantsize_width: 854, //constant video size width + slider_video_constantsize_height: 480, //constant video size height + + slider_video_padding_top: 0, //padding top of the video player inside the item + slider_video_padding_bottom: 0, //padding bottom of the video player inside the item + slider_video_padding_left: 0, //padding left of the video player inside the item + slider_video_padding_right: 0, //padding right of the video player inside the item + + slider_video_enable_closebutton: true, //enable video close button + + slider_transition: "slide", //fade, slide - the transition of the slide change + slider_transition_speed:300, //transition duration of slide change + slider_transition_easing: "easeInOutQuad", //transition easing function of slide change + + slider_control_swipe:true, //true,false - enable swiping control + slider_control_zoom:true, //true, false - enable zooming control + slider_zoom_mousewheel: true, //enable zoom on mousewheel + slider_vertical_scroll_ondrag: false, //vertical scroll on slider area drag + slider_loader_type: 1, //shape of the loader (1-7) + slider_loader_color:"white", //color of the loader: (black , white) + slider_enable_links: true, //enable slide as link functionality + slider_links_newpage: false, //open the slide link in new page + + slider_enable_bullets: false, //enable the bullets onslider element + slider_bullets_skin: "", //skin of the bullets, if empty inherit from gallery skin + slider_bullets_space_between: -1, //set the space between bullets. If -1 then will be set default space from the skins + slider_bullets_align_hor:"center", //left, center, right - bullets horizontal align + slider_bullets_align_vert:"bottom", //top, middle, bottom - bullets vertical algin + slider_bullets_offset_hor:0, //bullets horizontal offset + slider_bullets_offset_vert:10, //bullets vertical offset + + slider_enable_arrows: true, //enable arrows onslider element + slider_arrows_skin: "", //skin of the slider arrows, if empty inherit from gallery skin + + slider_arrow_left_align_hor:"left", //left, center, right - left arrow horizonal align + slider_arrow_left_align_vert:"middle", //top, middle, bottom - left arrow vertical align + slider_arrow_left_offset_hor:20, //left arrow horizontal offset + slider_arrow_left_offset_vert:0, //left arrow vertical offset + + slider_arrow_right_align_hor:"right", //left, center, right - right arrow horizontal algin + slider_arrow_right_align_vert:"middle", //top, middle, bottom - right arrow vertical align + slider_arrow_right_offset_hor:20, //right arrow horizontal offset + slider_arrow_right_offset_vert:0, //right arrow vertical offset + + slider_enable_progress_indicator: true, //enable progress indicator element + slider_progress_indicator_type: "pie", //pie, pie2, bar (if pie not supported, it will switch to bar automatically) + slider_progress_indicator_align_hor:"right", //left, center, right - progress indicator horizontal align + slider_progress_indicator_align_vert:"top", //top, middle, bottom - progress indicator vertical align + slider_progress_indicator_offset_hor:10, //progress indicator horizontal offset + slider_progress_indicator_offset_vert:10, //progress indicator vertical offset + + slider_enable_play_button: true, //true,false - enable play / pause button onslider element + slider_play_button_skin: "", //skin of the slider play button, if empty inherit from gallery skin + slider_play_button_align_hor:"left", //left, center, right - play button horizontal align + slider_play_button_align_vert:"top", //top, middle, bottom - play button vertical align + slider_play_button_offset_hor:40, //play button horizontal offset + slider_play_button_offset_vert:8, //play button vertical offset + slider_play_button_mobilehide:false, //hide the play button on mobile + + slider_enable_fullscreen_button: true, //true,false - enable fullscreen button onslider element + slider_fullscreen_button_skin: "", //skin of the slider fullscreen button, if empty inherit from gallery skin + slider_fullscreen_button_align_hor:"left", //left, center, right - fullscreen button horizonatal align + slider_fullscreen_button_align_vert:"top", //top, middle, bottom - fullscreen button vertical align + slider_fullscreen_button_offset_hor:11, //fullscreen button horizontal offset + slider_fullscreen_button_offset_vert:9, //fullscreen button vertical offset + slider_fullscreen_button_mobilehide:false, //hide the button on mobile + + slider_enable_zoom_panel: true, //true,false - enable the zoom buttons, works together with zoom control. + slider_zoompanel_skin: "", //skin of the slider zoom panel, if empty inherit from gallery skin + slider_zoompanel_align_hor:"left", //left, center, right - zoom panel horizontal align + slider_zoompanel_align_vert:"top", //top, middle, bottom - zoom panel vertical align + slider_zoompanel_offset_hor:12, //zoom panel horizontal offset + slider_zoompanel_offset_vert:92, //zoom panel vertical offset + slider_zoompanel_mobilehide:false, //hide the zoom panel on mobile + + slider_controls_always_on: false, //true,false - controls are always on, false - show only on mouseover + slider_controls_appear_ontap: true, //true,false - appear controls on tap event on touch devices + slider_controls_appear_duration: 300, //the duration of appearing controls + + slider_enable_text_panel: true, //true,false - enable the text panel + slider_textpanel_always_on: true, //true,false - text panel are always on, false - show only on mouseover + + slider_videoplay_button_type: "square" //square, round - the videoplay button type, square or round + }; + + + //default options for bar progress incicator + var g_defaultsProgressBar = { + slider_progress_indicator_align_hor:"left", //left, center, right + slider_progress_indicator_align_vert:"bottom", //top, middle, bottom + slider_progress_indicator_offset_hor:0, //horizontal offset + slider_progress_indicator_offset_vert:0 //vertical offset + }; + + var g_temp = { + isRunOnce: false, + isTextPanelSaparateHover: false, //if the panel need to be appeared without the controls object on mouseover + numPrev:1, + numCurrent:2, + numNext: 3, + isControlsVisible: true, + currentControlsMode: "image" + }; + + function __________GENERAL___________(){}; + + + /** + * init the slider + */ + function initSlider(objGallery, objOptions, optionsPrefix){ + g_gallery = objGallery; + + //change options by prefix + if(optionsPrefix){ + g_optionsPrefix = optionsPrefix; + objOptions = g_functions.convertCustomPrefixOptions(objOptions, g_optionsPrefix, "slider"); + } + + g_objGallery = jQuery(objGallery); + + var objects = g_gallery.getObjects(); + g_objWrapper = objects.g_objWrapper; + g_objThumbs = objects.g_objThumbs; + + //set progress indicator bar defaults if type bar + if(objOptions.hasOwnProperty("slider_progress_indicator_type")) + g_options.slider_progress_indicator_type = objOptions.slider_progress_indicator_type; + + if(g_options.slider_progress_indicator_type == "bar"){ + g_options = jQuery.extend(g_options, g_defaultsProgressBar); + } + + if(objOptions) + t.setOptions(objOptions); + + processOptions(); + + //init bullets: + if(g_options.slider_enable_bullets == true){ + g_objBullets = new UGBullets(); + var bulletsOptions = { + bullets_skin: g_options.slider_bullets_skin, + bullets_space_between: g_options.slider_bullets_space_between + } + g_objBullets.init(g_gallery, bulletsOptions); + } + + //init text panel + if(g_options.slider_enable_text_panel){ + g_objTextPanel = new UGTextPanel(); + g_objTextPanel.init(g_gallery, g_options, "slider"); + } + + if(g_options.slider_enable_zoom_panel){ + g_objZoomPanel = new UGZoomButtonsPanel(); + g_objZoomPanel.init(t, g_options); + } + + var galleryID = g_gallery.getGalleryID(); + + //init video player + g_objVideoPlayer.init(g_options, false, galleryID); + } + + + /** + * run the slider functionality + */ + function runSlider(){ + + if(g_temp.isRunOnce == true) + return(false); + + g_temp.isRunOnce = true; + + //set background color + if(g_options.slider_background_color){ + var bgColor = g_options.slider_background_color; + + if(g_options.slider_background_opacity != 1) + bgColor = g_functions.convertHexToRGB(bgColor, g_options.slider_background_opacity); + + g_objSlider.css("background-color", bgColor); + + }else if(g_options.slider_background_opacity != 1){ //set opacity with default color + + bgColor = g_functions.convertHexToRGB("#000000", g_options.slider_background_opacity); + g_objSlider.css("background-color", bgColor); + + } + + //init touch slider control + if(g_options.slider_control_swipe == true){ + g_objTouchSlider = new UGTouchSliderControl(); + g_objTouchSlider.init(t, g_options); + } + + //init zoom slider control + if(g_options.slider_control_zoom == true){ + g_objZoomSlider = new UGZoomSliderControl(); + g_objZoomSlider.init(t, g_options); + } + + //run the text panel + if(g_objTextPanel) + g_objTextPanel.run(); + + initEvents(); + } + + + /** + * process the options + */ + function processOptions(){ + var galleryOptions = g_gallery.getOptions(); + + //set skins: + var globalSkin = galleryOptions.gallery_skin; + + if(g_options.slider_bullets_skin == "") + g_options.slider_bullets_skin = globalSkin; + + if(g_options.slider_arrows_skin == "") + g_options.slider_arrows_skin = globalSkin; + + if(g_options.slider_zoompanel_skin == "") + g_options.slider_zoompanel_skin = globalSkin; + + if(g_options.slider_play_button_skin == "") + g_options.slider_play_button_skin = globalSkin; + + if(g_options.slider_fullscreen_button_skin == "") + g_options.slider_fullscreen_button_skin = globalSkin; + + g_options.video_enable_closebutton = g_options.slider_video_enable_closebutton; + + //set mousewheel option depends on the gallery option + if(galleryOptions.gallery_mousewheel_role != "zoom") + g_options.slider_zoom_mousewheel = false; + + } + + + /** + * + * get html slide + */ + function getHtmlSlide(loaderClass, numSlide){ + + var classVideoplay = "ug-type-square"; + if(g_options.slider_videoplay_button_type == "round") + classVideoplay = "ug-type-round"; + + var html = ""; + html += "
      "; + html += "
      "; + html += "
      "; + html += ""; + html += "
      "; + + return(html); + } + + + /** + * set the slider html + */ + function setHtmlSlider(objParent){ + + if(objParent) + g_objWrapper = objParent; + + //get if the slide has controls + var loaderClass = getLoaderClass(); + var galleryOptions = g_gallery.getOptions(); + + var html = "
      "; + + html += "
      "; + html += getHtmlSlide(loaderClass,1); + html += getHtmlSlide(loaderClass,2); + html += getHtmlSlide(loaderClass,3); + + html += "
      "; //end inner + + //---------------- + + //add arrows + if(g_options.slider_enable_arrows == true){ + html += "
      "; + html += "
      "; + } + + //add play button + if(g_options.slider_enable_play_button == true){ + html += "
      "; + } + + //add fullscreen button + if(g_options.slider_enable_fullscreen_button == true){ + html += "
      "; + } + + + html += "
      "; //end slider + + + g_objWrapper.append(html); + + //---------------- + + //set objects + g_objSlider = g_objWrapper.children(".ug-slider-wrapper"); + g_objInner = g_objSlider.children(".ug-slider-inner"); + + + g_objSlide1 = g_objInner.children(".ug-slide1"); + g_objSlide2 = g_objInner.children(".ug-slide2"); + g_objSlide3 = g_objInner.children(".ug-slide3"); + + //set slides data + g_objSlide1.data("slidenum",1); + g_objSlide2.data("slidenum",2); + g_objSlide3.data("slidenum",3); + + //add bullets + if(g_objBullets) + g_objBullets.appendHTML(g_objSlider); + + //---------------- + + //get arrows object + if(g_options.slider_enable_arrows == true){ + g_objArrowLeft = g_objSlider.children(".ug-arrow-left"); + g_objArrowRight = g_objSlider.children(".ug-arrow-right"); + } + + //get play button + if(g_options.slider_enable_play_button == true){ + g_objButtonPlay = g_objSlider.children(".ug-button-play"); + } + + //get fullscreen button + if(g_options.slider_enable_fullscreen_button == true){ + g_objButtonFullscreen = g_objSlider.children(".ug-button-fullscreen"); + } + + + //---------------- + + //add progress indicator + if(g_options.slider_enable_progress_indicator == true){ + + g_objProgress = g_functions.initProgressIndicator(g_options.slider_progress_indicator_type, g_options, g_objSlider); + + var finalType = g_objProgress.getType(); + + //change options in case of type change + if(finalType == "bar" && g_options.slider_progress_indicator_type == "pie"){ + g_options.slider_progress_indicator_type = "bar"; + g_options = jQuery.extend(g_options, g_defaultsProgressBar); + } + + g_gallery.setProgressIndicator(g_objProgress); + } + + //---------------- + + //add text panel (hidden) + if(g_options.slider_enable_text_panel == true){ + + g_objTextPanel.appendHTML(g_objSlider); + + //hide panel saparatelly from the controls object + if(g_options.slider_textpanel_always_on == false){ + + //hide the panel + var panelElement = g_objTextPanel.getElement(); + panelElement.hide().data("isHidden", true); + + g_temp.isTextPanelSaparateHover = true; + + } + + } + + //---------------- + + //add zoom buttons panel: + if(g_options.slider_enable_zoom_panel == true){ + g_objZoomPanel.appendHTML(g_objSlider); + } + + + //add video player + g_objVideoPlayer.setHtml(g_objInner); + } + + + /** + * position elements that related to slide + */ + function positionSlideElements(objSlide){ + + //position preloader + var objPreloader = getSlidePreloader(objSlide); + g_functions.placeElementInParentCenter(objPreloader); + + //position video play button + var objVideoPlayButton = getSlideVideoPlayButton(objSlide); + g_functions.placeElementInParentCenter(objVideoPlayButton); + } + + + /** + * position elements + */ + function positionElements(){ + + //place bullets + if(g_objBullets){ + objBullets = g_objBullets.getElement(); + + //strange bug fix (bullets width: 20) by double placing + g_functions.placeElement(objBullets, g_options.slider_bullets_align_hor, g_options.slider_bullets_align_vert, g_options.slider_bullets_offset_hor, g_options.slider_bullets_offset_vert); + g_functions.placeElement(objBullets, g_options.slider_bullets_align_hor, g_options.slider_bullets_align_vert, g_options.slider_bullets_offset_hor, g_options.slider_bullets_offset_vert); + + } + + //place arrows + if(g_options.slider_enable_arrows == true){ + g_functions.placeElement(g_objArrowLeft, g_options.slider_arrow_left_align_hor, g_options.slider_arrow_left_align_vert, g_options.slider_arrow_left_offset_hor, g_options.slider_arrow_left_offset_vert); + g_functions.placeElement(g_objArrowRight, g_options.slider_arrow_right_align_hor, g_options.slider_arrow_left_align_vert, g_options.slider_arrow_right_offset_hor, g_options.slider_arrow_right_offset_vert); + } + + //hide controls + if(g_options.slider_controls_always_on == false) + hideControls(true); + + //place progress indicator + if(g_objProgress){ + + var objProgressElement = g_objProgress.getElement(); + + if(g_options.slider_progress_indicator_type == "bar"){ + var sliderWidth = g_objSlider.width(); + g_objProgress.setSize(sliderWidth); + g_functions.placeElement(objProgressElement, "left", g_options.slider_progress_indicator_align_vert, 0, g_options.slider_progress_indicator_offset_vert); + + }else{ + g_functions.placeElement(objProgressElement, g_options.slider_progress_indicator_align_hor, g_options.slider_progress_indicator_align_vert, g_options.slider_progress_indicator_offset_hor, g_options.slider_progress_indicator_offset_vert); + + } + } + + //position text panel + if(g_objTextPanel) + g_objTextPanel.positionPanel(); + + //position controls elements + placeControlsElements(); + + //place slide elements + positionSlideElements(g_objSlide1); + positionSlideElements(g_objSlide2); + positionSlideElements(g_objSlide3); + + checkMobileModify(); + + } + + + /** + * place elements that located on "controls" div + */ + function placeControlsElements(){ + + if(g_objButtonPlay) + g_functions.placeElement(g_objButtonPlay, g_options.slider_play_button_align_hor, g_options.slider_play_button_align_vert, g_options.slider_play_button_offset_hor, g_options.slider_play_button_offset_vert); + + //position fullscreen button + if(g_objButtonFullscreen) + g_functions.placeElement(g_objButtonFullscreen, g_options.slider_fullscreen_button_align_hor, g_options.slider_fullscreen_button_align_vert, g_options.slider_fullscreen_button_offset_hor, g_options.slider_fullscreen_button_offset_vert); + + //position zoom panel + if(g_objZoomPanel){ + var zoomPanelElement = g_objZoomPanel.getElement(); + g_functions.placeElement(zoomPanelElement, g_options.slider_zoompanel_align_hor, g_options.slider_zoompanel_align_vert, g_options.slider_zoompanel_offset_hor, g_options.slider_zoompanel_offset_vert); + } + } + + + + /** + * position slides by their order + */ + function positionSlides(){ + + var slides = t.getSlidesReference(); + var posX = 0, posY = 0, innerWidth; + var posXPrev=0, posXCurrent = 0, posXNext, nextHasItem, prevHasItem; + + nextHasItem = t.isSlideHasItem(slides.objNextSlide); + prevHasItem = t.isSlideHasItem(slides.objPrevSlide); + + if(prevHasItem){ + posXCurrent = slides.objPrevSlide.outerWidth(); + slides.objPrevSlide.css("z-index",1); + }else + slides.objPrevSlide.hide(); + + posXNext = posXCurrent + slides.objCurrentSlide.outerWidth(); + + innerWidth = posXNext; + if(nextHasItem){ + innerWidth = posXNext + slides.objNextSlide.outerWidth(); + slides.objPrevSlide.css("z-index",2); + }else + slides.objNextSlide.hide(); + + slides.objCurrentSlide.css("z-index",3); + + //set inner size and position + g_functions.placeElement(slides.objCurrentSlide, posXCurrent, posY); + g_objInner.css({"left":-posXCurrent+"px", width:innerWidth+"px"}); + + //position prev + if(prevHasItem){ + g_functions.placeElement(slides.objPrevSlide, posXPrev, posY); + g_functions.showElement(slides.objPrevSlide); + } + + if(nextHasItem){ + g_functions.showElement(slides.objNextSlide); + g_functions.placeElement(slides.objNextSlide, posXNext, posY); + } + + } + + + + /** + * resize the slide image inside item + */ + function resizeSlideItem(objSlide){ + var index = objSlide.data("index"); + if(index === undefined || index == null) + return(false); + + var objItem = g_gallery.getItem(index); + + if(!objItem) + return(false); + + setItemToSlide(objSlide, objItem); + } + + + /** + * show the preloader + * show the index, so only the current index load will hide. + */ + function showPreloader(objPreloader){ + + objPreloader.stop(true).show(100); + + } + + /** + * hide the preloader + */ + function hidePreloader(objPreloader){ + + objPreloader.stop(true).hide(100); + } + + /** + * get proper image border width + */ + function getImageBorderWidth(objImage, imageData){ + + var borderWidth = g_options.slider_image_border_width; + + if(borderWidth <= 10) + return(borderWidth); + + //set image size + var imageSize = g_functions.getElementSize(objImage); + var imageWidth = imageSize.width; + var imageHeight = imageSize.height; + + if(imageData){ + if(imageData.hasOwnProperty("imageWidth")) + imageWidth = imageData.imageWidth; + + if(imageData.hasOwnProperty("imageHeight")) + imageHeight = imageData.imageHeight; + + } + + if(imageWidth <= 0) + return(borderWidth); + + //take the less size + var totalSize = (imageWidth < imageHeight)?imageWidth:imageHeight; + var borderSize = borderWidth * 2; + + var borderRatio = borderSize / totalSize; + + if(borderRatio < g_options.slider_image_border_maxratio) + return(borderWidth); + + //change border width + var borderWidth = (totalSize * g_options.slider_image_border_maxratio)/2; + + borderWidth = Math.round(borderWidth); + + return(borderWidth); + + } + + + /** + * set slider image css design according the settings + */ + function setImageDesign(objImage, slideType, imageData){ + + var css = {}; + if(g_options.slider_image_border == true){ + css["border-style"] = "solid"; + + var borderWidth = getImageBorderWidth(objImage, imageData); + + css["border-width"] = borderWidth+"px"; + css["border-color"] = g_options.slider_image_border_color; + css["border-radius"] = g_options.slider_image_border_radius; + } + + if(slideType != "image" && g_options.slider_video_constantsize == true) + css["background-color"] = "#000000"; + + if(g_options.slider_image_shadow == true){ + css["box-shadow"] = "3px 3px 10px 0px #353535"; + } + + objImage.css(css); + } + + + /** + * scale image constant size (for video items) + */ + function scaleImageConstantSize(objImage, objItem){ + + var constantWidth = g_options.slider_video_constantsize_width; + var constantHeight = g_options.slider_video_constantsize_height; + var scaleMode = g_options.slider_video_constantsize_scalemode; + + var objSize = g_functions.scaleImageExactSizeInParent(objImage, objItem.imageWidth, objItem.imageHeight, constantWidth, constantHeight, scaleMode); + + + return(objSize); + } + + + /** + * + * set item to slide + */ + function setImageToSlide(objSlide, objItem, isForce){ + + var objItemWrapper = objSlide.children(".ug-item-wrapper"); + + var objPreloader = getSlidePreloader(objSlide); + + if(typeof objItem.urlImage == "undefined" || objItem.urlImage == "") + throw new Error("The slide don't have big image defined ( data-image='imageurl' ). Please check gallery items.", "showbig"); + + var urlImage = objItem.urlImage; + + var currentImage = objSlide.data("urlImage"); + + objSlide.data("urlImage",urlImage); + + var scaleMode = t.getScaleMode(objSlide); + + var slideType = t.getSlideType(objSlide); + + objPadding = t.getObjImagePadding(); + + + if(currentImage == urlImage && isForce !== true){ + + var objImage = objItemWrapper.children("img"); + + if(objItem.imageWidth == 0 || objItem.imageHeight == 0){ + g_gallery.checkFillImageSize(objImage, objItem); + } + + var objImageData = {}; + + if(slideType != "image" && g_options.slider_video_constantsize == true){ + objImageData = scaleImageConstantSize(objImage, objItem); + } + else{ + objImageData = g_functions.scaleImageFitParent(objImage, objItem.imageWidth, objItem.imageHeight, scaleMode, objPadding); + } + + setImageDesign(objImage, slideType, objImageData); + g_objThis.trigger(t.events.AFTER_PUT_IMAGE, objSlide); + + } + else{ //place the image inside parent first time + + objImage = g_functions.placeImageInsideParent(urlImage, objItemWrapper, objItem.imageWidth, objItem.imageHeight, scaleMode, objPadding); + + //set image loaded on load: + if(objItem.isBigImageLoaded == true){ + objImage.fadeTo(0,1); + hidePreloader(objPreloader); + + if(slideType != "image" && g_options.slider_video_constantsize == true) + var objImageData = scaleImageConstantSize(objImage, objItem); + else + var objImageData = g_functions.getImageInsideParentData(objItemWrapper, objItem.imageWidth, objItem.imageHeight, scaleMode, objPadding); + + //set missing css width + objImage.css("width",objImageData.imageWidth+"px"); + + setImageDesign(objImage, slideType, objImageData); + + g_objThis.trigger(t.events.AFTER_PUT_IMAGE, objSlide); + } + else{ //if the image not loaded, load the image and show it after. + objImage.fadeTo(0,0); + showPreloader(objPreloader); + objSlide.data("isLoading", true); + + if(t.isSlideCurrent(objSlide)) + g_objThis.trigger(t.events.CURRENTSLIDE_LOAD_START); + + objImage.data("itemIndex", objItem.index); + objImage.on("load",function(){ + + //place the image normally with coordinates + var objImage = jQuery(this); + var itemIndex = objImage.data("itemIndex"); + + objImage.fadeTo(0,1); + + //get and hide preloader + var objSlide = objImage.parent().parent(); + var slideType = t.getSlideType(objSlide); + var objPreloader = getSlidePreloader(objSlide); + var objPadding = t.getObjImagePadding(); + var scaleMode = t.getScaleMode(objSlide); + + hidePreloader(objPreloader); + objSlide.data("isLoading", false); + + if(t.isSlideCurrent(objSlide)) + g_objThis.trigger(t.events.CURRENTSLIDE_LOAD_END); + + g_gallery.onItemBigImageLoaded(null, objImage); + + var objItem = g_gallery.getItem(itemIndex); + + var objImageData = {}; + + if(slideType != "image" && g_options.slider_video_constantsize == true) + scaleImageConstantSize(objImage, objItem); + else{ + objImageData = g_functions.scaleImageFitParent(objImage, objItem.imageWidth, objItem.imageHeight, scaleMode, objPadding); + } + + objImage.fadeTo(0,1); + + setImageDesign(objImage, slideType, objImageData); + + g_objThis.trigger(t.events.AFTER_PUT_IMAGE, objSlide); + }); + } + + } + + + } + + + + /** + * set slider image by url + * if item not set, get current slide item + */ + function setItemToSlide(objSlide, objItem){ + + try{ + + var objItemWrapper = objSlide.children(".ug-item-wrapper"); + + //if the item is empty, remove the image from slide + if(objItem == null){ + objItemWrapper.html(""); + objSlide.removeData("index"); + objSlide.removeData("type"); + objSlide.removeData("urlImage"); + return(false); + } + + var currentIndex = objSlide.data("index"); + + objSlide.data("index",objItem.index); + objSlide.data("type", objItem.type); + + //set link class + if(g_options.slider_enable_links == true && objItem.type == "image"){ + + if(objItem.link) + objSlide.addClass("ug-slide-clickable"); + else + objSlide.removeClass("ug-slide-clickable"); + } + + setImageToSlide(objSlide, objItem); + + //show type related elements + var objVideoPlayButton = getSlideVideoPlayButton(objSlide); + switch(objItem.type){ + case "image": + objVideoPlayButton.hide(); + break; + default: //video + objVideoPlayButton.show(); + break; + } + + }catch(error){ + + if(typeof error.fileName != "undefined" && error.fileName == "showbig") + g_gallery.showErrorMessageReplaceGallery(error.message); + + objItemWrapper.html(""); + throw new Error(error); + return(true); + } + + } + + + + /** + * hide the panel + */ + function hideTextPanel(){ + + if(!g_objTextPanel) + return(false); + + if(isTextPanelHidden() == true) + return(false); + + var panelElement = g_objTextPanel.getElement(); + + var animationTime = 0; + if(g_temp.isTextPanelSaparateHover == true || g_options.slider_textpanel_always_on == true){ + animationTime = g_options.slider_controls_appear_duration; + } + + panelElement.stop().fadeTo(animationTime, 0); + + panelElement.data("isHidden", true); + } + + + /** + * show the text panel + */ + function showTextPanel(){ + + if(!g_objTextPanel) + return(false); + + if(isTextPanelHidden() == false) + return(false); + + var panelElement = g_objTextPanel.getElement(); + + var animationTime = 0; + + if(g_temp.isTextPanelSaparateHover == true || g_options.slider_textpanel_always_on == true){ + + panelElement.show(); + g_objTextPanel.positionElements(); + + animationTime = g_options.slider_controls_appear_duration; + } + + panelElement.stop().show().fadeTo(animationTime,1); + + panelElement.data("isHidden", false); + + } + + + /** + * check if the text panel is hidden or not + */ + function isTextPanelHidden(){ + + var panelElement = g_objTextPanel.getElement(); + + var isHidden = panelElement.data("isHidden"); + if(isHidden === false) + return(false); + + return(true); + } + + + /** + * validate that the slide is certain type, if not, throw error + */ + function validateSlideType(type, objSlide){ + if(objSlide == undefined) + var objSlide = t.getCurrentSlide(); + + var slideType = t.getSlideType(objSlide); + + if(slideType != type){ + throw new Error("Wrong slide type: "+ slideType +", should be: "+type); + return(false); + } + + return(true); + } + + function __________VIDEO_PLAYER_______(){}; + + + + /** + * set video player position + */ + function setVideoPlayerPosition(){ + + var objCurrentSlide = t.getCurrentSlide(); + var objImage = t.getSlideImage(objCurrentSlide); + + var slideSize = g_functions.getElementSize(objCurrentSlide); + var left = slideSize.left; + var top = slideSize.top; + + //set by image position + if(g_options.slider_video_constantsize == true){ + + var imageSize = g_functions.getElementSize(objImage); + left += imageSize.left; + top += imageSize.top; + + }else{ //set video padding + + left += g_options.slider_video_padding_left; + top += g_options.slider_video_padding_top; + + } + + g_objVideoPlayer.setPosition(left, top); + } + + + /** + * set video player constant size + */ + function setVideoPlayerConstantSize(){ + + var videoWidth = g_options.slider_video_constantsize_width; + var videoHeight = g_options.slider_video_constantsize_height; + + g_objVideoPlayer.setSize(videoWidth, videoHeight); + + //set video design + var videoElement = g_objVideoPlayer.getObject(); + + setImageDesign(videoElement, "video"); + } + + + function __________TRANSITION_______(){}; + + + + /** + * do the transition between the current and the next + */ + function doTransition(direction, objItem, forceTransition){ + + g_objThis.trigger(t.events.TRANSITION_START); + + var transition = g_options.slider_transition; + if(forceTransition) + transition = forceTransition; + + //stop current slide action + t.stopSlideAction(null, true); + + switch(transition){ + default: + case "fade": + transitionFade(objItem); + break; + case "slide": + transitionSlide(direction, objItem); + break; + case "lightbox_open": //fade transition without animation + transitionFade(objItem, false, true); + break; + } + + } + + + /** + * switch slide numbers after transition (by direction) + * + */ + this.switchSlideNums = function(direction){ + + //trigger item changed effect + g_objThis.trigger(t.events.BEFORE_SWITCH_SLIDES); + + switch(direction){ + case "left": + var currentNum = g_temp.numCurrent; + g_temp.numCurrent = g_temp.numNext; + g_temp.numNext = g_temp.numPrev; + g_temp.numPrev = currentNum; + break; + case "right": + var currentNum = g_temp.numCurrent; + g_temp.numCurrent = g_temp.numPrev; + g_temp.numPrev = g_temp.numNext; + g_temp.numNext = currentNum; + break; + default: + throw new Error("wrong direction: "+ direction); + break; + } + + //trace(g_temp.numCurrent); + + //trigger item changed effect + g_objThis.trigger(t.events.ITEM_CHANGED); + } + + + /** + * do slide transition + */ + function transitionSlide(direction, objItem){ + + var animating = t.isAnimating(); + + if(animating == true){ + g_temp.itemWaiting = objItem; + return(true); + } + + //always clear next item on transition start + // next item can be only in the middle of the transition. + if(g_temp.itemWaiting != null) + g_temp.itemWaiting = null; + + var slides = t.getSlidesReference(); + + + switch(direction){ + case "right": //change to prev item + setItemToSlide(slides.objPrevSlide, objItem); + positionSlides(); + + var posPrev = g_functions.getElementSize(slides.objPrevSlide); + var destX = -posPrev.left; + + t.switchSlideNums("right"); + + break; + case "left": //change to next item + setItemToSlide(slides.objNextSlide, objItem); + positionSlides(); + + var posNext = g_functions.getElementSize(slides.objNextSlide); + var destX = -posNext.left; + + t.switchSlideNums("left"); + + break; + default: + throw new Error("wrong direction: "+direction); + break; + } + + var transSpeed = g_options.slider_transition_speed; + var transEasing = g_options.slider_transition_easing; + + + var animateParams = { + duration: transSpeed, + easing: transEasing, + queue: false, + always:function(){ + + t.stopSlideAction(); + g_objVideoPlayer.hide(); + + //transit next item if waiting + if(g_temp.itemWaiting != null){ + var direction = getSlideDirection(g_temp.itemWaiting); + transitionSlide(direction, g_temp.itemWaiting); + }else{ + //if no item waiting, please neighbour items in places + t.placeNabourItems(); + g_objThis.trigger(t.events.TRANSITION_END); + } + + } + }; + + + g_objInner.animate({left:destX+"px"}, animateParams); + + } + + + /** + * + * animate opacity in and out + */ + function animateOpacity(objItem, opacity, completeFunction){ + + if(completeFunction) + objItem.fadeTo(g_options.slider_transition_speed, opacity, completeFunction); + else + objItem.fadeTo(g_options.slider_transition_speed, opacity); + } + + + /** + * do fade transition + */ + function transitionFade(objItem, noAnimation, noHidePlayer){ + + if(!noAnimation) + var noAnimation = false; + + var slides = t.getSlidesReference(); + + setItemToSlide(slides.objNextSlide, objItem); + + var objCurrentPos = g_functions.getElementSize(slides.objCurrentSlide); + + g_functions.placeElement(slides.objNextSlide,objCurrentPos.left,objCurrentPos.top); + + //switch slide nums + var currentNum = g_temp.numCurrent; + g_temp.numCurrent = g_temp.numNext; + g_temp.numNext = currentNum; + + g_objThis.trigger(t.events.ITEM_CHANGED); + + slides.objNextSlide.stop(true); + slides.objCurrentSlide.stop(true); + + if(noAnimation == true){ + + slides.objCurrentSlide.fadeTo(0, 0); + slides.objNextSlide.fadeTo(0, 1); + t.placeNabourItems(); + g_objThis.trigger(t.events.TRANSITION_END); + + if(noHidePlayer !== true) + g_objVideoPlayer.hide(); + + }else{ + slides.objNextSlide.fadeTo(0,0); + + animateOpacity(slides.objCurrentSlide,0,function(){ + t.placeNabourItems(); + g_objThis.trigger(t.events.TRANSITION_END); + if(noHidePlayer !== true) + g_objVideoPlayer.hide(); + }); + + if(g_objVideoPlayer.isVisible() == true){ + var videoElement = g_objVideoPlayer.getObject(); + animateOpacity(videoElement, 0); + } + + //animate to next show next + animateOpacity(slides.objNextSlide,1); + } + + } + + + + + function __________CONTROLS_OBJECT_______(){}; + + /** + * modify the slider for mobile + */ + function modifyForMobile(){ + + if(g_options.slider_fullscreen_button_mobilehide == true && g_objButtonFullscreen) + g_objButtonFullscreen.hide(); + + if(g_options.slider_play_button_mobilehide == true && g_objButtonPlay) + g_objButtonPlay.hide(); + + if(g_options.slider_zoompanel_mobilehide == true && g_objZoomPanel) + g_objZoomPanel.getElement().hide(); + + } + + + /** + * modify for no mobile + */ + function modifyForDesctop(){ + + if(g_options.slider_fullscreen_button_mobilehide == true && g_objButtonFullscreen) + g_objButtonFullscreen.show(); + + if(g_options.slider_play_button_mobilehide == true && g_objButtonPlay) + g_objButtonPlay.show(); + + if(g_options.slider_zoompanel_mobilehide == true && g_objZoomPanel) + g_objZoomPanel.getElement().show(); + + } + + + /** + * check and modify for mobile or desctop + */ + function checkMobileModify(){ + + var isMobile = g_gallery.isMobileMode(); + + if(isMobile) + modifyForMobile(); + else + modifyForDesctop(); + + } + + + /** + * get a jquery set of the controls objects + */ + function getControlsObjects(){ + + var objControl = g_objSlider.children(".ug-slider-control"); + + return(objControl); + } + + + /** + * hide the controls + */ + function hideControls(noAnimation){ + + if(g_functions.isTimePassed("sliderControlsToggle") == false) + return(false); + + if(g_temp.isControlsVisible == false) + return(false); + + if(!noAnimation) + var noAnimation = false; + + var objControls = getControlsObjects(); + + if(noAnimation === true) + objControls.stop().fadeTo(0,0).hide(); + else{ + objControls.stop().fadeTo(g_options.slider_controls_appear_duration, 0, function(){objControls.hide()}); + } + + g_temp.isControlsVisible = false; + } + + + /** + * show controls only if they meaned to be shown + * @param noAnimation + */ + function checkAndShowControls(noAnimation){ + + if(g_options.slider_controls_always_on == true) + showControls(noAnimation); + } + + + /** + * hide the controls + */ + function showControls(noAnimation){ + + //validate for short time pass + if(g_functions.isTimePassed("sliderControlsToggle") == false) + return(false); + + if(g_temp.isControlsVisible == true) + return(true); + + + if(!noAnimation) + var noAnimation = false; + + var objControls = getControlsObjects(); + + if(noAnimation === true) + objControls.stop().show(); + else{ + + objControls.stop().show().fadeTo(0,0); + objControls.fadeTo(g_options.slider_controls_appear_duration, 1); + + } + + g_temp.isControlsVisible = true; + + } + + + + /** + * toggle the controls (show, hide) + */ + function toggleControls(){ + + if(g_temp.isControlsVisible == false) + showControls(); + else + hideControls(); + } + + + /** + * set controls mode + * modes: image, video + */ + function setControlsMode(mode){ + + if(mode == g_temp.currentControlsMode) + return(false); + + switch(mode){ + case "image": + if(g_objZoomPanel) + g_objZoomPanel.getElement().show(); + break; + case "video": + if(g_objZoomPanel) + g_objZoomPanel.getElement().hide(); + break; + default: + throw new Error("wrong controld mode: " + mode); + break; + } + + g_temp.currentControlsMode = mode; + + } + + + + function __________EVENTS___________(){}; + + /** + * on item change event + */ + function onItemChange(data, arg_objItem, role){ + + //trace("slider on change"); + + var objItem = g_gallery.getSelectedItem(); + + t.setItem(objItem, false, role); + + var itemIndex = objItem.index; + + //set active bullet + if(g_objBullets) + g_objBullets.setActive(itemIndex); + + //handle text panel + if(g_objTextPanel){ + if(g_temp.isTextPanelSaparateHover == false) + showTextPanel(); + } + + if(objItem.type == "image"){ + setControlsMode("image"); + //placeControlsElements(); + } + else{ + setControlsMode("video"); + } + + } + + + /** + * on bullet click - change the item to selected + */ + function onBulletClick(event, bulletIndex){ + g_gallery.selectItem(bulletIndex); + } + + + /** + * on touch end + * toggle controls + */ + function onClick(event){ + + //double tap action + if(g_objTouchSlider && g_objTouchSlider.isTapEventOccured(event) == false) + return(true); + + g_objThis.trigger(t.events.CLICK, event); + + + } + + + /** + * on actual click event + */ + function onActualClick(){ + + //check link + var currentSlide = t.getCurrentSlide(); + var isClickable = currentSlide.hasClass("ug-slide-clickable"); + var objItem = t.getCurrentItem(); + + if(isClickable){ + + //redirect to link + if(g_options.slider_links_newpage == false){ + location.href = objItem.link; + }else{ + window.open(objItem.link, '_blank'); + } + + return(true); + } + + //check toggle controls + if(g_options.slider_controls_always_on == false && + g_options.slider_controls_appear_ontap == true && t.isCurrentSlideType("image") == true){ + + toggleControls(); + + //show text panel if hidden + if(g_objTextPanel && g_options.slider_textpanel_always_on == true && t.isCurrentSlideType("image") && t.isCurrentSlideImageFit()) + showTextPanel(); + } + + + } + + + /** + * on zoom start event + */ + function onZoomChange(event){ + + if(g_objTextPanel && t.isCurrentSlideType("image") && t.isCurrentSlideImageFit() == false) + hideTextPanel(); + } + + + /** + * on mouse enter + */ + function onMouseEnter(){ + + showControls(); + + } + + + /** + * on mouse leave + */ + function onMouseLeave(){ + + hideControls(); + + } + + + /** + * on slide video play button click + */ + function objVideoPlayClick(objButton){ + var objSlide = objButton.parent(); + t.startSlideAction(objSlide); + } + + /** + * on video player show event + */ + function onVideoPlayerShow(){ + + if(g_gallery.isPlayMode()){ + g_gallery.pausePlaying(); + } + + g_objThis.trigger(t.events.ACTION_START); + } + + + /** + * on video player hide event + */ + function onVideoPlayerHide(){ + + if(g_gallery.isPlayMode()){ + g_gallery.continuePlaying(); + } + + g_objThis.trigger(t.events.ACTION_END); + } + + + /** + * on item image update, update the image inside the slider if relevant + */ + function onItemImageUpdate(event, index, urlImage){ + + if(g_objSlide1.data("index") == index){ + objItem = g_gallery.getItem(index); + setImageToSlide(g_objSlide1, objItem, true); //force + } + + if(g_objSlide2.data("index") == index){ + objItem = g_gallery.getItem(index); + setImageToSlide(g_objSlide2, objItem, true); + } + + if(g_objSlide3.data("index") == index){ + objItem = g_gallery.getItem(index); + setImageToSlide(g_objSlide3, objItem, true); + } + + } + + + /** + * after image loaded. position video play button + */ + function onSlideImageLoaded(data, objSlide){ + + objSlide = jQuery(objSlide); + var objImage = t.getSlideImage(objSlide); + var objButtonVideoPlay = getSlideVideoPlayButton(objSlide); + var objSize = g_functions.getElementSize(objImage); + + g_functions.placeElement(objButtonVideoPlay, "center", "middle", objSize.left, objSize.top, objImage); + } + + + /** + * init event of current slide + */ + function initSlideEvents(objSlide){ + + //set video player events + var objVideoPlayButton = getSlideVideoPlayButton(objSlide); + g_functions.addClassOnHover(objVideoPlayButton); + + g_functions.setButtonOnClick(objVideoPlayButton, objVideoPlayClick); + + } + + + /** + * init events + */ + function initEvents(){ + + //on item image update, update the image inside the slider if relevant + g_objGallery.on(g_gallery.events.ITEM_IMAGE_UPDATED, onItemImageUpdate); + + + //on item change, change the item in the slider. + g_objGallery.on(g_gallery.events.ITEM_CHANGE, onItemChange); + + if(g_objBullets) + jQuery(g_objBullets).on(g_objBullets.events.BULLET_CLICK,onBulletClick); + + //arrows events + if(g_options.slider_enable_arrows == true){ + + g_functions.addClassOnHover(g_objArrowRight, "ug-arrow-hover"); + g_functions.addClassOnHover(g_objArrowLeft, "ug-arrow-hover"); + + g_gallery.setNextButton(g_objArrowRight); + g_gallery.setPrevButton(g_objArrowLeft); + } + + + //show / hide controls + if(g_options.slider_controls_always_on == false){ + + //assign hover evens only if no touch device + g_objSlider.hover(onMouseEnter, onMouseLeave); + + } + + //touch events appear on tap event + g_objSlider.on("touchend click", onClick); + + //actual click event + g_objThis.on(t.events.CLICK, onActualClick); + + //show / hide text panel, if it's saparate from controls + if(g_objTextPanel && g_temp.isTextPanelSaparateHover == true){ + g_objSlider.hover(showTextPanel, hideTextPanel); + } + + //init play / pause button + if(g_objButtonPlay){ + g_functions.addClassOnHover(g_objButtonPlay, "ug-button-hover"); + g_gallery.setPlayButton(g_objButtonPlay); + } + + //init fullscreen button + if(g_objButtonFullscreen){ + g_functions.addClassOnHover(g_objButtonFullscreen, "ug-button-hover"); + g_gallery.setFullScreenToggleButton(g_objButtonFullscreen); + } + + //on zoom start / end events + if(g_objZoomSlider){ + g_objThis.on(t.events.ZOOM_CHANGE, onZoomChange); + } + + if(g_objZoomPanel) + g_objZoomPanel.initEvents(); + + //init video player related events + g_objVideoPlayer.initEvents(); + + //video API events + jQuery(g_objVideoPlayer).on(g_objVideoPlayer.events.SHOW, onVideoPlayerShow); + jQuery(g_objVideoPlayer).on(g_objVideoPlayer.events.HIDE, onVideoPlayerHide); + + //add slide events + initSlideEvents(g_objSlide1); + initSlideEvents(g_objSlide2); + initSlideEvents(g_objSlide3); + + //on image loaded + g_objThis.on(t.events.AFTER_PUT_IMAGE, onSlideImageLoaded); + + //image mouseenter / mouseleave event + + //set mouseover events on the images + g_objSlider.on("mouseenter",".ug-item-wrapper img",function(event){ + g_objThis.trigger(t.events.IMAGE_MOUSEENTER); + }); + + g_objSlider.on("mouseleave",".ug-item-wrapper img",function(event){ + var isMouseOver = t.isMouseInsideSlideImage(event); + + if(isMouseOver == false) + g_objThis.trigger(t.events.IMAGE_MOUSELEAVE); + }); + + } + + + /** + * destroy slider events + */ + this.destroy = function(){ + + g_objThis.off(t.events.AFTER_PUT_IMAGE); + + g_objGallery.off(g_gallery.events.ITEM_IMAGE_UPDATED); + g_objGallery.off(g_gallery.events.ITEM_CHANGE); + + if(g_objBullets) + jQuery(g_objBullets).on(g_objBullets.events.BULLET_CLICK); + + g_objSlider.off("mouseenter"); + g_objSlider.off("mouseleave"); + + g_objSlider.off("touchend"); + g_objSlider.off("click"); + g_objThis.off(t.events.CLICK); + + if(g_objZoomSlider) + g_objThis.off(t.events.ZOOM_CHANGE); + + g_objThis.off(t.events.BEFORE_SWITCH_SLIDES); + jQuery(g_objVideoPlayer).off(g_objVideoPlayer.events.SHOW); + jQuery(g_objVideoPlayer).off(g_objVideoPlayer.events.HIDE); + + g_objVideoPlayer.destroy(); + + g_objSlider.off("mouseenter",".ug-item-wrapper img"); + g_objSlider.off("mouseleave",".ug-item-wrapper img"); + } + + + function __________GETTERS___________(){}; + + /** + * get loader class by loader type + */ + function getLoaderClass(){ + var loaderClass; + switch(g_options.slider_loader_type){ + default: + case 1: loaderClass = "ug-loader1";break; + case 2: loaderClass = "ug-loader2";break; + case 3: loaderClass = "ug-loader3";break; + case 4: loaderClass = "ug-loader4";break; + case 5: loaderClass = "ug-loader5";break; + case 6: loaderClass = "ug-loader6";break; + case 7: loaderClass = "ug-loader7";break; + case 8: loaderClass = "ug-loader8";break; + case 9: loaderClass = "ug-loader9";break; + } + + if(g_options.slider_loader_color == "black") + loaderClass += " ug-loader-black"; + + return(loaderClass); + } + + + + /** + * + * get slide by number + */ + function getSlideByNum(num){ + + switch(num){ + case 1: + return(g_objSlide1); + break; + case 2: + return(g_objSlide2); + break; + case 3: + return(g_objSlide3); + break; + default: + throw new Error("wrong num: " + num); + break; + } + } + + + /** + * + * get slide direction of current item + */ + function getSlideDirection(objItem){ + + var slides = t.getSlidesReference(); + + //validate if the item is not selected already + var currentIndex = slides.objCurrentSlide.data("index"); + var nextIndex = objItem.index; + + var direction = "left"; + if(currentIndex > nextIndex) + direction = "right"; + + return(direction); + } + + + /** + * get slide preloader + */ + function getSlidePreloader(objSlide){ + + if(!objSlide) + var objSlide = t.getCurrentSlide(); + + var objPreloader = objSlide.children(".ug-slider-preloader"); + return(objPreloader); + } + + /** + * get slide videoplay button + */ + function getSlideVideoPlayButton(objSlide){ + var objButton = objSlide.children(".ug-button-videoplay"); + return(objButton); + } + + + + /** + * get slide item + */ + function getSlideItem(objSlide){ + if(!objSlide) + var objSlide = t.getCurrentSlide(); + + var index = objSlide.data("index"); + if(index == undefined) + return(null); + + var objItem = g_gallery.getItem(index); + return(objItem); + } + + + /** + * get slide number + */ + function getNumSlide(objSlide){ + var numSlide = objSlide.data("slidenum"); + return(numSlide); + } + + + + this.________EXTERNAL_GENERAL___________ = function(){}; + + + /** + * init function for avia controls + * options: width / height + */ + this.init = function(objGallery, objOptions, optionsPrefix){ + + initSlider(objGallery, objOptions, optionsPrefix); + } + + /** + * get slide image + */ + this.getSlideImage = function(objSlide){ + + if(!objSlide) + var objSlide = t.getCurrentSlide(); + + var objImage = objSlide.find(".ug-item-wrapper img"); + return(objImage); + } + + + /** + * set slider html + */ + this.setHtml = function(objParent){ + + setHtmlSlider(objParent); + } + + + /** + * run the slider + */ + this.run = function(){ + + runSlider(); + } + + + /** + * check if the inner object in place, for panning znd zooming posibility check + */ + this.isInnerInPlace = function(){ + + var slides = t.getSlidesReference(); + + var posCurrent = g_functions.getElementSize(slides.objCurrentSlide); + var inPlaceX = -posCurrent.left; + var objInnerSize = g_functions.getElementSize(g_objInner); + + if(inPlaceX == objInnerSize.left) + return(true); + else + return(false); + } + + /** + * is animating + */ + this.isAnimating = function(){ + + var isAnimated = g_objInner.is(":animated"); + + return(isAnimated); + } + + /** + * check if the slide is current + */ + this.isSlideCurrent = function(objSlide){ + var numSlide = objSlide.data("slidenum"); + if(g_temp.numCurrent == numSlide) + return(true); + + return(false); + } + + + /** + * + * tells if the slide has item + */ + this.isSlideHasItem = function(objSlide){ + var index = objSlide.data("index"); + if(index === undefined || index === null) + return(false); + + return(true); + } + + + /** + * get image padding object for scaling the image + */ + this.getObjImagePadding = function(){ + + var objPadding = { + padding_top: g_options.slider_image_padding_top, + padding_bottom: g_options.slider_image_padding_bottom, + padding_left: g_options.slider_image_padding_left, + padding_right: g_options.slider_image_padding_right + }; + + return(objPadding); + } + + + /** + * get items reference by their order + */ + this.getSlidesReference = function(){ + + var obj = { + objPrevSlide: getSlideByNum(g_temp.numPrev), + objNextSlide: getSlideByNum(g_temp.numNext), + objCurrentSlide: getSlideByNum(g_temp.numCurrent) + }; + + return(obj); + } + + + /** + * get current slide + */ + this.getCurrentSlide = function(){ + + var slides = t.getSlidesReference(); + + return(slides.objCurrentSlide); + } + + + /** + * get index of current item + */ + this.getCurrentItemIndex = function(){ + + var slides = t.getSlidesReference(); + + var currentIndex = slides.objCurrentSlide.data("index"); + if(currentIndex === null || currentIndex === undefined) + currentIndex = -1; + + return(currentIndex); + } + + + /** + * get current slide item + */ + this.getCurrentItem = function(){ + var currentIndex = t.getCurrentItemIndex(); + if(currentIndex == -1) + return(null); + + var objItem = g_gallery.getItem(currentIndex); + + return(objItem); + } + + + /** + * get type of some slide + */ + this.getSlideType = function(objSlide){ + + if(objSlide == undefined) + objSlide = t.getCurrentSlide(); + + var type = objSlide.data("type"); + return(type); + } + + + /** + * is mouse inside slide image + * get mouse position from event + */ + this.isMouseInsideSlideImage = function(event){ + + var objImage = t.getSlideImage(); + + var point = g_functions.getMousePosition(event); + if(point.pageX === undefined) + point = g_objTouchSlider.getLastMousePos(); + + var pointImg = g_functions.getMouseElementPoint(point, objImage); + var objSize = g_functions.getElementSize(objImage); + isMouseInside = g_functions.isPointInsideElement(pointImg, objSize); + + return(isMouseInside); + } + + + /** + * check if current slide type is certain type + */ + this.isCurrentSlideType = function(type){ + var currentSlideType = t.getSlideType(); + if(currentSlideType == type) + return(true); + + return(false); + } + + + /** + * check if current slide is loading image + */ + this.isCurrentSlideLoadingImage = function(){ + var currentSlide = t.getCurrentSlide(); + var isLoading = currentSlide.data("isLoading"); + if(isLoading === true) + return(true); + + return(false); + } + + + /** + * change the slider to some item content + */ + this.setItem = function(objItem, forseTransition, role){ + + var slides = t.getSlidesReference(); + + //validate if the item is not selected already + var currentIndex = slides.objCurrentSlide.data("index"); + var nextIndex = objItem.index; + + if(nextIndex == currentIndex){ + return(true); + } + + var isFirstSlide = (currentIndex == undefined); + + if(isFirstSlide){ + setItemToSlide(slides.objCurrentSlide, objItem); + t.placeNabourItems(); + + }else{ + + var direction = "left"; //move foreward + + var numItems = g_gallery.getNumItems(); + + if(role == "next") + direction = "left"; + else if(role == "prev" || currentIndex > nextIndex) + direction = "right"; + else if(currentIndex > nextIndex) + direction = "right"; + + doTransition(direction, objItem, forseTransition); + } + + } + + + /** + * when the transition complete, put the next / prev items at their place + */ + this.placeNabourItems = function(){ + + var slides = t.getSlidesReference(); + var currentIndex = slides.objCurrentSlide.data("index"); + + var itemPrev = g_gallery.getPrevItem(currentIndex); + var itemNext = g_gallery.getNextItem(currentIndex); + + //trace(itemPrev); + //trace(itemNext); + + //trace("place " + currentIndex, "next: "+); + + setItemToSlide(slides.objNextSlide, itemNext); + setItemToSlide(slides.objPrevSlide, itemPrev); + + positionSlides(); + } + + + + this.________EXTERNAL_API___________ = function(){}; + + + /** + * stop some slide action if active + */ + this.stopSlideAction = function(objSlide, isPause){ + + if(!objSlide) + objSlide = t.getCurrentSlide(); + + if(isPause === true) + g_objVideoPlayer.pause(); + else + g_objVideoPlayer.hide(); + + // trace("stop action"); + + } + + + + /** + * start some slide action if exists + */ + this.startSlideAction = function(objSlide){ + + // trace("start action"); + + if(!objSlide) + objSlide = t.getCurrentSlide(); + + var objItem = getSlideItem(objSlide); + + if(objItem.type == "image") + return(true) + + if(g_options.slider_video_constantsize == true) + setVideoPlayerConstantSize(); + + setVideoPlayerPosition(); + + g_objVideoPlayer.show(); + + switch(objItem.type){ + case "youtube": + g_objVideoPlayer.playYoutube(objItem.videoid); + break; + case "vimeo": + g_objVideoPlayer.playVimeo(objItem.videoid); + break; + case "html5video": + g_objVideoPlayer.playHtml5Video(objItem.videoogv, objItem.videowebm, objItem.videomp4, objItem.urlImage); + break; + case "soundcloud": + g_objVideoPlayer.playSoundCloud(objItem.trackid); + break; + case "wistia": + g_objVideoPlayer.playWistia(objItem.videoid); + break; + } + + } + + + /** + * get the scale mode according the state (normal, fullscreen). + */ + this.getScaleMode = function(objSlide){ + + if(!objSlide) + var objSlide = t.getCurrentSlide(); + + var slideType = t.getSlideType(objSlide); + + //return media scale mode + if(slideType != "image") + return(g_options.slider_scale_mode_media); + + if(g_options.slider_scale_mode == g_options.slider_scale_mode_fullscreen) + return(g_options.slider_scale_mode); + + if(g_gallery.isFullScreen() == true) + return(g_options.slider_scale_mode_fullscreen); + else + return(g_options.slider_scale_mode); + + } + + + /** + * get slider objects + */ + this.getObjects = function(){ + + var obj = { + g_objSlider: g_objSlider, + g_objInner: g_objInner, + g_options: g_options, + g_objZoomSlider: g_objZoomSlider + }; + + return(obj); + } + + + /** + * get zoom object + */ + this.getObjZoom = function(){ + + return(g_objZoomSlider); + } + + + + /** + * get slider options + */ + this.getOptions = function(){ + + return(g_options); + } + + + /** + * get slider element + */ + this.getElement = function(){ + + return(g_objSlider); + } + + /** + * get video object + */ + this.getVideoObject = function(){ + return(g_objVideoPlayer); + } + + + /** + * return true if current slider image fit the slider + * @returns + */ + this.isCurrentSlideImageFit = function(){ + var objSlide = t.getCurrentSlide(); + + var slideType = t.getSlideType(objSlide); + + validateSlideType("image", objSlide); + + var objImage = t.getSlideImage(objSlide); + + //if image don't yet added to dom, return false + if(objImage.length == 0) + return(false); + + var isFit = g_functions.isImageFitParent(objImage); + + return(isFit); + } + + + /** + * check if current image in place + */ + this.isCurrentImageInPlace = function(){ + + var objImage = t.getSlideImage(); + if(objImage.length == 0) + return(false); + + var scaleMode = t.getScaleMode(); + var objPadding = t.getObjImagePadding(); + var objItem = getSlideItem(); + + var objParent = objImage.parent(); + + var objFitSize = g_functions.getImageInsideParentData(objParent, objItem.imageWidth, objItem.imageHeight, scaleMode, objPadding); + var objSize = g_functions.getElementSize(objImage); + + var output = false; + + if(objFitSize.imageWidth == objSize.width) + output = true; + + return(output); + } + + + /** + * if slide is bussy in some action + */ + this.isSlideActionActive = function(){ + + return g_objVideoPlayer.isVisible(); + } + + /** + * return if swipe action active + */ + this.isSwiping = function(){ + if(!g_objTouchSlider) + return(false); + + var isActive = g_objTouchSlider.isTouchActive(); + + return(isActive); + } + + + /** + * if slider preloading image (if preloader visible) + */ + this.isPreloading = function(){ + + var objPreloader = getSlidePreloader(); + if(objPreloader.is(":visible")) + return(true); + + return(false); + } + + /** + * set the options + */ + this.setOptions = function(objOptions){ + + //change options by prefix + if(g_optionsPrefix) + objOptions = g_functions.convertCustomPrefixOptions(objOptions, g_optionsPrefix, "slider"); + + g_options = jQuery.extend(g_options, objOptions); + + } + + + /** + * set the slider size + * works well on resize too. + */ + this.setSize = function(width, height){ + + if(width < 0 || height < 0) + return(true); + + var objCssSlider = {}; + objCssSlider["width"] = width + "px"; + objCssSlider["height"] = height + "px"; + g_objSlider.css(objCssSlider); + + //set inner: + var objCssInner = {}; + objCssInner["height"] = height + "px"; + objCssInner["top"] = "0px"; + objCssInner["left"] = "0px"; + g_objInner.css(objCssInner); + + //set slide wrapper + var objCssSlide = {}; + objCssSlide["height"] = height + "px"; + objCssSlide["width"] = width + "px"; + + g_objSlide1.css(objCssSlide); + g_objSlide2.css(objCssSlide); + g_objSlide3.css(objCssSlide); + + var itemWidth = width - g_options.slider_item_padding_left - g_options.slider_item_padding_right; + var itemHeight = height - g_options.slider_item_padding_top - g_options.slider_item_padding_bottom; + + //set item wrapper + var objCssItemWrapper = {}; + objCssItemWrapper["width"] = itemWidth + "px"; + objCssItemWrapper["height"] = itemHeight + "px"; + objCssItemWrapper["top"] = g_options.slider_item_padding_top + "px"; + objCssItemWrapper["left"] = g_options.slider_item_padding_left + "px"; + + g_objSlider.find(".ug-item-wrapper").css(objCssItemWrapper); + + + //set text panel size + if(g_objTextPanel){ + g_objTextPanel.setSizeByParent(); + } + + positionElements(); + + //set image to slides + resizeSlideItem(g_objSlide1); + resizeSlideItem(g_objSlide2); + resizeSlideItem(g_objSlide3); + + positionSlides(); + + //set video player size + var currentSlideType = t.getSlideType(); + + if(currentSlideType != "image" && g_options.slider_video_constantsize == true){ + + setVideoPlayerConstantSize(); + }else{ + var videoWidth = width - g_options.slider_video_padding_left - g_options.slider_video_padding_right; + var videoHeight = height - g_options.slider_video_padding_top - g_options.slider_video_padding_bottom; + + //set video player size + g_objVideoPlayer.setSize(videoWidth, videoHeight); + } + + setVideoPlayerPosition(); + + } + + + /** + * refresh slide items after options change + */ + this.refreshSlideItems = function(){ + + if(t.isAnimating() == true) + return(true); + + resizeSlideItem(g_objSlide1); + resizeSlideItem(g_objSlide2); + resizeSlideItem(g_objSlide3); + positionSlides(); + + } + + + /** + * is mouse over the slider + */ + this.isMouseOver = function(){ + + return g_objSlider.ismouseover(); + } + + /** + * set slider position + */ + this.setPosition = function(left, top){ + + g_functions.placeElement(g_objSlider, left, top); + + } + + + /** + * zoom in + */ + this.zoomIn = function(){ + if(!g_objZoomSlider) + return(true); + + g_objZoomSlider.zoomIn(); + } + + /** + * zoom out + */ + this.zoomOut = function(){ + + if(!g_objZoomSlider) + return(true); + + g_objZoomSlider.zoomOut(); + + } + + /** + * zoom back to original + */ + this.zoomBack = function(){ + + if(!g_objZoomSlider) + return(true); + + g_objZoomSlider.zoomBack(); + } + + +} + +/** -------------- TextPanel class ---------------------*/ + +function UGTextPanel(){ + + var t = this; + var g_objPanel, g_objParent, g_objTitle, g_objDesc; + var g_objBG, g_objTextWrapper, g_gallery; + var g_functions = new UGFunctions(), g_optionsPrefix = ""; + + var g_options = { + textpanel_align:"bottom", //(top , bottom), textpanel align according the parent + textpanel_margin:0, //margin from the textpanel position according the textpanel_align + textpanel_text_valign:"middle", //middle, top, bottom - text vertical align + textpanel_padding_top:10, //textpanel padding top + textpanel_padding_bottom:10, //textpanel padding bottom + textpanel_height: null, //textpanel height. if null it will be set dynamically + textpanel_padding_title_description: 5, //the space between the title and the description + textpanel_padding_right: 11, //cut some space for text from right + textpanel_padding_left: 11, //cut some space for text from left + textpanel_fade_duration: 200, //the fade duration of textpanel appear + textpanel_enable_title: true, //enable the title text + textpanel_enable_description: true, //enable the description text + textpanel_enable_bg: true, //enable the textpanel background + textpanel_bg_color:"#000000", //textpanel background color + textpanel_bg_opacity: 0.4, //textpanel background opacity + + textpanel_title_color:null, //textpanel title color. if null - take from css + textpanel_title_font_family:null, //textpanel title font family. if null - take from css + textpanel_title_text_align:null, //textpanel title text align. if null - take from css + textpanel_title_font_size:null, //textpanel title font size. if null - take from css + textpanel_title_bold:null, //textpanel title bold. if null - take from css + textpanel_css_title:{}, //textpanel additional css of the title + + textpanel_desc_color:null, //textpanel description font family. if null - take from css + textpanel_desc_font_family:null, //textpanel description font family. if null - take from css + textpanel_desc_text_align:null, //textpanel description text align. if null - take from css + textpanel_desc_font_size:null, //textpanel description font size. if null - take from css + textpanel_desc_bold:null, //textpanel description bold. if null - take from css + textpanel_css_description:{}, //textpanel additional css of the description + + textpanel_desc_style_as_title: false, //set that the description style will be as title + + textpanel_bg_css:{} //textpanel background css + }; + + var g_temp = { + isFirstTime: true, + setInternalHeight: true, //flag if set internal height or not + lastTitleBottom: 0, + lastDescHeight: 0 + }; + + + /** + * position elements from top + */ + function positionElementsTop(animateHeight, startY){ + + if(!startY) + var startY = g_options.textpanel_padding_top; + + //place title + var maxy = startY; + + //place title + if(g_objTitle){ + var titleY = maxy; + g_functions.placeElement(g_objTitle, 0, titleY); + + var isTitleVisible = g_objTitle.is(":visible"); + if(isTitleVisible == true){ + var objTitleSize = g_functions.getElementSize(g_objTitle); + + var maxy = objTitleSize.bottom; + if(maxy > 0) + g_temp.lastTitleBottom = maxy; + }else{ + var maxy = 20; //get last or assumed maxy + + if(g_temp.lastTitleBottom > 0) + maxy = g_temp.lastTitleBottom; + } + + } + + + //place description + var textDesc = ""; + if(g_objDesc) + textDesc = jQuery.trim(g_objDesc.text()); + + if(textDesc != ""){ + + var descY = maxy; + + if(g_objTitle) + descY += g_options.textpanel_padding_title_description; + + g_functions.placeElement(g_objDesc, 0, descY); + + var isVisible = jQuery(g_objDesc).is(":visible"); + + if(isVisible == true){ + var objDescSize = g_functions.getElementSize(g_objDesc); + maxy = objDescSize.bottom; + + if(objDescSize.height > 0) + g_temp.lastDescHeight = objDescSize.height; + + }else{ + var descHeight = 16; //take from last saved + if(g_temp.lastDescHeight > 0) + descHeight = g_temp.lastDescHeight; + + maxy = descY + descHeight; + } + + } + + + //change panel height + if(!g_options.textpanel_height && g_temp.setInternalHeight == true){ + + var panelHeight = maxy + g_options.textpanel_padding_bottom; + + setHeight(panelHeight, animateHeight); + } + + } + + /** + * get total text and description height + */ + function getTotalTextHeight(){ + var totalHeight = 0; + + if(g_objTitle) + totalHeight += g_objTitle.outerHeight(); + + if(g_objDesc){ + var textDesc = ""; + if(g_objDesc) + textDesc = jQuery.trim(g_objDesc.text()); + + if(textDesc != ""){ + if(g_objTitle) + totalHeight += g_options.textpanel_padding_title_description; + + totalHeight += g_objDesc.outerHeight(); + } + + } + + + return(totalHeight); + } + + + /** + * position elements to center + */ + function positionElementsMiddle(){ + + var totalTextHeight = getTotalTextHeight(); + var startY = (g_objTextWrapper.height() - totalTextHeight) / 2; + + positionElementsTop(false, startY); + } + + + /** + * position elements to bottom + */ + function positionElementBottom(){ + + var totalTextHeight = getTotalTextHeight(); + var startY = g_objTextWrapper.height() - totalTextHeight - g_options.textpanel_padding_bottom; + + positionElementsTop(false, startY); + } + + + /** + * position elements inside the panel + */ + this.positionElements = function(animateHeight){ + + //if(g_objPanel.is(":visible") == false) + //trace("the text panel is hidden. can't position elements") + + //if height not set, position only top + if(!g_options.textpanel_height || g_options.textpanel_text_valign == "top"){ + positionElementsTop(animateHeight); + return(false); + } + + switch(g_options.textpanel_text_valign){ + default: + case "top": + positionElementsTop(false); //no animation in this case + break; + case "bottom": + positionElementBottom(); + break; + case "center": + case "middle": + positionElementsMiddle(); + break; + } + + } + + + /** + * set new panel height + */ + function setHeight(height, animateHeight){ + + if(!animateHeight) + var animateHeight = false; + + if(animateHeight == true){ + + if(g_objBG){ + + //avoid background jumps + var currentHeight = g_objBG.height(); + if(height > currentHeight) + g_objBG.height(height); + } + + var objCss = {height: height+"px"}; + g_objPanel.add(g_objTextWrapper).animate(objCss, g_options.textpanel_fade_duration); + + }else{ + + if(g_objBG) + g_objBG.height(height); + + g_objPanel.add(g_objTextWrapper).height(height); + } + + } + + + + + /** + * init the panel + */ + this.init = function(objGallery, customOptions, optionsPrefix){ + + g_gallery = objGallery; + + //change options by prefix + if(optionsPrefix){ + g_optionsPrefix = optionsPrefix; + customOptions = g_functions.convertCustomPrefixOptions(customOptions,g_optionsPrefix,"textpanel"); + + } + + if(customOptions) + g_options = jQuery.extend(g_options, customOptions); + + //validation: + if(g_options.textpanel_enable_title == false && g_options.textpanel_enable_description == false) + throw new Error("Textpanel Error: The title or description must be enabled"); + + if(g_options.textpanel_height && g_options.textpanel_height < 0) + g_options.textpanel_height = null; + + //copy desc style from title + if(g_options.textpanel_desc_style_as_title == true){ + if(!g_options.textpanel_desc_color) + g_options.textpanel_desc_color = g_options.textpanel_title_color; + + if(!g_options.textpanel_desc_bold) + g_options.textpanel_desc_bold = g_options.textpanel_title_bold; + + if(!g_options.textpanel_desc_font_family) + g_options.textpanel_desc_font_family = g_options.textpanel_title_font_family; + + if(!g_options.textpanel_desc_font_size) + g_options.textpanel_desc_font_size = g_options.textpanel_title_font_size; + + if(!g_options.textpanel_desc_text_align) + g_options.textpanel_desc_text_align = g_options.textpanel_title_text_align; + } + + } + + + /** + * append the bullets html to some parent + */ + this.appendHTML = function(objParent, addClass){ + g_objParent = objParent; + + if(addClass){ + addClass = " "+addClass; + }else + addClass = ""; + + var html = "
      "; + + if(g_options.textpanel_enable_bg == true) + html += "
      "; + + html += "
      "; + + if(g_options.textpanel_enable_title == true) + html += "
      "; + + if(g_options.textpanel_enable_description == true) + html += "
      "; + + html += "
      "; + + objParent.append(html); + + g_objPanel = objParent.children(".ug-textpanel"); + g_objTextWrapper = g_objPanel.children(".ug-textpanel-textwrapper"); + + setCss(); + + } + + + /** + * set panel css according the options + */ + function setCss(){ + + //set background css + if(g_options.textpanel_enable_bg == true){ + g_objBG = g_objPanel.children(".ug-textpanel-bg"); + g_objBG.fadeTo(0,g_options.textpanel_bg_opacity); + + var objCssBG = {"background-color":g_options.textpanel_bg_color}; + objCssBG = jQuery.extend(objCssBG, g_options.textpanel_bg_css); + + g_objBG.css(objCssBG); + } + + + //set title css from options + if(g_options.textpanel_enable_title == true){ + g_objTitle = g_objTextWrapper.children(".ug-textpanel-title"); + var objCssTitle = {}; + + if(g_options.textpanel_title_color !== null) + objCssTitle["color"] = g_options.textpanel_title_color; + + if(g_options.textpanel_title_font_family !== null) + objCssTitle["font-family"] = g_options.textpanel_title_font_family; + + if(g_options.textpanel_title_text_align !== null) + objCssTitle["text-align"] = g_options.textpanel_title_text_align; + + if(g_options.textpanel_title_font_size !== null) + objCssTitle["font-size"] = g_options.textpanel_title_font_size+"px"; + + if(g_options.textpanel_title_bold !== null){ + + if(g_options.textpanel_title_bold === true) + objCssTitle["font-weight"] = "bold"; + else + objCssTitle["font-weight"] = "normal"; + + } + + //set additional css + if(g_options.textpanel_css_title) + objCssTitle = jQuery.extend(objCssTitle, g_options.textpanel_css_title); + + g_objTitle.css(objCssTitle); + } + + //set description css + if(g_options.textpanel_enable_description == true){ + g_objDesc = g_objTextWrapper.children(".ug-textpanel-description"); + + var objCssDesc = {}; + + if(g_options.textpanel_desc_color !== null) + objCssDesc["color"] = g_options.textpanel_desc_color; + + if(g_options.textpanel_desc_font_family !== null) + objCssDesc["font-family"] = g_options.textpanel_desc_font_family; + + if(g_options.textpanel_desc_text_align !== null) + objCssDesc["text-align"] = g_options.textpanel_desc_text_align; + + if(g_options.textpanel_desc_font_size !== null) + objCssDesc["font-size"] = g_options.textpanel_desc_font_size+"px"; + + if(g_options.textpanel_desc_bold !== null){ + + if(g_options.textpanel_desc_bold === true) + objCssDesc["font-weight"] = "bold"; + else + objCssDesc["font-weight"] = "normal"; + + } + + //set additional css + if(g_options.textpanel_css_title) + objCssDesc = jQuery.extend(objCssDesc, g_options.textpanel_css_description); + + g_objDesc.css(objCssDesc); + } + + } + + /** + * on item change, set the text + */ + function onItemChange(){ + var objItem = g_gallery.getSelectedItem(); + t.setText(objItem.title, objItem.description); + } + + + /** + * init events + */ + function initEvents(){ + + //on item change, set the text in the slider. + jQuery(g_gallery).on(g_gallery.events.ITEM_CHANGE, onItemChange); + } + + + /** + * destroy the events + */ + this.destroy = function(){ + jQuery(g_gallery).off(g_gallery.events.ITEM_CHANGE); + } + + /** + * run the text panel + */ + this.run = function(){ + + t.setSizeByParent(); + + initEvents(); + } + + /** + * set panel size + */ + this.setPanelSize = function(panelWidth, panelHeight){ + + g_temp.setInternalHeight = true; + + if(!panelHeight) + var panelHeight = 80; //some default number + else + g_temp.setInternalHeight = false; + + if(g_options.textpanel_height) + panelHeight = g_options.textpanel_height; + + g_objPanel.width(panelWidth); + g_objPanel.height(panelHeight); + + //set background size + if(g_objBG){ + g_objBG.width(panelWidth); + g_objBG.height(panelHeight); + } + + //set textwrapper size and position + var textWrapperWidth = panelWidth - g_options.textpanel_padding_left - g_options.textpanel_padding_right; + var textWrapperLeft = g_options.textpanel_padding_left; + + g_functions.setElementSizeAndPosition(g_objTextWrapper, textWrapperLeft, 0, textWrapperWidth, panelHeight); + + //set text width + if(g_objTitle) + g_objTitle.width(textWrapperWidth); + + //set description height + if(g_objDesc) + g_objDesc.width(textWrapperWidth); + + if(g_temp.isFirstTime == false) + t.positionElements(false); + } + + + /** + * set size by parent. the height is set to default meanwhile + */ + this.setSizeByParent = function(){ + + var objSize = g_functions.getElementSize(g_objParent); + t.setPanelSize(objSize.width); + } + + /** + * set plain sext without other manipulations + */ + this.setTextPlain = function(title, description){ + + if(g_objTitle) + g_objTitle.html(title); + + if(g_objDesc) + g_objDesc.html(description); + + } + + + /** + * set html text + */ + this.setText = function(title, description){ + + if(g_temp.isFirstTime == true){ + + t.setTextPlain(title, description); + + g_temp.isFirstTime = false; + + t.positionElements(false); + + }else{ //width animation + + g_objTextWrapper.stop().fadeTo(g_options.textpanel_fade_duration,0,function(){ + + t.setTextPlain(title, description); + + t.positionElements(true); + + jQuery(this).fadeTo(g_options.textpanel_fade_duration,1); + }); + + } + + } + + + + + /** + * position the panel + */ + this.positionPanel = function(customTop, customLeft){ + + var objCss = {}; + + if(customTop !== undefined && customTop !== null){ + objCss.top = customTop; + objCss.bottom = "auto"; + }else{ + + switch(g_options.textpanel_align){ + case "top": + objCss.top = g_options.textpanel_margin + "px"; + break; + case "bottom": + objCss.top = "auto"; + objCss.bottom = g_options.textpanel_margin + "px"; + break; + case "middle": + objCss.top = g_functions.getElementRelativePos(g_objPanel, "middle", g_options.textpanel_margin); + break; + } + + } + + if(customLeft !== undefined && customLeft !== null) + objCss.left = customLeft; + + g_objPanel.css(objCss); + } + + + /** + * set custom options + */ + this.setOptions = function(objOptions){ + + if(g_optionsPrefix) + objOptions = g_functions.convertCustomPrefixOptions(objOptions, g_optionsPrefix, "textpanel"); + + g_options = jQuery.extend(g_options, objOptions); + + } + + + /** + * get html element + */ + this.getElement = function(){ + + return(g_objPanel); + } + + /** + * get element size + */ + this.getSize = function(){ + + var objSize = g_functions.getElementSize(g_objPanel); + return(objSize); + } + + + /** + * refresh panel size, position and contents + */ + this.refresh = function(toShow, noPosition, panelWidth, panelHeight){ + + setCss(); + + if(!panelWidth) + t.setSizeByParent(); + else + t.setPanelSize(panelWidth, panelHeight); + + t.positionElements(false); + + if(noPosition !== true) + t.positionPanel(); + + if(toShow === true) + t.show(); + } + + + /** + * hide the panel + */ + this.hide = function(){ + + g_objPanel.hide(); + } + + /** + * show the panel + */ + this.show = function(){ + g_objPanel.show(); + } + + /** + * get options + */ + this.getOptions = function(){ + return(g_options); + } + + /** + * get text panel option + */ + this.getOption = function(optionName){ + + if(g_options.hasOwnProperty(optionName) == false) + return(null); + + return(g_options[optionName]); + } + + +} + +/** -------------- UGZoomButtonsPanel class ---------------------*/ + +/** + * zoom buttons panel class + */ +function UGZoomButtonsPanel(){ + + var t = this; + var g_objPanel, g_objParent, g_objButtonPlus, g_objButtonMinus, g_objButtonReturn; + var g_slider = new UGSlider; + var g_functions = new UGFunctions(); + + var g_options = { + slider_zoompanel_skin: "" //skin of the zoom panel, if empty inherit from gallery skin + }; + + var g_temp = { + + }; + + + /** + * init the panel + */ + this.init = function(objSlider, customOptions){ + + g_slider = objSlider; + + if(customOptions) + g_options = jQuery.extend(g_options, customOptions); + } + + + /** + * append the bullets html to some parent + */ + this.appendHTML = function(objParent){ + g_objParent = objParent; + + var html = "
      "; + + html += "
      "; + html += "
      "; + html += "
      "; + + html += "
      "; + + objParent.append(html); + + g_objPanel = objParent.children(".ug-zoompanel"); + g_objButtonPlus = g_objPanel.children(".ug-zoompanel-plus"); + g_objButtonMinus = g_objPanel.children(".ug-zoompanel-minus"); + g_objButtonReturn = g_objPanel.children(".ug-zoompanel-return"); + + } + + + /** + * set objects - use it instead insert html + */ + this.setObjects = function(objButtonPlus, objButtonMinus, objButtonReturn){ + + g_objButtonPlus = objButtonPlus; + g_objButtonMinus = objButtonMinus; + g_objButtonReturn = objButtonReturn; + + if(g_objButtonMinus) + g_objButtonMinus.addClass("ug-zoompanel-button-disabled"); + + if(g_objButtonReturn) + g_objButtonReturn.addClass("ug-zoompanel-button-disabled"); + + } + + + /** + * get buttons element + */ + this.getElement = function(){ + + return(g_objPanel); + } + + + /** + * check if the button disabled + */ + function isButtonDisabled(objButton){ + + if(!objButton) + return(true); + + if(objButton.hasClass("ug-zoompanel-button-disabled")) + return(true); + + return(false); + } + + + /** + * disable some button + */ + function disableButton(objButton){ + + if(objButton) + objButton.addClass("ug-zoompanel-button-disabled"); + } + + /** + * enable some button + */ + function enableButton(objButton){ + + if(objButton) + objButton.removeClass("ug-zoompanel-button-disabled"); + } + + + /** + * on zoom change + */ + function onZoomChange(){ + + //skip not image types + if(g_slider.isCurrentSlideType("image") == false) + return(true); + + var isFit = g_slider.isCurrentSlideImageFit(); + + if(isFit == true){ //if fit, disable buttons + + if(isButtonDisabled(g_objButtonMinus) == false){ + disableButton(g_objButtonMinus); + disableButton(g_objButtonReturn); + } + + }else{ //if not fit, enable minus buttons + + if(isButtonDisabled(g_objButtonMinus) == true){ + enableButton(g_objButtonMinus); + enableButton(g_objButtonReturn); + } + + } + + } + + /** + * init zoompanel events + */ + t.initEvents = function(){ + + //add hover class on buttons + g_functions.addClassOnHover(g_objButtonPlus, "ug-button-hover"); + g_functions.addClassOnHover(g_objButtonMinus, "ug-button-hover"); + g_functions.addClassOnHover(g_objButtonReturn, "ug-button-hover"); + + //set buttons click events + + g_functions.setButtonOnClick(g_objButtonPlus, function(){ + + if(isButtonDisabled(g_objButtonPlus) == true) + return(true); + + g_slider.zoomIn(); + }); + + g_functions.setButtonOnClick(g_objButtonMinus, function(){ + + if(isButtonDisabled(g_objButtonMinus) == true) + return(true); + + g_slider.zoomOut(); + }); + + g_functions.setButtonOnClick(g_objButtonReturn, function(){ + + if(isButtonDisabled(g_objButtonReturn) == true) + return(true); + + g_slider.zoomBack(); + }); + + //on zoom change event + jQuery(g_slider).on(g_slider.events.ZOOM_CHANGE,onZoomChange); + jQuery(g_slider).on(g_slider.events.ITEM_CHANGED,onZoomChange); + + } + + +} + + +/** -------------- UgBullets class ---------------------*/ + +function UGBullets(){ + + var t = this, g_numBullets = 0, g_gallery = new UniteGalleryMain(); + var g_objBullets, g_objParent, g_activeIndex = -1, g_bulletWidth; + var g_functions = new UGFunctions(); + + var g_temp = { + isInited:false + }; + + var g_options = { + bullets_skin: "", //bullets_skin: "" //skin of the bullets, if empty inherit from gallery skin + bullets_addclass: "", //bullets object class addition + bullets_space_between:-1 //set the space between bullets. If -1 then will be set default space from the skins + } + + + /** + * the events + */ + this.events = { + BULLET_CLICK : "bullet_click" + }; + + /** + * init the bullets + */ + this.init = function(gallery, customOptions, numBullets){ + g_gallery = gallery; + + if(numBullets) + g_numBullets = numBullets; + else + g_numBullets = g_gallery.getNumItems(); + + g_temp.isInited = true; + g_options = jQuery.extend(g_options, customOptions); + + if(g_options.bullets_skin == "") + g_options.bullets_skin = g_options.gallery_skin; + + } + + /** + * add bullets to the html + */ + function setHtmlBullets(){ + var html = ""; + + var addHtml = ""; + if(g_options.bullets_space_between != -1) + addHtml = " style='margin-left:" + g_options.bullets_space_between + "px'"; + + for(var i=0; i< g_numBullets; i++){ + if(i == 0) + html += "
      "; + else + html += "
      "; + } + + g_objBullets.html(html); + + //set bullet width value + if(!g_bulletWidth){ + var objBullet = g_objBullets.find(".ug-bullet:first-child"); + if(objBullet.length) + g_bulletWidth = objBullet.width(); + } + } + + /** + * get total bullets width + */ + this.getBulletsWidth = function(){ + if(g_numBullets == 0) + return(0); + + if(!g_bulletWidth) + return(0); + + var totalWidth = g_numBullets*g_bulletWidth+(g_numBullets-1)*g_options.bullets_space_between; + return(totalWidth); + } + + + /** + * append the bullets html to some parent + */ + this.appendHTML = function(objParent){ + g_objParent = objParent; + + validateInited(); + var addClass = ""; + if(g_options.bullets_addclass != "") + addClass = " " + g_options.bullets_addclass; + + var html = "
      "; + + html += "
      "; + + g_objBullets = jQuery(html); + + objParent.append(g_objBullets); + + setHtmlBullets(); + + initEvents(); + } + + + /** + * update number of bullets + */ + this.updateNumBullets = function(numBullets){ + + g_numBullets = numBullets; + setHtmlBullets(); + initEvents(); + } + + + /** + * + * on bullet click + */ + function onBulletClick(objBullet){ + + //filter not active only + if(t.isActive(objBullet) == true) + return(true); + + var index = objBullet.index(); + + jQuery(t).trigger(t.events.BULLET_CLICK, index); + } + + + /** + * init the bullets events + * trigger bullet click event + */ + function initEvents(){ + + var objBullets = g_objBullets.children(".ug-bullet"); + + g_functions.setButtonOnClick(objBullets, onBulletClick); + + objBullets.on("mousedown mouseup",function(event){ + //event.preventDefault(); + return(false); + }); + + } + + + /** + * get the bullets element + */ + this.getElement = function(){ + return g_objBullets; + } + + + /** + * set some item active + */ + this.setActive = function(index){ + validateInited(); + validateIndex(index); + + var children = g_objBullets.children(".ug-bullet"); + children.removeClass("ug-bullet-active"); + + var bullet = jQuery(children[index]); + bullet.addClass("ug-bullet-active"); + + g_activeIndex = index; + } + + + /** + * check if the bullet is active + */ + this.isActive = function(index){ + validateIndex(index); + + if(typeof index != "number") + var objBullet = index; + else{ + var objBullet = g_objBullets.children(".ug-bullet")[index]; + } + + if(objBullet.hasClass("ug-bullet-active")) + return(true); + + return(false); + } + + + /** + * get bullets number + */ + this.getNumBullets = function(){ + return(g_numBullets); + } + + /** + * validate bullets index + */ + function validateIndex(index){ + if(index < 0 || index >= g_numBullets) + throw new Error("wrong bullet index: " + index); + } + + + /** + * validate that the bullets are inited + */ + function validateInited(){ + + if(g_temp.isInited == true) + return(true); + + throw new Error("The bullets are not inited!"); + } + + + +} + +/** -------------- UgProgressBar class ---------------------*/ + +function UGProgressBar(){ + + var t = this, g_isInited = false; + var g_percent = 0, g_objBar, g_objInner, g_functions = new UGFunctions(); + + var g_options = { + slider_progressbar_color:"#ffffff", //progress bar color + slider_progressbar_opacity: 0.6, //progress bar opacity + slider_progressbar_line_width: 5 //progress bar line width + } + + + /** + * put progress pie to some wrapper + */ + this.put = function(g_objWrapper, userOptions){ + + if(userOptions) + g_options = jQuery.extend(g_options, userOptions); + + g_objWrapper.append("
      "); + g_objBar = g_objWrapper.children(".ug-progress-bar"); + g_objInner = g_objBar.children(".ug-progress-bar-inner"); + + //init the objects + g_objInner.css("background-color", g_options.slider_progressbar_color); + g_objBar.height(g_options.slider_progressbar_line_width); + g_objInner.height(g_options.slider_progressbar_line_width); + g_objInner.width("0%"); + + //set opacity old way (because ie bug) + var opacity = g_options.slider_progressbar_opacity; + + var objInnerHTML = g_objInner[0]; + objInnerHTML.style.opacity = opacity; + objInnerHTML.style.filter = 'alpha(opacity=' + opacity*100 + ')'; + } + + + /** + * put the pie hidden + */ + this.putHidden = function(g_objWrapper, userOptions){ + t.put(g_objWrapper, userOptions); + g_objBar.hide(); + } + + /** + * get the bar object + */ + this.getElement = function(){ + + return(g_objBar); + } + + /** + * set progress bar size + */ + this.setSize = function(width){ + + g_objBar.width(width); + g_objInner.width(width); + t.draw(); + } + + + /** + * set position + */ + this.setPosition = function(left, top, offsetLeft, offsetTop){ + + g_functions.placeElement(g_objBar, left, top, offsetLeft, offsetTop); + } + + + /** + * draw the progress bar + */ + this.draw = function(){ + var innerWidth = g_percent * 100; + + g_objInner.width(innerWidth + "%"); + } + + + /** + * set and draw the progress + */ + this.setProgress = function(percent){ + + g_percent = g_functions.normalizePercent(percent); + + //debugLine(g_percent, true); + + t.draw(); + } + + /** + * get type string + */ + this.getType = function(){ + return("bar"); + } + +} + +/** -------------- UgProgressPie class ---------------------*/ + +function UGProgressPie(){ + + var t = this, g_isInited = false; + var g_percent, g_objPie, g_functions = new UGFunctions(); + + var g_options = { + slider_progresspie_type_fill: false, //false is stroke, true is fill - the progress pie type, stroke of fill + slider_progresspie_color1: "#B5B5B5", //the first color of the progress pie + slider_progresspie_color2: "#E5E5E5", //progress pie second color + slider_progresspie_stroke_width: 6, //progress pie stroke width + slider_progresspie_width: 30, //progess pie width + slider_progresspie_height:30 //progress pie height + } + + + /** + * put progress pie to some wrapper + */ + this.put = function(g_objWrapper, userOptions){ + + if(userOptions) + g_options = jQuery.extend(g_options, userOptions); + + g_objWrapper.append(""); + g_objPie = g_objWrapper.children(".ug-canvas-pie"); + } + + + /** + * put the pie hidden + */ + this.putHidden = function(g_objWrapper, userOptions){ + t.put(g_objWrapper, userOptions); + draw(0.1); + g_objPie.hide(); + } + + + /** + * get jquery object + */ + this.getElement = function(){ + return(g_objPie); + } + + /** + * set position + */ + this.setPosition = function(left, top){ + + g_functions.placeElement(g_objPie, left, top); + + } + + /** + * get the height and width of the object + */ + this.getSize = function(){ + + var obj = { + width: g_options.slider_progresspie_width, + height: g_options.slider_progresspie_height + }; + + return(obj); + } + + /** + * draw the progress pie + */ + function draw(percent){ + + if(!percent) + var percent = 0; + + var radius = Math.min(g_options.slider_progresspie_width, g_options.slider_progresspie_height) / 2; + + var ctx = g_objPie[0].getContext('2d'); + + //init the context + if(g_isInited == false){ + + g_isInited = true; + + ctx.rotate(Math.PI*(3/2)); + ctx.translate(-2 * radius,0); + } + + ctx.clearRect(0,0,g_options.slider_progresspie_width, g_options.slider_progresspie_height); + + var centerX = g_options.slider_progresspie_width / 2; + var centerY = g_options.slider_progresspie_height / 2; + + //draw main arc + var startPoint = 0; + var endPoint = percent * Math.PI * 2; + + + if(g_options.slider_progresspie_type_fill == true){ //fill + + ctx.beginPath(); + ctx.moveTo(centerX, centerY); + ctx.arc(centerX,centerY,radius,startPoint, endPoint); + ctx.lineTo(centerX, centerY); + + ctx.fillStyle = g_options.slider_progresspie_color1; + ctx.fill(); + ctx.closePath(); + + }else{ //stroke + ctx.globalCompositeOperation = "source-over"; + + ctx.beginPath(); + ctx.moveTo(centerX, centerY); + ctx.arc(centerX,centerY,radius,startPoint, endPoint); + ctx.lineTo(centerX, centerY); + + ctx.fillStyle = g_options.slider_progresspie_color1; + ctx.fill(); + ctx.closePath(); + + ctx.globalCompositeOperation = "destination-out"; + + var radius2 = radius - g_options.slider_progresspie_stroke_width; + + ctx.beginPath(); + + ctx.moveTo(centerX, centerY); + ctx.arc(centerX,centerY,radius2,startPoint, endPoint); + ctx.lineTo(centerX, centerY); + + ctx.fillStyle = g_options.slider_progresspie_color1; + ctx.fill(); + + ctx.closePath(); + } + + + //draw rest arc (only on fill type): + if(g_options.slider_progresspie_type_fill == true){ + startPoint = endPoint; + endPoint = Math.PI * 2; + ctx.beginPath(); + ctx.arc(centerX,centerY,radius,startPoint, endPoint); + ctx.lineTo(centerX, centerY); + ctx.fillStyle = g_options.slider_progresspie_color2; + ctx.fill(); + ctx.closePath(); + } + + } + + + /** + * set progress (0-1) + */ + this.setProgress = function(percent){ + + percent = g_functions.normalizePercent(percent); + + g_percent = percent; + draw(percent); + } + + /** + * get type string + */ + this.getType = function(){ + return("pie"); + } + +} + +/**f + * touch thumbs control class + * addon to strip gallery + */ +function UGTouchSliderControl(){ + + var g_objSlider, g_objInner, g_parent = new UGSlider(); + var g_objParent, g_options, t=this; + + var g_functions = new UGFunctions(); + + + var g_options = { + slider_transition_continuedrag_speed: 250, //the duration of continue dragging after drag end + slider_transition_continuedrag_easing: "linear", //easing function of continue dragging animation + slider_transition_return_speed: 300, //the duration of the "return to place" animation + slider_transition_return_easing: "easeInOutQuad" //easing function of the "return to place" animation + }; + + var g_temp = { + touch_active: false, + startMouseX: 0, + startMouseY: 0, + lastMouseX: 0, + lastMouseY: 0, + startPosx:0, + startTime:0, + isInitDataValid:false, + slides: null, + lastNumTouches:0, + isDragging: false, + storedEventID: "touchSlider", + videoStartX: 0, + isDragVideo: false, + videoObject: null + }; + + + /** + * get diff inner object position from current item pos + */ + function getDiffPosFromCurrentItem(slides){ + + if(!slides) + var slides = g_parent.getSlidesReference(); + + var posCurrent = g_functions.getElementSize(slides.objCurrentSlide); + var inPlaceX = -posCurrent.left; + var objInnerSize = g_functions.getElementSize(g_objInner); + var diffPos = inPlaceX - objInnerSize.left; + + return(diffPos); + } + + /** + * check if the movement that was held is valid for slide change + */ + function isMovementValidForChange(){ + + var slides = g_parent.getSlidesReference(); + + //check position, if more then half, move + var diffPos = getDiffPosFromCurrentItem(slides); + + var breakSize = Math.round(slides.objCurrentSlide.width() * 3 / 8); + + if(Math.abs(diffPos) >= breakSize) + return(true); + + //check gesture, if vertical mostly then not move + var diffX = Math.abs(g_temp.lastMouseX - g_temp.startMouseX); + var diffY = Math.abs(g_temp.lastMouseY - g_temp.startMouseY); + + //debugLine("diffx: " + diffX, true, true); + + if(diffX < 20) + return(false); + + //if(diffY >= diffX) + //return(false); + + //check time. Short time always move + var endTime = jQuery.now(); + var diffTime = endTime - g_temp.startTime; + + //debugLine("time: " + diffTime, true); + + if(diffTime < 500) + return(true); + + + return(false); + } + + /** + * check tab event occured + * invokes on touchend event on the slider object + */ + this.isTapEventOccured = function(event){ + + //validate one touch + var arrTouches = g_functions.getArrTouches(event); + var numTouches = arrTouches.length; + + if(numTouches != 0 || g_temp.lastNumTouches != 0){ + g_temp.lastNumTouches = numTouches; + return(false); + } + + g_temp.lastNumTouches = numTouches; + + var slides = g_parent.getSlidesReference(); + + //check position, if more then half, move + var diffPos = getDiffPosFromCurrentItem(slides); + + //check gesture, if vertical mostly then not move + var diffX = Math.abs(g_temp.lastMouseX - g_temp.startMouseX); + var diffY = Math.abs(g_temp.lastMouseY - g_temp.startMouseY); + + //check by time + var endTime = jQuery.now(); + var diffTime = endTime - g_temp.startTime; + + //combine move and time + if(diffX < 20 && diffY < 50 && diffTime < 500) + return(true); + + return(false); + } + + /** + * return the item to place + */ + function returnToPlace(slides){ + + if(g_parent.isInnerInPlace() == true) + return(false); + + //trigger before return event + g_objParent.trigger(g_parent.events.BEFORE_RETURN); + + if(!slides) + var slides = g_parent.getSlidesReference(); + + var posCurrent = g_functions.getElementSize(slides.objCurrentSlide); + var destX = -posCurrent.left; + + //animate objects + g_objInner.animate({left:destX+"px"},{ + duration: g_options.slider_transition_return_speed, + easing: g_options.slider_transition_continuedrag_easing, + queue: false, + progress: function(animation, number, remainingMS){ + + //check drag video + if(g_temp.isDragVideo == true){ + var objSize = g_functions.getElementSize(g_objInner); + var innerX = objSize.left; + + var posDiff = innerX - destX; + + var videoPosX = g_temp.videoStartX + posDiff; + g_temp.videoObject.css("left", videoPosX); + } + + }, + complete: function(){ + g_objParent.trigger(g_parent.events.AFTER_RETURN); + } + }); + + } + + + /** + * + * change the item to given direction + */ + function changeItem(direction){ + + g_parent.getVideoObject().hide(); + g_parent.switchSlideNums(direction); + g_parent.placeNabourItems(); + + } + + /** + * continue the dragging by changing the slides to the right place. + */ + function continueSlideDragChange(){ + + //get data + var slides = g_parent.getSlidesReference(); + + var diffPos = getDiffPosFromCurrentItem(slides); + + if(diffPos == 0) + return(false); + + var direction = (diffPos > 0) ? "left" : "right"; + + var isReturn = false; + + switch(direction){ + case "right": //change to prev item + + if( g_parent.isSlideHasItem(slides.objPrevSlide) ){ + + var posPrev = g_functions.getElementSize(slides.objPrevSlide); + var destX = -posPrev.left; + + }else //return current item + isReturn = true; + + break; + case "left": //change to next item + + if( g_parent.isSlideHasItem(slides.objNextSlide) ){ + + var posNext = g_functions.getElementSize(slides.objNextSlide); + var destX = -posNext.left; + + }else + isReturn = true; + break; + } + + + if(isReturn == true){ + returnToPlace(slides); + + }else{ + + //animate objects + g_objInner.stop().animate({left:destX+"px"},{ + duration: g_options.slider_transition_continuedrag_speed, + easing: g_options.slider_transition_continuedrag_easing, + queue: false, + progress: function(){ + + //check drag video + if(g_temp.isDragVideo == true){ + var objSize = g_functions.getElementSize(g_objInner); + var innerX = objSize.left; + var posDiff = innerX - g_temp.startPosx; + var videoPosX = g_temp.videoStartX + posDiff; + g_temp.videoObject.css("left", videoPosX); + } + + }, + always:function(){ + changeItem(direction); + g_objParent.trigger(g_parent.events.AFTER_DRAG_CHANGE); + } + }); + + } + + + } + + + /** + * handle slider drag on mouse drag + */ + function handleSliderDrag(event){ + + var diff = g_temp.lastMouseX - g_temp.startMouseX; + + if(diff == 0) + return(true); + + var direction = (diff < 0) ? "left":"right"; + + var objZoomSlider = g_parent.getObjZoom(); + + //don't drag if the zoom panning enabled + //store init position after image zoom pan end + if(objZoomSlider){ + + var isPanEnabled = objZoomSlider.isPanEnabled(event,direction); + + if(isPanEnabled == true){ + g_temp.isInitDataValid = false; + return(true); + }else{ + + if(g_temp.isInitDataValid == false){ + storeInitTouchData(event); + return(true); + } + + } + } + + //set inner div position + var currentPosx = g_temp.startPosx + diff; + + //check out of borders and slow down the motion: + if(diff > 0 && currentPosx > 0) + currentPosx = currentPosx / 3; + + else if(diff < 0 ){ + + var innerEnd = currentPosx + g_objInner.width(); + var sliderWidth = g_objSlider.width(); + + if( innerEnd < sliderWidth ){ + currentPosx = g_temp.startPosx + diff/3; + } + } + + if(g_temp.isDragging == false){ + g_temp.isDragging = true; + g_objParent.trigger(g_parent.events.START_DRAG); + } + + g_objInner.css("left", currentPosx+"px"); + + //drag video + if(g_temp.isDragVideo == true){ + var posDiff = currentPosx - g_temp.startPosx; + var videoPosX = g_temp.videoStartX + posDiff; + + g_temp.videoObject.css("left", videoPosX); + } + + } + + /** + * store init touch position + */ + function storeInitTouchData(event){ + + var mousePos = g_functions.getMousePosition(event); + + g_temp.startMouseX = mousePos.pageX; + + //debugLine("startx:" + g_temp.startMouseX, true, true); + + g_temp.startMouseY = mousePos.pageY; + + g_temp.lastMouseX = g_temp.startMouseX; + g_temp.lastMouseY = g_temp.startMouseY; + g_temp.startTime = jQuery.now(); + + var arrTouches = g_functions.getArrTouches(event); + g_temp.startArrTouches = g_functions.getArrTouchPositions(arrTouches); + + var objPos = g_functions.getElementSize(g_objInner); + + g_temp.startPosx = objPos.left; + + g_temp.isInitDataValid = true; + + //check if video object need to be dragged + g_temp.isDragVideo = false; + + + g_functions.storeEventData(event, g_temp.storedEventID); + } + + /** + * disable touch active + */ + function disableTouchActive(who){ + + g_temp.touch_active = false; + + //debugLine("disable: " + who, true, true); + } + + /** + * enable the touch active + */ + function enableTouchActive(who, event){ + + g_temp.touch_active = true; + storeInitTouchData(event); + + //debugLine("enable: " + who, true, true); + } + + + /** + * on touch slide start + * + */ + function onTouchStart(event){ + + event.preventDefault(); + + g_temp.isDragging = false; + + //debugLine("touchstart", true, true); + + //check if the slides are changing from another event. + if(g_parent.isAnimating() == true){ + g_objInner.stop(true, true); + } + + //check num touches + var arrTouches = g_functions.getArrTouches(event); + if(arrTouches.length > 1){ + + if(g_temp.touch_active == true){ + disableTouchActive("1"); + } + + return(true); + } + + if(g_temp.touch_active == true){ + return(true); + } + + enableTouchActive("1", event); + + } + + + /** + * + * on touch move event + */ + function onTouchMove(event){ + + if(g_temp.touch_active == false) + return(true); + + //detect moving without button press + if(event.buttons == 0){ + disableTouchActive("2"); + + continueSlideDragChange(); + + return(true); + } + + g_functions.updateStoredEventData(event, g_temp.storedEventID); + + event.preventDefault(); + + var mousePos = g_functions.getMousePosition(event); + g_temp.lastMouseX = mousePos.pageX; + g_temp.lastMouseY = mousePos.pageY; + + //debugLine("lastX:" + g_temp.lastMouseX, true, true); + + var scrollDir = null; + + if(g_options.slider_vertical_scroll_ondrag == true) + scrollDir = g_functions.handleScrollTop(g_temp.storedEventID); + + if(scrollDir !== "vert") + handleSliderDrag(event); + + } + + /** + * on touch end event + */ + function onTouchEnd(event){ + + //debugLine("touchend", true, true); + + var arrTouches = g_functions.getArrTouches(event); + var numTouches = arrTouches.length; + var isParentInPlace = g_parent.isInnerInPlace(); + + if(isParentInPlace == true && g_temp.touch_active == false && numTouches == 0){ + + return(true); + } + + if(numTouches == 0 && g_temp.touch_active == true){ + + disableTouchActive("3"); + + var isValid = false; + + var wasVerticalScroll = g_functions.wasVerticalScroll(g_temp.storedEventID); + + if(wasVerticalScroll == false) + isValid = isMovementValidForChange(); + + if(isValid == true) + continueSlideDragChange(); //change the slide + else + returnToPlace(); //return the inner object to place (if not in place) + + }else{ + + if(numTouches == 1 && g_temp.touch_active == false){ + + enableTouchActive("2",event); + } + + } + + } + + + /** + * init touch events + */ + function initEvents(){ + + //slider mouse down - drag start + g_objSlider.bind("mousedown touchstart",onTouchStart); + + //on body move + jQuery("body").bind("mousemove touchmove",onTouchMove); + + //on body mouse up - drag end + jQuery(window).add("body").bind("mouseup touchend", onTouchEnd); + + } + + + + /** + * init function for avia controls + */ + this.init = function(objSlider, customOptions){ + + g_parent = objSlider; + g_objParent = jQuery(g_parent); + g_objects = objSlider.getObjects(); + + g_objSlider = g_objects.g_objSlider; + g_objInner = g_objects.g_objInner; + + g_options = jQuery.extend(g_options, customOptions); + + initEvents(); + } + + + /** + * get last mouse position + */ + this.getLastMousePos = function(){ + var obj = { + pageX: g_temp.lastMouseX, + pageY: g_temp.lastMouseY + }; + + return(obj); + } + + + /** + * is touch active + */ + this.isTouchActive = function(){ + + return(g_temp.touch_active); + + } + + +} +/** + * touch thumbs control class + * addon to strip gallery + */ +function UGZoomSliderControl(){ + + var g_objSlider, g_objInner, g_parent = new UGSlider(), g_objParent; + + var g_functions = new UGFunctions(); + + var t = this; + + var g_options = { + slider_zoom_step: 1.2, //the step of zooming with mouse wheel or zoom button + slider_zoom_max_ratio: 6, //max zoom ratio + slider_zoom_return_pan_duration: 400, //the return from pan animation duration + slider_zoom_return_pan_easing: "easeOutCubic" //the return from pan wasing function + }; + + var g_temp = { + isPanActive:false, + startMouseX:0, + startMouseY:0, + lastMouseX:0, + lastMouseY:0, + startImageX:0, + startImageY:0, + panXActive:false, + panYActive:false, + objImage:null, + objImageSize:null, + objParent:null, + objParentSize:null, + objSlide:null, + storeImageLastTime:0, + + isZoomActive: false, + startDistance:0, + startMiddlePoint:null, + imageOrientPoint:null, + objFitImageSize:null, + isZoomedOnce:false + }; + + + /** + * init the object + */ + function initObject(objSlider, customOptions){ + + g_parent = objSlider; + g_objParent = jQuery(g_parent); + g_objects = objSlider.getObjects(); + g_objSlider = g_objects.g_objSlider; + g_objInner = g_objects.g_objInner; + + g_options = jQuery.extend(g_options, customOptions); + + initEvents(); + } + + + /** + * get fit image to slider scale mode + * the fill become fit + */ + function getFitScaleMode(){ + + var scaleMode = g_parent.getScaleMode(); + + if(scaleMode != "down") + scaleMode = "fit"; + + return(scaleMode); + } + + + /** + * cache current slide and image + */ + function storeCurrentImage(){ + + //prevent continious image storring + var currentTime = jQuery.now(); + var diff = currentTime - g_temp.storeImageLastTime; + + if(diff < 20) + return(false); + + var slides = g_parent.getSlidesReference(); + g_temp.objSlide = slides.objCurrentSlide; + g_temp.objImage = slides.objCurrentSlide.find("img"); + + if(g_temp.objImage.length == 0) + return(false); + + g_temp.objImageSize = g_functions.getElementSize(g_temp.objImage); + g_temp.objParent = g_temp.objImage.parent(); + g_temp.objParentSize = g_functions.getElementSize(g_temp.objParent); + + var scaleMode = getFitScaleMode(); + + objPadding = g_parent.getObjImagePadding(); + + g_temp.objFitImageSize = g_functions.getImageInsideParentDataByImage(g_temp.objImage, scaleMode, objPadding); + + var currentTime = jQuery.now(); + g_temp.storeImageLastTime = currentTime; + + return(true); + } + + + /** + * zoom current image + * mode: in, out, back + */ + function zoomCurrentImage(mode, mousePos){ + + var slides = g_parent.getSlidesReference(); + var objImage = slides.objCurrentSlide.find("img"); + var scaleMode = getFitScaleMode(); + + g_objParent.trigger(g_parent.events.ZOOM_START); + + //flag if the images zoomed + var isZoomed = true; + + var objPadding = g_parent.getObjImagePadding(); + + if(mode == "back"){ + var objOriginalSize = g_functions.getImageOriginalSize(objImage); + g_functions.scaleImageFitParent(objImage, objOriginalSize.width, objOriginalSize.height, scaleMode, objPadding); + } + else{ + var zoomIn = (mode == "in")?true:false; + + isZoomed = g_functions.zoomImageInsideParent(objImage, zoomIn, g_options.slider_zoom_step, mousePos, scaleMode, g_options.slider_zoom_max_ratio, objPadding); + } + + if(isZoomed == true){ + g_objParent.trigger(g_parent.events.ZOOMING); + g_objParent.trigger(g_parent.events.ZOOM_CHANGE); + g_objParent.trigger(g_parent.events.ZOOM_END); + } + + } + + + function ____________PAN_____________(){}; + + + /** + * check if pan is posible for the current image + * check if the image is bigger then the parent + */ + function isPanPosible(objImage, event, stictTouchesCheck){ + + //check num touches, strict means that even if 0 - pan not posible + var arrTouches = g_functions.getArrTouches(event); + + if(stictTouchesCheck === true){ + + if(arrTouches.length != 1) + return(false); + }else{ + if(arrTouches.length > 1) + return(false); + } + + if(g_functions.isElementBiggerThenParent(objImage)) + return(true); + + return(false); + } + + + /** + * store pan values + */ + function storePanInitValues(event){ + + var mousePos = g_functions.getMousePosition(event); + + g_temp.startMouseX = mousePos.pageX; + g_temp.startMouseY = mousePos.pageY; + + g_temp.lastMouseX = g_temp.startMouseX; + g_temp.lastMouseY = g_temp.startMouseY; + + g_temp.startImageX = g_temp.objImageSize.left; + g_temp.startImageY = g_temp.objImageSize.top; + + g_temp.panXActive = (g_temp.objImageSize.width > g_temp.objParentSize.width); + g_temp.panYActive = (g_temp.objImageSize.height > g_temp.objParentSize.height); + + } + + + /** + * check pan start, and start if posible + */ + function startPan(event){ + + g_temp.isPanActive = true; + storePanInitValues(event); + + } + + + /** + * pan the image + */ + function panImage(event){ + + if(g_temp.objImage == undefined || g_temp.objImage.length == 0) + return(true); + + var mousePos = g_functions.getMousePosition(event); + + var diffX = mousePos.pageX - g_temp.startMouseX; + var diffY = mousePos.pageY - g_temp.startMouseY; + + //get active direction + var diffLastX = mousePos.pageX - g_temp.lastMouseX; + var diffLastY = mousePos.pageY - g_temp.lastMouseY; + + var directionX = (diffLastX < 0) ? "left":"right"; + var directionY = (diffLastY < 0) ? "up":"down"; + + g_temp.lastMouseX = mousePos.pageX; + g_temp.lastMouseY = mousePos.pageY; + + var posImage = g_functions.getElementSize(g_temp.objImage); + + //var imageX = g_temp.startImageX + diffX; + //var imageY = g_temp.startImageY + diffY; + + + //slow down if no pan available in this point + //slow down y + + if(g_temp.panYActive == false){ + + diffLastY = 0; + + }else{ //zoom enabled + + if(directionY == "down" && posImage.top > 0){ + + diffLastY = diffLastY / 3; + + }else if(directionY == "up" && posImage.bottom < g_temp.objParentSize.height){ + + diffLastY = diffLastY / 3; + + } + } + + //slow down x (only if the pan enabled) + if(g_temp.panXActive == false || g_parent.isInnerInPlace() == false){ + + diffLastX = 0; + + }else{ //zoom enabled + + if(directionX == "right" && posImage.left > 0){ + diffLastX = diffLastX / 3; + } + else if(directionX == "left" && posImage.right < g_temp.objParentSize.width){ + diffLastX = diffLastX / 3; + } + } + + var imageX = posImage.left + diffLastX; + var imageY = posImage.top + diffLastY; + + + g_functions.placeElement(g_temp.objImage, imageX, imageY); + + } + + + + + /** + * return the image to place if it's out of borders + */ + function checkReturnAfterPan(){ + + var isReturnX = false, isReturnY = false, newX = 0, newY = 0; + var objSize = g_functions.getElementSize(g_temp.objImage); + var objImagePadding = g_parent.getObjImagePadding(); + + var objCenterPos = g_functions.getElementCenterPosition(g_temp.objImage, objImagePadding); + + g_temp.panXActive = (g_temp.objImageSize.width > g_temp.objParentSize.width); + g_temp.panYActive = (g_temp.objImageSize.height > g_temp.objParentSize.height); + + + if(g_temp.panYActive == true){ + + if(objSize.top > 0){ //off limit top + + newY = 0; + isReturnY = true; + + }else if(objSize.bottom < g_temp.objParentSize.height){ //off limit bottom + + newY = g_temp.objParentSize.height - objSize.height; + isReturnY = true; + + } + + }else{ //pan not active y - return to center + + if(objSize.top != objCenterPos.top){ + isReturnY = true; + newY = objCenterPos.top; + } + + } + + + //check return x to place + if(g_temp.panXActive == true){ + + if(objSize.left > 0){ //off limit left + + newX = 0; + isReturnX = true; + + }else if(objSize.right < g_temp.objParentSize.width){ //off limit right + + newX = g_temp.objParentSize.width - objSize.width; + isReturnX = true; + + } + + }else{ //pan not active x - return to center + + // debugLine("not active", true); + + if(objSize.left != objCenterPos.left){ + isReturnX = true; + newX = objCenterPos.left; + } + } + + + //do the animation + var objCss = {}; + if(isReturnY == true) + objCss.top = newY + "px"; + + if(isReturnX == true) + objCss.left = newX + "px"; + + + if(isReturnY == true || isReturnX == true){ + + g_temp.objImage.animate(objCss,{ + duration: g_options.slider_zoom_return_pan_duration, + easing: g_options.slider_zoom_return_pan_easing, + queue: false + }); + + } + + } + + /** + * check if the image animating or not + */ + function isImageAnimating(){ + + if(g_temp.objImage && g_temp.objImage.is(":animated")) + return(true); + + return(false); + } + + function ____________END_PAN_____________(){}; + + function ________TOUCH_ZOOM_____________(){}; + + /** + * start touch zoom + */ + function startTouchZoom(arrTouches){ + + g_temp.isZoomActive = true; + + //store init diff + g_temp.startDistance = g_functions.getDistance(arrTouches[0].pageX, arrTouches[0].pageY, arrTouches[1].pageX, arrTouches[1].pageY); + if(g_temp.startDistance == 0) + g_temp.startDistance = 1; + + + //store init positions + g_temp.startMiddlePoint = g_functions.getMiddlePoint(arrTouches[0].pageX, arrTouches[0].pageY, arrTouches[1].pageX, arrTouches[1].pageY); + + g_temp.objImageSize = g_functions.getElementSize(g_temp.objImage); + + g_temp.startImageX = g_temp.objImageSize.left; + g_temp.startImageY = g_temp.objImageSize.top; + + //set orient point + g_temp.imageOrientPoint = g_functions.getElementLocalPoint(g_temp.startMiddlePoint, g_temp.objImage); + + var isInsideImage = g_functions.isPointInsideElement(g_temp.imageOrientPoint, g_temp.objImageSize); + if(isInsideImage == false){ + g_temp.imageOrientPoint = g_functions.getElementCenterPoint(g_temp.objImage); + } + + //trigger start zoom event + g_objParent.trigger(g_parent.events.ZOOM_START); + } + + + /** + * check num touches, if not 2 - end zoom + */ + function checkTouchZoomEnd(event){ + + if(g_temp.isZoomActive == false) + return(false); + + var arrTouches = g_functions.getArrTouches(event); + if(arrTouches.length != 2){ //end touch zoom + + g_temp.isZoomActive = false; + + //trigger end zoom event + g_objParent.trigger(g_parent.events.ZOOM_END); + } + + } + + + /** + * check start touch zoom + */ + function checkTouchZoomStart(event){ + + if(g_temp.isZoomActive == true) + return(true); + + + var arrTouches = g_functions.getArrTouches(event); + + if(arrTouches.length != 2) + return(true); + + startTouchZoom(arrTouches); + } + + + /** + * do touch zoom on touch devices + */ + function doTouchZoom(event){ + + var arrTouches = g_functions.getArrTouches(event); + + var distance = g_functions.getDistance(arrTouches[0].pageX, arrTouches[0].pageY, arrTouches[1].pageX, arrTouches[1].pageY); + var zoomRatio = distance / g_temp.startDistance; + + var middlePoint = g_functions.getMiddlePoint(arrTouches[0].pageX, arrTouches[0].pageY, arrTouches[1].pageX, arrTouches[1].pageY); + + //set zoom data: + var newWidth = g_temp.objImageSize.width * zoomRatio; + var newHeight = g_temp.objImageSize.height * zoomRatio; + + //check max zoom ratio: + var objOriginalSize = g_functions.getImageOriginalSize(g_temp.objImage); + var expectedZoomRatio = 1; + if(objOriginalSize.width > 0) + expectedZoomRatio = newWidth / objOriginalSize.width; + + if(expectedZoomRatio > g_options.slider_zoom_max_ratio) + return(true); + + //set pan data: + panX = -(g_temp.imageOrientPoint.x * zoomRatio - g_temp.imageOrientPoint.x); + panY = -(g_temp.imageOrientPoint.y * zoomRatio - g_temp.imageOrientPoint.y); + + var diffMiddleX = (middlePoint.x - g_temp.startMiddlePoint.x); + var diffMiddleY = (middlePoint.y - g_temp.startMiddlePoint.y); + + var posx = g_temp.startImageX + panX + diffMiddleX; + var posy = g_temp.startImageY + panY + diffMiddleY; + + + //resize and place: + g_functions.setElementSizeAndPosition(g_temp.objImage, posx, posy, newWidth, newHeight); + + //trigger zooming event + g_objParent.trigger(g_parent.events.ZOOMING); + g_objParent.trigger(g_parent.events.ZOOM_CHANGE); + + /* + debugLine({ + middleStartX: g_temp.startMiddlePoint.x, + middleX: middlePoint.x, + diffMiddleX: diffMiddleX + }); + */ + + } + + + /** + * check return the image from zoom + */ + function checkReturnFromZoom(){ + + if(g_temp.objImage == undefined || g_temp.objImage.length == 0) + return(true); + + var objSize = g_functions.getElementSize(g_temp.objImage); + + if(objSize.width < g_temp.objFitImageSize.imageWidth){ + + g_temp.objImage.css({ + position:"absolute", + margin:"none" + }); + + var objCss = { + top: g_temp.objFitImageSize.imageTop + "px", + left: g_temp.objFitImageSize.imageLeft + "px", + width: g_temp.objFitImageSize.imageWidth + "px", + height: g_temp.objFitImageSize.imageHeight + "px" + }; + + g_temp.objImage.animate(objCss,{ + duration: g_options.slider_zoom_return_pan_duration, + easing: g_options.slider_zoom_return_pan_easing, + queue: false + }); + + }else{ + checkReturnAfterPan(); + } + + } + + + function ________END_TOUCH_ZOOM_____________(){}; + + + /** + * + * touch start event - start pan, remember start pan data + */ + function onTouchStart(event){ + + //if no image type, exit + if(g_parent.isCurrentSlideType("image") == false) + return(true); + + var isStored = storeCurrentImage(); + + if(g_temp.objImage == undefined || g_temp.objImage.length == 0) + return(true); + + event.preventDefault(); + + //stop animation if exists + if(isImageAnimating() == true){ + g_temp.objImage.stop(true); + } + + if(g_temp.isZoomActive == true){ + + checkTouchZoomEnd(event); + + }else{ + + checkTouchZoomStart(event); + + } + + //if zoom started stop panning, if not, start panning + if(g_temp.isZoomActive == true){ + + g_temp.isPanActive = false; + + }else if(isPanPosible(g_temp.objImage, event) == true && g_temp.isZoomedOnce == true){ + + startPan(event); + } + + /* + debugLine({ + pan: g_temp.isPanActive, + zoom: g_temp.isZoomActive, + event: "start" + }, true); + */ + + } + + + /** + * touch end event - bring the image to place + */ + function onTouchEnd(event){ + + if(g_parent.isCurrentSlideType("image") == false) + return(true); + + //check if some gallery button clicked + var objTarget = jQuery(event.target); + if(objTarget.data("ug-button") == true){ + //event.preventDefault(); + return(false); + } + + var isStored = storeCurrentImage(); + + if(g_temp.objImage == undefined || g_temp.objImage.length == 0) + return(true); + + var panWasActive = g_temp.isPanActive; + var zoomWasActive = g_temp.isZoomActive; + + //if the inner not in place, don't return noting, let the slide change + if(g_parent.isInnerInPlace() == false){ + g_temp.isZoomActive = false; + g_temp.isPanActive = false; + return(true); + } + + //check end zoom + if(g_temp.isZoomActive == true){ + checkTouchZoomEnd(event); + }else{ + checkTouchZoomStart(event); + } + + + if(g_temp.isZoomActive == true){ + + g_temp.isPanActive = false; + + }else{ + + var panPosible = isPanPosible(g_temp.objImage, event, true); + + if(g_temp.isPanActive == true ){ + + g_temp.isPanActive = false; + + }else if(panPosible == true){ + + startPan(event); + } + + } + + /* + debugLine({ + pan:g_temp.isPanActive, + zoom: g_temp.isZoomActive + }, true); + */ + + if((panWasActive || zoomWasActive) && g_temp.isZoomActive == false && g_temp.isPanActive == false){ + checkReturnFromZoom(); + } + + + } + + + /** + * + * touch move event - pan + */ + function onTouchMove(event){ + + if(g_parent.isCurrentSlideType("image") == false) + return(true); + + //check touch zoom (pinch gesture) + if(g_temp.isZoomActive == true){ + + doTouchZoom(event); + + }else if(g_temp.isPanActive == true){ + + panImage(event); + + } + + + } + + + /** + * on slider mousewheel event + */ + function onSliderMouseWheel(event, delta, deltaX, deltaY){ + + if(g_options.slider_zoom_mousewheel == false) + return(true); + + if(g_parent.isCurrentSlideType("image") == false) + return(true); + + event.preventDefault(); + + //prevent default only if needed + //if(zoomIn == true || zoomIn == false && g_functions.isElementBiggerThenParent(objImage)) + //event.preventDefault(); + + + var zoomIn = (delta > 0); + var mousePos = g_functions.getMousePosition(event); + var mode = (zoomIn == true) ? "in":"out"; + + zoomCurrentImage(mode, mousePos); + } + + + /** + * init touch events + */ + function initEvents(){ + + //debugLine("init"); + g_objSlider.on("mousewheel",onSliderMouseWheel); + + //slider mouse down - pan start + g_objSlider.bind("mousedown touchstart",onTouchStart); + + //on body move + jQuery("body").bind("mousemove touchmove",onTouchMove); + + //on body mouse up - pan end + jQuery(window).add("body").bind("mouseup touchend", onTouchEnd); + + //event before image returned to init position + g_objParent.bind(g_parent.events.BEFORE_RETURN, function(){ + + checkReturnFromZoom(); + }); + + //on item change update isZoomedOnce event. Allow panning only if zoomed once + g_objParent.bind(g_parent.events.ITEM_CHANGED, function(){ + g_temp.isZoomedOnce = false; + }); + + g_objParent.bind(g_parent.events.ZOOM_CHANGE, function(){ + g_temp.isZoomedOnce = true; + }); + + } + + this.________EXTERNAL_____________ = function(){}; + + /** + * check if the image is zoomed, and there is a place for left panning + */ + this.isPanEnabled = function(event, direction){ + + storeCurrentImage(); + + if(g_temp.objImage == undefined || g_temp.objImage.length == 0) + return(false); + + if(g_temp.isZoomedOnce == false) + return(false); + + if(isPanPosible(g_temp.objImage, event) == false) + return(false); + + if(g_parent.isInnerInPlace() == false) + return(false); + + if(direction == "left"){ + + if(g_temp.objImageSize.right <= g_temp.objParentSize.width) + return(false); + + }else{ //right direction + + if(g_temp.objImageSize.left >= 0) + return(false); + } + + return(true); + } + + + /** + * init function for avia controls + */ + this.init = function(objSlider, customOptions){ + + initObject(objSlider, customOptions); + } + + /** + * zoom in + */ + this.zoomIn = function(){ + zoomCurrentImage("in"); + } + + /** + * zoom out + */ + this.zoomOut = function(){ + zoomCurrentImage("out"); + } + + /** + * zoom back + */ + this.zoomBack = function(){ + + zoomCurrentImage("back"); + } +} +/** -------------- Wistia API ---------------------*/ + +function UGWistiaAPI(){ + + this.isAPILoaded = false; + var t = this, g_objThis = jQuery(this), g_intHandle; + var g_player, g_isPlayerReady = false; + + this.events = { + START_PLAYING: "start_playing", + STOP_PLAYING: "stop_playing", + VIDEO_ENDED: "video_ended" + }; + + + /** + * check if sound cloud active + */ + function isWistiaActive(){ + + return(typeof Wistia != "undefined"); + } + + /** + * load vimeo API + */ + this.loadAPI = function(){ + + if(g_ugWistiaAPI.isAPILoaded == true) + return(true); + + if(isWistiaActive()){ + g_ugWistiaAPI.isAPILoaded = true; + return(true); + } + + g_ugFunctions.loadJs("fast.wistia.com/assets/external/E-v1.js", true); + + g_ugWistiaAPI.isAPILoaded = true; + } + + + /** + * actually put the video + */ + function putVideoActually(divID, videoID, width, height, isAutoplay){ + + g_player = null; + g_isPlayerReady = false; + + var htmlID = divID + "_video"; + + var html = "
       
      "; + + jQuery("#"+divID).html(html); + + g_player = Wistia.embed(videoID, { + version: "v1", + videoWidth: width, + videoHeight: height, + container: htmlID, + autoPlay: isAutoplay + }); + + g_isPlayerReady = true; + + initEvents(); + } + + + /** + * init events + */ + function initEvents(){ + + //set "play" event + + g_player.bind('play', function(){ + g_objThis.trigger(t.events.START_PLAYING); + }); + + //set "pause event" + g_player.bind('pause', function(){ + g_objThis.trigger(t.events.STOP_PLAYING); + }); + + g_player.bind('end', function(){ + g_objThis.trigger(t.events.STOP_PLAYING); + g_objThis.trigger(t.events.VIDEO_ENDED); + }); + + } + + + /** + * do some command + */ + this.doCommand = function(command){ + + if(g_player == null) + return(false); + + if(g_isPlayerReady == false) + return(false); + + switch(command){ + case "play": + g_player.play(); + break; + case "pause": + g_player.pause(); + break; + } + + } + + /** + * do pause command + */ + this.pause = function(){ + t.doCommand("pause"); + } + + /** + * do play command + */ + this.play = function(){ + t.doCommand("play"); + } + + + /** + * put the vimeo video + */ + this.putVideo = function(divID, videoID, width, height, isAutoplay){ + + if(isWistiaActive()){ + putVideoActually(divID, videoID, width, height, isAutoplay); + return(true); + } + + + //if no API present, wait for the API being ready + this.loadAPI(); + g_intHandle = setInterval(function(){ + + if(isWistiaActive()){ + putVideoActually(divID, videoID, width, height, isAutoplay); + clearInterval(g_intHandle); + } + + }, 500); + + } + + + /** + * get if the player is ready + */ + this.isPlayerReady = function(){ + + if(g_isPlayerReady && g_player) + return(true); + + return(false); + } + + +} + +/** -------------- Sound Cloud API ---------------------*/ + +function UGSoundCloudAPI(){ + + this.isAPILoaded = false; + var t = this, g_objThis = jQuery(this), g_intHandle; + var g_player, g_lastContainerID; + + this.events = { + START_PLAYING: "start_playing", + STOP_PLAYING: "stop_playing", + VIDEO_ENDED: "video_ended" + }; + + /** + * check if sound cloud active + */ + function isSCActive(){ + + return(typeof SC != "undefined"); + } + + /** + * load vimeo API + */ + this.loadAPI = function(){ + + if(g_ugSoundCloudAPI.isAPILoaded == true) + return(true); + + if(isSCActive()){ + g_ugSoundCloudAPI.isAPILoaded = true; + return(true); + } + + g_ugFunctions.loadJs("w.soundcloud.com/player/api.js", true); + + g_ugSoundCloudAPI.isAPILoaded = true; + } + + /** + * actually put the video + */ + function putSoundActually(divID, trackID, width, height, isAutoplay){ + + g_player = null; + g_isPlayerReady = false; + + var iframeID = divID+"_iframe"; + + var url = location.protocol+"//w.soundcloud.com/player/?url=http://api.soundcloud.com/tracks/"+trackID; + url += "&buying=false&liking=false&download=false&sharing=false&show_artwork=true&show_comments=false&show_playcount=true&show_user=false&hide_related=true&visual=true&start_track=0&callback=true"; + + if(isAutoplay === true) + url += "&auto_play=true"; + else + url += "&auto_play=false"; + + var html = ""; + + jQuery("#"+divID).html(html); + + //get the player object + g_player = SC.Widget(iframeID); + + g_player.bind(SC.Widget.Events.READY, function() { + + if(g_player){ + g_isPlayerReady = true; + initEvents(); + } + + }); + + g_lastContainerID = divID; + } + + + /** + * init events + */ + function initEvents(){ + + + //set "play" event + g_player.bind(SC.Widget.Events.PLAY, function(){ + g_objThis.trigger(t.events.START_PLAYING); + }); + + //set "pause event" + g_player.bind(SC.Widget.Events.PAUSE, function(){ + g_objThis.trigger(t.events.STOP_PLAYING); + }); + + g_player.bind(SC.Widget.Events.FINISH, function(){ + g_objThis.trigger(t.events.STOP_PLAYING); + g_objThis.trigger(t.events.VIDEO_ENDED); + }); + + } + + + /** + * put the youtube video + */ + this.putSound = function(divID, trackID, width, height, isAutoplay){ + + if(isSCActive()){ + putSoundActually(divID, trackID, width, height, isAutoplay); + return(true); + } + + + //if no API present, wait for the API being ready + this.loadAPI(); + g_intHandle = setInterval(function(){ + + if(isSCActive()){ + putSoundActually(divID, trackID, width, height, isAutoplay); + clearInterval(g_intHandle); + } + + }, 500); + + } + + + /** + * do some command + */ + this.doCommand = function(command){ + + if(g_player == null) + return(false); + + if(g_isPlayerReady == false) + return(false); + + switch(command){ + case "play": + g_player.play(); + break; + case "pause": + g_player.pause(); + break; + } + + } + + + /** + * pause video + */ + this.pause = function(){ + t.doCommand("pause"); + } + + + /** + * play video + */ + this.play = function(){ + t.doCommand("play"); + } + + /** + * destroy the player + */ + this.destroy = function(){ + + g_isPlayerReady = false; + g_player = null; + + if(g_lastContainerID){ + jQuery("#" + g_lastContainerID).html(""); + g_lastContainerID = null; + } + + } + +} + +/** -------------- html5 Video API ---------------------*/ + +function UGHtml5MediaAPI(){ + + this.isAPILoaded = false; + var t = this, g_objThis = jQuery(this), g_intHandle; + var g_player; + + this.events = { + START_PLAYING: "start_playing", + STOP_PLAYING: "stop_playing", + VIDEO_ENDED: "video_ended" + }; + + /** + * load vimeo API + */ + this.loadAPI = function(){ + + if(g_ugHtml5MediaAPI.isAPILoaded == true) + return(true); + + + if(isMediaElementActive()){ + g_ugHtml5MediaAPI.isAPILoaded = true; + return(true); + } + + g_ugFunctions.loadJs("cdnjs.cloudflare.com/ajax/libs/mediaelement/2.18.1/mediaelement.min.js", true); + g_ugFunctions.loadCss("cdnjs.cloudflare.com/ajax/libs/mediaelement/2.18.1/mediaelementplayer.min.css", true); + + g_ugHtml5MediaAPI.isAPILoaded = true; + } + + /** + * return true if the mediaelement is active + */ + function isMediaElementActive(){ + + return(typeof mejs != "undefined"); + } + + + /** + * actually put the video + */ + function putVideoActually(divID, data, width, height, isAutoplay){ + + g_player = null; + g_isPlayerReady = false; + + var urlFlash = location.protocol + "//cdnjs.cloudflare.com/ajax/libs/mediaelement/2.18.1/flashmediaelement-cdn.swf"; + var urlSilverlight = location.protocol + "//cdnjs.cloudflare.com/ajax/libs/mediaelement/2.18.1/silverlightmediaelement.xap"; + + var htmlID = divID + "_video"; + var htmlAutoplay = ""; + if(isAutoplay && isAutoplay === true) + htmlAutoplay = "autoplay='autoplay'" + + var htmlPoster = ""; + if(data.posterImage) + htmlPoster = "poster='"+data.posterImage+"'"; + + var html = ""; + + jQuery("#"+divID).html(html); + + new MediaElement(htmlID, { + enablePluginDebug: false, + flashName: urlFlash, + silverlightName: urlSilverlight, + success: function (mediaElement, domObject) { + g_isPlayerReady = true; + g_player = mediaElement; + + if(isAutoplay == false) + g_player.pause(); + + initEvents(); + }, + error: function (objError) { + trace(objError); + } + }); + + } + + + /** + * init player events function + */ + function initEvents(){ + + g_ugFunctions.addEvent(g_player, "play", function(){ + g_objThis.trigger(t.events.START_PLAYING); + }); + + g_ugFunctions.addEvent(g_player, "pause", function(){ + g_objThis.trigger(t.events.STOP_PLAYING); + }); + + g_ugFunctions.addEvent(g_player, "ended", function(){ + g_objThis.trigger(t.events.STOP_PLAYING); + g_objThis.trigger(t.events.VIDEO_ENDED); + }); + + } + + + /** + * put the vimeo video + */ + this.putVideo = function(divID, data, width, height, isAutoplay){ + + if(isMediaElementActive()){ + putVideoActually(divID, data, width, height, isAutoplay); + return(true); + } + + + //if no API present, wait for the API being ready + this.loadAPI(); + g_intHandle = setInterval(function(){ + + if(isMediaElementActive()){ + putVideoActually(divID, data, width, height, isAutoplay); + clearInterval(g_intHandle); + } + + }, 500); + + } + + /** + * do some command + */ + this.doCommand = function(command){ + + if(g_player == null) + return(false); + + if(g_isPlayerReady == false) + return(false); + + switch(command){ + case "play": + g_player.play(); + break; + case "pause": + g_player.pause(); + break; + } + + } + + + /** + * pause video + */ + this.pause = function(){ + t.doCommand("pause"); + } + + + /** + * play video + */ + this.play = function(){ + t.doCommand("play"); + } + +} + + +/** -------------- Vimeo API class ---------------------*/ + +function UGVimeoAPI(){ + + this.isAPILoaded = false; + + var t = this, g_objThis = jQuery(this), g_intHandle; + var g_player = null, g_isPlayerReady = false, g_lastCotnainerID, g_cueChangeAutoplay = false; + + + this.events = { + START_PLAYING: "start_playing", + STOP_PLAYING: "stop_playing", + VIDEO_ENDED: "video_ended" + }; + + /** + * load vimeo API + */ + this.loadAPI = function(){ + + if(g_ugVimeoAPI.isAPILoaded == true) + return(true); + + if(isFroogaloopActive()){ + g_ugVimeoAPI.isAPILoaded = true; + return(true); + } + + g_ugFunctions.loadJs("f.vimeocdn.com/js/froogaloop2.min.js", true); + + g_ugVimeoAPI.isAPILoaded = true; + } + + + + /** + * tells if the froogaloop library active + */ + function isFroogaloopActive(){ + + return(typeof Froogaloop != "undefined"); + } + + + /** + * actually put the video + */ + function putVideoActually(divID, videoID, width, height, isAutoplay){ + + g_player = null; + g_isPlayerReady = false; + + var url = location.protocol+"//player.vimeo.com/video/"+videoID+"?api=1"; + + if(isAutoplay === true) + url += "&byline=0&autoplay=1&title=0&portrait=0"; + + var html = ""; + + jQuery("#"+divID).html(html); + + //get the player object + var iframe = jQuery("#"+divID + " iframe")[0]; + + g_player = Froogaloop(iframe); + + g_player.addEvent('ready', function(){ + + if(g_player){ + g_isPlayerReady = true; + initEvents(); + } + + }); + + g_lastCotnainerID = divID; + } + + /** + * init events + */ + function initEvents(){ + + if(!g_player) + return(false); + + //set "cuechange" event + g_player.addEvent('cuechange', function(){ + + if(g_cueChangeAutoplay == true) + t.play(); + + }); + + //set "play" event + g_player.addEvent('play', function(){ + g_objThis.trigger(t.events.START_PLAYING); + }); + + //set "pause event" + g_player.addEvent('pause', function(){ + g_objThis.trigger(t.events.STOP_PLAYING); + }); + + g_player.addEvent('finish', function(){ + g_objThis.trigger(t.events.STOP_PLAYING); + g_objThis.trigger(t.events.VIDEO_ENDED); + }); + + } + + + /** + * do some command + */ + this.doCommand = function(command){ + + if(g_player == null) + return(false); + + if(g_isPlayerReady == false) + return(false); + + switch(command){ + default: + g_player.api(command); + break; + } + + } + + /** + * do pause command + */ + this.pause = function(){ + t.doCommand("pause"); + } + + /** + * do play command + */ + this.play = function(){ + t.doCommand("play"); + } + + /** + * desrtoy the player and empty the div + */ + this.destroy = function(){ + + if(g_player){ + g_player.api("unload"); + g_player = null; + g_isPlayerReady = false; + } + + if(g_lastCotnainerID){ + jQuery("#" + g_lastCotnainerID).html(""); + } + + } + + /** + * put the vimeo video + */ + this.putVideo = function(divID, videoID, width, height, isAutoplay){ + + if(isFroogaloopActive()){ + putVideoActually(divID, videoID, width, height, isAutoplay); + return(true); + } + + + //if no API present, wait for the API being ready + this.loadAPI(); + g_intHandle = setInterval(function(){ + + if(isFroogaloopActive()){ + putVideoActually(divID, videoID, width, height, isAutoplay); + clearInterval(g_intHandle); + } + + }, 500); + + } + + + /** + * get if the player is ready + */ + this.isPlayerReady = function(){ + + if(g_isPlayerReady && g_player) + return(true); + + return(false); + } + + /** + * change the video + */ + this.changeVideo = function(videoID, isAutoplay){ + + if(t.isPlayerReady() == false) + return(false); + + g_cueChangeAutoplay = isAutoplay; + + g_player.api("loadVideo", videoID); + } + + + /** + * get video images + */ + this.getVideoImages = function(videoID, itemIndex, onSuccessFunction){ + + var url = location.protocol+"//vimeo.com/api/v2/video/"+videoID+".json"; + jQuery.get(url, {}, function(data){ + var obj = {}; + obj.preview = data[0].thumbnail_large; + obj.thumb = data[0].thumbnail_medium; + onSuccessFunction(itemIndex, obj); + }); + } + + +} + + +/** -------------- Youtube API class ---------------------*/ + +function UGYoutubeAPI(){ + + this.isAPILoaded = false; + var t = this, g_player = null, g_intHandle, g_isPlayerReady = false; + var g_objThis = jQuery(this), g_prevState = -1, g_lastContainerID; //unstarted + + var g_options = { + video_youtube_showinfo: true + } + + this.events = { + START_PLAYING: "start_playing", + STOP_PLAYING: "stop_playing", + VIDEO_ENDED: "video_ended" + }; + + + /** + * actually put the video + */ + function putVideoActually(divID, videoID, width, height, isAutoplay){ + + if(g_player && g_isPlayerReady){ + g_player.destroy(); + } + + var playerVars = { + controls:2, + showinfo:g_options.video_youtube_showinfo, + rel:0 + }; + + if(isAutoplay === true) + playerVars.autoplay = 1; + + g_isPlayerReady = false; + + g_player = new YT.Player(divID, { + height: height, + width: width, + videoId: videoID, + playerVars: playerVars, + events: { + 'onReady': onPlayerReady, + 'onStateChange': onPlayerStateChange + } + }); + + g_lastContainerID = divID; + } + + + /** + * check if YT active + */ + function isYTActive(){ + + if(typeof YT != "undefined" && typeof YT.Player != "undefined") + return(true); + + return(false); + } + + + /** + * set options + */ + this.setOptions = function(objOptions){ + g_options = jQuery.extend(g_options, objOptions); + } + + + /** + * put the youtube video + */ + this.putVideo = function(divID, videoID, width, height, isAutoplay){ + + if(isYTActive()){ + putVideoActually(divID, videoID, width, height, isAutoplay); + return(true); + } + + //if no API present, wait for the API being ready + this.loadAPI(); + g_intHandle = setInterval(function(){ + + if(isYTActive()){ + putVideoActually(divID, videoID, width, height, isAutoplay); + clearInterval(g_intHandle); + } + + }, 500); + + } + + + /** + * on player ready event + */ + function onPlayerReady(){ + g_isPlayerReady = true; + } + + + /** + * on player state change event + * trigger events + */ + function onPlayerStateChange(){ + + if(typeof g_player.getPlayerState != "function"){ + trace("Youtube API error: can't get player state"); + return(false); + } + + var state = g_player.getPlayerState(); + + switch(state){ + case YT.PlayerState.PLAYING: + g_objThis.trigger(t.events.START_PLAYING); + break; + case YT.PlayerState.ENDED: + g_objThis.trigger(t.events.STOP_PLAYING); + g_objThis.trigger(t.events.VIDEO_ENDED); + break; + default: + if(g_prevState == YT.PlayerState.PLAYING) + g_objThis.trigger(t.events.STOP_PLAYING); + break; + } + + g_prevState = state; + } + + + /** + * load youtube API + */ + this.loadAPI = function(){ + + if(g_ugYoutubeAPI.isAPILoaded == true) + return(true); + + if(typeof YT != "undefined"){ + g_ugYoutubeAPI.isAPILoaded = true; + return(true); + } + + g_ugFunctions.loadJs("https://www.youtube.com/player_api", false); + + g_ugYoutubeAPI.isAPILoaded = true; + + } + + + /** + * do some command + */ + this.doCommand = function(command, opt1){ + + if(!g_player) + return(true); + + if(g_isPlayerReady == false) + return(false); + + switch(command){ + case "play": + if(typeof g_player.playVideo != "function") + return(false); + + g_player.playVideo(); + break; + case "pause": + if(typeof g_player.pauseVideo != "function") + return(false); + + g_player.pauseVideo(); + break; + case "seek": + if(typeof g_player.seekTo != "function") + return(false); + + g_player.seekTo(opt1); + break; + case "stopToBeginning": + var state = g_player.getPlayerState(); + + g_player.pauseVideo(); + + switch(state){ + case YT.PlayerState.PLAYING: + case YT.PlayerState.ENDED: + case YT.PlayerState.PAUSED: + g_player.seekTo(0); + break; + } + break; + } + } + + /** + * play video + */ + this.play = function(){ + t.doCommand("play"); + } + + /** + * stop the video + */ + this.pause = function(){ + t.doCommand("pause"); + } + + /** + * destroy player + */ + this.destroy = function(){ + try{ + + if(g_player){ + g_isPlayerReady = false; + g_player.clearVideo(); + g_player.destroy(); + } + + }catch(objError){ + + jQuery("#"+g_lastContainerID).html(""); + + } + + } + + /** + * stop the video and seek to start + */ + this.stopToBeginning = function(){ + t.doCommand("stopToBeginning"); + } + + /** + * change the video + */ + this.changeVideo = function(videoID, isAutoplay){ + + if(t.isPlayerReady() == false) + return(false); + + if(isAutoplay && isAutoplay == true) + g_player.loadVideoById(videoID, 0, "large"); + else + g_player.cueVideoById(videoID, 0, "large"); + } + + + /** + * get if the player is ready + */ + this.isPlayerReady = function(){ + + if(g_isPlayerReady && g_player) + return(true); + + return(false); + } + + + + /** + * get preview and thumbs images according the ID + */ + this.getVideoImages = function(videoID){ + var obj = {}; + obj.preview = "https://i.ytimg.com/vi/"+videoID+"/sddefault.jpg"; + obj.thumb = "https://i.ytimg.com/vi/"+videoID+"/default.jpg"; + return(obj); + } + + +} + +/** -------------- Video Player Class ---------------------*/ + + +function UGVideoPlayer(){ + + var t = this, g_galleryID, g_objThis = jQuery(this), g_functions = new UGFunctions(); + var g_youtubeAPI = new UGYoutubeAPI(), g_vimeoAPI = new UGVimeoAPI(); + var g_html5API = new UGHtml5MediaAPI(), g_soundCloudAPI = new UGSoundCloudAPI(), g_wistiaAPI = new UGWistiaAPI(); + var g_objPlayer, g_objYoutube, g_objVimeo, g_objHtml5, g_objButtonClose, g_objSoundCloud, g_objWistia; + var g_activePlayerType = null; + + var g_options = { + video_enable_closebutton: true + }; + + this.events = { + SHOW: "video_show", + HIDE: "video_hide", + PLAY_START: "video_play_start", + PLAY_STOP: "video_play_stop", + VIDEO_ENDED: "video_ended" + }; + + var g_temp = { + standAloneMode: false, + youtubeInnerID:"", + vimeoPlayerID:"", + html5PlayerID:"", + wistiaPlayerID:"", + soundCloudPlayerID:"" + }; + + + /** + * init the object + */ + this.init = function(optOptions, isStandAloneMode, galleryID){ + g_galleryID = galleryID; + + if(!g_galleryID) + throw new Error("missing gallery ID for video player, it's a must!"); + + g_options = jQuery.extend(g_options, optOptions); + + g_youtubeAPI.setOptions(g_options); + + if(isStandAloneMode && isStandAloneMode == true) + g_temp.standAloneMode = true; + + } + + + /** + * set the player html + */ + this.setHtml = function(objParent){ + + g_temp.youtubeInnerID = g_galleryID + "_youtube_inner"; + g_temp.vimeoPlayerID = g_galleryID + "_videoplayer_vimeo"; + g_temp.html5PlayerID = g_galleryID + "_videoplayer_html5"; + g_temp.wistiaPlayerID = g_galleryID + "_videoplayer_wistia"; + g_temp.soundCloudPlayerID = g_galleryID + "_videoplayer_soundcloud"; + + + var html = ""; + + objParent.append(html); + + g_objPlayer = objParent.children(".ug-videoplayer"); + g_objYoutube = g_objPlayer.children(".ug-videoplayer-youtube"); + g_objVimeo = g_objPlayer.children(".ug-videoplayer-vimeo"); + g_objHtml5 = g_objPlayer.children(".ug-videoplayer-html5"); + g_objSoundCloud = g_objPlayer.children(".ug-videoplayer-soundcloud"); + g_objWistia = g_objPlayer.children(".ug-videoplayer-wistia"); + + if(g_temp.standAloneMode == false && g_options.video_enable_closebutton == true) + g_objButtonClose = g_objPlayer.children(".ug-videoplayer-button-close") + } + + + function __________EVENTS___________(){}; + + /** + * on close button click event + */ + function onCloseButtonClick(){ + t.hide(); + } + + /** + * on some video play start + */ + function onPlayStart(){ + + g_objThis.trigger(t.events.PLAY_START); + + if(g_objButtonClose) + g_objButtonClose.hide(); + } + + + /** + * on some video play stop + */ + function onPlayStop(){ + + g_objThis.trigger(t.events.PLAY_STOP); + + if(g_objButtonClose) + g_objButtonClose.show(); + } + + /** + * on video ended + */ + function onVideoEnded(){ + + g_objThis.trigger(t.events.VIDEO_ENDED); + + } + + + /** + * init events + */ + function initEvents(){ + + //close button events + if(g_objButtonClose){ + g_functions.setButtonMobileReady(g_objButtonClose); + g_functions.setButtonOnClick(g_objButtonClose, onCloseButtonClick); + } + + //youtube events + jQuery(g_youtubeAPI).on(g_youtubeAPI.events.START_PLAYING, onPlayStart); + jQuery(g_youtubeAPI).on(g_youtubeAPI.events.STOP_PLAYING, onPlayStop); + jQuery(g_youtubeAPI).on(g_youtubeAPI.events.VIDEO_ENDED, onVideoEnded); + + //vimeo events + jQuery(g_vimeoAPI).on(g_vimeoAPI.events.START_PLAYING, onPlayStart); + jQuery(g_vimeoAPI).on(g_vimeoAPI.events.STOP_PLAYING, onPlayStop); + jQuery(g_vimeoAPI).on(g_vimeoAPI.events.VIDEO_ENDED, onVideoEnded); + + //html5 video events + jQuery(g_html5API).on(g_html5API.events.START_PLAYING, onPlayStart); + jQuery(g_html5API).on(g_html5API.events.STOP_PLAYING, onPlayStop); + jQuery(g_html5API).on(g_html5API.events.VIDEO_ENDED, onVideoEnded); + + jQuery(g_soundCloudAPI).on(g_soundCloudAPI.events.START_PLAYING, onPlayStart); + jQuery(g_soundCloudAPI).on(g_soundCloudAPI.events.STOP_PLAYING, onPlayStop); + jQuery(g_soundCloudAPI).on(g_soundCloudAPI.events.VIDEO_ENDED, onVideoEnded); + + jQuery(g_wistiaAPI).on(g_wistiaAPI.events.START_PLAYING, onPlayStart); + jQuery(g_wistiaAPI).on(g_wistiaAPI.events.STOP_PLAYING, onPlayStop); + jQuery(g_wistiaAPI).on(g_wistiaAPI.events.VIDEO_ENDED, onVideoEnded); + + } + + + /** + * destroy the video player events + */ + this.destroy = function(){ + + if(g_objButtonClose){ + g_objButtonClose.off("click"); + g_objButtonClose.off("touchend"); + } + + //youtube events + jQuery(g_youtubeAPI).off(g_youtubeAPI.events.START_PLAYING); + jQuery(g_youtubeAPI).off(g_youtubeAPI.events.STOP_PLAYING); + + //vimeo events + jQuery(g_vimeoAPI).off(g_vimeoAPI.events.START_PLAYING); + jQuery(g_vimeoAPI).off(g_vimeoAPI.events.STOP_PLAYING); + + //html5 video events + jQuery(g_html5API).off(g_html5API.events.START_PLAYING); + jQuery(g_html5API).off(g_html5API.events.STOP_PLAYING); + + jQuery(g_soundCloudAPI).off(g_soundCloudAPI.events.START_PLAYING, onPlayStart); + jQuery(g_soundCloudAPI).off(g_soundCloudAPI.events.STOP_PLAYING, onPlayStop); + + jQuery(g_wistiaAPI).off(g_wistiaAPI.events.START_PLAYING, onPlayStart); + jQuery(g_wistiaAPI).off(g_wistiaAPI.events.STOP_PLAYING, onPlayStop); + + g_activePlayerType = null; + } + + + /** + * init events + */ + this.initEvents = function(){ + + initEvents(); + } + + + /** + * set element size and position the button + */ + this.setSize = function(width, height){ + + g_functions.setElementSize(g_objPlayer, width, height); + + if(g_objButtonClose) + g_functions.placeElement(g_objButtonClose, "right", "top"); + + } + + + /** + * set video player position + */ + this.setPosition = function(left, top){ + g_functions.placeElement(g_objPlayer, left, top); + } + + /** + * get video player object for placing + */ + this.getObject = function(){ + return(g_objPlayer); + } + + + /** + * show the player + */ + this.show = function(){ + + if(t.isVisible() == true) + return(true); + + g_objPlayer.show(); + + g_objPlayer.fadeTo(0,1); + + if(g_objButtonClose) + g_objButtonClose.show(); + + g_objThis.trigger(t.events.SHOW); + } + + + /** + * hide the player + */ + this.hide = function(){ + + if(t.isVisible() == false) + return(true); + + //pause all players + stopAndHidePlayers(); + + g_activePlayerType = null; + + g_objPlayer.hide(); + + g_objThis.trigger(t.events.HIDE); + } + + + /** + * get active player + */ + this.getActiveAPI = function(){ + + switch(g_activePlayerType){ + case "youtube": + return g_youtubeAPI; + break; + case "vimeo": + return g_vimeoAPI; + break; + case "wistia": + return g_wistiaAPI; + break; + case "soundcloud": + return g_soundCloudAPI; + break; + case "html5": + return g_html5API; + break; + default: + return null; + break; + } + } + + + /** + * pause active player if playing + */ + this.pause = function(){ + + var activeAPI = t.getActiveAPI(); + if(activeAPI == null) + return(false); + + if(typeof activeAPI.pause == "function") + activeAPI.pause(); + + } + + + /** + * return if the player is visible + */ + this.isVisible = function(){ + + return g_objPlayer.is(":visible"); + } + + + /** + * stop and hide other elements except some + */ + function stopAndHidePlayers(except){ + + var arrPlayers = ["youtube", "vimeo", "html5", "soundcloud", "wistia"]; + for(var index in arrPlayers){ + var player = arrPlayers[index]; + if(player == except) + continue; + switch(player){ + case "youtube": + g_youtubeAPI.pause(); + g_youtubeAPI.destroy(); + g_objYoutube.hide(); + break; + case "vimeo": + g_vimeoAPI.pause(); + g_vimeoAPI.destroy(); + g_objVimeo.hide(); + break; + case "html5": + g_html5API.pause(); + g_objHtml5.hide(); + break; + case "soundcloud": + g_soundCloudAPI.pause(); + g_soundCloudAPI.destroy(); + g_objSoundCloud.hide(); + break; + case "wistia": + g_wistiaAPI.pause(); + g_objWistia.hide(); + break; + } + } + + } + + + /** + * play youtube inside the video, isAutoplay - true by default + */ + this.playYoutube = function(videoID, isAutoplay){ + + if(typeof isAutoplay == "undefined") + var isAutoplay = true; + + stopAndHidePlayers("youtube"); + + g_objYoutube.show(); + + var objYoutubeInner = g_objYoutube.children("#"+g_temp.youtubeInnerID); + if(objYoutubeInner.length == 0) + g_objYoutube.append("
      "); + + + if(g_youtubeAPI.isPlayerReady() == true && g_temp.standAloneMode == true) + g_youtubeAPI.changeVideo(videoID, isAutoplay); + else{ + g_youtubeAPI.putVideo(g_temp.youtubeInnerID, videoID, "100%", "100%", isAutoplay); + } + + g_activePlayerType = "youtube"; + } + + + /** + * play vimeo + */ + this.playVimeo = function(videoID, isAutoplay){ + + if(typeof isAutoplay == "undefined") + var isAutoplay = true; + + stopAndHidePlayers("vimeo"); + + g_objVimeo.show(); + + g_vimeoAPI.putVideo(g_temp.vimeoPlayerID, videoID, "100%", "100%", isAutoplay); + + /* + if(g_vimeoAPI.isPlayerReady() && g_temp.standAloneMode == true){ + g_vimeoAPI.changeVideo(videoID, isAutoplay); + } + else + g_vimeoAPI.putVideo(g_temp.vimeoPlayerID, videoID, "100%", "100%", isAutoplay); + */ + + g_activePlayerType = "vimeo"; + + } + + + /** + * play html5 video + */ + this.playHtml5Video = function(ogv, webm, mp4, posterImage, isAutoplay){ + + if(typeof isAutoplay == "undefined") + var isAutoplay = true; + + stopAndHidePlayers("html5"); + + g_objHtml5.show(); + + //trace(posterImage); + + var data = { + ogv: ogv, + webm: webm, + mp4: mp4, + posterImage: posterImage + }; + + g_html5API.putVideo(g_temp.html5PlayerID, data, "100%", "100%", isAutoplay); + + g_activePlayerType = "html5"; + + } + + /** + * play sound cloud + */ + this.playSoundCloud = function(trackID, isAutoplay){ + + if(typeof isAutoplay == "undefined") + var isAutoplay = true; + + stopAndHidePlayers("soundcloud"); + + g_objSoundCloud.show(); + + g_soundCloudAPI.putSound(g_temp.soundCloudPlayerID, trackID, "100%", "100%", isAutoplay); + + g_activePlayerType = "soundcloud"; + + } + + + /** + * play sound cloud + */ + this.playWistia = function(videoID, isAutoplay){ + + if(typeof isAutoplay == "undefined") + var isAutoplay = true; + + stopAndHidePlayers("wistia"); + + g_objWistia.show(); + + g_wistiaAPI.putVideo(g_temp.wistiaPlayerID, videoID, "100%", "100%", isAutoplay); + + g_activePlayerType = "wistia"; + + } + +} + + +var g_ugYoutubeAPI = new UGYoutubeAPI(); +var g_ugVimeoAPI = new UGVimeoAPI(); +var g_ugHtml5MediaAPI = new UGHtml5MediaAPI(); +var g_ugSoundCloudAPI = new UGSoundCloudAPI(); +var g_ugWistiaAPI = new UGWistiaAPI(); + + + /** + * prototype gallery funciton + */ + jQuery.fn.unitegallery = function(options){ + var element = jQuery(this); + var galleryID = "#" + element.attr("id"); + + if(!options) + var options = {}; + + var objGallery = new UniteGalleryMain(); + objGallery.run(galleryID, options); + + var api = new UG_API(objGallery); + + return(api); + } + + + /** + * check for min jquery version + */ + function ugCheckForMinJQueryVersion(){ + + var isMinJQuery = g_ugFunctions.checkMinJqueryVersion("1.8.0"); + + if(isMinJQuery == false) + throw new Error("The gallery can run from jquery 1.8 You have jQuery "+jQuery.fn.jquery+" Please update your jQuery library."); + } + + + /** + * check for errors function + */ + function ugCheckForErrors(galleryID, type){ + + /** + * check for jquery presents + */ + function checkForJqueryPresents(){ + if(typeof jQuery == "undefined") + throw new Error("jQuery library not included"); + } + + /** + * check for double jquery error + */ + function checkForDoubleJQuery(){ + + if(typeof jQuery.fn.unitegallery == "function") + return(true); + + var errorMessage = "You have some jquery.js library include that comes after the gallery files js include."; + errorMessage += "
      This include eliminates the gallery libraries, and make it not work."; + + if(type == "cms"){ + errorMessage += "

      To fix it you can:
          1. In the Gallery Settings -> Troubleshooting set option: Put JS Includes To Body option to true."; + errorMessage += "
          2. Find the double jquery.js include and remove it."; + }else{ + errorMessage += "

      Please find and remove this jquery.js include and the gallery will work.
      * There should be only one jquery.js include before all other js includes in the page."; + } + + + throw new Error(errorMessage); + } + + try{ + if(type == "jquery"){ + checkForJqueryPresents(); + ugCheckForMinJQueryVersion(); + }else{ + ugCheckForMinJQueryVersion(); + checkForDoubleJQuery(); + } + + }catch(objError){ + + var message = objError.message; + message = "Unite Gallery Error: "+ message; + message = "
      " + message + "
      " + + if(type == "jquery"){ + var objGallery = document.getElementById(galleryID); + objGallery.innerHTML = message; + objGallery.style.display = "block"; + } + else + jQuery(galleryID).show().html(message); + + return(false); + } + + return(true); + } + + +function UniteGalleryMain(){ + + var t = this; + var g_galleryID; + var g_objGallery = jQuery(t), g_objWrapper, g_objParent; + var g_objThumbs, g_objSlider, g_functions = new UGFunctions(), g_objTabs, g_objLoadMore; + var g_arrItems = [], g_numItems, g_selectedItem = null, g_selectedItemIndex = -1; + var g_objTheme, g_objCache = {}; + + this.events = { + ITEM_CHANGE: "item_change", + SIZE_CHANGE: "size_change", + ENTER_FULLSCREEN: "enter_fullscreen", + EXIT_FULLSCREEN: "exit_fullscreen", + START_PLAY: "start_play", + STOP_PLAY: "stop_play", + PAUSE_PLAYING: "pause_playing", + CONTINUE_PLAYING: "continue_playing", + SLIDER_ACTION_START: "slider_action_start", + SLIDER_ACTION_END: "slider_action_end", + ITEM_IMAGE_UPDATED: "item_image_updated", + GALLERY_KEYPRESS: "gallery_keypress", + GALLERY_BEFORE_REQUEST_ITEMS: "gallery_before_request_items", //before ajax load items + OPEN_LIGHTBOX:"open_lightbox", + CLOSE_LIGHTBOX:"close_lightbox" + }; + + + //set the default gallery options + var g_options = { + gallery_width:900, //gallery width + gallery_height:500, //gallery height + + gallery_min_width: 150, //gallery minimal width when resizing + gallery_min_height: 100, //gallery minimal height when resizing + + gallery_theme:"default", //default,compact,grid,slider - select your desired theme from the list of themes. + gallery_skin:"default", //default, alexis etc... - the global skin of the gallery. Will change all gallery items by default. + + gallery_images_preload_type:"minimal", //all , minimal , visible - preload type of the images. + //minimal - only image nabours will be loaded each time. + //visible - visible thumbs images will be loaded each time. + //all - load all the images first time. + + gallery_autoplay:false, //true / false - begin slideshow autoplay on start + gallery_play_interval: 3000, //play interval of the slideshow + gallery_pause_on_mouseover: true, //true,false - pause on mouseover when playing slideshow true/false + + gallery_mousewheel_role:"zoom", //none, zoom, advance + gallery_control_keyboard: true, //true,false - enable / disble keyboard controls + gallery_carousel:true, //true,false - next button on last image goes to first image. + + gallery_preserve_ratio: true, //true, false - preserver ratio when on window resize + gallery_background_color: "", //set custom background color. If not set it will be taken from css. + gallery_debug_errors:false, //show error message when there is some error on the gallery area. + gallery_shuffle:false, //randomise position of items at start. + gallery_urlajax:null, //ajax url for requesting new items etc. + gallery_enable_tabs: false, //enable/disable category tabs + gallery_enable_loadmore: false, //enable / disable loadmore button + gallery_enable_cache: true, //enable caching items + gallery_initial_catid: "" //initial category id (for caching) + }; + + //gallery_control_thumbs_mousewheel + + var g_temp = { //temp variables + objCustomOptions:{}, + isAllItemsPreloaded:false, //flag that tells that all items started preloading + isFreestyleMode:false, //no special html additions + lastWidth:0, + lastHeigh:0, + handleResize: null, + isInited: false, + isPlayMode: false, + isPlayModePaused: false, + playTimePassed: 0, + playTimeLastStep: 0, + playHandle: "", + playStepInterval: 33, + objProgress: null, + isFakeFullscreen: false, + thumbsType:null, + isYoutubePresent:false, //flag if present youtube items + isVimeoPresent:false, //flag if present vimeo items + isHtml5VideoPresent:false, //flag if present html5 video items + isSoundCloudPresent:false, //flag if present soundcloud items + isWistiaPresent: false, //flag if some wistia movie present + resizeDelay: 100, + isRunFirstTime: true, + originalOptions: {}, + funcCustomHeight: null //custom height function, set by the theme if needed + }; + + + + function __________INIT_GALLERY_______(){}; + + /** + * get theme function from theme name + */ + function getThemeFunction(themeName){ + var themeFunction = themeName; + if(themeFunction.indexOf("UGTheme_") == -1) + themeFunction = "UGTheme_" + themeFunction; + + return(themeFunction); + } + + /** + * init the theme + */ + function initTheme(objCustomOptions){ + + //set theme function: + if(objCustomOptions.hasOwnProperty("gallery_theme")) + g_options.gallery_theme = objCustomOptions.gallery_theme; + else{ + var defaultTheme = g_options.gallery_theme; + if(g_ugFunctions.isThemeRegistered(defaultTheme) == false) + g_options.gallery_theme = g_ugFunctions.getFirstRegisteredTheme(); + } + + var themeFunction = getThemeFunction(g_options.gallery_theme); + + try{ + g_options.gallery_theme = eval(themeFunction); + }catch(e){ + //check registered themes + }; + + g_options.gallery_theme = eval(themeFunction); + + //init the theme + g_objTheme = new g_options.gallery_theme(); + g_objTheme.init(t, objCustomOptions); + } + + + /** + * reset all the options for the second time run + */ + function resetOptions(){ + + g_options = jQuery.extend({}, g_temp.originalOptions); + + g_selectedItemIndex = -1; + g_selectedItem = null; + g_objSlider = undefined; + g_objThumbs = undefined; + g_objSlider = undefined; + } + + + /** + * check for some errors and fire error if needed + */ + function checkForStartupErrors(){ + + //protection agains old jquery version + try{ + ugCheckForMinJQueryVersion(); + }catch(e){ + throwErrorShowMessage(e.message); + } + + //protection against some jquery ui function change + if(typeof g_objWrapper.outerWidth() == "object") + throwErrorShowMessage("You have some buggy script. most chances jquery-ui.js that destroy jquery outerWidth, outerHeight functions. The gallery can't run. Please update jquery-ui.js to latest version."); + + //check for late jquery include + setTimeout(function(){ugCheckForErrors(g_galleryID, "cms")} , 5000); + + } + + + + /** + * the gallery + */ + function runGallery(galleryID, objCustomOptions, htmlItems, cacheID){ + + var isCustomOptions = (typeof objCustomOptions == "object"); + + if(isCustomOptions) + g_temp.objCustomOptions = objCustomOptions; + + if(g_temp.isRunFirstTime == true){ + + g_galleryID = galleryID; + g_objWrapper = jQuery(g_galleryID); + if(g_objWrapper.length == 0){ + trace("div with id: "+g_galleryID+" not found"); + return(false); + } + + g_objParent = g_objWrapper.parent(); + + checkForStartupErrors(); + + g_temp.originalOptions = jQuery.extend({}, g_options); + + //merge options + if(isCustomOptions) + g_options = jQuery.extend(g_options, objCustomOptions); + + //cache items + if(g_options.gallery_enable_cache == true && g_options.gallery_initial_catid) + cacheItems(g_options.gallery_initial_catid); + + //set size class + t.setSizeClass(); + + //fill arrItems + var objItems = g_objWrapper.children(); + + fillItemsArray(objItems); + loadAPIs(); + + //hide images: + g_objWrapper.find("img").fadeTo(0,0).hide(); + g_objWrapper.show(); + + clearInitData(); + + }else{ //reset options - not first time run + + t.destroy(); + + resetOptions(); + + g_options = jQuery.extend(g_options, g_temp.objCustomOptions); + + if(htmlItems){ + + //cache items + if(cacheID && g_options.gallery_enable_cache == true) + cacheItems(cacheID, htmlItems); + + if(htmlItems == "noitems"){ + showErrorMessage("No items in this category",""); + return(false); + } + + g_objWrapper.html(htmlItems); + + var objItems = g_objWrapper.children(); + fillItemsArray(objItems); + + loadAPIs(); + + g_objWrapper.children().fadeTo(0,0).hide(); + + g_objWrapper.show(); + clearInitData(); + } + + } + + //init tabs + if(g_temp.isRunFirstTime == true && g_options.gallery_enable_tabs == true){ + g_objTabs = new UGTabs(); + g_objTabs.init(t, g_options); + } + + //init loadmore button + if(g_temp.isRunFirstTime == true && g_options.gallery_enable_loadmore == true){ + g_objLoadMore = new UGLoadMore(); + g_objLoadMore.init(t, g_options); + } + + //modify and verify the params + if(isCustomOptions) + modifyInitParams(g_temp.objCustomOptions); + + validateParams(); + + //shuffle items + if(g_options.gallery_shuffle == true) + t.shuffleItems(); + + //init the theme + initTheme(g_temp.objCustomOptions); + + //set gallery html elements + setGalleryHtml(); + + //set html properties to all elements + setHtmlObjectsProperties(); + + var galleryWidth = g_objWrapper.width(); + + if(galleryWidth == 0){ + g_functions.waitForWidth(g_objWrapper, runGalleryActually); + }else + runGalleryActually(); + + } + + + /** + * actually run the gallery + */ + function runGalleryActually(){ + + t.setSizeClass(); + + if(g_temp.isFreestyleMode == false){ + + if(g_options.gallery_preserve_ratio == true) + setHeightByOriginalRatio(); + } + + g_objTheme.run(); + + if(g_objTabs && g_temp.isRunFirstTime) + g_objTabs.run(); + + + preloadBigImages(); + + initEvents(); + + //select first item + if(g_numItems > 0) + t.selectItem(0); + + + //set autoplay + if(g_options.gallery_autoplay == true) + t.startPlayMode(); + + g_temp.isRunFirstTime = false; + + } + + + /** + * + * show error message + */ + function showErrorMessage(message, prefix){ + + if(typeof prefix == "undefined") + var prefix = "Unite Gallery Error: "; + else + prefix = ""+prefix+": "; + + message = prefix + message; + + var html = "
      " + message + "
      "; + + g_objWrapper.children().remove(); + + g_objWrapper.html(html); + g_objWrapper.show(); + } + + /** + * show error message and throw error + */ + function throwErrorShowMessage(message){ + showErrorMessage(message); + throw new Error(message); + } + + + /** + * + * @param objParams + */ + function modifyInitParams(){ + + //set default for preloading + if(!g_options.gallery_images_preload_type) + g_options.gallery_images_preload_type = "minimal"; + + //normalize gallery min height and width + if(g_options.gallery_min_height == undefined || g_options.gallery_height < g_options.gallery_min_height){ + g_options.gallery_min_height = 0; + } + + if(g_options.gallery_min_width == undefined || g_options.gallery_width < g_options.gallery_min_width){ + g_options.gallery_min_width = 0; + } + + } + + + /** + * validate the init parameters + */ + function validateParams(){ + + //validate theme: + if(!g_options.gallery_theme) + throw new Error("The gallery can't run without theme"); + + //if(typeof g_options.theme != "function") + //throw new Error("Wrong theme function: " + g_options.theme.toString()); + + //validate height and width + if(jQuery.isNumeric(g_options.gallery_height) && g_options.gallery_height < g_options.gallery_min_height) + throw new Error("The gallery_height option must be bigger then gallery_min_height option"); + + if(g_options.gallery_width < g_options.gallery_min_width) + throw new Error("The gallery_width option must be bigger then gallery_min_width option"); + + + } + + + /** + * set gallery html + */ + function setGalleryHtml(){ + + //add classes and divs + g_objWrapper.addClass("ug-gallery-wrapper"); + + g_objWrapper.append(""); + + t.setSizeClass(); + } + + + /** + * if the thumbs panel don't exists, delete initial images from dom + */ + function clearInitData(){ + + var objItems = g_objWrapper.children().remove(); + } + + + /** + * store last gallery size + */ + function storeLastSize(){ + var objSize = t.getSize(); + + g_temp.lastWidth = objSize.width; + g_temp.lastHeight = objSize.height; + } + + + /** + * set gallery height by original ratio + */ + function setHeightByOriginalRatio(){ + + var objSize = t.getSize(); + + var ratio = objSize.width / objSize.height; + + if(ratio != objSize.orig_ratio){ + + var newHeight = objSize.width / objSize.orig_ratio; + newHeight = Math.round(newHeight); + + if(newHeight < g_options.gallery_min_height) + newHeight = g_options.gallery_min_height; + + g_objWrapper.height(newHeight); + } + + } + + + /** + * set properties of the html objects + */ + function setHtmlObjectsProperties(){ + + var optionWidth = g_functions.getCssSizeParam(g_options.gallery_width); + + //set size + var objCss = { + //"width":optionWidth, //make it work within tabs + "max-width":optionWidth, + "min-width":g_functions.getCssSizeParam(g_options.gallery_min_width) + }; + + if(g_temp.isFreestyleMode == false){ + + var galleryHeight = g_functions.getCssSizeParam(g_options.gallery_height); + objCss["height"] = galleryHeight; + + }else{ + objCss["overflow"] = "visible"; + } + + //set background color + if(g_options.gallery_background_color) + objCss["background-color"] = g_options.gallery_background_color; + + + g_objWrapper.css(objCss); + + } + + + /** + * fill item by html child + */ + function fillItemByChild(objChild){ + + var isMobile = t.isMobileMode(); + + var tagname = objChild.prop("tagName").toLowerCase(); + + //handle link wrapper + var itemLink = ""; + if(tagname == "a"){ + itemLink = objChild.attr("href"); + objChild = objChild.children(); + var tagname = objChild.prop("tagName").toLowerCase(); + } + + var itemType = objChild.data("type"); + if(itemType == undefined) + itemType = "image"; + + var objItem = {}; + objItem.type = itemType; + + if(tagname == "img"){ + + //protection agains lasy load + var lasyLoadSrc = objChild.data("lazyload-src"); + if(lasyLoadSrc && lasyLoadSrc != ""){ + objChild.attr("src", lasyLoadSrc); + jQuery.removeData(objChild, "lazyload-src"); + } + + //src is thumb + var urlImage = objChild.data("image"); + var urlThumb = objChild.data("thumb"); + + if(typeof(urlImage) == "undefined") + urlImage = null; + + if(typeof(urlThumb) == "undefined") + urlThumb = null; + + var imageSrc = objChild.attr("src"); + + if(!urlImage) + urlImage = imageSrc; + + if(!urlThumb) + urlThumb = imageSrc; + + if(!urlThumb) + urlThumb = urlImage; + + if(!urlImage) + urlImage = urlThumb; + + objItem.urlThumb = urlThumb; + objItem.urlImage = urlImage; + + objItem.title = objChild.attr("alt"); + + //always set thumb image to object + objItem.objThumbImage = objChild; + + objItem.objThumbImage.attr("src", objItem.urlThumb); + + }else{ + + if(itemType == "image"){ + trace("Problematic gallery item found:"); + trace(objChild); + trace("Please look for some third party js script that could add this item to the gallery"); + throw new Error("The item should not be image type"); + } + + objItem.urlThumb = objChild.data("thumb"); + objItem.title = objChild.data("title"); + objItem.objThumbImage = null; + objItem.urlImage = objChild.data("image"); + } + + //trace(isMobile); + + //check mobile version images + if(isMobile == true){ + + var urlThumbMobile = objChild.data("thumb-mobile"); + if(typeof urlThumbMobile != "undefined" && urlThumbMobile != ""){ + objItem.urlThumb = urlThumbMobile; + + if(tagname == "img") + objChild.attr("src",objItem.urlThumb); + } + + var urlImageMobile = objChild.data("image-mobile"); + if(typeof urlImageMobile != "undefined" && urlImageMobile != "") + objItem.urlImage = urlImageMobile; + } + + objItem.link = itemLink; + + //get description: + objItem.description = objChild.attr("title"); + if(!objItem.description) + objItem.description = objChild.data("description"); + + if(!objItem.description) + objItem.description = ""; + + objItem.isNewAdded = false; //fill outside + objItem.isLoaded = false; + objItem.isThumbImageLoaded = false; //if the image loaded or error load + objItem.objPreloadImage = null; + objItem.isBigImageLoadStarted = false; + objItem.isBigImageLoaded = false; + objItem.isBigImageLoadError = false; + objItem.imageWidth = 0; + objItem.imageHeight = 0; + + //set thumb size + objItem.thumbWidth = 0; + objItem.thumbHeight = 0; + objItem.thumbRatioByWidth = 0; + objItem.thumbRatioByHeight = 0; + + var dataWidth = objChild.data("width"); + var dataHeight = objChild.data("height"); + if(dataWidth && typeof dataWidth == "number" && dataHeight && typeof dataHeight == "number"){ + objItem.thumbWidth = dataWidth; + objItem.thumbHeight = dataHeight; + objItem.thumbRatioByWidth = dataWidth / dataHeight; + objItem.thumbRatioByHeight = dataHeight / dataWidth; + } + + objItem.addHtml = null; + + var isImageMissing = (objItem.urlImage == undefined || objItem.urlImage == ""); + var isThumbMissing = (objItem.urlThumb == undefined || objItem.urlThumb == ""); + + switch(objItem.type){ + case "youtube": + objItem.videoid = objChild.data("videoid"); + + if(isImageMissing || isThumbMissing){ + + var objImages = g_ugYoutubeAPI.getVideoImages(objItem.videoid); + + //set preview image + if(isImageMissing) + objItem.urlImage = objImages.preview; + + //set thumb image + if(isThumbMissing){ + objItem.urlThumb = objImages.thumb; + + if(tagname == "img") + objChild.attr("src",objItem.urlThumb); + } + + } + + g_temp.isYoutubePresent = true; + break; + case "vimeo": + + objItem.videoid = objChild.data("videoid"); + + g_temp.isVimeoPresent = true; + break; + case "html5video": + objItem.videoogv = objChild.data("videoogv"); + objItem.videowebm = objChild.data("videowebm"); + objItem.videomp4 = objChild.data("videomp4"); + + g_temp.isHtml5VideoPresent = true; + break; + case "soundcloud": + objItem.trackid = objChild.data("trackid"); + g_temp.isSoundCloudPresent = true; + break; + case "wistia": + objItem.videoid = objChild.data("videoid"); + g_temp.isWistiaPresent = true; + break; + case "custom": + var objChildImage = objChild.children("img"); + + if(objChildImage.length){ + objChildImage = jQuery(objChildImage[0]); + + objItem.urlThumb = objChildImage.attr("src"); + objItem.title = objChildImage.attr("alt"); + objItem.objThumbImage = objChildImage; + } + + //add additional html + var objChildren = objChild.children().not("img:first-child"); + + if(objChildren.length) + objItem.addHtml = objChildren.clone(); + + break; + } + + //clear not needed attributes + if(objItem.objThumbImage){ + objItem.objThumbImage.removeAttr("data-description", ""); + objItem.objThumbImage.removeAttr("data-image", ""); + objItem.objThumbImage.removeAttr("data-thumb", ""); + objItem.objThumbImage.removeAttr("title", ""); + } + + + return(objItem); + } + + + /** + * fill items array from images object + */ + function fillItemsArray(arrChildren, isAppend){ + + if(isAppend !== true){ + g_arrItems = []; + }else{ //append + + //clear last new added items + for(var i=0;i').attr("src", imageUrl); + objItem.objPreloadImage.data("itemIndex", objItem.index); + + //set image load event (not that reliable) + objItem.objPreloadImage.on("load", t.onItemBigImageLoaded); + + //set load error event + objItem.objPreloadImage.on( "error", function(){ + var objImage = jQuery(this); + var itemIndex = objImage.data("itemIndex"); + var objItem = g_arrItems[itemIndex]; + + //update error: + objItem.isBigImageLoadError = true; + objItem.isBigImageLoaded = false; + + //print error + var imageUrl = jQuery(this).attr("src"); + console.log("Can't load image: "+ imageUrl); + + //try to load the image again + g_objGallery.trigger(t.events.ITEM_IMAGE_UPDATED, [itemIndex, objItem.urlImage]); + objItem.objThumbImage.attr("src", objItem.urlThumb); + + }); + + //check the all items started preloading flag + checkAllItemsStartedPreloading(); + + } + + + /** + * on item big image loaded event function. + * Update image size and that the image has been preloaded + * can be called from another objects like the slider + */ + this.onItemBigImageLoaded = function(event, objImage){ + + if(!objImage) + var objImage = jQuery(this); + + var itemIndex = objImage.data("itemIndex"); + + var objItem = g_arrItems[itemIndex]; + + objItem.isBigImageLoaded = true; + + //get size with fallback function + var objSize = g_functions.getImageOriginalSize(objImage); + + objItem.imageWidth = objSize.width; + objItem.imageHeight = objSize.height; + } + + /** + * check and fill image size in item object + */ + this.checkFillImageSize = function(objImage, objItem){ + + if(!objItem){ + var itemIndex = objImage.data("itemIndex"); + if(itemIndex === undefined) + throw new Error("Wrong image given to gallery.checkFillImageSize"); + + var objItem = g_arrItems[itemIndex]; + } + + var objSize = g_functions.getImageOriginalSize(objImage); + + objItem.imageWidth = objSize.width; + objItem.imageHeight = objSize.height; + } + + + /** + * preload next images near current item + */ + function preloadNearBigImages(objItem){ + + if(g_temp.isAllItemsPreloaded == true) + return(false); + + if(!objItem) + var objItem = g_selectedItem; + + if(!objItem) + return(true); + + var currentIndex = objItem.index; + var lastItemIndex = currentIndex - 1; + var nextItemIndex = currentIndex + 1; + + if(lastItemIndex > 0) + preloadItemImage(g_arrItems[lastItemIndex]); + + if(nextItemIndex < g_numItems) + preloadItemImage(g_arrItems[nextItemIndex]); + + } + + + /** + * check that all items started preloading, if do, set + * flag g_temp.isAllItemsPreloaded to true + */ + function checkAllItemsStartedPreloading(){ + + if(g_temp.isAllItemsPreloaded == true) + return(false); + + //if some of the items not started, exit function: + for(var index in g_arrItems){ + if(g_arrItems[index].isBigImageLoadStarted == false) + return(false); + } + + //if all started, set flag to true + g_temp.isAllItemsPreloaded = true; + } + + + /** + * set freestyle mode + */ + this.setFreestyleMode = function(){ + + g_temp.isFreestyleMode = true; + + } + + + /** + * attach thumbs panel to the gallery + */ + this.attachThumbsPanel = function(type, objThumbs){ + g_temp.thumbsType = type; + g_objThumbs = objThumbs; + } + + + /** + * init the slider + */ + this.initSlider = function(customOptions, optionsPrefix){ + + //mix options with user options + if(!customOptions) + var customOptions = {}; + + customOptions = jQuery.extend(g_temp.objCustomOptions, customOptions); + + g_objSlider = new UGSlider(); + g_objSlider.init(t, customOptions, optionsPrefix); + } + + + function __________END_INIT_GALLERY_______(){}; + + function __________EVENTS_____________(){}; + + + /** + * on gallery mousewheel event handler, advance the thumbs + */ + this.onGalleryMouseWheel = function(event, delta, deltaX, deltaY){ + + event.preventDefault(); + + if(delta > 0) + t.prevItem(); + else + t.nextItem(); + } + + + /** + * on mouse enter event + */ + function onSliderMouseEnter(event){ + + if(g_options.gallery_pause_on_mouseover == true && t.isFullScreen() == false && g_temp.isPlayMode == true && g_objSlider && g_objSlider.isSlideActionActive() == false) + t.pausePlaying(); + } + + + /** + * on mouse move event + */ + function onSliderMouseLeave(event){ + + if(g_options.gallery_pause_on_mouseover == true && g_temp.isPlayMode == true && g_objSlider && g_objSlider.isSlideActionActive() == false){ + + var isStillLoading = g_objSlider.isCurrentSlideLoadingImage(); + + if(isStillLoading == false) + t.continuePlaying(); + } + + } + + + /** + * on keypress - keyboard control + */ + function onKeyPress(event){ + + var obj = jQuery(event.target); + if(obj.is("textarea") || obj.is("select") || obj.is("input")) + return(true); + + var keyCode = (event.charCode) ? event.charCode :((event.keyCode) ? event.keyCode :((event.which) ? event.which : 0)); + + var wasAction = true; + + switch(keyCode){ + case 39: //right key + t.nextItem(); + break; + case 37: //left key + t.prevItem(); + break; + default: + wasAction = false; + break; + } + + //only first page gallery affected + + if(wasAction == true){ + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + } + + g_objGallery.trigger(t.events.GALLERY_KEYPRESS, [keyCode,event]); + } + + + /** + * check that the gallery resized, if do, trigger onresize event + */ + function onGalleryResized(){ + + var objSize = t.getSize(); + + if(objSize.width == 0) //fix hidden gallery change + return(true); + + t.setSizeClass(); + + var objSize = t.getSize(); + + if(objSize.width != g_temp.lastWidth || (g_temp.isFreestyleMode == false && objSize.height != g_temp.lastHeight)){ + + var heightWasSet = false; + + //set height with custom function (if exists) + if(g_temp.funcCustomHeight){ + var newHeight = g_temp.funcCustomHeight(objSize); + if(newHeight){ + g_objWrapper.height(newHeight); + heightWasSet = true; + } + } + + if(heightWasSet == false && g_options.gallery_preserve_ratio == true && g_temp.isFreestyleMode == false) + setHeightByOriginalRatio(); + + storeLastSize(); + g_objGallery.trigger(t.events.SIZE_CHANGE); + + } + + } + + + /** + * on strip move event + * preload visible images if that option selected + */ + function onThumbsChange(event){ + + //preload visible images + if(g_options.gallery_images_preload_type == "visible" && g_temp.isAllItemsPreloaded == false){ + preloadBigImages(); + } + + } + + + /** + * on full screen change event + */ + function onFullScreenChange(){ + + + var isFullscreen = g_functions.isFullScreen(); + var event = isFullscreen ? t.events.ENTER_FULLSCREEN:t.events.EXIT_FULLSCREEN; + + var fullscreenID = g_functions.getGlobalData("fullscreenID"); + + //check if this gallery was affected + if(g_galleryID !== fullscreenID) + return(true); + + //add classes for the gallery + if(isFullscreen){ + g_objWrapper.addClass("ug-fullscreen"); + }else{ + g_objWrapper.removeClass("ug-fullscreen"); + } + + g_objGallery.trigger(event); + + onGalleryResized(); + } + + /** + * on big image updated, if needed - preload this item image + */ + function onItemImageUpdated(event, index){ + + var objItem = t.getItem(index); + checkPreloadItemImage(objItem); + } + + /** + * on current slide image load end. If playing mode, begin playing again + */ + function onCurrentSlideImageLoadEnd(){ + + if(t.isPlayMode() == true) + t.continuePlaying(); + } + + + /** + * init all events + */ + function initEvents(){ + + //avoid annoyong firefox image dragging + g_objWrapper.on("dragstart",function(event){ + event.preventDefault(); + }); + + //on big image updated, if needed - preload this item image + g_objGallery.on(t.events.ITEM_IMAGE_UPDATED, onItemImageUpdated); + + //init custom event on strip moving + if(g_objThumbs){ + switch(g_temp.thumbsType){ + case "strip": + jQuery(g_objThumbs).on(g_objThumbs.events.STRIP_MOVE, onThumbsChange); + break; + case "grid": + jQuery(g_objThumbs).on(g_objThumbs.events.PANE_CHANGE, onThumbsChange); + break; + } + } + + //init mouse wheel + if(g_options.gallery_mousewheel_role == "advance" && g_temp.isFreestyleMode == false) + g_objWrapper.on("mousewheel", t.onGalleryMouseWheel); + + //on resize event + storeLastSize(); + + jQuery(window).resize(function(){ + g_objWrapper.css("width","auto"); + g_functions.whenContiniousEventOver("gallery_resize", onGalleryResized, g_temp.resizeDelay); + }); + + //check resize once in a time, start from 10 seconds + setTimeout(function(){ + setInterval(onGalleryResized, 2000); + }, 10000); + + //fullscreen: + g_functions.addFullScreenChangeEvent(onFullScreenChange); + + //on slider item changed event + if(g_objSlider){ + + jQuery(g_objSlider).on(g_objSlider.events.ITEM_CHANGED, function(){ + var sliderIndex = g_objSlider.getCurrentItemIndex(); + + if(sliderIndex != -1) + t.selectItem(sliderIndex); + }); + + //add slider onmousemove event + if(g_options.gallery_pause_on_mouseover == true){ + var sliderElement = g_objSlider.getElement(); + sliderElement.hover(onSliderMouseEnter, onSliderMouseLeave); + + //on full screen, run on mouse leave + g_objGallery.on(t.events.ENTER_FULLSCREEN, function(){ + onSliderMouseLeave(); + }); + + } + + //retrigger slider events + retriggerEvent(g_objSlider, g_objSlider.events.ACTION_START, t.events.SLIDER_ACTION_START); + retriggerEvent(g_objSlider, g_objSlider.events.ACTION_END, t.events.SLIDER_ACTION_END); + + jQuery(g_objSlider).on(g_objSlider.events.CURRENTSLIDE_LOAD_END, onCurrentSlideImageLoadEnd); + + } + + //add keyboard events + if(g_options.gallery_control_keyboard == true) + jQuery(document).keydown(onKeyPress); + + } + + + /** + * destroy all gallery events + */ + this.destroy = function(){ + + g_objWrapper.off("dragstart"); + g_objGallery.off(t.events.ITEM_IMAGE_UPDATED); + + //init custom event on strip moving + if(g_objThumbs){ + switch(g_temp.thumbsType){ + case "strip": + jQuery(g_objThumbs).off(g_objThumbs.events.STRIP_MOVE); + break; + case "grid": + jQuery(g_objThumbs).off(g_objThumbs.events.PANE_CHANGE); + break; + } + } + + g_objWrapper.off("mousewheel"); + + jQuery(window).off("resize"); + + //fullscreen: + g_functions.destroyFullScreenChangeEvent(); + + //on slider item changed event + if(g_objSlider){ + + jQuery(g_objSlider).off(g_objSlider.events.ITEM_CHANGED); + + //add slider onmousemove event + var sliderElement = g_objSlider.getElement(); + sliderElement.off("mouseenter"); + sliderElement.off("mouseleave"); + + g_objGallery.off(t.events.ENTER_FULLSCREEN); + jQuery(g_objSlider).off(g_objSlider.events.ACTION_START); + jQuery(g_objSlider).off(g_objSlider.events.ACTION_END); + jQuery(g_objSlider).off(g_objSlider.events.CURRENTSLIDE_LOAD_END); + } + + //add keyboard events + if(g_options.gallery_control_keyboard == true) + jQuery(document).off("keydown"); + + //destroy theme + if(g_objTheme && typeof g_objTheme.destroy == "function"){ + g_objTheme.destroy(); + } + + g_objWrapper.html(""); + + } + + + function __________GENERAL_______(){}; + + /** + * get items array + */ + this.getArrItems = function(){ + return g_arrItems; + } + + /** + * get gallery objects + */ + this.getObjects = function(){ + + var objects = { + g_galleryID:g_galleryID, + g_objWrapper:g_objWrapper, + g_objThumbs:g_objThumbs, + g_objSlider:g_objSlider, + g_options:g_options, + g_arrItems:g_arrItems, + g_numItems:g_numItems + }; + + return(objects); + } + + /** + * get slider object + */ + this.getObjSlider = function(){ + + return(g_objSlider); + } + + + /** + * + * get item by index, if the index don't fit, trace error + */ + this.getItem = function(index){ + if(index < 0){ + throw new Error("item with index: " + index + " not found"); + return(null); + } + if(index >= g_numItems){ + throw new Error("item with index: " + index + " not found"); + return(null); + } + + return(g_arrItems[index]); + } + + + /** + * get gallery width + */ + this.getWidth = function(){ + + var objSize = t.getSize(); + + return(objSize.width); + } + + /** + * get gallery height + */ + this.getHeight = function(){ + + var objSize = t.getSize(); + + return(objSize.height); + } + + + /** + * get gallery size + */ + this.getSize = function(){ + + var objSize = g_functions.getElementSize(g_objWrapper); + + objSize.orig_width = g_options.gallery_width; + objSize.orig_height = g_options.gallery_height; + objSize.orig_ratio = objSize.orig_width / objSize.orig_height; + + return(objSize); + } + + /** + * get gallery ID + */ + this.getGalleryID = function(){ + + var id = g_galleryID.replace("#",""); + + return(id); + } + + /** + * get next item by current index (or current object) + */ + this.getNextItem = function(index, forceCarousel){ + + if(typeof index == "object") + index = index.index; + + var nextIndex = index + 1; + + if(forceCarousel !== true && g_numItems == 1) + return(null); + + if(nextIndex >= g_numItems){ + + if(g_options.gallery_carousel == true || forceCarousel === true) + nextIndex = 0; + else + return(null); + } + + var objItem = g_arrItems[nextIndex]; + + return(objItem); + } + + + /** + * get previous item by index (or item object) + */ + this.getPrevItem = function(index){ + + if(typeof index == "object") + index = index.index; + + var prevIndex = index - 1; + + if(prevIndex < 0){ + if(g_options.gallery_carousel == true || forceCarousel === true) + prevIndex = g_numItems - 1; + else + return(null); + } + + var objItem = g_arrItems[prevIndex]; + + return(objItem); + } + + + + /** + * get selected item + */ + this.getSelectedItem = function(){ + + return(g_selectedItem); + } + + /** + * get selected item index + */ + this.getSelectedItemIndex = function(){ + + return(g_selectedItemIndex); + } + + + /** + * get number of items + */ + this.getNumItems = function(){ + return g_numItems; + } + + /** + * get true if the current item is last + */ + this.isLastItem = function(){ + if(g_selectedItemIndex == g_numItems - 1) + return(true); + + return(false); + } + + + /** + * get true if the current item is first + */ + this.isFirstItem = function(){ + if(g_selectedItemIndex == 0) + return(true); + return(false); + } + + + /** + * get gallery options + */ + this.getOptions = function(){ + return g_options; + } + + + /** + * get the gallery wrapper element + */ + this.getElement = function(){ + return(g_objWrapper); + } + + + this.___________SET_CONTROLS___________ = function(){} + + /** + * set next button element + * set onclick event + */ + this.setNextButton = function(objButton){ + + //register button as a unite gallery belong + objButton.data("ug-button", true); + + g_functions.setButtonOnClick(objButton, t.nextItem); + + } + + + /** + * set prev button element + * set onclick event + */ + this.setPrevButton = function(objButton){ + + //register button as a unite gallery belong + objButton.data("ug-button", true); + + g_functions.setButtonOnClick(objButton, t.prevItem); + + } + + + /** + * set fullscreen button to enter / exit fullscreen. + * on fullscreen mode ug-fullscreenmode class wil be added + */ + this.setFullScreenToggleButton = function(objButton){ + + //register button as a unite gallery belong + objButton.data("ug-button", true); + + g_functions.setButtonOnTap(objButton, t.toggleFullscreen); + + g_objGallery.on(t.events.ENTER_FULLSCREEN,function(){ + objButton.addClass("ug-fullscreenmode"); + }); + + g_objGallery.on(t.events.EXIT_FULLSCREEN,function(){ + objButton.removeClass("ug-fullscreenmode"); + }); + + } + + + /** + * destroy full screen button + */ + this.destroyFullscreenButton = function(objButton){ + + g_functions.destroyButton(objButton); + + g_objGallery.off(t.events.ENTER_FULLSCREEN); + g_objGallery.off(t.events.EXIT_FULLSCREEN); + } + + + /** + * set play button event + */ + this.setPlayButton = function(objButton){ + + //register button as a unite gallery belong + objButton.data("ug-button", true); + + g_functions.setButtonOnClick(objButton, t.togglePlayMode); + + g_objGallery.on(t.events.START_PLAY,function(){ + objButton.addClass("ug-stop-mode"); + }); + + g_objGallery.on(t.events.STOP_PLAY, function(){ + objButton.removeClass("ug-stop-mode"); + }); + + } + + /** + * destroy the play button + */ + this.destroyPlayButton = function(objButton){ + g_functions.destroyButton(objButton); + g_objGallery.off(t.events.START_PLAY); + g_objGallery.off(t.events.STOP_PLAY); + } + + /** + * set playing progress indicator + */ + this.setProgressIndicator = function(objProgress){ + + g_temp.objProgress = objProgress; + } + + + /** + * set title and descreiption containers + */ + this.setTextContainers = function(objTitle, objDescription){ + + g_objGallery.on(t.events.ITEM_CHANGE, function(){ + + var objItem = t.getSelectedItem(); + objTitle.html(objItem.title); + objDescription.html(objItem.description); + + }); + + } + + /** + * show overlay disabled + */ + this.showDisabledOverlay = function(){ + g_objWrapper.children(".ug-overlay-disabled").show(); + } + + /** + * show overlay disabled + */ + this.hideDisabledOverlay = function(){ + g_objWrapper.children(".ug-overlay-disabled").hide(); + } + + this.___________END_SET_CONTROLS___________ = function(){} + + + /** + * cache items, put to cache array by id + * the items must be unprocessed yet + */ + function cacheItems(cacheID, htmlItemsArg){ + + if(htmlItemsArg){ + var htmlItems = htmlItemsArg; + if(htmlItems != "noitems") + htmlItems = jQuery(htmlItemsArg).clone(); + }else{ + var htmlItems = g_objWrapper.children().clone(); + } + + g_objCache[cacheID] = htmlItems; + } + + + /** + * remove all size classes + */ + function removeAllSizeClasses(objWrapper){ + + if(!objWrapper) + objWrapper = g_objWrapper; + + objWrapper.removeClass("ug-under-480"); + objWrapper.removeClass("ug-under-780"); + objWrapper.removeClass("ug-under-960"); + } + + + /** + * retrigger event from another objects + * the second parameter will be the second object + */ + function retriggerEvent(object, originalEvent, newEvent){ + + jQuery(object).on(originalEvent, function(event){ + g_objGallery.trigger(newEvent, [this]); + }); + + } + + + + /** + * advance next play step + */ + function advanceNextStep(){ + + var timeNow = jQuery.now(); + var timeDiff = timeNow - g_temp.playTimeLastStep; + g_temp.playTimeLastStep = timeNow; + + var isVisible = t.isGalleryVisible(); + if(isVisible == false){ + return(false); + } + + g_temp.playTimePassed += timeDiff; + + //set the progress + if(g_temp.objProgress){ + var percent = g_temp.playTimePassed / g_options.gallery_play_interval; + g_temp.objProgress.setProgress(percent); + } + + //if interval passed - proceed to next item + if(g_temp.playTimePassed >= g_options.gallery_play_interval){ + + t.nextItem(); + g_temp.playTimePassed = 0; + } + + + } + + this.___________PLAY_MODE___________ = function(){} + + + /** + * start play mode + */ + this.startPlayMode = function(){ + + g_temp.isPlayMode = true; + g_temp.isPlayModePaused = false; + + g_temp.playTimePassed = 0; + g_temp.playTimeLastStep = jQuery.now(); + + g_temp.playHandle = setInterval(advanceNextStep, g_temp.playStepInterval); + + //show and reset progress indicator + if(g_temp.objProgress){ + var objElement = g_temp.objProgress.getElement(); + g_temp.objProgress.setProgress(0); + objElement.show(); + } + + g_objGallery.trigger(t.events.START_PLAY); + + //check if there is a need to pause + if(g_objSlider && g_objSlider.isCurrentSlideLoadingImage() == true){ + t.pausePlaying(); + } + + } + + + /** + * reset playing - set the timer to 0 + */ + this.resetPlaying = function(){ + + if(g_temp.isPlayMode == false) + return(true); + + g_temp.playTimePassed = 0; + g_temp.playTimeLastStep = jQuery.now(); + } + + + /** + * pause playing slideshow + */ + this.pausePlaying = function(){ + + if(g_temp.isPlayModePaused == true) + return(true); + + g_temp.isPlayModePaused = true; + clearInterval(g_temp.playHandle); + + g_objGallery.trigger(t.events.PAUSE_PLAYING); + } + + + /** + * continue playing slideshow + */ + this.continuePlaying = function(){ + + if(g_temp.isPlayModePaused == false) + return(true); + + g_temp.isPlayModePaused = false; + g_temp.playTimeLastStep = jQuery.now(); + g_temp.playHandle = setInterval(advanceNextStep, g_temp.playStepInterval); + + } + + + /** + * stop play mode + */ + this.stopPlayMode = function(){ + g_temp.isPlayMode = false; + clearInterval(g_temp.playHandle); + + g_temp.playTimePassed = 0; + + //hide progress indicator + if(g_temp.objProgress){ + var objElement = g_temp.objProgress.getElement(); + objElement.hide(); + } + + g_objGallery.trigger(t.events.STOP_PLAY); + } + + + /** + * tells if the play mode are active + */ + this.isPlayMode = function(){ + + return(g_temp.isPlayMode); + } + + + /** + * start / stop play mode + */ + this.togglePlayMode = function(){ + + if(t.isPlayMode() == false) + t.startPlayMode(); + else + t.stopPlayMode(); + } + + + /** + * unselect all items + */ + function unselectSeletedItem(){ + + if(g_selectedItem == null) + return(true); + + if(g_objThumbs) + g_objThumbs.setThumbUnselected(g_selectedItem.objThumbWrapper); + + g_selectedItem = null; + g_selectedItemIndex = -1; + } + + + this.___________GENERAL_EXTERNAL___________ = function(){} + + /** + * shuffle items - usually before theme start + */ + this.shuffleItems = function(){ + + g_arrItems = g_functions.arrayShuffle(g_arrItems); + + for(var index in g_arrItems) //fix index + g_arrItems[index].index = parseInt(index); + + } + + /** + * set main gallery params, extend current params + */ + this.setOptions = function(customOptions){ + + g_options = jQuery.extend(g_options, customOptions); + } + + + /** + * select some item + * the input can be index or object + * role - the role of the object who selected the item + */ + this.selectItem = function(objItem, role){ + + if(typeof objItem == "number") + objItem = t.getItem(objItem); + + var itemIndex = objItem.index; + if(itemIndex == g_selectedItemIndex) + return(true); + + unselectSeletedItem(); + + //set selected item + g_selectedItem = objItem; + g_selectedItemIndex = itemIndex; + + g_objGallery.trigger(t.events.ITEM_CHANGE, [objItem,role]); + + //reset playback, if playing + if(g_temp.isPlayMode == true){ + t.resetPlaying(); + + var stillLoading = g_objSlider.isCurrentSlideLoadingImage(); + if(stillLoading == true) + t.pausePlaying(); + } + + } + + + /** + * go to next item + */ + this.nextItem = function(){ + + var newItemIndex = g_selectedItemIndex + 1; + + if(g_numItems == 0) + return(true); + + if(g_options.gallery_carousel == false && newItemIndex >= g_numItems) + return(true); + + if(newItemIndex >= g_numItems) + newItemIndex = 0; + + //debugLine(newItemIndex,true); + + t.selectItem(newItemIndex, "next"); + } + + + /** + * go to previous item + */ + this.prevItem = function(){ + + var newItemIndex = g_selectedItemIndex - 1; + + if(g_selectedItemIndex == -1) + newItemIndex = 0; + + if(g_numItems == 0) + return(true); + + if(g_options.gallery_carousel == false && newItemIndex < 0) + return(true); + + if(newItemIndex < 0) + newItemIndex = g_numItems - 1; + + t.selectItem(newItemIndex, "prev"); + + } + + + /** + * expand gallery to body size + */ + function toFakeFullScreen(){ + + jQuery("body").addClass("ug-body-fullscreen"); + g_objWrapper.addClass("ug-fake-fullscreen"); + + g_temp.isFakeFullscreen = true; + + g_objGallery.trigger(t.events.ENTER_FULLSCREEN); + g_objGallery.trigger(t.events.SIZE_CHANGE); + } + + + /** + * exit fake fullscreen + */ + function exitFakeFullscreen(){ + + jQuery("body").removeClass("ug-body-fullscreen"); + g_objWrapper.removeClass("ug-fake-fullscreen"); + + g_temp.isFakeFullscreen = false; + + g_objGallery.trigger(t.events.EXIT_FULLSCREEN); + g_objGallery.trigger(t.events.SIZE_CHANGE); + + } + + /** + * return if the fullscreen mode is available + */ + this.isFullScreen = function(){ + + if(g_temp.isFakeFullscreen == true) + return(true); + + if(g_functions.isFullScreen() == true) + return(true); + + return(false); + } + + + /** + * tells if it's fake fullscreen + */ + this.isFakeFullscreen = function(){ + + return(g_temp.isFakeFullscreen); + } + + + /** + * go to fullscreen mode + */ + this.toFullScreen = function(){ + + g_functions.setGlobalData("fullscreenID", g_galleryID); + + var divGallery = g_objWrapper.get(0); + + var isSupported = g_functions.toFullscreen(divGallery); + + if(isSupported == false) + toFakeFullScreen(); + + } + + + /** + * exit full screen + */ + this.exitFullScreen = function(){ + + if(g_temp.isFakeFullscreen == true) + exitFakeFullscreen(); + else + g_functions.exitFullscreen(); + + } + + /** + * toggle fullscreen + */ + this.toggleFullscreen = function(){ + + if(t.isFullScreen() == false){ + t.toFullScreen(); + }else{ + t.exitFullScreen(); + } + + } + + /** + * resize the gallery + * noevent - initally false + */ + this.resize = function(newWidth, newHeight, noevent){ + + g_objWrapper.css("width", "auto"); + g_objWrapper.css("max-width",newWidth+"px"); + + if(newHeight) + g_objWrapper.height(newHeight); + + if(!noevent && noevent !== true) + onGalleryResized(); + + } + + + /** + * set size class to the wrapper + * this can work to any other wrapper too + */ + this.setSizeClass = function(objWrapper, width){ + + if(!objWrapper) + var objWrapper = g_objWrapper; + + if(!width){ + var objSize = t.getSize(); + var width = objSize.width; + } + + if(width == 0) + var width = jQuery(window).width(); + + var addClass = ""; + + if(width <= 480){ + addClass = "ug-under-480"; + }else + if(width <= 780){ + addClass = "ug-under-780"; + }else + if(width < 960){ + addClass = "ug-under-960"; + } + + if(objWrapper.hasClass(addClass) == true) + return(true); + + removeAllSizeClasses(objWrapper); + if(addClass != "") + objWrapper.addClass(addClass); + } + + + /** + * return if the size is suited for mobile + */ + this.isMobileMode = function(){ + + if(g_objWrapper.hasClass("ug-under-480")) + return(true); + + return(false); + } + + + /** + * get if small screen + */ + this.isSmallWindow = function(){ + + var windowWidth = jQuery(window).width(); + + + if(!windowWidth) + return(true); + + if(windowWidth <= 480) + return(true); + + return(false); + } + + + /** + * check if the gallery is visible + */ + this.isGalleryVisible = function(){ + + var isVisible = g_objWrapper.is(":visible"); + + return(isVisible); + } + + + /** + * change gallery items + */ + this.changeItems = function(itemsContent, cacheID){ + + if(!itemsContent) + var itemsContent = "noitems"; + + runGallery(g_galleryID, "nochange", itemsContent, cacheID); + } + + + + /** + * add items + */ + this.addItems = function(itemsContent){ + + if(!itemsContent || itemsContent.length == 0) + return(false); + + //add new items wrapper + var objNewItemsWrapper = g_objWrapper.children(".ug-newitems-wrapper"); + if(objNewItemsWrapper.length == 0) + g_objWrapper.append(""); + + objNewItemsWrapper = g_objWrapper.children(".ug-newitems-wrapper"); + + //add the items + objNewItemsWrapper.append(itemsContent); + + var objChildren = jQuery(objNewItemsWrapper.children()); + + fillItemsArray(objChildren, true); + + loadAPIs(); + + if(!g_objTheme || typeof g_objTheme.addItems != "function") + throw new Error("addItems function not found in the theme"); + + objNewItemsWrapper.remove(); + + g_objTheme.addItems(); + } + + + /** + * get new added items indexes array + */ + this.getNewAddedItemsIndexes = function(){ + + var arrIndexes = []; + + jQuery.each(g_arrItems, function(index, objItem){ + + if(objItem.isNewAdded == true) + arrIndexes.push(index); + }); + + return(arrIndexes); + } + + + /** + * show error message, replace whole gallery div + */ + this.showErrorMessageReplaceGallery = function(message){ + showErrorMessage(message); + } + + /** + * set custom height function by width + */ + this.setFuncCustomHeight = function(func){ + g_temp.funcCustomHeight = func; + } + + + this.__________EXTERNAL_EVENTS_______ = function(){}; + + + /** + * trigger event + */ + this.triggerEvent = function(event, arrParams){ + + if(!arrParams) + g_objGallery.trigger(event); + else{ + if(jQuery.type(arrParams) != "array") + arrParams = [arrParams]; + + g_objGallery.trigger(event, arrParams); + } + + } + + /** + * on event + */ + this.onEvent = function(event, func){ + + g_objGallery.on(event, func); + } + + + /** + * destroy event + */ + this.destroyEvent = function(event){ + + g_objGallery.off(event); + } + + + this.__________AJAX_REQUEST_______ = function(){}; + + + /** + * ajax request + */ + this.ajaxRequest = function(action, data, successFunction, errorFunction){ + + if(!successFunction || typeof successFunction != "function") + throw new Error("ajaxRequest error: success function should be passed"); + + var urlAjax = g_options.gallery_urlajax; + if(!urlAjax || urlAjax == "") + throw new Error("ajaxRequest error: Ajax url don't passed"); + + if(typeof data == "undefined") + var data = {}; + + //add galleryID to data + var objData = { + action:"unitegallery_ajax_action", + client_action:action, + galleryID: g_galleryID, + data:data + }; + + jQuery.ajax({ + type:"post", + url:g_options.gallery_urlajax, + dataType: 'json', + data:objData, + success:function(response){ + + if(!response){ + throw new Error("Empty ajax response!"); + } + + if(response == -1 || response === 0) + throw new Error("ajax error!!!"); + + + if(typeof response.success == "undefined") + throw new Error("ajax error!!!"); + + if(response.success == false){ + showErrorMessage(response.message, "ajax error"); + return(false); + } + + successFunction(response); + }, + error:function(jqXHR, textStatus, errorThrown){ + console.log("Ajax Error!!! " + textStatus); + responseText = jqXHR.responseText; + if(errorFunction && typeof errorFunction == "function"){ + errorFunction(responseText); + }else + trace(responseText); + } + }); + + } + + + /** + * request new items + * isForce - don't take from cache + */ + this.requestNewItems = function(catID, isForce, cacheID){ + + var checkCache = g_options.gallery_enable_cache; + + if(!cacheID) + cacheID = catID; + + if(isForce == true) + checkCache = false; + + //get items from cache + if(checkCache == true && g_objCache.hasOwnProperty(cacheID)){ + + var htmlItems = g_objCache[cacheID]; + + t.changeItems(htmlItems, cacheID); + + }else{ + + g_objGallery.trigger(t.events.GALLERY_BEFORE_REQUEST_ITEMS); + + t.ajaxRequest("front_get_cat_items",{catid:catID}, function(response){ + + var htmlItems = response.html; + + t.changeItems(htmlItems, cacheID); + + }); + + } + + } + + + /** + * run the gallery + */ + this.run = function(galleryID, objParams){ + + + var debug_errors = g_options.gallery_debug_errors; + if(objParams && objParams.hasOwnProperty("gallery_debug_errors")) + g_options.gallery_debug_errors = objParams.gallery_debug_errors; + + + if(g_options.gallery_debug_errors == true){ + + try{ + + runGallery(galleryID, objParams); + + + }catch(objError){ + if(typeof objError == "object"){ + + var message = objError.message; + var lineNumber = objError.lineNumber; + var fileName = objError.fileName; + var stack = objError.stack; + + message += "

      in file: "+fileName; + message += " line " + lineNumber + ""; + + trace(objError); + + }else{ + var message = objError; + } + + //remove double "error:" text + message = message.replace("Error:",""); + + showErrorMessage(message); + } + + }else{ + runGallery(galleryID, objParams); + } + + + + } + +} //unitegallery object end + + +/** + * tiles class + */ +function UGLightbox(){ + + var t = this, g_objThis = jQuery(this); + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objWrapper; + var g_objSlider = new UGSlider(), g_objOverlay, g_objArrowLeft, g_objArrowRight, g_objButtonClose; + var g_functions = new UGFunctions(), g_objTextPanel = new UGTextPanel(), g_objNumbers; + var g_objTopPanel; + + var g_options = { + lightbox_type: "wide", //compact / wide - lightbox type + + lightbox_show_textpanel: true, //show the text panel + lightbox_textpanel_width: 550, //the width of the text panel. + + lightbox_hide_arrows_onvideoplay: true, //hide the arrows when video start playing and show when stop + lightbox_arrows_position: "sides", //sides, inside: position of the arrows, used on compact type + lightbox_arrows_offset: 10, //The horizontal offset of the arrows + lightbox_arrows_inside_offset: 10, //The offset from the image border if the arrows placed inside + lightbox_arrows_inside_alwayson: false, //Show the arrows on mouseover, or always on. + + lightbox_overlay_color:null, //the color of the overlay. if null - will take from css + lightbox_overlay_opacity:1, //the opacity of the overlay. if null - will take from css + lightbox_top_panel_opacity: null, //the opacity of the top panel + + lightbox_show_numbers: true, //show numbers on the right side + lightbox_numbers_size: null, //the size of the numbers string + lightbox_numbers_color: null, //the color of the numbers + lightbox_numbers_padding_top:null, //the top padding of the numbers (used in compact mode) + lightbox_numbers_padding_right:null, //the right padding of the numbers (used in compact mode) + + lightbox_compact_closebutton_offsetx: 1, //the offsetx of the close button. Valid only for compact mode + lightbox_compact_closebutton_offsety: 1, //the offsetx of the close button. Valid only for compact mode + + lightbox_close_on_emptyspace:true //close the lightbox on empty space + }; + + this.events = { + LIGHTBOX_INIT: "lightbox_init" + }; + + var g_temp = { + topPanelHeight: 44, + initTextPanelHeight: 26, //init height for compact mode + isOpened: false, + isRightNowOpened:false, + putSlider: true, + isCompact: false, + fadeDuration: 300, + positionFrom: null, + textPanelTop: null, + textPanelLeft: null, + isArrowsInside: false, + isArrowsOnHoverMode: false, + lastMouseX: null, + lastMouseY: null, + originalOptions: null, + isSliderChangedOnce:false, + isTopPanelEnabled:true + }; + + var g_defaults = { + lightbox_slider_controls_always_on: true, + lightbox_slider_enable_bullets: false, + lightbox_slider_enable_arrows: false, + lightbox_slider_enable_progress_indicator: false, + lightbox_slider_enable_play_button: false, + lightbox_slider_enable_fullscreen_button: false, + lightbox_slider_enable_zoom_panel: false, + lightbox_slider_enable_text_panel: false, + lightbox_slider_scale_mode_media: "down", + lightbox_slider_scale_mode: "down", + lightbox_slider_loader_type: 3, + lightbox_slider_loader_color: "black", + lightbox_slider_transition: "fade", + + lightbox_slider_image_padding_top: g_temp.topPanelHeight, + lightbox_slider_image_padding_bottom: 0, + + lightbox_slider_video_padding_top: 0, + lightbox_slider_video_padding_bottom: 0, + + lightbox_textpanel_align: "middle", + lightbox_textpanel_padding_top: 5, + lightbox_textpanel_padding_bottom: 5, + + slider_video_constantsize: false, + lightbox_slider_image_border: false, + + lightbox_textpanel_enable_title: true, + lightbox_textpanel_enable_description: false, + lightbox_textpanel_desc_style_as_title: true, + + lightbox_textpanel_enable_bg:false, + + video_enable_closebutton: false, + lightbox_slider_video_enable_closebutton: false, + video_youtube_showinfo: false, + lightbox_slider_enable_links:false + }; + + var g_defaultsCompact = { + lightbox_overlay_opacity:0.6, + + lightbox_slider_image_border: true, + lightbox_slider_image_shadow:true, + lightbox_slider_image_padding_top: 30, + lightbox_slider_image_padding_bottom: 30, + + slider_video_constantsize: true, + + lightbox_textpanel_align: "bottom", + lightbox_textpanel_title_text_align: "left", + lightbox_textpanel_desc_text_align: "left", + lightbox_textpanel_padding_left: 10, //the padding left of the textpanel + lightbox_textpanel_padding_right: 10 + }; + + + function __________GENERAL_________(){}; + + + /** + * init the gallery + */ + function initLightbox(gallery, customOptions){ + + g_gallery = gallery; + g_objGallery = jQuery(gallery); + + g_options = jQuery.extend(g_options, g_defaults); + g_options = jQuery.extend(g_options, customOptions); + + g_temp.originalOptions = jQuery.extend({}, g_options); + + if(g_options.lightbox_type == "compact"){ + g_temp.isCompact = true; + g_options = jQuery.extend(g_options, g_defaultsCompact); + g_options = jQuery.extend(g_options, customOptions); + } + + //modify some options + modifyOptions(); + + if(g_temp.putSlider == true){ + + g_gallery.initSlider(g_options, "lightbox"); + g_objects = gallery.getObjects(); + g_objSlider = g_objects.g_objSlider; + + }else{ + g_objSlider = null; + } + + if(g_options.lightbox_show_textpanel == true){ + g_objTextPanel.init(g_gallery, g_options, "lightbox"); + } + else + g_objTextPanel = null; + + + } + + + /** + * modify some options according user options + */ + function modifyOptions(){ + + if(g_temp.isCompact == true && g_options.lightbox_show_textpanel == true){ + g_options.lightbox_slider_image_padding_bottom = g_temp.initTextPanelHeight; + } + + if(g_temp.isCompact == true && g_options.lightbox_arrows_position == "inside"){ + g_temp.isArrowsInside = true; + } + + if(g_temp.isArrowsInside == true && g_options.lightbox_arrows_inside_alwayson == false) + g_temp.isArrowsOnHoverMode = true; + + //disable top panel if no text panel enabled + if(g_options.lightbox_show_textpanel == false){ + g_temp.isTopPanelEnabled = false; + g_temp.topPanelHeight = 0; + g_options.lightbox_slider_image_padding_top = 0; + } + + //modify slider image border width + + + } + + + /** + * put the lightbox html + */ + function putLightboxHtml(){ + + var html = ""; + var classAddition = ""; + if(g_temp.isCompact == true){ + classAddition = " ug-lightbox-compact"; + } + + html += ""; + + g_objWrapper = jQuery(html); + + jQuery("body").append(g_objWrapper); + + if(g_objSlider) + g_objSlider.setHtml(g_objWrapper); + + g_objOverlay = g_objWrapper.children(".ug-lightbox-overlay"); + + if(g_temp.isCompact == false && g_temp.isTopPanelEnabled == true){ + g_objTopPanel = g_objWrapper.children(".ug-lightbox-top-panel"); + if(g_objTopPanel.length == 0) + g_objTopPanel = null; + } + + g_objButtonClose = g_objWrapper.find(".ug-lightbox-button-close"); + + if(g_options.lightbox_show_numbers) + g_objNumbers = g_objWrapper.find(".ug-lightbox-numbers"); + + g_objArrowLeft = g_objWrapper.children(".ug-lightbox-arrow-left"); + g_objArrowRight = g_objWrapper.children(".ug-lightbox-arrow-right"); + + if(g_objTextPanel){ + if(g_objTopPanel) + g_objTextPanel.appendHTML(g_objTopPanel); + else + g_objTextPanel.appendHTML(g_objWrapper); + } + + } + + + /** + * set lightbox properties + */ + function setProperties(){ + + if(g_options.lightbox_overlay_color !== null) + g_objOverlay.css("background-color", g_options.lightbox_overlay_color); + + if(g_options.lightbox_overlay_opacity !== null) + g_objOverlay.fadeTo(0, g_options.lightbox_overlay_opacity); + + if(g_objTopPanel && g_options.lightbox_top_panel_opacity !== null){ + g_objTopPanel.children(".ug-lightbox-top-panel-overlay").fadeTo(0, g_options.lightbox_top_panel_opacity); + } + + //set numbers properties + if(g_objNumbers){ + var cssNumbers = {}; + + if(g_options.lightbox_numbers_size !== null) + cssNumbers["font-size"] = g_options.lightbox_numbers_size+"px"; + + if(g_options.lightbox_numbers_color) + cssNumbers["color"] = g_options.lightbox_numbers_color; + + if(g_options.lightbox_numbers_padding_right !== null) + cssNumbers["padding-right"] = g_options.lightbox_numbers_padding_right + "px"; + + if(g_options.lightbox_numbers_padding_top !== null) + cssNumbers["padding-top"] = g_options.lightbox_numbers_padding_top + "px"; + + + g_objNumbers.css(cssNumbers); + } + + } + + + /** + * refresh slider item with new height + */ + function refreshSliderItem(newHeight){ + + if(!g_objSlider) + return(true); + + //set slider new image position + var objOptions = { + slider_image_padding_top: newHeight + }; + + g_objSlider.setOptions(objOptions); + g_objSlider.refreshSlideItems(); + + } + + function __________WIDE_ONLY_________(){}; + + + /** + * handle panel height according text height + */ + function handlePanelHeight(fromWhere){ + + if(!g_objTopPanel) + return(false); + + if(!g_objTextPanel) + return(false); + + //check text panel size, get the panel bigger then + var panelHeight = g_objTopPanel.height(); + if(panelHeight == 0) + return(false); + + if(g_objTopPanel.is(":visible") == false) + return(false); + + var newPanelHeight = panelHeight; + + var objTextPanelSize = g_objTextPanel.getSize(); + + var textPanelHeight = objTextPanelSize.height; + + if(panelHeight != g_temp.topPanelHeight) + newPanelHeight = g_temp.topPanelHeight; + + if(textPanelHeight > newPanelHeight) + newPanelHeight = textPanelHeight; + + if(panelHeight != newPanelHeight){ + g_objTopPanel.height(newPanelHeight); + + if(g_objSlider && g_objSlider.isAnimating() == false) + refreshSliderItem(newPanelHeight); + } + + } + + + /** + * position text panel for wide + * size - wrapper size + */ + function positionTextPanelWide(size){ + + var objOptions = {}; + + var textWidth = g_options.lightbox_textpanel_width; + var minPaddingLeft = 47; + var minPaddingRight = 40; + var maxTextPanelWidth = size.width - minPaddingLeft - minPaddingRight; + + if(textWidth > maxTextPanelWidth){ //mobile mode + + objOptions.textpanel_padding_left = minPaddingLeft; + objOptions.textpanel_padding_right = minPaddingRight; + + objOptions.textpanel_title_text_align = "center"; + objOptions.textpanel_desc_text_align = "center"; + }else{ + objOptions.textpanel_padding_left = Math.floor((size.width - textWidth) / 2); + objOptions.textpanel_padding_right = objOptions.textpanel_padding_left; + objOptions.textpanel_title_text_align = "left"; + objOptions.textpanel_desc_text_align = "left"; + + if(g_options.lightbox_textpanel_title_text_align) + objOptions.textpanel_title_text_align = g_options.lightbox_textpanel_desc_text_align; + + if(g_options.lightbox_textpanel_desc_text_align) + objOptions.textpanel_desc_text_align = g_options.lightbox_textpanel_desc_text_align; + + } + + g_objTextPanel.setOptions(objOptions); + + g_objTextPanel.refresh(true, true); + + handlePanelHeight("positionTextPanelWide"); + g_objTextPanel.positionPanel(); + } + + /** + * hide top panel + */ + function hideTopPanel(){ + + if(!g_objTopPanel) + return(false); + + g_objTopPanel.hide(); + } + + + /** + * show top panel + */ + function showTopPanel(){ + + if(!g_objTopPanel) + return(false); + + g_objTopPanel.show(); + } + + + function __________COMPACT_ONLY_________(){}; + + /** + * handle slider image height according the textpanel height + * refresh the slider if the height is not in place + */ + function handleCompactHeight(objImageSize){ + + if(g_temp.isOpened == false) + return(false); + + if(!g_objTextPanel) + return(false); + + if(!g_objSlider) + return(false); + + var wrapperSize = g_functions.getElementSize(g_objWrapper); + var textPanelSize = g_objTextPanel.getSize(); + + if(textPanelSize.width == 0 || textPanelSize.height > 120) + return(false); + + if(!objImageSize){ + var objImage = g_objSlider.getSlideImage(); + var objImageSize = g_functions.getElementSize(objImage); + } + + if(objImageSize.height == 0 || objImageSize.width == 0) + return(false); + + //check elements end size + var totalBottom = objImageSize.bottom + textPanelSize.height; + + if(totalBottom < wrapperSize.height) + return(false); + + var sliderOptions = g_objSlider.getOptions(); + + var imagePaddingBottom = textPanelSize.height; + + if(imagePaddingBottom != sliderOptions.slider_image_padding_bottom){ + + var objOptions = { + slider_image_padding_bottom: imagePaddingBottom + }; + + if(g_objSlider.isAnimating() == false){ + g_objSlider.setOptions(objOptions); + g_objSlider.refreshSlideItems(); + return(true); + } + + } + + return(false); + } + + /** + * set text panel top of compact mode + */ + function setCompactTextpanelTop(objImageSize, positionPanel){ + + if(!objImageSize){ + var objImage = g_objSlider.getSlideImage(); + var objImageSize = g_functions.getElementSize(objImage); + } + + g_temp.textPanelTop = objImageSize.bottom; + + if(positionPanel === true) + g_objTextPanel.positionPanel(g_temp.textPanelTop, g_temp.textPanelLeft); + } + + + /** + * handle text panel width on compact mode, + * run when the image is ready. + * Set top position of the panel as well + * position numbers as well + */ + function handleCompactTextpanelSizes(showTextpanel){ + + var wrapperSize = g_functions.getElementSize(g_objWrapper); + var objImage = g_objSlider.getSlideImage(); + var objImageSize = g_functions.getElementSize(objImage); + + if(objImageSize.width == 0) + return(false); + + + g_temp.textPanelLeft = objImageSize.left; + g_temp.textPanelTop = objImageSize.bottom; + + var textPanelWidth = objImageSize.width; + + if(g_objNumbers){ + + var objNumbersSize = g_functions.getElementSize(g_objNumbers); + textPanelWidth -= objNumbersSize.width; + + //place numbers object + var numbersLeft = objImageSize.right - objNumbersSize.width; + g_functions.placeElement(g_objNumbers, numbersLeft, g_temp.textPanelTop); + } + + + if(g_objTextPanel){ + g_objTextPanel.show(); + g_objTextPanel.refresh(true, true, textPanelWidth); + setCompactTextpanelTop(objImageSize); + } + + var isChanged = handleCompactHeight(objImageSize); + + if(isChanged == false){ + + g_temp.positionFrom = "handleCompactTextpanelSizes"; + + if(g_objTextPanel){ + g_objTextPanel.positionPanel(g_temp.textPanelTop, g_temp.textPanelLeft); + if(showTextpanel === true){ + showTextpanel(); + showNumbers(); + } + } + + } + + } + + + + /** + * return that current slider image is in place + */ + function isSliderImageInPlace(){ + + if(g_objSlider.isCurrentSlideType("image") == false) + return(true); + + var isImageInPlace = (g_objSlider.isCurrentImageInPlace() == true); + + return(isImageInPlace); + } + + + /** + * position the arrows inside mode + */ + function positionArrowsInside(toShow, isAnimation){ + + if(g_temp.isArrowsInside == false) + return(false); + + if(!g_objArrowLeft) + return(false); + + var isImageInPlace = isSliderImageInPlace(); + + g_objArrowLeft.show(); + g_objArrowRight.show(); + + g_temp.positionFrom = "positionArrowsInside"; + + if(g_temp.isArrowsOnHoverMode == true && isImageInPlace == true && isMouseInsideImage() == false) + hideArrows(true); + + if(isImageInPlace == false){ + var leftArrowLeft = g_functions.getElementRelativePos(g_objArrowLeft, "left", g_options.lightbox_arrows_offset); + var leftArrowTop = g_functions.getElementRelativePos(g_objArrowLeft, "middle"); + + var rightArrowLeft = g_functions.getElementRelativePos(g_objArrowRight, "right", g_options.lightbox_arrows_offset); + var rightArrowTop = leftArrowTop; + + }else{ + + var objImage = g_objSlider.getSlideImage(); + var objImageSize = g_functions.getElementSize(objImage); + var objSliderSize = g_functions.getElementSize(g_objSlider.getElement()); + + var leftArrowLeft = g_functions.getElementRelativePos(g_objArrowLeft, "left", 0, objImage) + objImageSize.left + g_options.lightbox_arrows_inside_offset; + var leftArrowTop = g_functions.getElementRelativePos(g_objArrowLeft, "middle", 0, objImage) + objImageSize.top; + var rightArrowLeft = g_functions.getElementRelativePos(g_objArrowLeft, "right", 0, objImage) + objImageSize.left - g_options.lightbox_arrows_inside_offset; + var rightArrowTop = leftArrowTop; + + } + + + //place the image with animation or not + if(isAnimation === true){ + + var objCssLeft = { + left: leftArrowLeft, + top: leftArrowTop + }; + + var objCssRight = { + left: rightArrowLeft, + top: rightArrowTop + }; + + g_objArrowLeft.stop().animate(objCssLeft,{ + duration: g_temp.fadeDuration + }); + + g_objArrowRight.stop().animate(objCssRight,{ + duration: g_temp.fadeDuration + }); + + + }else{ + g_objArrowLeft.stop(); + g_objArrowRight.stop(); + + g_functions.placeElement(g_objArrowLeft, leftArrowLeft, leftArrowTop); + g_functions.placeElement(g_objArrowRight, rightArrowLeft, rightArrowTop); + } + + + if(toShow == true) + showArrows(isAnimation); + + } + + + + /** + * position close button for compact type + */ + function positionCloseButton(toShow, isAnimation){ + + g_temp.positionFrom = null; + + var isImageInPlace = isSliderImageInPlace(); + + var minButtonTop = 2; + var maxButtonLeft = g_functions.getElementRelativePos(g_objButtonClose, "right", 2, g_objWrapper); + + if(isImageInPlace == false){ //put image to corner + + var closeButtonTop = minButtonTop; + var closeButtonLeft = maxButtonLeft; + + }else{ + var objImage = g_objSlider.getSlideImage(); + var objImageSize = g_functions.getElementSize(objImage); + var objSliderSize = g_functions.getElementSize(g_objSlider.getElement()); + var objButtonSize = g_functions.getElementSize(g_objButtonClose); + + //some strange bug + if(objSliderSize.top == objSliderSize.height) + objSliderSize.top = 0; + + var closeButtonLeft = objSliderSize.left + objImageSize.right - objButtonSize.width / 2 + g_options.lightbox_compact_closebutton_offsetx; + var closeButtonTop = objSliderSize.top + objImageSize.top - objButtonSize.height / 2 - g_options.lightbox_compact_closebutton_offsety; + + if(closeButtonTop < minButtonTop) + closeButtonTop = minButtonTop; + + if(closeButtonLeft > maxButtonLeft) + closeButtonLeft = maxButtonLeft; + + } + + //place the image with animation or not + if(isAnimation === true){ + var objCss = { + left: closeButtonLeft, + top: closeButtonTop + }; + + g_objButtonClose.stop().animate(objCss,{ + duration: g_temp.fadeDuration + }); + + }else{ + g_objButtonClose.stop(); + g_functions.placeElement(g_objButtonClose, closeButtonLeft, closeButtonTop); + } + + if(toShow === true) + showCloseButton(isAnimation); + + } + + + /** + * hide close button + */ + function hideCompactElements(){ + + if(g_objButtonClose) + g_objButtonClose.stop().fadeTo(g_temp.fadeDuration, 0); + + hideTextPanel(); + + hideNumbers(); + + g_temp.positionFrom = "hideCompactElements"; + if(g_temp.isArrowsInside == true) + hideArrows(); + } + + + /** + * actual hide all compact type elements + */ + function actualHideCompactElements(){ + + if(g_objButtonClose) + g_objButtonClose.hide(); + + if(g_objArrowLeft && g_temp.isArrowsInside == true){ + g_objArrowLeft.hide(); + g_objArrowRight.hide(); + } + + if(g_objNumbers) + g_objNumbers.hide(); + + if(g_objTextPanel) + g_objTextPanel.hide(); + + } + + + function __________COMMON_________(){}; + + + /** + * position the elements + */ + function positionElements(){ + + var size = g_functions.getElementSize(g_objWrapper); + + //position top panel: + if(g_objTopPanel) + g_functions.setElementSizeAndPosition(g_objTopPanel, 0, 0, size.width, g_temp.topPanelHeight); + + //position arrows + if(g_objArrowLeft && g_temp.isArrowsInside == false){ + + if(g_options.lightbox_hide_arrows_onvideoplay == true){ + g_objArrowLeft.show(); + g_objArrowRight.show(); + } + + g_functions.placeElement(g_objArrowLeft, "left", "middle", g_options.lightbox_arrows_offset); + g_functions.placeElement(g_objArrowRight, "right", "middle", g_options.lightbox_arrows_offset); + } + + if(g_temp.isCompact == false) + g_functions.placeElement(g_objButtonClose, "right", "top", 2, 2); + + //place text panel + if(g_objTextPanel){ + + g_temp.positionFrom = "positionElements"; + + if(g_temp.isCompact == false) + positionTextPanelWide(size); + else{ + showTextPanel(); + showNumbers(); + } + + } + + var sliderWidth = size.width; + var sliderHeight = size.height; + var sliderTop = 0; + var sliderLeft = 0; + + if(g_objSlider){ + + if(g_objTopPanel){ + var topPanelHeight = g_objTopPanel.height(); + var objOptions = { + slider_image_padding_top: topPanelHeight + }; + g_objSlider.setOptions(objOptions); + } + + g_objSlider.setSize(sliderWidth, sliderHeight); + g_objSlider.setPosition(sliderLeft, sliderTop); + } + + } + + + /** + * hide the text panel + */ + function hideTextPanel(){ + + if(g_objTextPanel) + g_objTextPanel.getElement().stop().fadeTo(g_temp.fadeDuration, 0); + + } + + + /** + * hide the numbers text + */ + function hideNumbers(){ + + if(g_objNumbers) + g_objNumbers.stop().fadeTo(g_temp.fadeDuration, 0); + } + + + /** + * is mouse inside image + */ + function isMouseInsideImage(){ + if(!g_temp.lastMouseX) + return(true); + var obj = { + pageX: g_temp.lastMouseX, + pageY: g_temp.lastMouseY + }; + + var isMouseInside = g_objSlider.isMouseInsideSlideImage(obj); + + return(isMouseInside); + } + + + /** + * hide the arrows + */ + function hideArrows(noAnimation, isForce){ + + if(!g_objArrowLeft) + return(false); + + //don't hide the arrows if mouse inside image + if(g_temp.isArrowsOnHoverMode == true && isForce === false){ + if(isMouseInsideImage() == true); + return(true); + } + + if(noAnimation === true){ + g_objArrowLeft.stop().fadeTo(0, 0); + g_objArrowRight.stop().fadeTo(0, 0); + }else{ + g_objArrowLeft.stop().fadeTo(g_temp.fadeDuration, 0); + g_objArrowRight.stop().fadeTo(g_temp.fadeDuration, 0); + } + + } + + /** + * get if the arrows are hidden + */ + function isArrowsHidden(){ + + if(!g_objArrowLeft) + return(true); + if(g_objArrowLeft.is(":visible") == false) + return(true); + + var opacity = g_objArrowLeft.css("opacity"); + if(opacity != 1) + return(true); + + return(false); + } + + /** + * show the arrows + */ + function showArrows(noStop, fromHover){ + + if(!g_objArrowLeft) + return(false); + + //don't show every time on arrowsonhover mode + if(g_temp.isArrowsOnHoverMode == true && fromHover !== true && isSliderImageInPlace() == true) + return(true); + + //don't show if swiping + if(g_objSlider.isSwiping() == true) + return(true); + + if(noStop !== true){ + g_objArrowLeft.stop(); + g_objArrowRight.stop(); + } + + g_objArrowLeft.fadeTo(g_temp.fadeDuration, 1); + g_objArrowRight.fadeTo(g_temp.fadeDuration, 1); + + } + + + + + + /** + * show close button + */ + function showCloseButton(noStop){ + + if(noStop !== true) + g_objButtonClose.stop(); + + g_objButtonClose.fadeTo(g_temp.fadeDuration, 1); + } + + + /** + * update text panel text of the curren item + */ + function updateTextPanelText(currentItem){ + + if(!g_objTextPanel) + return(false); + + if(!currentItem) + var currentItem = g_objSlider.getCurrentItem(); + + g_objTextPanel.setTextPlain(currentItem.title, currentItem.description); + } + + + /** + * update numbers text + */ + function updateNumbersText(currentItem){ + + if(!g_objNumbers) + return(false); + + if(!currentItem) + var currentItem = g_objSlider.getCurrentItem(); + + var numItems = g_gallery.getNumItems(); + var numCurrentItem = currentItem.index + 1; + g_objNumbers.html(numCurrentItem + " / " + numItems); + } + + + /** + * show the text panel + */ + function showTextPanel(){ + + if(!g_objTextPanel) + return(false); + + g_objTextPanel.getElement().show().stop().fadeTo(g_temp.fadeDuration, 1); + + } + + + /** + * Show the numbers object + */ + function showNumbers(){ + + if(g_objNumbers) + g_objNumbers.stop().fadeTo(g_temp.fadeDuration, 1); + } + + + function __________EVENTS_________(){}; + + + /** + * on start dragging slider item event. hide the elements + */ + function onSliderDragStart(){ + if(g_temp.isCompact == false) + return(true); + + hideCompactElements(); + } + + + /** + * on zoom change + * move the assets of compact to their places + */ + function onZoomChange(){ + if(g_temp.isCompact == false) + return(true); + + g_temp.positionFrom = "onZoomChange"; + + positionCloseButton(false, true); + positionArrowsInside(false, true); + + //handle compact text panel mode + if(g_temp.isCompact == true){ + var isImageInPlace = (g_objSlider.isCurrentSlideType("image") && g_objSlider.isCurrentImageInPlace() == true); + if(isImageInPlace == false){ + hideTextPanel(); + hideNumbers(); + } + else{ + g_temp.positionFrom = "onZoomChange"; + showTextPanel(); + showNumbers(); + } + } + + } + + + /** + * after return slider to it's place + * show close button + */ + function onSliderAfterReturn(){ + + if(g_temp.isCompact == false) + return(true); + + g_temp.positionFrom = "onSliderAfterReturn"; + + positionCloseButton(true); + positionArrowsInside(true); + + var isChanged = handleCompactHeight(); + if(isChanged == false) + handleCompactTextpanelSizes(); + + showTextPanel(); + showNumbers(); + } + + + /** + * after put image to the slide + * position compact elements + */ + function onSliderAfterPutImage(data, objSlide){ + + objSlide = jQuery(objSlide); + + if(g_temp.isCompact == false) + return(true); + + if(g_objSlider.isSlideCurrent(objSlide) == false) + return(true); + + g_temp.positionFrom = "onSliderAfterPutImage"; + + positionCloseButton(true); + + positionArrowsInside(true); + + handleCompactTextpanelSizes(); + } + + + /** + * on slider transition end, handle panel height + */ + function onSliderTransitionEnd(){ + + var sliderOptions = g_objSlider.getOptions(); + var imagePaddingTop = sliderOptions.slider_image_padding_top; + + //handle wide + if(g_objTopPanel){ + var panelHeight = g_objTopPanel.height(); + + if(panelHeight != imagePaddingTop) + refreshSliderItem(panelHeight); + } + + //handle compact + if(g_temp.isCompact == true){ + + updateTextPanelText(); + updateNumbersText(); + + g_temp.positionFrom = "onSliderTransitionEnd"; + + positionCloseButton(true); + positionArrowsInside(true); + + if(g_objSlider.isSlideActionActive() == false){ + var isChanged = handleCompactHeight(); + if(isChanged == false) + handleCompactTextpanelSizes(); + } + + showTextPanel(); + showNumbers(); + + } + + } + + + /** + * on item change + * update numbers text and text panel text/position + */ + function onItemChange(data, currentItem){ + + if(g_temp.isCompact == false){ //wide mode + + if(g_objNumbers) + updateNumbersText(currentItem); + + if(g_objTextPanel){ + updateTextPanelText(currentItem); + + //update panel height only if the lightbox is already opened, and the items changed within it. + if(g_temp.isRightNowOpened == false){ + g_objTextPanel.positionElements(false); + handlePanelHeight("onchange"); + g_objTextPanel.positionPanel(); + } + + } + + }else{ + + if(g_objSlider.isAnimating() == false){ + + if(g_objTextPanel) + updateTextPanelText(currentItem); + + if(g_objNumbers) + updateNumbersText(currentItem); + } + + } + + + //trigger lightbox init event + if(g_temp.isSliderChangedOnce == false){ + g_temp.isSliderChangedOnce = true; + g_objThis.trigger(t.events.LIGHTBOX_INIT); + } + + } + + + /** + * on slider click + */ + function onSliderClick(data, event){ + + var slideType = g_objSlider.getSlideType(); + if(slideType != "image" && g_temp.isCompact == false && g_objSlider.isSlideActionActive() ) + return(true); + + var isPreloading = g_objSlider.isPreloading(); + if(isPreloading == true){ + t.close("slider"); + return(true); + } + + //close the lightbox on empty space click + if(g_options.lightbox_close_on_emptyspace == true){ + + var isInside = g_objSlider.isMouseInsideSlideImage(event); + + if(isInside == false) + t.close("slider_inside"); + } + + } + + + /** + * on lightbox resize + */ + function onResize(){ + + positionElements(); + } + + + + /** + * on start play - hide the side buttons + */ + function onPlayVideo(){ + + if(g_objTopPanel){ + hideTopPanel(); + }else{ + if(g_objNumbers) + g_objNumbers.hide(); + } + + if(g_objArrowLeft && g_options.lightbox_hide_arrows_onvideoplay == true){ + g_objArrowLeft.hide(); + g_objArrowRight.hide(); + } + + } + + + /** + * on stop video - show the side buttons + */ + function onStopVideo(){ + + if(g_objTopPanel){ + showTopPanel(); + handlePanelHeight("onStopVideo"); + }else{ + + if(g_objNumbers) + g_objNumbers.show(); + } + + if(g_objArrowLeft && g_options.lightbox_hide_arrows_onvideoplay == true){ + g_objArrowLeft.show(); + g_objArrowRight.show(); + } + + } + + /** + * on gallery keypres, do operations + */ + function onKeyPress(data, key, event){ + + var isScrollKey = false; + + switch(key){ + case 27: //escape - close lightbox + if(g_temp.isOpened == true) + t.close("keypress"); + break; + case 38: //up and down arrows + case 40: + case 33: //page up and down + case 34: + isScrollKey = true; + break; + } + + if(g_temp.isOpened == true && isScrollKey == true) + event.preventDefault(); + + + } + + /** + * on image mouse enter event + */ + function onImageMouseEnter(){ + + if(g_temp.isArrowsOnHoverMode == true) + showArrows(false, true); + + } + + /** + * on image mouse leave + */ + function onImageMouseLeave(event){ + + g_temp.positionFrom = "hideCompactElements"; + + if(g_temp.isArrowsOnHoverMode == true && isSliderImageInPlace() == true) + hideArrows(false, true); + + } + + + /** + * on mouse move event + * show arrows if inside image + */ + function onMouseMove(event){ + g_temp.lastMouseX = event.pageX; + g_temp.lastMouseY = event.pageY; + + var isHidden = isArrowsHidden() + + + if(isHidden == true && isMouseInsideImage() && g_objSlider.isAnimating() == false){ + g_temp.positionFrom = "onMouseMove"; + if(g_objArrowLeft && g_objArrowLeft.is(":animated") == false) + showArrows(false, true); + } + + } + + + /** + * on mouse wheel + */ + function onMouseWheel(event, delta, deltaX, deltaY){ + + if(g_temp.isOpened == false) + return(true); + + switch(g_options.gallery_mousewheel_role){ + default: + case "zoom": + var slideType = g_objSlider.getSlideType(); + if(slideType != "image") + event.preventDefault(); + break; + case "none": + event.preventDefault(); + break; + case "advance": + g_gallery.onGalleryMouseWheel(event, delta, deltaX, deltaY); + break; + } + + } + + + /** + * init events + */ + function initEvents(){ + + g_objOverlay.on("touchstart", function(event){ + event.preventDefault(); + }); + + g_objOverlay.on("touchend", function(event){ + t.close("overlay"); + }); + + + g_functions.addClassOnHover(g_objArrowRight, "ug-arrow-hover"); + g_functions.addClassOnHover(g_objArrowLeft, "ug-arrow-hover"); + + g_functions.addClassOnHover(g_objButtonClose); + + g_gallery.setNextButton(g_objArrowRight); + g_gallery.setPrevButton(g_objArrowLeft); + + g_objButtonClose.click(function(){ + t.close("button"); + }); + + g_objGallery.on(g_gallery.events.ITEM_CHANGE, onItemChange); + + if(g_objSlider){ + jQuery(g_objSlider).on(g_objSlider.events.TRANSITION_END, onSliderTransitionEnd); + + //on slider click event + jQuery(g_objSlider).on(g_objSlider.events.CLICK, onSliderClick); + + //on slider video + var objVideo = g_objSlider.getVideoObject(); + + jQuery(objVideo).on(objVideo.events.PLAY_START, onPlayVideo); + jQuery(objVideo).on(objVideo.events.PLAY_STOP, onStopVideo); + + //handle close button hide / appear + jQuery(g_objSlider).on(g_objSlider.events.START_DRAG, onSliderDragStart); + jQuery(g_objSlider).on(g_objSlider.events.TRANSITION_START, onSliderDragStart); + + jQuery(g_objSlider).on(g_objSlider.events.AFTER_DRAG_CHANGE, onSliderAfterReturn); + jQuery(g_objSlider).on(g_objSlider.events.AFTER_RETURN, onSliderAfterReturn); + jQuery(g_objSlider).on(g_objSlider.events.AFTER_PUT_IMAGE, onSliderAfterPutImage); + + jQuery(g_objSlider).on(g_objSlider.events.ZOOM_CHANGE, onZoomChange); + + jQuery(g_objSlider).on(g_objSlider.events.IMAGE_MOUSEENTER, onImageMouseEnter); + jQuery(g_objSlider).on(g_objSlider.events.IMAGE_MOUSELEAVE, onImageMouseLeave); + + } + + //on resize + jQuery(window).resize(function(){ + + if(g_temp.isOpened == false) + return(true); + + g_functions.whenContiniousEventOver("lightbox_resize", onResize, 100); + }); + + g_objGallery.on(g_gallery.events.GALLERY_KEYPRESS, onKeyPress); + + //store last mouse x and y + if(g_temp.isArrowsOnHoverMode == true){ + + jQuery(document).bind('mousemove', onMouseMove); + + } + + //on mouse wheel - disable functionality if video + g_objWrapper.on("mousewheel", onMouseWheel); + + } + + + /** + * destroy the lightbox events and the html it created + */ + this.destroy = function(){ + + jQuery(document).unbind("mousemove"); + + g_objOverlay.off("touchstart"); + g_objOverlay.off("touchend"); + g_objButtonClose.off("click"); + g_objGallery.off(g_gallery.events.ITEM_CHANGE); + + if(g_objSlider){ + jQuery(g_objSlider).off(g_objSlider.events.TRANSITION_END); + jQuery(g_objSlider).off(g_objSlider.events.CLICK); + jQuery(g_objSlider).off(g_objSlider.events.START_DRAG); + jQuery(g_objSlider).off(g_objSlider.events.TRANSITION_START); + jQuery(g_objSlider).off(g_objSlider.events.AFTER_DRAG_CHANGE); + jQuery(g_objSlider).off(g_objSlider.events.AFTER_RETURN); + + var objVideo = g_objSlider.getVideoObject(); + jQuery(objVideo).off(objVideo.events.PLAY_START); + jQuery(objVideo).off(objVideo.events.PLAY_STOP); + + jQuery(g_objSlider).on(g_objSlider.events.IMAGE_MOUSEENTER, onImageMouseEnter); + jQuery(g_objSlider).on(g_objSlider.events.IMAGE_MOUSELEAVE, onImageMouseLeave); + + g_objSlider.destroy(); + } + + jQuery(window).unbind("resize"); + g_objGallery.off(g_gallery.events.GALLERY_KEYPRESS, onKeyPress); + + g_objWrapper.off("mousewheel"); + + //remove the html + g_objWrapper.remove(); + } + + + /** + * open the lightbox with some item index + */ + this.open = function(index){ + + var objItem = g_gallery.getItem(index); + + g_temp.isOpened = true; + + //set if the panel right now opened + g_temp.isRightNowOpened = true; + setTimeout(function(){g_temp.isRightNowOpened = false},100); + + if(g_objSlider){ + g_objSlider.setItem(objItem, "lightbox_open"); + } + + if(g_objTextPanel){ + g_objTextPanel.setTextPlain(objItem.title, objItem.description); + } + + g_objOverlay.stop().fadeTo(0,0); + g_objWrapper.show(); + g_objWrapper.fadeTo(0,1); + + //show the overlay + g_objOverlay.stop().fadeTo(g_temp.fadeDuration, g_options.lightbox_overlay_opacity); + + positionElements(); + + if(g_temp.isCompact == true){ + + var isPreloading = g_objSlider.isPreloading(); + if(isPreloading == true){ + + actualHideCompactElements(); + + }else{ + + //hide only arrows if they are inside + if(g_temp.isArrowsInside == true){ + g_objArrowLeft.hide(); + g_objArrowRight.hide(); + } + + } + + } + + if(g_objSlider) + g_objSlider.startSlideAction(); + + //trigger gallery event + g_objGallery.trigger(g_gallery.events.OPEN_LIGHTBOX, objItem); + + } + + + /** + * close the lightbox + */ + this.close = function(fromWhere){ + + g_temp.isOpened = false; + + if(g_temp.isCompact == true) + hideCompactElements(); + + if(g_objSlider) + g_objSlider.stopSlideAction(); + + var slideType = g_objSlider.getSlideType(); + + if(slideType != "image") + g_objWrapper.hide(); + else{ + g_objWrapper.fadeTo(g_temp.fadeDuration,0,function(){ + g_objWrapper.hide(); + }); + } + + g_objGallery.trigger(g_gallery.events.CLOSE_LIGHTBOX); + + } + + + /** + * external init function + */ + this.init = function(gallery, customOptions){ + + initLightbox(gallery, customOptions); + } + + + /** + * switch to wide mode from compact mode + */ + function switchToWide(){ + g_temp.isCompact = false; + modifyOptions(); + + g_temp.isArrowsInside = false; + g_temp.isArrowsOnHoverMode = false; + + g_options = jQuery.extend({}, g_temp.originalOptions); + + g_options.lightbox_arrows_position = "sides"; + + g_objSlider.setOptions(g_options); + } + + + /** + * external put html function + */ + this.putHtml = function(){ + + //check if switch to wide mode + var isSmallWindow = g_gallery.isSmallWindow(); + + if(isSmallWindow && g_temp.isCompact == true) + switchToWide(); + + putLightboxHtml(); + } + + + /** + * run lightbox elements + */ + this.run = function(){ + + setProperties(); + + if(g_objSlider) + g_objSlider.run(); + + initEvents(); + } + + + +} + + + +/** + * carousel class + */ +function UGCarousel(){ + + var t = this, g_objThis = jQuery(this); + var g_gallery = new UniteGalleryMain(), g_objGallery, g_objWrapper; + var g_functions = new UGFunctions(), g_arrItems, g_objTileDesign = new UGTileDesign(); + var g_thumbs = new UGThumbsGeneral(), g_objCarouselWrapper, g_objInner, arrOrigTiles = []; + + var g_options = { + carousel_padding: 8, //padding at the sides of the carousel + carousel_space_between_tiles: 20, //space between tiles + carousel_navigation_numtiles:3, //number of tiles to scroll when user clicks on next/prev button + carousel_scroll_duration:500, //duration of scrolling to tile + carousel_scroll_easing:"easeOutCubic", //easing of scrolling to tile animation + + carousel_autoplay: true, //true,false - autoplay of the carousel on start + carousel_autoplay_timeout: 3000, //autoplay timeout + carousel_autoplay_direction: "right", //left,right - autoplay direction + carousel_autoplay_pause_onhover: true, //pause the autoplay on mouse over + carousel_vertical_scroll_ondrag: false //vertical screen scroll on carousel drag + }; + + this.events = { + START_PLAY: "carousel_start_play", + PAUSE_PLAY: "carousel_pause_play", + STOP_PLAY: "carousel_stop_play" + }; + + var g_temp = { + eventSizeChange: "thumb_size_change", + isFirstTimeRun:true, //if run once + carouselMaxWidth: null, + tileWidth:0, + initTileWidth:0, + initTileHeight:0, + sideSpace:1500, //the space that must be filled with items + spaceActionSize:500, + numCurrent:0, + touchActive: false, + startInnerPos: 0, + lastTime:0, + startTime:0, + startMousePos:0, + lastMousePos:0, + scrollShortDuration: 200, + scrollShortEasing: "easeOutQuad", + handle:null, + isPlayMode: false, + isPaused: false, + storedEventID: "carousel" + }; + + + function __________GENERAL_________(){}; + + /** + * init the gallery + */ + function init(gallery, customOptions){ + + g_objects = gallery.getObjects(); + g_gallery = gallery; + g_objGallery = jQuery(gallery); + g_objWrapper = g_objects.g_objWrapper; + g_arrItems = g_objects.g_arrItems; + + g_options = jQuery.extend(g_options, customOptions); + + g_objTileDesign.setFixedMode(); + g_objTileDesign.setApproveClickFunction(isApproveTileClick); + g_objTileDesign.init(gallery, g_options); + + g_thumbs = g_objTileDesign.getObjThumbs(); + g_options = g_objTileDesign.getOptions(); + + g_temp.initTileWidth = g_options.tile_width; + g_temp.initTileHeight = g_options.tile_height; + + g_temp.tileWidth = g_options.tile_width; + } + + + /** + * set the grid panel html + */ + function setHtml(objParent){ + + if(!objParent) + var objParent = g_objWrapper; + + var html = ""; + g_objWrapper.append(html); + + g_objCarouselWrapper = g_objWrapper.children(".ug-carousel-wrapper"); + g_objInner = g_objCarouselWrapper.children(".ug-carousel-inner"); + + g_objTileDesign.setHtml(g_objInner); + + g_thumbs.getThumbs().fadeTo(0,1); + + } + + + /** + * resize tiles to new width / height + */ + function resizeTiles(newTileWidth, newTileHeight){ + + if(!newTileHeight){ + + var newTileHeight = g_temp.initTileHeight / g_temp.initTileWidth * newTileWidth; + + } + + g_temp.tileWidth = newTileWidth; + + //update all options + var optUpdate = { + tile_width: newTileWidth, + tile_height: newTileHeight + }; + + g_objTileDesign.setOptions(optUpdate); + + g_options.tile_width = newTileWidth; + g_options.tile_height = newTileHeight; + + //resize all tiles + g_objTileDesign.resizeAllTiles(newTileWidth); + + //reposition tiles + positionTiles(true); //must to position tiles right after size change, for inner size change + } + + /** + * run the gallery after init and set html + */ + function run(){ + + //validation + if(g_temp.carouselMaxWidth === null){ + throw new Error("The carousel width not set"); + return(false); + } + + //if the size changed, change it anyway + if(g_temp.tileWidth < g_temp.initTileWidth){ + + var newTileWidth = g_temp.carouselMaxWidth - g_options.carousel_padding * 2; + if(newTileWidth > g_temp.initTileWidth) + newTileWidth = g_temp.initTileWidth; + + resizeTiles(newTileWidth); + + var numTiles = g_functions.getNumItemsInSpace(g_temp.carouselMaxWidth, newTileWidth, g_options.carousel_space_between_tiles); + + }else{ + + //check if need to lower tiles size + var numTiles = g_functions.getNumItemsInSpace(g_temp.carouselMaxWidth, g_temp.tileWidth, g_options.carousel_space_between_tiles); + + //if no tiles fit, resize the tiles + if(numTiles <= 0){ + numTiles = 1; + + var newTileWidth = g_temp.carouselMaxWidth - g_options.carousel_padding * 2; + + resizeTiles(newTileWidth); + } + + } + + //set wrapper width + var realWidth = g_functions.getSpaceByNumItems(numTiles, g_temp.tileWidth, g_options.carousel_space_between_tiles); + realWidth += g_options.carousel_padding * 2; + + g_objCarouselWrapper.width(realWidth); + + if(g_temp.isFirstTimeRun == true){ + + initEvents(); + + g_objTileDesign.run(); + + //set data indexes to tiles + jQuery.each(g_arrItems, function(index, item){ + item.objThumbWrapper.data("index", index); + + g_objWrapper.trigger(g_temp.eventSizeChange, [item.objThumbWrapper,true]); + item.objTileOriginal = item.objThumbWrapper.clone(true, true); + }); + + positionTiles(true); //set heights at first time + + if(g_options.carousel_autoplay == true) + t.startAutoplay(); + }else{ + + if(g_options.carousel_autoplay == true) + t.pauseAutoplay(); + + scrollToTile(0, false); + + if(g_options.carousel_autoplay == true) + t.startAutoplay(); + } + + positionElements(); + + g_temp.isFirstTimeRun = false; + } + + + + function __________GETTERS_______(){}; + + /** + * get inner position + */ + function getInnerPos(){ + return g_functions.getElementSize(g_objInner).left; + } + + /** + * get mouse position + */ + function getMousePos(event){ + return g_functions.getMousePosition(event).pageX; + } + + + /** + * get all tiles + */ + function getTiles(){ + + var objTiles = g_objInner.children(".ug-thumb-wrapper"); + + return(objTiles); + } + + + /** + * get num tiles in some space + */ + function getNumTilesInSpace(space){ + + var numItems = g_functions.getNumItemsInSpace(space, g_temp.tileWidth, g_options.carousel_space_between_tiles) + + return(numItems); + } + + + /** + * get num tiles + */ + function getNumTiles(){ + return getTiles().length; + } + + + /** + * get tile + */ + function getTile(numTile){ + validateTileNum(numTile); + var objTiles = getTiles(); + var objTile = jQuery(objTiles[numTile]); + + return(objTile); + } + + + /** + * get first tile in the inner object + */ + function getFirstTile(){ + + return g_objInner.children(".ug-thumb-wrapper").first(); + } + + /** + * get last tile in the inner object + */ + function getLastTile(){ + + return g_objInner.children(".ug-thumb-wrapper").last(); + } + + + + /** + * get clone of the time next or prev the given + */ + function getTileClones(objTile, numClones, dir){ + + var index = objTile.data("index"); + if(index == undefined){ + throw new Error("every tile should have index!"); + } + + var arrClonedItems = []; + + for(var i=0;i (getTiles().length-1)) + throw new Error("Wrogn tile number: " + numTile); + } + + + /** + * add tile to left + */ + function addTiles(numTiles, dir){ + + if(dir == "left") + var anchorTile = getFirstTile(); + else + var anchorTile = getLastTile(); + + var clonesType = (dir == "left")?"prev":"next"; + + var arrNewTiles = getTileClones(anchorTile, numTiles, clonesType); + + jQuery.each(arrNewTiles, function(index, objTile){ + + if(dir == "left") + g_objInner.prepend(objTile); + else + g_objInner.append(objTile); + + g_objWrapper.trigger(g_temp.eventSizeChange, objTile); + g_objTileDesign.loadTileImage(objTile); + + }); + + + } + + /** + * remove some tiles + */ + function removeTiles(numTilesToRemove, direction){ + + validateTileNum(numTiles); + + var arrTiles = getTiles(); + var numTiles = arrTiles.length; + + for(var i=0; i g_temp.spaceActionSize){ + numItemsLeft = getNumTilesInSpace(spaceLeft); + addTiles(numItemsLeft, "left"); + + g_temp.numCurrent += numItemsLeft; + + }else if(spaceLeft < -g_temp.spaceActionSize){ + var numItemsRemoveLeft = getNumTilesInSpace(Math.abs(spaceLeft)); + removeTiles(numItemsRemoveLeft, "left"); + g_temp.numCurrent -= numItemsRemoveLeft; + } + + //add tiles to right + if(spaceRight > g_temp.spaceActionSize){ + numItemsRight = getNumTilesInSpace(spaceRight); + addTiles(numItemsRight, "right"); + }else if(spaceRight < -g_temp.spaceActionSize){ + numItemsRemoveRight = getNumTilesInSpace(Math.abs(spaceRight)); + removeTiles(numItemsRemoveRight, "right"); + } + + + //small validation + if(numItemsRemoveRight > numTiles){ + + throw new Error("Can't remove more then num tiles"); + } + + //trace(numItemsRemoveRight); + //trace("numItems: " + getNumTiles()); + + //scroll to tile and position inner object + var isPositioned = false; + if(numItemsLeft || numItemsRight || numItemsRemoveLeft || numItemsRemoveRight){ + + /* + debugLine({ + numItemsLeft:numItemsLeft, + numItemsRight:numItemsRight, + numItemsRemoveLeft:numItemsRemoveLeft, + numItemsRemoveRight: numItemsRemoveRight + }); + */ + //trace("do something"); + + positionTiles(); + isPositioned = true + } + + return(isPositioned); + } + + + /** + * position tiles + */ + function positionElements(isFirstTime){ + + //position inner strip + g_functions.placeElement(g_objInner, 0, g_options.carousel_padding); + + //position sides + fillSidesWithTiles(); + + } + + + + function __________AUTOPLAY_______(){}; + + /** + * start autoplay + */ + this.startAutoplay = function(){ + + g_temp.isPlayMode = true; + g_temp.isPaused = false; + + g_objThis.trigger(t.events.START_PLAY); + + if(g_temp.handle) + clearInterval(g_temp.handle); + + g_temp.handle = setInterval(autoplayStep, g_options.carousel_autoplay_timeout); + + } + + + /** + * unpause autoplay after pause + */ + this.unpauseAutoplay = function(){ + + if(g_temp.isPlayMode == false) + return(true); + + if(g_temp.isPaused == false) + return(true); + + t.startAutoplay(); + } + + + /** + * pause the autoplay + */ + this.pauseAutoplay = function(){ + + if(g_temp.isPlayMode == false) + return(true); + + g_temp.isPaused = true; + + if(g_temp.handle) + clearInterval(g_temp.handle); + + g_objThis.trigger(t.events.PAUSE_PLAY); + + } + + + /** + * stop autoplay + */ + this.stopAutoplay = function(){ + + if(g_temp.isPlayMode == false) + return(true); + + g_temp.isPaused = false; + g_temp.isPlayMode = false; + + if(g_temp.handle) + clearInterval(g_temp.handle); + + g_objThis.trigger(t.events.STOP_PLAY); + } + + + /** + * autoplay step, advance the carousel by 1 + */ + function autoplayStep(){ + + if(g_options.carousel_autoplay_direction == "left"){ + t.scrollRight(1); + }else{ + t.scrollLeft(1); + } + + } + + function __________EVENTS_______(){}; + + + /** + * on touch start + */ + function onTouchStart(event){ + + if(g_temp.touchActive == true) + return(true); + + g_temp.touchActive = true; + + t.pauseAutoplay(); + + g_temp.startTime = jQuery.now(); + g_temp.startMousePos = getMousePos(event); + g_temp.startInnerPos = getInnerPos(); + g_temp.lastTime = g_temp.startTime; + g_temp.lastMousePos = g_temp.startMousePos; + + g_functions.storeEventData(event, g_temp.storedEventID); + } + + + /** + * on touch move + */ + function onTouchMove(event){ + + if(g_temp.touchActive == false) + return(true); + + g_functions.updateStoredEventData(event, g_temp.storedEventID); + + event.preventDefault(); + + var scrollDir = null; + + if(g_options.carousel_vertical_scroll_ondrag == true) + scrollDir = g_functions.handleScrollTop(g_temp.storedEventID); + + if(scrollDir === "vert") + return(true); + + g_temp.lastMousePos = getMousePos(event); + + var diff = g_temp.lastMousePos - g_temp.startMousePos; + var innerPos = g_temp.startInnerPos + diff; + var direction = (diff > 0) ? "prev":"next"; + var innerSize = g_functions.getElementSize(g_objInner).width; + + //slow down when off limits + if(innerPos > 0 && direction == "prev"){ + innerPos = innerPos / 3; + } + + if(innerPos < -innerSize && direction == "next"){ + innerPos = g_temp.startInnerPos + diff / 3; + } + + setInnerPos(innerPos); + } + + + /** + * on touch end + * change panes or return to current pane + */ + function onTouchEnd(event){ + + if(g_temp.touchActive == false) + return(true); + + //event.preventDefault(); + g_temp.touchActive = false; + + scrollToNeerestTile(); + + t.unpauseAutoplay(); + } + + /** + * pause the playing + */ + function onMouseEnter(event){ + + if(g_options.carousel_autoplay_pause_onhover == false) + return(true); + + if(g_temp.isPlayMode == true && g_temp.isPaused == false) + t.pauseAutoplay(); + } + + /** + * start the playing again + */ + function onMouseLeave(event){ + + if(g_options.carousel_autoplay_pause_onhover == false) + return(true); + + t.unpauseAutoplay(); + } + + + /** + * init panel events + */ + function initEvents(){ + + g_objTileDesign.initEvents(); + + //touch drag events + //slider mouse down - drag start + g_objCarouselWrapper.bind("mousedown touchstart",onTouchStart); + + //on body move + jQuery("body").bind("mousemove touchmove",onTouchMove); + + //on body mouse up - drag end + jQuery(window).add("body").bind("mouseup touchend", onTouchEnd); + + g_objCarouselWrapper.hover(onMouseEnter, onMouseLeave); + + } + + + /** + * destroy the carousel events + */ + this.destroy = function(){ + + if(g_temp.handle) + clearInterval(g_temp.handle); + + g_objThis.off(t.events.START_PLAY); + g_objThis.off(t.events.STOP_PLAY); + + //touch drag events + //slider mouse down - drag start + g_objCarouselWrapper.unbind("mousedown"); + g_objCarouselWrapper.unbind("touchstart"); + + //on body move + jQuery("body").unbind("mousemove"); + jQuery("body").unbind("touchmove"); + + //on body mouse up - drag end + jQuery(window).add("body").unbind("mouseup").unbind("touchend"); + + g_objCarouselWrapper.off("mouseenter").off("mouseleave"); + + g_objTileDesign.destroy(); + } + + + /** + * init function for avia controls + */ + this.init = function(gallery, customOptions, width){ + if(width) + this.setMaxWidth(width); + + init(gallery, customOptions); + } + + + /** + * set the width + */ + this.setMaxWidth = function(width){ + + g_temp.carouselMaxWidth = width; + } + + + /** + * set html + */ + this.setHtml = function(objParent){ + setHtml(objParent); + } + + /** + * get the carousel element + */ + this.getElement = function(){ + return g_objCarouselWrapper; + } + + /** + * get tile design object + */ + this.getObjTileDesign = function(){ + return(g_objTileDesign); + } + + + /** + * get estimated height + */ + this.getEstimatedHeight = function(){ + var height = g_options.tile_height + g_options.carousel_padding * 2; + return(height); + } + + + /** + * set html and properties + */ + this.run = function(){ + run(); + } + + + /** + * scroll to right + */ + this.scrollRight = function(tilesToScroll){ + + if(!tilesToScroll || typeof tilesToScroll == "object") + var tilesToScroll = g_options.carousel_navigation_numtiles; + + var numTilesInCarousel = getNumTilesInCarousel(); + if(tilesToScroll > numTilesInCarousel) + tilesToScroll = numTilesInCarousel; + + var numPrev = g_temp.numCurrent - tilesToScroll; + if(numPrev <=0) + numPrev = 0; + + scrollToTile(numPrev); + } + + + /** + * scroll to left + */ + this.scrollLeft = function(tilesToScroll){ + + if(!tilesToScroll || typeof tilesToScroll == "object") + var tilesToScroll = g_options.carousel_navigation_numtiles; + + var numTilesInCarousel = getNumTilesInCarousel(); + if(tilesToScroll > numTilesInCarousel) + tilesToScroll = numTilesInCarousel; + + var numTiles = getNumTiles(); + + var numNext = g_temp.numCurrent + tilesToScroll; + if(numNext >= numTiles) + numNext = numTiles-1; + + scrollToTile(numNext); + } + + /** + * set scroll left button + */ + this.setScrollLeftButton = function(objButton){ + g_functions.setButtonMobileReady(objButton); + g_functions.setButtonOnClick(objButton, t.scrollLeft); + } + + + /** + * set scroll right button + */ + this.setScrollRightButton = function(objButton){ + g_functions.setButtonMobileReady(objButton); + g_functions.setButtonOnClick(objButton, t.scrollRight); + } + + + /** + * set scroll right button + */ + this.setPlayPauseButton = function(objButton){ + g_functions.setButtonMobileReady(objButton); + + if(g_temp.isPlayMode == true && g_temp.isPaused == false){ + objButton.addClass("ug-pause-icon"); + } + + g_objThis.on(t.events.START_PLAY, function(){ + objButton.addClass("ug-pause-icon"); + }); + + g_objThis.on(t.events.STOP_PLAY, function(){ + objButton.removeClass("ug-pause-icon"); + }); + + g_functions.setButtonOnClick(objButton, function(){ + + if(g_temp.isPlayMode == false || g_temp.isPaused == true) + t.startAutoplay(); + else + t.stopAutoplay(); + + }); + } + + + /** + * return if passed some significant movement + */ + function isApproveTileClick(){ + + var passedTime = g_temp.lastTime - g_temp.startTime; + var passedDistanceAbs = Math.abs(g_temp.lastMousePos - g_temp.startMousePos); + + if(passedTime > 300) + return(false); + + if(passedDistanceAbs > 30) + return(false); + + return(true); + } + + +} + + + +/** + tabs panel class addon to unite gallery + */ +function UGTabs(){ + + var t = this, g_objThis = jQuery(this),g_objGallery; + var g_gallery = new UniteGalleryMain(), g_functions = new UGFunctions(); + var g_objTabs, g_objSelect; + + + var g_options = { + tabs_type:"tabs", //tabs type: tabs, select + tabs_container: "#ug_tabs", //tabs container + tabs_class_selected: "ug-tab-selected" + }; + + this.events = { + + }; + + + /** + * init tabs function + */ + function initTabs(gallery, customOptions){ + g_gallery = gallery; + + g_objGallery = jQuery(g_gallery); + + g_options = jQuery.extend(g_options, customOptions); + + if(g_options.tabs_type == "select") + g_objSelect = jQuery(g_options.tabs_container); + else + g_objTabs = jQuery(g_options.tabs_container + " .ug-tab"); + + } + + + + /** + * run the tabs + */ + function runTabs(){ + + initEvents(); + } + + + /** + * request new gallery items + */ + function requestGalleryItems(catid){ + + g_gallery.requestNewItems(catid); + + } + + + /** + * on tab click + */ + function onTabClick(){ + + var classSelected = g_options.tabs_class_selected; + + var objTab = jQuery(this); + if(objTab.hasClass(classSelected)) + return(true); + + g_objTabs.not(objTab).removeClass(classSelected); + objTab.addClass(classSelected); + + var catID = objTab.data("catid"); + if(!catID) + return(true); + + requestGalleryItems(catID); + + } + + + /** + * on select change + */ + function onSelectChange(){ + var objSelect = jQuery(this); + var catID = objSelect.val(); + + if(!catID) + return(true); + + requestGalleryItems(catID); + } + + + /** + * init tabs events + */ + function initEvents(){ + + if(g_options.tabs_type == "select") + g_objSelect.change(onSelectChange); + else + g_objTabs.click(onTabClick); + } + + /** + * destroy + */ + this.destroy = function(){ + + if(g_objSelect) + g_objSelect.off("change"); + + if(g_objTabs) + g_objTabs.off("click"); + } + + + /** + * outer init function, move to inner init + */ + this.init = function(gallery, customOptions){ + initTabs(gallery, customOptions); + } + + + /** + * run the tabs + */ + this.run = function(){ + + runTabs(); + } + + +} + + +/** + * API Class + * addon to Unite gallery + */ +function UG_API(gallery){ + + var t = this, g_objThis = jQuery(t); + var g_gallery = new UniteGalleryMain(), g_objGallery; + var g_arrEvents = []; + + g_gallery = gallery; + g_objGallery = jQuery(gallery); + + + this.events = { + API_INIT_FUNCTIONS:"api_init", + API_ON_EVENT:"api_on_event" + } + + + /** + * get item data for output + */ + function convertItemDataForOutput(item){ + + var output = { + index: item.index, + title: item.title, + description: item.description, + urlImage: item.urlImage, + urlThumb: item.urlThumb + }; + + //add aditional variables to the output + var addData = item.objThumbImage.data(); + + for(var key in addData){ + switch(key){ + case "image": + case "description": + continue; + break; + } + output[key] = addData[key]; + } + + return(output); + } + + + /** + * event handling function + */ + this.on = function(event, handlerFunction, notCache){ + + //remember cache + if(notCache !== true){ + g_arrEvents.push({event:event,func:handlerFunction}); + } + + switch(event){ + case "item_change": + + g_objGallery.on(g_gallery.events.ITEM_CHANGE, function(){ + var currentItem = g_gallery.getSelectedItem(); + var output = convertItemDataForOutput(currentItem); + handlerFunction(output.index, output); + }); + + break; + case "resize": + g_objGallery.on(g_gallery.events.SIZE_CHANGE, handlerFunction); + break; + case "enter_fullscreen": + g_objGallery.on(g_gallery.events.ENTER_FULLSCREEN, handlerFunction); + break; + case "exit_fullscreen": + g_objGallery.on(g_gallery.events.EXIT_FULLSCREEN, handlerFunction); + break; + case "play": + g_objGallery.on(g_gallery.events.START_PLAY, handlerFunction); + break; + case "stop": + g_objGallery.on(g_gallery.events.STOP_PLAY, handlerFunction); + break; + case "pause": + g_objGallery.on(g_gallery.events.PAUSE_PLAYING, handlerFunction); + break; + case "continue": + g_objGallery.on(g_gallery.events.CONTINUE_PLAYING, handlerFunction); + break; + case "open_lightbox": + g_objGallery.on(g_gallery.events.OPEN_LIGHTBOX, handlerFunction); + break; + case "close_lightbox": + g_objGallery.on(g_gallery.events.CLOSE_LIGHTBOX, handlerFunction); + break; + default: + if(console) + console.log("wrong api event: " + event); + break; + } + + g_objGallery.trigger(t.events.API_ON_EVENT, [event, handlerFunction]); + } + + + /** + * start playing + */ + this.play = function(){ + g_gallery.startPlayMode(); + } + + + /** + * stop playing + */ + this.stop = function(){ + g_gallery.stopPlayMode(); + } + + + /** + * toggle playing + */ + this.togglePlay = function(){ + g_gallery.togglePlayMode(); + } + + + /** + * enter fullscreen + */ + this.enterFullscreen = function(){ + g_gallery.toFullScreen(); + } + + /** + * exit fullscreen + */ + this.exitFullscreen = function(){ + g_gallery.exitFullScreen(); + } + + /** + * toggle fullscreen + */ + this.toggleFullscreen = function(){ + + g_gallery.toggleFullscreen(); + } + + + /** + * reset zoom + */ + this.resetZoom = function(){ + var objSlider = g_gallery.getObjSlider(); + if(!objSlider) + return(false); + + objSlider.zoomBack(); + } + + + /** + * zoom in + */ + this.zoomIn = function(){ + + var objSlider = g_gallery.getObjSlider(); + if(!objSlider) + return(false); + + objSlider.zoomIn(); + } + + /** + * zoom in + */ + this.zoomOut = function(){ + + var objSlider = g_gallery.getObjSlider(); + if(!objSlider) + return(false); + + objSlider.zoomOut(); + } + + /** + * next item + */ + this.nextItem = function(){ + g_gallery.nextItem(); + } + + + /** + * prev item + */ + this.prevItem = function(){ + g_gallery.prevItem(); + } + + /** + * go to some item by index (0-numItems) + */ + this.selectItem = function(numItem){ + + g_gallery.selectItem(numItem); + + } + + + /** + * resize the gallery to some width (height). + */ + this.resize = function(width, height){ + + if(height) + g_gallery.resize(width, height); + else + g_gallery.resize(width); + } + + + /** + * get some item by index + */ + this.getItem = function(numItem){ + + var data = g_gallery.getItem(numItem); + var output = convertItemDataForOutput(data); + + return(output); + } + + + /** + * get number of items in the gallery + */ + this.getNumItems = function(){ + var numItems = g_gallery.getNumItems(); + return(numItems); + } + + /** + * refresh gallery with another options + */ + this.reloadGallery = function(customOptions){ + if(!customOptions) + var customOptions = {}; + + g_gallery.run(null, customOptions); + + //restore events: + g_arrEvents.map(function(obj){ + t.on(obj.event,obj.func,true); + }); + + } + + + /** + * destroy the gallery + */ + this.destroy = function(){ + g_gallery.destroy(); + } + + //trigger api on init event + g_objGallery.trigger(t.events.API_INIT_FUNCTIONS, t); + +} + +/** + loadmore panel class addon to unite gallery + */ +function UGLoadMore(){ + + var t = this, g_objThis = jQuery(this),g_objGallery; + var g_gallery = new UniteGalleryMain(), g_functions = new UGFunctions(); + var g_objWrapper, g_objButton, g_objLoader, g_objError; + + var g_temp = { + isInited:false + } + + var g_options = { + loadmore_container: "ug_loadmore_wrapper" //tabs container + }; + + + this.events = { + + }; + + + /** + * init wrapper + */ + function initObjects(){ + + g_objWrapper = jQuery("#"+g_options.loadmore_container); + if(g_objWrapper.length == 0) + return(false); + + g_objButton = g_objWrapper.find(".ug-loadmore-button"); + if(g_objButton.length == 0) + return(false); + + g_objLoader = g_objWrapper.find(".ug-loadmore-loader"); + if(g_objLoader.length == 0) + return(false); + + g_objError = g_objWrapper.find(".ug-loadmore-error"); + if(g_objError.length == 0) + return(false); + + g_temp.isInited = true; + } + + + /** + * show loadmore + */ + function showLoadmore(){ + + g_objWrapper.show(); + } + + + /** + * on loadore click event + */ + function onLoadmoreClick(){ + + g_objButton.hide(); + g_objLoader.show(); + + var data = { + numitems:g_gallery.getNumItems() + }; + + g_gallery.ajaxRequest("front_loadmore", data, function(response){ + + g_objLoader.hide(); + + var htmlItems = response.html_items; + var showLoadmore = response.show_loadmore; + + if(showLoadmore == true){ + g_objButton.blur().show(); + g_objLoader.hide(); + }else{ + g_objWrapper.hide(); + } + + g_gallery.addItems(htmlItems); + + },function(errorText){ + errorText = "Ajax Error!" + errorText; + + g_objLoader.hide(); + g_objError.show(); + g_objError.html(errorText); + + }); + + } + + + /** + * init events + */ + function initEvents(){ + + g_gallery.onEvent("tiles_first_placed", showLoadmore); + + g_objButton.click(onLoadmoreClick); + } + + + /** + * destroy + */ + this.destroy = function(){ + if(g_temp.isInited == false) + return(false); + } + + + /** + * init the loadmore button + */ + this.init = function(gallery, customOptions){ + g_gallery = gallery; + + g_objGallery = jQuery(g_gallery); + g_options = jQuery.extend(g_options, customOptions); + + initObjects(); + + if(g_temp.isInited == false){ + trace("load more not inited, something is wrong"); + return(false); + } + + initEvents(); + } + + +} diff --git a/pro-features/shortcode-bmi-calculator.php b/pro-features/shortcode-bmi-calculator.php index 20753d02..e17b0403 100644 --- a/pro-features/shortcode-bmi-calculator.php +++ b/pro-features/shortcode-bmi-calculator.php @@ -94,15 +94,15 @@ function ws_ls_bmi_calculator( $user_defined_arguments ) {
      ', - __( 'Weight', WE_LS_SLUG ), - __( 'Kg', WE_LS_SLUG ), - __( 'Height', WE_LS_SLUG ), - __( 'Cm', WE_LS_SLUG ), - __( 'Calculate BMI', WE_LS_SLUG ), - __( 'Stones', WE_LS_SLUG ), - __( 'Pounds', WE_LS_SLUG ), - __( 'Feet', WE_LS_SLUG ), - __( 'Inches', WE_LS_SLUG ), + esc_html__( 'Weight', WE_LS_SLUG ), + esc_html__( 'Kg', WE_LS_SLUG ), + esc_html__( 'Height', WE_LS_SLUG ), + esc_html__( 'Cm', WE_LS_SLUG ), + esc_html__( 'Calculate BMI', WE_LS_SLUG ), + esc_html__( 'Stones', WE_LS_SLUG ), + esc_html__( 'Pounds', WE_LS_SLUG ), + esc_html__( 'Feet', WE_LS_SLUG ), + esc_html__( 'Inches', WE_LS_SLUG ), ( true === $pounds_only ) ? 1 : 2, ( true === $pounds_only ) ? ' ws-ls-hide' : '' ); @@ -158,7 +158,7 @@ function ws_ls_bmi_calculator_ajax() { if ( false === empty( $bmi ) ) { $text = sprintf( '

      %s

      %s %s.

      ', ws_ls_round_number( $bmi, 1 ), - __( 'Your weight suggests you are', WE_LS_SLUG ), + esc_html__( 'Your weight suggests you are', WE_LS_SLUG ), strtolower( ws_ls_bmi_display( $bmi, 'label' ) ) ); $class = ws_ls_calculate_bmi_uikit_class( $bmi ); diff --git a/pro-features/shortcode-chart.php b/pro-features/shortcode-chart.php index 8eaf0383..a7ab12c8 100755 --- a/pro-features/shortcode-chart.php +++ b/pro-features/shortcode-chart.php @@ -19,7 +19,7 @@ function ws_ls_shortcode_chart( $user_defined_arguments ) { 'bezier' => ws_ls_option_to_bool( 'ws-ls-bezier-curve' ), 'height' => 250, 'ignore-login-status' => false, - 'message-no-data' => __( 'Currently there is no data to display on the chart.', WE_LS_SLUG ), + 'message-no-data' => esc_html__( 'Currently there is no data to display on the chart.', WE_LS_SLUG ), 'max-data-points' => ws_ls_option( 'ws-ls-max-points', '25', true ), 'show-gridlines' => ws_ls_option_to_bool( 'ws-ls-grid-lines' ), 'show-custom-fields' => true, diff --git a/pro-features/shortcode-hip-waist-ratio-calculator.php b/pro-features/shortcode-hip-waist-ratio-calculator.php index e729bcd0..39a01632 100644 --- a/pro-features/shortcode-hip-waist-ratio-calculator.php +++ b/pro-features/shortcode-hip-waist-ratio-calculator.php @@ -113,20 +113,15 @@ function ws_ls_waist_to_hip_ratio_calculator( $user_defined_arguments ) {
      - ', - __( 'Waist (cm)', WE_LS_SLUG ), - __( 'Waist (inches)', WE_LS_SLUG ), - __( 'Hip (cm)', WE_LS_SLUG ), - __( 'Hip (inches)', WE_LS_SLUG ), - __( 'Calculate', WE_LS_SLUG ), - __( 'Gender', WE_LS_SLUG ), - __( 'Female', WE_LS_SLUG ), - __( 'Male', WE_LS_SLUG ), + esc_html__( 'Waist (cm)', WE_LS_SLUG ), + esc_html__( 'Waist (inches)', WE_LS_SLUG ), + esc_html__( 'Hip (cm)', WE_LS_SLUG ), + esc_html__( 'Hip (inches)', WE_LS_SLUG ), + esc_html__( 'Calculate', WE_LS_SLUG ), + esc_html__( 'Gender', WE_LS_SLUG ), + esc_html__( 'Female', WE_LS_SLUG ), + esc_html__( 'Male', WE_LS_SLUG ), selected( 2, $gender, false ) ); @@ -162,17 +157,17 @@ function ws_ls_waist_to_hip_ratio_calculator_ajax() { switch ( $class ){ case 'ykuk-alert-warning': - $description = __( 'Moderate Health Risk', WE_LS_SLUG ); + $description = esc_html__( 'Moderate Health Risk', WE_LS_SLUG ); break; case 'ykuk-alert-danger': - $description = __( 'High Health Risk', WE_LS_SLUG ); + $description = esc_html__( 'High Health Risk', WE_LS_SLUG ); break; default: - $description = __( 'Low Health Risk', WE_LS_SLUG ); + $description = esc_html__( 'Low Health Risk', WE_LS_SLUG ); } $text = sprintf( '

      %s: %s

      %s.

      ', - __( 'Your ratio', WE_LS_SLUG ), + esc_html__( 'Your ratio', WE_LS_SLUG ), ws_ls_round_number( $ratio, 2 ), $description ); @@ -222,5 +217,5 @@ function ws_ls_calculate_waist_to_hip_ratio_uikit_class( $ratio, $gender ) { } - return __( 'Err', WE_LS_SLUG ); + return esc_html__( 'Err', WE_LS_SLUG ); } diff --git a/pro-features/shortcode-if.php b/pro-features/shortcode-if.php index 597dd020..11d96d79 100755 --- a/pro-features/shortcode-if.php +++ b/pro-features/shortcode-if.php @@ -18,7 +18,7 @@ function ws_ls_shortcode_if( $user_defined_arguments, $content = null, $level = // Check if we have content between opening and closing [wlt-if] tags, if we don't then nothing to render so why bother proceeding? if( true === empty( $content ) ) { - return sprintf( '

      %s

      ', __( 'To use this shortcode, you must specify content between opening and closing tag e.g. [wlt-if]something to show if IF is true[/wlt-if]', WE_LS_SLUG ) ); + return sprintf( '

      %s

      ', esc_html__( 'To use this shortcode, you must specify content between opening and closing tag e.g. [wlt-if]something to show if IF is true[/wlt-if]', WE_LS_SLUG ) ); } $arguments = shortcode_atts( [ 'user-id' => get_current_user_id(), @@ -39,7 +39,7 @@ function ws_ls_shortcode_if( $user_defined_arguments, $content = null, $level = // Remove Pro Plus fields if they don't have a license if( false === WS_LS_IS_PRO_PLUS && true === ( $arguments['field'] == 'bmr' ) ) { - return sprintf( '

      %s

      ', __( 'Unfortunately the field you specified is for Pro Plus licenses only.', WE_LS_SLUG ) ); + return sprintf( '

      %s

      ', esc_html__( 'Unfortunately the field you specified is for Pro Plus licenses only.', WE_LS_SLUG ) ); } $else_content = ''; diff --git a/pro-features/shortcode-progress-bar.php b/pro-features/shortcode-progress-bar.php index 35dfd52d..2142f3cc 100755 --- a/pro-features/shortcode-progress-bar.php +++ b/pro-features/shortcode-progress-bar.php @@ -28,7 +28,7 @@ function ws_ls_shortcode_progress_bar( $user_defined_arguments ) { 'animation-duration' => 1400, // Animation time in ms. Defaults to 1400 'width' => '100%', // % or pixels 'height' => '100%', // % or pixels - 'percentage-text' => __( 'towards your target of {t}.', WE_LS_SLUG ), + 'percentage-text' => esc_html__( 'towards your target of {t}.', WE_LS_SLUG ), 'user-id' => get_current_user_id() ], $user_defined_arguments ); @@ -36,19 +36,19 @@ function ws_ls_shortcode_progress_bar( $user_defined_arguments ) { // Are targets enabled? If not, no point carrying on! if( false === ws_ls_targets_enabled() ) { - return ws_ls_shortcode_progress_bar_display_error( __( 'This shortcode can not be used as Target weights have been disabled in the plugin\'s settings.', WE_LS_SLUG ), $display_errors ); + return ws_ls_shortcode_progress_bar_display_error( esc_html__( 'This shortcode can not be used as Target weights have been disabled in the plugin\'s settings.', WE_LS_SLUG ), $display_errors ); } $arguments[ 'target-weight' ] = ws_ls_target_get( $arguments[ 'user-id' ], 'kg' ); if ( true === empty( $arguments[ 'target-weight' ] ) ) { - return ws_ls_shortcode_progress_bar_display_error( __( 'Please enter a target weight to see your progress.', WE_LS_SLUG ), $display_errors ); + return ws_ls_shortcode_progress_bar_display_error( esc_html__( 'Please enter a target weight to see your progress.', WE_LS_SLUG ), $display_errors ); } $arguments[ 'weight' ] = ws_ls_entry_get_latest_kg(); if ( true === empty( $arguments[ 'weight' ] ) ) { - return ws_ls_shortcode_progress_bar_display_error( __( 'Please add a weight entry to see your progress.', WE_LS_SLUG ), $display_errors ); + return ws_ls_shortcode_progress_bar_display_error( esc_html__( 'Please add a weight entry to see your progress.', WE_LS_SLUG ), $display_errors ); } $arguments[ 'target-weight-display' ] = ws_ls_weight_display( $arguments[ 'target-weight' ], $arguments[ 'user-id' ], 'display' ); @@ -159,7 +159,7 @@ function ws_ls_shortcode_progress_bar_render( $arguments ) { ws_ls_enqueue_files(); // Enqueue Progress library - wp_enqueue_script('ws-ls-progress-bar', plugins_url( '../assets/js/libraries/progress-bar.js', __FILE__ ), [ 'jquery' ], WE_LS_CURRENT_VERSION ); + wp_enqueue_script('ws-ls-progress-bar', plugins_url( '../assets/js/libraries/progress-bar.min.js', __FILE__ ), [ 'jquery' ], WE_LS_CURRENT_VERSION ); $arguments[ 'percentage-text' ] = str_replace('{t}', $arguments['target-weight-display'], $arguments[ 'percentage-text' ] ); diff --git a/pro-features/shortcode-reminders.php b/pro-features/shortcode-reminders.php index c2fc91e8..7f49b8dd 100755 --- a/pro-features/shortcode-reminders.php +++ b/pro-features/shortcode-reminders.php @@ -53,13 +53,13 @@ function ws_ls_shortcode_reminder($user_defined_arguments, $content = null) { // Missing both? if ( 'both' == $arguments[ 'type' ] && $target_required && $weight_required ) { - $message = __( 'Please remember to enter your weight for today as well as your target weight.', WE_LS_SLUG ) ; + $message = esc_html__( 'Please remember to enter your weight for today as well as your target weight.', WE_LS_SLUG ) ; // Do they have a target weight? } else if ( 'target' == $arguments[ 'type' ] && $target_required ) { - $message = __( 'Please remember to enter your target weight.', WE_LS_SLUG ); + $message = esc_html__( 'Please remember to enter your target weight.', WE_LS_SLUG ); // Do they have a weight entry for today? } else if ( 'weight' == $arguments[ 'type' ] && $weight_required) { - $message = __( 'Please remember to enter your weight.', WE_LS_SLUG ) ; + $message = esc_html__( 'Please remember to enter your weight.', WE_LS_SLUG ) ; } // Do we have a message to display? diff --git a/pro-features/shortcode-stats.php b/pro-features/shortcode-stats.php index dbe2c210..c6dbbdc4 100755 --- a/pro-features/shortcode-stats.php +++ b/pro-features/shortcode-stats.php @@ -35,14 +35,14 @@ function ws_ls_shortcode_stats_league_total( $user_defined_arguments ) { - - '; + + '; if( $arguments['show_percentage'] ) { $html .= ''; } - $html .= ' + $html .= ' @@ -103,7 +103,7 @@ function ws_ls_shortcode_stats_league_total( $user_defined_arguments ) { return apply_filters( 'wlt-filter-stats-table-html', $html); } - return sprintf( '

      %s

      ', __( 'The league table has not been generated yet. This is a scheduled task so please check back in 15 minutes or try pressing the button below.', WE_LS_SLUG ) ); + return sprintf( '

      %s

      ', esc_html__( 'The league table has not been generated yet. This is a scheduled task so please check back in 15 minutes or try pressing the button below.', WE_LS_SLUG ) ); } add_shortcode( 'wlt-league-table', 'ws_ls_shortcode_stats_league_total' ); add_shortcode( 'wt-league-table', 'ws_ls_shortcode_stats_league_total' ); @@ -147,7 +147,7 @@ function ws_ls_shortcode_stats_display_value( $stats, $arguments ) { // If display number text, remove sign and use text to represent gain / loss if( 'number/text' == $arguments[ 'display' ] ) { - $stats['display-value'] = ($difference <= 0) ? __( 'Lost', WE_LS_SLUG ) : __( 'Gained', WE_LS_SLUG ); + $stats['display-value'] = ($difference <= 0) ? esc_html__( 'Lost', WE_LS_SLUG ) : esc_html__( 'Gained', WE_LS_SLUG ); $stats['display-value'] .= ': '; $difference = abs( $difference ); @@ -163,14 +163,14 @@ function ws_ls_shortcode_stats_display_value( $stats, $arguments ) { switch ( $stats[ 'display-unit' ] ) { case 'pounds_only': - $stats[ 'display-value' ] .= ws_ls_convert_kg_to_lb( $difference ) . __( 'lbs', WE_LS_SLUG ); + $stats[ 'display-value' ] .= ws_ls_convert_kg_to_lb( $difference ) . esc_html__( 'lbs', WE_LS_SLUG ); break; case 'stones_pounds': $weight = ws_ls_convert_kg_to_stone_pounds( $difference ); $stats[ 'display-value' ] .= ws_ls_format_stones_pound_for_comparison_display( $weight ); break; default: - $stats['display-value'] .= ws_ls_round_decimals( $difference ) . __('kg', WE_LS_SLUG); + $stats['display-value'] .= ws_ls_round_decimals( $difference ) . esc_html__('kg', WE_LS_SLUG); } // Allow theme developer to override stats message diff --git a/pro-features/shortcode-various.php b/pro-features/shortcode-various.php index 05ce97dd..503ccc10 100755 --- a/pro-features/shortcode-various.php +++ b/pro-features/shortcode-various.php @@ -166,8 +166,8 @@ function ws_ls_shortcode_bmi( $arguments = [] ) { $arguments = shortcode_atts( [ 'bmi-type' => 'current', // current, start 'display' => 'index', // 'index' - Actual BMI value. 'label' - BMI label for given value. 'both' - Label and BMI value in brackets, - 'no-height-text' => __( 'Height needed', WE_LS_SLUG ), - 'no-weight-text' => __( 'Weight needed', WE_LS_SLUG ), + 'no-height-text' => esc_html__( 'Height needed', WE_LS_SLUG ), + 'no-weight-text' => esc_html__( 'Weight needed', WE_LS_SLUG ), 'user-id' => get_current_user_id() ], $arguments ); @@ -216,7 +216,7 @@ function ws_ls_shortcode_activity_level( $user_defined_arguments ) { return ws_ls_display_pro_upgrade_notice_for_shortcode(); } - $arguments = shortcode_atts( [ 'not-specified-text' => __( 'Not Specified', WE_LS_SLUG ), + $arguments = shortcode_atts( [ 'not-specified-text' => esc_html__( 'Not Specified', WE_LS_SLUG ), 'user-id' => get_current_user_id(), 'shorten' => false ], $user_defined_arguments ); @@ -242,7 +242,7 @@ function ws_ls_shortcode_gender( $user_defined_arguments ) { return ws_ls_display_pro_upgrade_notice_for_shortcode(); } - $arguments = shortcode_atts( [ 'not-specified-text' => __( 'Not Specified', WE_LS_SLUG ), + $arguments = shortcode_atts( [ 'not-specified-text' => esc_html__( 'Not Specified', WE_LS_SLUG ), 'user-id' => get_current_user_id(), 'shorten' => false ], $user_defined_arguments ); @@ -268,7 +268,7 @@ function ws_ls_shortcode_dob( $user_defined_arguments ) { return ws_ls_display_pro_upgrade_notice_for_shortcode(); } - $arguments = shortcode_atts( [ 'not-specified-text' => __( 'Not Specified', WE_LS_SLUG ), 'user-id' => get_current_user_id() ], $user_defined_arguments ); + $arguments = shortcode_atts( [ 'not-specified-text' => esc_html__( 'Not Specified', WE_LS_SLUG ), 'user-id' => get_current_user_id() ], $user_defined_arguments ); $cache_key = ws_ls_cache_generate_key_from_array( 'shortcode-dob', $arguments ); @@ -296,7 +296,7 @@ function ws_ls_shortcode_height( $user_defined_arguments ) { return ws_ls_display_pro_upgrade_notice_for_shortcode(); } - $arguments = shortcode_atts( [ 'not-specified-text' => __( 'Not Specified', WE_LS_SLUG ), + $arguments = shortcode_atts( [ 'not-specified-text' => esc_html__( 'Not Specified', WE_LS_SLUG ), 'user-id' => get_current_user_id() ], $user_defined_arguments ); @@ -448,7 +448,7 @@ function ws_ls_shortcode_days_between_start_and_latest( $user_defined_arguments, } $text = ( true === ws_ls_to_bool( $arguments[ 'include-days' ] ) ) ? - sprintf( '%d %s', $difference->days, __( 'days', WE_LS_SLUG ) ) : + sprintf( '%d %s', $difference->days, esc_html__( 'days', WE_LS_SLUG ) ) : $difference->days; return ( true === ws_ls_to_bool( $arguments[ 'include-brackets' ] ) ) ? diff --git a/pro-features/user-birthdays.php b/pro-features/user-birthdays.php index a21665e1..97e3ded9 100755 --- a/pro-features/user-birthdays.php +++ b/pro-features/user-birthdays.php @@ -33,7 +33,7 @@ function ws_ls_birthdays_activate() {

      All the best,

      {name}

      ', - __( 'Birthday Email' , WE_LS_SLUG ) + esc_html__( 'Birthday Email' , WE_LS_SLUG ) ); } diff --git a/pro-features/user-groups.php b/pro-features/user-groups.php index 42dfc5cd..d2d2a9ae 100755 --- a/pro-features/user-groups.php +++ b/pro-features/user-groups.php @@ -105,10 +105,10 @@ function ws_ls_groups_hooks_user_side_bar( $rows, $user_id ) { $groups = ws_ls_groups_user( $user_id ); - $text = ( false === empty( $groups ) ) ? $groups[ 0 ]['name'] : __('None', WE_LS_SLUG ); + $text = ( false === empty( $groups ) ) ? $groups[ 0 ]['name'] : esc_html__('None', WE_LS_SLUG ); $rows[] = [ - 'th' => __('Group', WE_LS_SLUG ), + 'th' => esc_html__('Group', WE_LS_SLUG ), 'td' => sprintf( '%s', esc_url( $settings_url ), esc_html( $text ) ) ]; @@ -145,19 +145,19 @@ function ws_ls_groups_hooks_user_preferences_form( $html, $user_id ) { $current_selection = ( false === empty( $current_selection[0]['id'] ) ) ? (int) $current_selection[0]['id'] : 0; if ( true === is_admin() ) { - $html .= sprintf( '

      %s

      ', ws_ls_groups_link(), __('Add / remove Groups', WE_LS_SLUG) ); + $html .= sprintf( '

      %s

      ', ws_ls_groups_link(), esc_html__('Add / remove Groups', WE_LS_SLUG) ); } $groups = wp_list_pluck( $groups, 'name', 'id' ); $html .= ws_ls_form_field_select( [ 'key' => 'ws-ls-group', - 'label' => __( 'Group', WE_LS_SLUG ), + 'label' => esc_html__( 'Group', WE_LS_SLUG ), 'values' => $groups, 'selected' => $current_selection, 'uikit' => true ] ); } else { - $html .= __('None', WE_LS_SLUG ); + $html .= esc_html__('None', WE_LS_SLUG ); } } @@ -449,11 +449,11 @@ function ws_ls_groups( $include_none = true, $include_all_groups = false ) { $data = $wpdb->get_results( $sql , ARRAY_A ); if ( true === $include_none ) { - $data = array_merge( [ [ 'id' => 0, 'name' => __('No Group', WE_LS_SLUG ) ] ], $data ); + $data = array_merge( [ [ 'id' => 0, 'name' => esc_html__('No Group', WE_LS_SLUG ) ] ], $data ); } if ( true === $include_all_groups ) { - $data = array_merge( [ [ 'id' => -1, 'name' => __('All Groups', WE_LS_SLUG ) ] ], $data ); + $data = array_merge( [ [ 'id' => -1, 'name' => esc_html__('All Groups', WE_LS_SLUG ) ] ], $data ); } ws_ls_cache_user_set( 'groups', $cache_key , $data ); @@ -562,7 +562,7 @@ function ws_ls_groups_export_add( $row ) { if ( false === empty( $group[ 0 ][ 'name' ] ) ) { $row[ 'group' ] = $group[ 0 ][ 'name' ]; } else { - $row[ 'group' ] = __( 'No Group', WE_LS_SLUG ); + $row[ 'group' ] = esc_html__( 'No Group', WE_LS_SLUG ); } return $row; @@ -580,7 +580,7 @@ function ws_ls_groups_export_columns( $columns ) { return $columns; } - $columns[ 'group' ] = __( 'Group', WE_LS_SLUG ); + $columns[ 'group' ] = esc_html__( 'Group', WE_LS_SLUG ); return $columns; } add_filter( 'wlt-export-columns', 'ws_ls_groups_export_columns' ); @@ -676,11 +676,11 @@ function ws_ls_ajax_groups_get(){ } $columns = [ - [ 'name' => 'id', 'title' => __('Group ID', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number', 'visible' => true ], - [ 'name' => 'name', 'title' => __('Name', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ], - [ 'name' => 'count', 'title' => __('No. Users', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number' ], + [ 'name' => 'id', 'title' => esc_html__('Group ID', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number', 'visible' => true ], + [ 'name' => 'name', 'title' => esc_html__('Name', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ], + [ 'name' => 'count', 'title' => esc_html__('No. Users', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number' ], [ 'name' => 'weight_difference', 'title' => '', 'breakpoints'=> '', 'type' => 'number', 'visible' => false ], - [ 'name' => 'weight_display', 'title' => __('Total Weight Difference', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ] + [ 'name' => 'weight_display', 'title' => esc_html__('Total Weight Difference', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ] ]; $rows = []; @@ -731,30 +731,30 @@ function ws_ls_ajax_groups_users_get(){ wp_send_json( $cache ); } - $diff_label = ( true === ws_ls_to_bool( $todays_entries_only ) ) ? __('Diff from start', WE_LS_SLUG) : __('Diff from prev', WE_LS_SLUG); + $diff_label = ( true === ws_ls_to_bool( $todays_entries_only ) ) ? esc_html__('Diff from start', WE_LS_SLUG) : esc_html__('Diff from prev', WE_LS_SLUG); $columns = [ - [ 'name' => 'id', 'title' => __('ID', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number', 'visible' => false ], - [ 'name' => 'display_name', 'title' => __('User', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ], - [ 'name' => 'number-of-entries', 'title' => __('No. entries', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'number' ], - [ 'name' => 'start-weight', 'title' => __('Start', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ], - [ 'name' => 'previous-weight', 'title' => __('Previous', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ], - [ 'name' => 'latest-weight', 'title' => __('Latest', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ] ]; + [ 'name' => 'id', 'title' => esc_html__('ID', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'number', 'visible' => false ], + [ 'name' => 'display_name', 'title' => esc_html__('User', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ], + [ 'name' => 'number-of-entries', 'title' => esc_html__('No. entries', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'number' ], + [ 'name' => 'start-weight', 'title' => esc_html__('Start', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ], + [ 'name' => 'previous-weight', 'title' => esc_html__('Previous', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ], + [ 'name' => 'latest-weight', 'title' => esc_html__('Latest', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ] ]; if ( false === $hide_col_diff_from_prev ) { $columns[] = [ 'name' => 'diff-weight', 'title' => $diff_label, 'breakpoints'=> 'md', 'type' => 'text' ]; } if ( false === $hide_column_losses ) { - $columns[] = [ 'name' => 'losses', 'title' => __('Losses', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ]; + $columns[] = [ 'name' => 'losses', 'title' => esc_html__('Losses', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ]; } if ( false === $hide_column_gains ) { - $columns[] = [ 'name' => 'gains', 'title' => __('Gains', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ]; + $columns[] = [ 'name' => 'gains', 'title' => esc_html__('Gains', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ]; } - $columns[] = [ 'name' => 'target', 'title' => __('Target', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ]; - $columns[] = [ 'name' => 'awards', 'title' => __('Awards', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ]; + $columns[] = [ 'name' => 'target', 'title' => esc_html__('Target', WE_LS_SLUG), 'breakpoints'=> '', 'type' => 'text' ]; + $columns[] = [ 'name' => 'awards', 'title' => esc_html__('Awards', WE_LS_SLUG), 'breakpoints'=> 'md', 'type' => 'text' ]; $rows = []; $total_difference = 0; @@ -908,7 +908,7 @@ function ws_ls_groups_shortcode( $user_defined_arguments ) { $arguments = shortcode_atts( [ 'id' => 0, 'auto-detect' => false, - 'text-no-difference' => __( 'There is no weight difference for this group.', WE_LS_SLUG ) + 'text-no-difference' => esc_html__( 'There is no weight difference for this group.', WE_LS_SLUG ) ], $user_defined_arguments ); if ( true === ws_ls_to_bool( $arguments[ 'auto-detect' ] ) ) { @@ -944,7 +944,7 @@ function ws_ls_groups_shortcode( $user_defined_arguments ) { */ function ws_ls_groups_current( $user_defined_arguments ) { - $arguments = shortcode_atts( [ 'user-id' => 0, 'no-group-text' => __('No Group', WE_LS_SLUG) ], $user_defined_arguments ); + $arguments = shortcode_atts( [ 'user-id' => 0, 'no-group-text' => esc_html__('No Group', WE_LS_SLUG) ], $user_defined_arguments ); $arguments[ 'user-id'] = ws_ls_force_numeric_argument( $arguments[ 'user-id'], NULL ); diff --git a/pro-features/user-preferences.php b/pro-features/user-preferences.php index debc27a6..2c3ef8ba 100755 --- a/pro-features/user-preferences.php +++ b/pro-features/user-preferences.php @@ -14,7 +14,7 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { // If not logged in then return no value if ( false === is_user_logged_in() ) { - return ws_ls_display_blockquote( __('You must be logged in to edit your settings.', WE_LS_SLUG) , '', false, true); + return ws_ls_display_blockquote( esc_html__('You must be logged in to edit your settings.', WE_LS_SLUG) , '', false, true); } $html_output = ''; @@ -41,7 +41,7 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { // Have user preferences been allowed in Settings? if ( false === ws_ls_user_preferences_is_enabled() && false === is_admin() && false === $arguments[ 'kiosk-mode' ]) { - return ws_ls_display_blockquote( __( 'To use this shortcode, please ensure you have enabled the setting "Allow user settings".', WE_LS_SLUG) ); + return ws_ls_display_blockquote( esc_html__( 'To use this shortcode, please ensure you have enabled the setting "Allow user settings".', WE_LS_SLUG) ); } // Delete all the user's data if selected @@ -54,25 +54,25 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { // Decide which set of labels to render $labels = [ - 'height' => __( 'Your height:', WE_LS_SLUG ), - 'weight' => __( 'In which unit would you like to record your weight:', WE_LS_SLUG ), - 'date' => __( 'Display dates in the following formats:', WE_LS_SLUG ), - 'gender' => __( 'Your Gender:', WE_LS_SLUG ), - 'dob' => __( 'Your Date of Birth:', WE_LS_SLUG ), - 'activitylevel' => __( 'Your Activity Level:', WE_LS_SLUG ), - 'aim' => __( 'Your aim:' , WE_LS_SLUG ) + 'height' => esc_html__( 'Your height:', WE_LS_SLUG ), + 'weight' => esc_html__( 'In which unit would you like to record your weight:', WE_LS_SLUG ), + 'date' => esc_html__( 'Display dates in the following formats:', WE_LS_SLUG ), + 'gender' => esc_html__( 'Your Gender:', WE_LS_SLUG ), + 'dob' => esc_html__( 'Your Date of Birth:', WE_LS_SLUG ), + 'activitylevel' => esc_html__( 'Your Activity Level:', WE_LS_SLUG ), + 'aim' => esc_html__( 'Your aim:' , WE_LS_SLUG ) ]; // If admin, add notice and override labels if( is_admin() ) { - $labels = [ 'height' => __( 'Height:', WE_LS_SLUG ), - 'weight' => __( 'Weight unit:', WE_LS_SLUG ), - 'date' => __( 'Date format:', WE_LS_SLUG ), - 'gender' => __( 'Gender:', WE_LS_SLUG ), - 'dob' => __( 'Date of Birth:', WE_LS_SLUG ), - 'activitylevel' => __( 'Activity Level:', WE_LS_SLUG ), - 'aim' => __( 'Aim:', WE_LS_SLUG ) + $labels = [ 'height' => esc_html__( 'Height:', WE_LS_SLUG ), + 'weight' => esc_html__( 'Weight unit:', WE_LS_SLUG ), + 'date' => esc_html__( 'Date format:', WE_LS_SLUG ), + 'gender' => esc_html__( 'Gender:', WE_LS_SLUG ), + 'dob' => esc_html__( 'Date of Birth:', WE_LS_SLUG ), + 'activitylevel' => esc_html__( 'Activity Level:', WE_LS_SLUG ), + 'aim' => esc_html__( 'Aim:', WE_LS_SLUG ) ]; // If we're in Admin screens, then hide "delete data" @@ -138,7 +138,7 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { $html_output .= ws_ls_form_field_select( [ 'key' => 'WE_LS_US_DATE', 'label' => $labels[ 'date' ], 'uikit' => $arguments[ 'uikit' ], - 'values' => [ 'false' => __( 'UK (DD/MM/YYYY)', WE_LS_SLUG ), 'true' => __( 'US (MM/DD/YYYY)', WE_LS_SLUG ) ], + 'values' => [ 'false' => esc_html__( 'UK (DD/MM/YYYY)', WE_LS_SLUG ), 'true' => esc_html__( 'US (MM/DD/YYYY)', WE_LS_SLUG ) ], 'selected' => ( true === ws_ls_setting( 'use-us-dates', $user_id ) ) ? 'true' : 'false' ] ); } @@ -147,8 +147,8 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { $html_output .= sprintf( '

      %s

      %s

      -
      ', __( 'Email notifications', WE_LS_SLUG ), - __( 'Select the email notifications that you would like to receive:', WE_LS_SLUG ) + ', esc_html__( 'Email notifications', WE_LS_SLUG ), + esc_html__( 'Select the email notifications that you would like to receive:', WE_LS_SLUG ) ); $html_output .= ws_ls_emailer_optout_form(); } @@ -160,7 +160,7 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { ', ws_ls_form_tab_index_next(), - __( 'Save Settings', WE_LS_SLUG ) + esc_html__( 'Save Settings', WE_LS_SLUG ) ); } @@ -173,7 +173,7 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { true === ws_ls_to_bool( $arguments[ 'show-delete-data' ] ) ) { if ( false === ws_ls_to_bool( $arguments[ 'hide-titles' ] ) ) { - $html_output .= ws_ls_title( __( 'Delete existing data', WE_LS_SLUG ) ); + $html_output .= ws_ls_title( esc_html__( 'Delete existing data', WE_LS_SLUG ) ); } $post_url = add_query_arg( 'user-delete-all', 'true', ws_ls_get_url() ); @@ -184,9 +184,9 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { ', esc_url( $post_url ) ); $html_output .= ws_ls_form_field_select( [ 'key' => 'ws-ls-delete-all', - 'label' => __( 'The button below allows you to clear your existing weight history. Confirm:', WE_LS_SLUG ), + 'label' => esc_html__( 'The button below allows you to clear your existing weight history. Confirm:', WE_LS_SLUG ), 'values' => [ '' => '', - 'yes' => __( 'DELETE ALL DATA', WE_LS_SLUG ) + 'yes' => esc_html__( 'DELETE ALL DATA', WE_LS_SLUG ) ], 'uikit' => $arguments[ 'uikit' ], 'required' => true ] ); @@ -196,7 +196,7 @@ function ws_ls_user_preferences_form( $user_defined_arguments ) { ', ws_ls_form_tab_index_next(), - __( 'Delete', WE_LS_SLUG ) + esc_html__( 'Delete', WE_LS_SLUG ) ); } diff --git a/pro-features/web-hooks.php b/pro-features/web-hooks.php index a0ec35f8..d3b9ce0e 100644 --- a/pro-features/web-hooks.php +++ b/pro-features/web-hooks.php @@ -255,7 +255,7 @@ function ws_ls_webhooks_data_prep_weight( $entry ) { 'type' => 'header', 'text' => [ 'type' => 'plain_text', - 'text' => __( 'A weight entry has been added/updated', WE_LS_SLUG ), + 'text' => esc_html__( 'A weight entry has been added/updated', WE_LS_SLUG ), 'emoji' => false ] ]; @@ -266,11 +266,11 @@ function ws_ls_webhooks_data_prep_weight( $entry ) { 'fields' => [ [ 'type' => 'mrkdwn', - 'text' => sprintf( '*%1$s:*%2$s %3$s', __( 'Name', WE_LS_SLUG ), PHP_EOL, $data[ 'user-display-name' ] ) + 'text' => sprintf( '*%1$s:*%2$s %3$s', esc_html__( 'Name', WE_LS_SLUG ), PHP_EOL, $data[ 'user-display-name' ] ) ], [ 'type' => 'mrkdwn', - 'text' => sprintf( '*%1$s:*%2$s %3$s', __( 'Date', WE_LS_SLUG ), PHP_EOL, $data[ 'weight-display' ] ) + 'text' => sprintf( '*%1$s:*%2$s %3$s', esc_html__( 'Date', WE_LS_SLUG ), PHP_EOL, $data[ 'weight-display' ] ) ] ] ]; @@ -281,11 +281,11 @@ function ws_ls_webhooks_data_prep_weight( $entry ) { 'fields' => [ [ 'type' => 'mrkdwn', - 'text' => sprintf( '*%1$s:*%2$s %3$s %2$s (%4$s)', __( 'Weight', WE_LS_SLUG ), PHP_EOL, $data[ 'weight-display' ], $data[ 'weight-difference-from-start-display' ] ) + 'text' => sprintf( '*%1$s:*%2$s %3$s %2$s (%4$s)', esc_html__( 'Weight', WE_LS_SLUG ), PHP_EOL, $data[ 'weight-display' ], $data[ 'weight-difference-from-start-display' ] ) ], [ 'type' => 'mrkdwn', - 'text' => sprintf( '*%1$s:*%2$s %3$s', __( 'Starting Weight', WE_LS_SLUG ), PHP_EOL, $data[ 'weight-first-display' ] ) + 'text' => sprintf( '*%1$s:*%2$s %3$s', esc_html__( 'Starting Weight', WE_LS_SLUG ), PHP_EOL, $data[ 'weight-first-display' ] ) ] ] @@ -317,7 +317,7 @@ function ws_ls_webhooks_data_prep_weight( $entry ) { 'fields' => [ [ 'type' => 'mrkdwn', - 'text' => sprintf( '*%1$s:*%2$s %3$s %2$s', __( 'Notes', WE_LS_SLUG ), PHP_EOL, wp_strip_all_tags( $data[ 'notes' ] ) ) + 'text' => sprintf( '*%1$s:*%2$s %3$s %2$s', esc_html__( 'Notes', WE_LS_SLUG ), PHP_EOL, wp_strip_all_tags( $data[ 'notes' ] ) ) ] ] ]; @@ -329,12 +329,12 @@ function ws_ls_webhooks_data_prep_weight( $entry ) { 'elements' => [ [ 'type' => 'button', - 'text' => [ 'type' => 'plain_text', 'text' => __( 'View Entry', WE_LS_SLUG ) ], + 'text' => [ 'type' => 'plain_text', 'text' => esc_html__( 'View Entry', WE_LS_SLUG ) ], 'url' => $data[ 'url-entry-edit' ] ], [ 'type' => 'button', - 'text' => [ 'type' => 'plain_text', 'text' => __( 'View Profile', WE_LS_SLUG ) ], + 'text' => [ 'type' => 'plain_text', 'text' => esc_html__( 'View Profile', WE_LS_SLUG ) ], 'url' => $data[ 'url-user-profile' ] ] ] @@ -374,7 +374,7 @@ function ws_ls_webhooks_data_prep_target( $target ) { 'type' => 'header', 'text' => [ 'type' => 'plain_text', - 'text' => __( 'A new target has been set', WE_LS_SLUG ), + 'text' => esc_html__( 'A new target has been set', WE_LS_SLUG ), 'emoji' => false ] ]; @@ -385,11 +385,11 @@ function ws_ls_webhooks_data_prep_target( $target ) { 'fields' => [ [ 'type' => 'mrkdwn', - 'text' => sprintf( '*%1$s:*%2$s %3$s', __( 'Name', WE_LS_SLUG ), PHP_EOL, $data[ 'user-display-name' ] ) + 'text' => sprintf( '*%1$s:*%2$s %3$s', esc_html__( 'Name', WE_LS_SLUG ), PHP_EOL, $data[ 'user-display-name' ] ) ], [ 'type' => 'mrkdwn', - 'text' => sprintf( '*%1$s:*%2$s %3$s', __( 'Target Weight', WE_LS_SLUG ), PHP_EOL, $data[ 'weight-display' ] ) + 'text' => sprintf( '*%1$s:*%2$s %3$s', esc_html__( 'Target Weight', WE_LS_SLUG ), PHP_EOL, $data[ 'weight-display' ] ) ] ] ]; @@ -400,7 +400,7 @@ function ws_ls_webhooks_data_prep_target( $target ) { 'elements' => [ [ 'type' => 'button', - 'text' => [ 'type' => 'plain_text', 'text' => __( 'View Profile', WE_LS_SLUG ) ], + 'text' => [ 'type' => 'plain_text', 'text' => esc_html__( 'View Profile', WE_LS_SLUG ) ], 'url' => $data[ 'url-user-profile' ] ] ] diff --git a/pro-features/widget-chart.php b/pro-features/widget-chart.php index e64f2835..a76e4bb6 100755 --- a/pro-features/widget-chart.php +++ b/pro-features/widget-chart.php @@ -10,12 +10,12 @@ function __construct() { parent::__construct( 'ws_ls_widget_chart', - __( 'Weight Tracker - Chart', WE_LS_SLUG ), - array( 'description' => __( 'Display a chart to see your current progress.', WE_LS_SLUG ) ) // Args + esc_html__( 'Weight Tracker - Chart', WE_LS_SLUG ), + array( 'description' => esc_html__( 'Display a chart to see your current progress.', WE_LS_SLUG ) ) // Args ); $this->field_values = array( - 'title' => __( 'Weight Tracker', WE_LS_SLUG ), + 'title' => esc_html__( 'Weight Tracker', WE_LS_SLUG ), 'max-points' => 5, 'user-id' => '', 'type' => 'line', @@ -98,30 +98,30 @@ public function form( $instance ) { } ?> -

      not logged in.', WE_LS_SLUG ); ?>

      +

      not logged in.', WE_LS_SLUG ); ?>

      get_field_id( 'title' ); ?> - +

      get_field_id( 'type' ); ?> - +

      get_field_id( 'max-points' ); ?> - +

      - +

      get_field_id( 'user-id' ); ?> - +

      - +

      get_field_id( 'exclude-measurements' ); ?> - +

      diff --git a/pro-features/widget-form.php b/pro-features/widget-form.php index 632e61d9..8062b5d8 100755 --- a/pro-features/widget-form.php +++ b/pro-features/widget-form.php @@ -8,11 +8,11 @@ class ws_ls_widget_form extends WP_Widget { function __construct() { - parent::__construct( 'ws_ls_widget_form', __( 'Weight Tracker - Form', WE_LS_SLUG ), - [ 'description' => __('Display a quick entry form for logged in users to record their weight for today.', WE_LS_SLUG ) ] ); + parent::__construct( 'ws_ls_widget_form', esc_html__( 'Weight Tracker - Form', WE_LS_SLUG ), + [ 'description' => esc_html__('Display a quick entry form for logged in users to record their weight for today.', WE_LS_SLUG ) ] ); $this->field_values = [ - 'title' => __( 'Your weight today', WE_LS_SLUG ), + 'title' => esc_html__( 'Your weight today', WE_LS_SLUG ), 'force_todays_date' => 'yes', 'not-logged-in-message' => '', 'exclude-measurements' => 'no', diff --git a/pro-features/widget-progress.php b/pro-features/widget-progress.php index e8485129..ab13d482 100755 --- a/pro-features/widget-progress.php +++ b/pro-features/widget-progress.php @@ -8,9 +8,9 @@ class ws_ls_widget_progress_bar extends WP_Widget { function __construct() { - parent::__construct( 'ws_ls_widget_progress_bar', __( 'Weight Tracker - Progress Bar', WE_LS_SLUG ),[ 'description' => __('A progress bar to indicate weight loss towards target.', WE_LS_SLUG ) ] ); + parent::__construct( 'ws_ls_widget_progress_bar', esc_html__( 'Weight Tracker - Progress Bar', WE_LS_SLUG ),[ 'description' => esc_html__('A progress bar to indicate weight loss towards target.', WE_LS_SLUG ) ] ); - $this->field_values = [ 'title' => __( 'Weight Progress', WE_LS_SLUG ), + $this->field_values = [ 'title' => esc_html__( 'Weight Progress', WE_LS_SLUG ), 'type' => 'line', 'stroke-width' => 3, 'stroke-colour' => '#FFEA82', @@ -18,7 +18,7 @@ function __construct() { 'trail-colour' => '#EEE', 'text-colour' => '#000', 'animation-duration' => 1400, - 'percentage-text' => __( 'towards your target of {t}.', WE_LS_SLUG ) + 'percentage-text' => esc_html__( 'towards your target of {t}.', WE_LS_SLUG ) ]; } diff --git a/readme.txt b/readme.txt index eac3a83a..bf7d5b9f 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: aliakro Tags: weight,tracker,chart,history,macronutrient Requires at least: 6.0 Tested up to: 6.5 -Stable tag: 10.9.2 +Stable tag: 10.10 Requires PHP: 7.4 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -92,9 +92,20 @@ Need further help? Please visit the dedicated site: [Weight Tracker Website](https://weight.yeken.uk "Weight Tracker Website") -= Donate = +== 3rd Party Libraries == -Paypal Donate: [www.paypal.me/yeken](https://www.paypal.me/yeken "www.paypal.me/yeken") +As with most modern software, this plugins utilises other 3rd party plugins. Depending on how you use the plugin (i.e. which shortcodes) determines which libraries maybe used. Below is a list of the 3rd party libraries used: + +* [UI Kit](https://getuikit.com/) +* [Chart.js](https://www.chartjs.org/) +* [Font Awesome](https://fontawesome.com/) +* [Footable](https://fooplugins.github.io/FooTable/) +* [Selectize](https://github.com/selectize/selectize.js) +* [jQuery Validation Plugin](https://jqueryvalidation.org/) +* [jQuery Slider Pips](https://github.com/simeydotme/jQuery-ui-Slider-Pips) +* [ProgressBar.js](https://kimmobrunfeldt.github.io/progressbar.js) +* [ZOZO UI Tabs](http://www.zozoui.com) +* [Unite Gallery](https://github.com/vvvmax/unitegallery) == Installation == @@ -152,6 +163,17 @@ Measurements are created using Custom Fields. You can therefore specify the unit == Changelog == += 10.10 = + +Several fixes based upon WordPress's plugin feedback: + +* Included all non minified versions of 3rd party JS. +* CDNs for the following have been removed, their files are now served via the plugins folder: Chart.js, FontAwesome +* Updated Readme to include references to 3rd party libraries. +* Moved inline
      $user_id ] ) ); ?>
      $user_id, 'display' => 'both', 'no-height-text' => __('No height specified', WE_LS_SLUG)]); ?> $user_id, 'display' => 'both', 'no-height-text' => esc_html__('No height specified', WE_LS_SLUG)]); ?>
      Upgrade to Pro Plus', ws_ls_upgrade_link()); } @@ -432,19 +365,19 @@ function ws_ls_postbox_sidebar_user_information( $user_id ) { function ws_ls_postbox_sidebar_add_entry( $user_id ) { ?>
      - __( 'Add Entry', WE_LS_SLUG ), 'postbox-id' => 'add-entry', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?> + esc_html__( 'Add Entry', WE_LS_SLUG ), 'postbox-id' => 'add-entry', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?>
      @@ -459,18 +392,18 @@ function ws_ls_postbox_sidebar_export_data( $user_id ) { ?>
      - __( 'Export data', WE_LS_SLUG ), 'postbox-id' => 'export-data', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?> + esc_html__( 'Export data', WE_LS_SLUG ), 'postbox-id' => 'export-data', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?>
      - %s

      ', __('You do not have permission to do this.', WE_LS_SLUG ) ); ?> + %s

      ', esc_html__('You do not have permission to do this.', WE_LS_SLUG ) ); ?> - + - +
      @@ -487,11 +420,11 @@ function ws_ls_postbox_sidebar_settings( $user_id ) { $settings_url = ws_ls_get_link_to_user_settings( $user_id ); ?>
      - __( 'Settings', WE_LS_SLUG ), 'postbox-id' => 'settings', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?> + esc_html__( 'Settings', WE_LS_SLUG ), 'postbox-id' => 'settings', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?> @@ -506,11 +439,11 @@ function ws_ls_postbox_sidebar_settings( $user_id ) { function ws_ls_postbox_sidebar_delete_cache( $user_id ) { ?>
      - __( 'Delete cache', WE_LS_SLUG ), 'postbox-id' => 'delete-cache', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?> + esc_html__( 'Delete cache', WE_LS_SLUG ), 'postbox-id' => 'delete-cache', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?>
      @@ -525,21 +458,21 @@ function ws_ls_postbox_sidebar_delete_cache( $user_id ) { function ws_ls_postbox_sidebar_delete_data( $user_id ) { ?>
      - __( 'Delete data', WE_LS_SLUG ), 'postbox-id' => 'delete-data', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?> + esc_html__( 'Delete data', WE_LS_SLUG ), 'postbox-id' => 'delete-data', 'postbox-col' => 'ws-ls-user-data-two' ] ); ?>
      - %s

      ', __('You do not have permission to do this.', WE_LS_SLUG ) ); ?> + %s

      ', esc_html__('You do not have permission to do this.', WE_LS_SLUG ) ); ?> - +

      ', + ws_ls_create_dialog_jquery_code(esc_html__('Are you sure you?', WE_LS_SLUG), + esc_html__('Are you sure you wish to remove the data for this user?', WE_LS_SLUG) . '

      ', 'delete-confirm'); } @@ -616,11 +549,11 @@ function ws_ls_user_header($user_id, $previous_url = false ) { ws_ls_user_display_name( $user_id ), ws_ls_get_email_link( $user_id, true ), $previous_url, - __( 'Back', WE_LS_SLUG ), + esc_html__( 'Back', WE_LS_SLUG ), get_edit_user_link( $user_id ), - __('WordPress Record', WE_LS_SLUG ), + esc_html__('WordPress Record', WE_LS_SLUG ), ws_ls_get_link_to_user_profile( $user_id ), - __('Weight Tracker Record', WE_LS_SLUG ), + esc_html__('Weight Tracker Record', WE_LS_SLUG ), wp_kses_post( $additional_links ) ); } @@ -631,7 +564,7 @@ function ws_ls_user_header($user_id, $previous_url = false ) { */ function ws_ls_postbox_header( $args = [] ) { - $args = wp_parse_args( $args, [ 'title' => __( 'Title', WE_LS_SLUG ), + $args = wp_parse_args( $args, [ 'title' => esc_html__( 'Title', WE_LS_SLUG ), 'show-controls' => true, 'postbox-id' => NULL, 'postbox-col' => 'ws-ls-user-summary-one' diff --git a/pro-features/functions.php b/pro-features/functions.php index 15f17721..01a66dde 100755 --- a/pro-features/functions.php +++ b/pro-features/functions.php @@ -55,7 +55,7 @@ function ws_ls_get_bmi_for_table( $cm, $kg, $no_height_text = false, $display = } else { $no_height_text = ( true === empty( $no_height_text ) ) ? - __( 'Add Height for BMI', WE_LS_SLUG ) : + esc_html__( 'Add Height for BMI', WE_LS_SLUG ) : $no_height_text; return esc_html( $no_height_text ); @@ -92,18 +92,18 @@ function ws_ls_calculate_bmi_label( $bmi ) { if( true === is_numeric( $bmi ) ) { if( $bmi < 18.5 ) { - return __( 'Underweight', WE_LS_SLUG ); + return esc_html__( 'Underweight', WE_LS_SLUG ); } else if ( $bmi >= 18.5 && $bmi <= 24.9 ) { - return __( 'Healthy', WE_LS_SLUG ); + return esc_html__( 'Healthy', WE_LS_SLUG ); } else if ( $bmi >= 25 && $bmi <= 29.9 ) { - return __( 'Overweight', WE_LS_SLUG ); + return esc_html__( 'Overweight', WE_LS_SLUG ); } else if ( $bmi >= 30 ) { - return __( 'Obese', WE_LS_SLUG ); + return esc_html__( 'Obese', WE_LS_SLUG ); } } - return __( 'Err', WE_LS_SLUG ); + return esc_html__( 'Err', WE_LS_SLUG ); } /** @@ -128,7 +128,7 @@ function ws_ls_calculate_bmi_uikit_class( $bmi ) { } } - return __( 'Err', WE_LS_SLUG ); + return esc_html__( 'Err', WE_LS_SLUG ); } /** @@ -160,10 +160,10 @@ function ws_ls_bmi_display( $bmi, $display = 'index' ) { */ function ws_ls_bmi_all_labels() { return [ - 0 => __( 'Underweight', WE_LS_SLUG ), - 1 => __( 'Healthy', WE_LS_SLUG ), - 2 => __( 'Overweight', WE_LS_SLUG ), - 3 => __( 'Heavily Overweight', WE_LS_SLUG ) + 0 => esc_html__( 'Underweight', WE_LS_SLUG ), + 1 => esc_html__( 'Healthy', WE_LS_SLUG ), + 2 => esc_html__( 'Overweight', WE_LS_SLUG ), + 3 => esc_html__( 'Heavily Overweight', WE_LS_SLUG ) ]; } @@ -391,8 +391,8 @@ function ws_ls_genders() { return [ 0 => '', - 1 => __('Female', WE_LS_SLUG), - 2 => __('Male', WE_LS_SLUG) + 1 => esc_html__('Female', WE_LS_SLUG), + 2 => esc_html__('Male', WE_LS_SLUG) ]; } @@ -416,11 +416,11 @@ function ws_ls_genders_get( $id ) { function ws_ls_activity_levels() { $activity_levels = [ '0' => '', - '1.2' => __( 'Little / No Exercise', WE_LS_SLUG ), - '1.375' => __( 'Light Exercise', WE_LS_SLUG ), - '1.55' => __( 'Moderate Exercise (3-5 days a week)', WE_LS_SLUG ), - '1.725' => __( 'Very Active (6-7 days a week)', WE_LS_SLUG ), - '1.9' => __( 'Extra Active (very active & physical job)', WE_LS_SLUG ) + '1.2' => esc_html__( 'Little / No Exercise', WE_LS_SLUG ), + '1.375' => esc_html__( 'Light Exercise', WE_LS_SLUG ), + '1.55' => esc_html__( 'Moderate Exercise (3-5 days a week)', WE_LS_SLUG ), + '1.725' => esc_html__( 'Very Active (6-7 days a week)', WE_LS_SLUG ), + '1.9' => esc_html__( 'Extra Active (very active & physical job)', WE_LS_SLUG ) ]; return apply_filters( 'wlt-filter-activity-levels', $activity_levels ); @@ -435,9 +435,9 @@ function ws_ls_aims() { $aims = [ 0 => '', - 1 => __('Maintain current weight', WE_LS_SLUG), - 2 => __('Lose weight', WE_LS_SLUG), - 3 => __('Gain weight', WE_LS_SLUG) + 1 => esc_html__('Maintain current weight', WE_LS_SLUG), + 2 => esc_html__('Lose weight', WE_LS_SLUG), + 3 => esc_html__('Gain weight', WE_LS_SLUG) ]; return apply_filters( 'wlt-filter-aims', $aims ); @@ -572,7 +572,7 @@ function ws_ls_heights_metric_to_imperial( $cm = NULL ) { * @param $key */ function ws_ls_heights_formatter( &$height, $key ) { - $height = sprintf( '%3$d%4$s - %1$d\' %2$s"', $height['feet'], $height['inches'], $key, __('cm', WE_LS_SLUG) ); + $height = sprintf( '%3$d%4$s - %1$d\' %2$s"', $height['feet'], $height['inches'], $key, esc_html__('cm', WE_LS_SLUG) ); } /** @@ -587,7 +587,7 @@ function ws_ls_heights_formatter( &$height, $key ) { function ws_ls_display_user_setting( $user_id, $field = 'dob', $not_specified_text = false, $shorten = false ) { $user_id = ( true === empty( $user_id )) ? get_current_user_id() : $user_id; - $not_specified_text = ( false === $not_specified_text ) ? __( 'Not Specified', WE_LS_SLUG ) : esc_html( $not_specified_text ); + $not_specified_text = ( false === $not_specified_text ) ? esc_html__( 'Not Specified', WE_LS_SLUG ) : esc_html( $not_specified_text ); $user_data = ws_ls_user_preferences_get( $field, $user_id ); switch ( $field ) { @@ -679,7 +679,7 @@ function ws_ls_get_dob_for_display( $user_id = NULL, $not_specified_text = '', $ $user_id = ( true === empty( $user_id ) ) ? get_current_user_id() : $user_id; $dob = ws_ls_get_dob( $user_id ); - $not_specified_text = ( false === $not_specified_text) ? __( 'Not Specified', WE_LS_SLUG ) : esc_html( $not_specified_text ); + $not_specified_text = ( false === $not_specified_text) ? esc_html__( 'Not Specified', WE_LS_SLUG ) : esc_html( $not_specified_text ); if (false === empty( $dob ) && '0000-00-00 00:00:00' !== $dob ) { $html = ws_ls_iso_date_into_correct_format( $dob, $user_id ); @@ -747,7 +747,7 @@ function ws_ls_age_from_dob( $dob ) { */ function ws_ls_permission_check_message() { if ( false === ws_ls_permission_check() ) { - wp_die( __( 'You do not have sufficient permissions to access this page.' , WE_LS_SLUG ) ); + wp_die( esc_html__( 'You do not have sufficient permissions to access this page.' , WE_LS_SLUG ) ); } } @@ -811,7 +811,7 @@ function ws_ls_user_exist( $user_id ) { function ws_ls_user_exist_check($user_id ) { if ( false === ws_ls_user_exist( $user_id ) ) { - wp_die( __( 'Error: The user does not appear to exist' , WE_LS_SLUG ) ); + wp_die( esc_html__( 'Error: The user does not appear to exist' , WE_LS_SLUG ) ); } return true; @@ -908,7 +908,7 @@ function ws_ls_user_preferences_get( $field = 'gender', $user_id = false, $defau */ function ws_ls_user_preferences_display( $arguments = [] ) { - $arguments = wp_parse_args( $arguments, [ 'user-id' => get_current_user_id(), 'field' => 'dob', 'shorten' => false , 'not-specified-text' => __( 'Not Specified', WE_LS_SLUG ) ] ); + $arguments = wp_parse_args( $arguments, [ 'user-id' => get_current_user_id(), 'field' => 'dob', 'shorten' => false , 'not-specified-text' => esc_html__( 'Not Specified', WE_LS_SLUG ) ] ); $cache_key = ws_ls_cache_generate_key_from_array( 'pref-display-', $arguments ); diff --git a/pro-features/gamification.php b/pro-features/gamification.php index 15b933c5..dd2efdd0 100644 --- a/pro-features/gamification.php +++ b/pro-features/gamification.php @@ -18,14 +18,14 @@ function ws_ls_mycred_add_hooks( $installed, $point_type ) { // Weight added - $installed[ 'ws_ls_weight_entry' ] = [ 'title' => __( 'Weight Tracker: Weight Entry Added', WE_LS_SLUG ), - 'description' => __( 'Reward a user when they have recorded a new weight entry.', WE_LS_SLUG ), + $installed[ 'ws_ls_weight_entry' ] = [ 'title' => esc_html__( 'Weight Tracker: Weight Entry Added', WE_LS_SLUG ), + 'description' => esc_html__( 'Reward a user when they have recorded a new weight entry.', WE_LS_SLUG ), 'callback' => [ 'ws_ls_mycred_weight_entry_class' ] ]; // Target added - $installed[ 'ws_ls_target_set' ] = [ 'title' => __( 'Weight Tracker: Target set', WE_LS_SLUG ), - 'description' => __( 'Reward a user when they have set their target.', WE_LS_SLUG ), + $installed[ 'ws_ls_target_set' ] = [ 'title' => esc_html__( 'Weight Tracker: Target set', WE_LS_SLUG ), + 'description' => esc_html__( 'Reward a user when they have set their target.', WE_LS_SLUG ), 'callback' => [ 'ws_ls_mycred_target_set_class' ] ]; @@ -51,7 +51,7 @@ function __construct( $hook_prefs, $type ) { parent::__construct( [ 'id' => 'ws_ls_weight_entry', 'defaults' => [ 'ws_ls_weight_entry' => [ 'creds' => 10, - 'log' => __( 'Weight entry added', WE_LS_SLUG ), + 'log' => esc_html__( 'Weight entry added', WE_LS_SLUG ), 'limit' => '0/x' ] ] @@ -167,7 +167,7 @@ function __construct( $hook_prefs, $type ) { parent::__construct( [ 'id' => 'ws_ls_target_set', 'defaults' => [ 'ws_ls_target_set' => [ 'creds' => 10, - 'log' => __( 'Weight target set', WE_LS_SLUG ), + 'log' => esc_html__( 'Weight target set', WE_LS_SLUG ), 'limit' => '0/x' ] ] diff --git a/pro-features/plus/awards/activate.php b/pro-features/plus/awards/activate.php index 062f4608..ec851f1d 100755 --- a/pro-features/plus/awards/activate.php +++ b/pro-features/plus/awards/activate.php @@ -74,7 +74,7 @@ function ws_ls_awards_activate() { {badge} {url-link} {custom_message}', - __( 'Receiving an award' , WE_LS_SLUG ) + esc_html__( 'Receiving an award' , WE_LS_SLUG ) ); } diff --git a/pro-features/plus/awards/functions.php b/pro-features/plus/awards/functions.php index 75cbc8b2..15dfa52e 100755 --- a/pro-features/plus/awards/functions.php +++ b/pro-features/plus/awards/functions.php @@ -29,21 +29,21 @@ function ws_ls_awards_categories( $simple = false) { if ( true === $simple ) { return [ - 'weight' => __('Weight', WE_LS_SLUG ), - 'weight-percentage' => __('Weight %', WE_LS_SLUG ), - 'weight-target' => __('Target met', WE_LS_SLUG ), - 'bmi' => __('BMI: Change', WE_LS_SLUG ), - 'bmi-equals' => __('BMI: Equals', WE_LS_SLUG ), + 'weight' => esc_html__('Weight', WE_LS_SLUG ), + 'weight-percentage' => esc_html__('Weight %', WE_LS_SLUG ), + 'weight-target' => esc_html__('Target met', WE_LS_SLUG ), + 'bmi' => esc_html__('BMI: Change', WE_LS_SLUG ), + 'bmi-equals' => esc_html__('BMI: Equals', WE_LS_SLUG ), ]; } return [ - 'bmi' => __('BMI: Change', WE_LS_SLUG ), - 'bmi-equals' => __('BMI: Equals', WE_LS_SLUG ), - 'weight-target' => __('Weight: Target met (based on user aim)', WE_LS_SLUG ), - 'weight' => __('Weight: Change in units', WE_LS_SLUG ), - 'weight-percentage' => __('Weight: Change as a percentage', WE_LS_SLUG ) + 'bmi' => esc_html__('BMI: Change', WE_LS_SLUG ), + 'bmi-equals' => esc_html__('BMI: Equals', WE_LS_SLUG ), + 'weight-target' => esc_html__('Weight: Target met (based on user aim)', WE_LS_SLUG ), + 'weight' => esc_html__('Weight: Change in units', WE_LS_SLUG ), + 'weight-percentage' => esc_html__('Weight: Change as a percentage', WE_LS_SLUG ) ]; @@ -60,9 +60,9 @@ function ws_ls_awards_gain_loss_display( $gain_loss ) { switch ( $gain_loss ) { case 'gain'; - return __('Gain', WE_LS_SLUG); + return esc_html__('Gain', WE_LS_SLUG); case 'loss': - return __('Loss', WE_LS_SLUG); + return esc_html__('Loss', WE_LS_SLUG); default: return ''; } @@ -310,7 +310,7 @@ function ws_ls_awards_render_badges( $user_defined_arguments ) { $html = ''; $arguments = shortcode_atts([ - 'error-message' => ( is_admin() ) ? __('The user has no awards', WE_LS_SLUG ) : __('You have no awards yet', WE_LS_SLUG ), + 'error-message' => ( is_admin() ) ? esc_html__('The user has no awards', WE_LS_SLUG ) : esc_html__('You have no awards yet', WE_LS_SLUG ), 'user-id' => get_current_user_id(), ], $user_defined_arguments ); diff --git a/pro-features/plus/awards/hooks.php b/pro-features/plus/awards/hooks.php index 7e7e6ff3..dc7c7971 100755 --- a/pro-features/plus/awards/hooks.php +++ b/pro-features/plus/awards/hooks.php @@ -346,7 +346,7 @@ function ws_ls_awards_send_email( $weight_object, $award, $info ) { // Does this award have a URL? if ( false === empty( $award['url' ] ) ) { $award['url-link'] = sprintf('

      %s

      ', - esc_url( $award['url' ] ), __('View Award', WE_LS_SLUG) ); + esc_url( $award['url' ] ), esc_html__('View Award', WE_LS_SLUG) ); } else { $award['url-link'] = ''; } @@ -368,15 +368,15 @@ function ws_ls_awards_ajax_list() { $columns = [ [ 'name' => 'id', 'title' => 'ID', 'visible'=> false, 'type' => 'number' ], - [ 'name' => 'title', 'title' => __('Title', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'category', 'title' => __('Category', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'gain_loss', 'title' => __('Gain / Loss', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'value', 'title' => __('Value', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - //[ 'name' => 'max_awards', 'title' => __('Max. Awards', WE_LS_SLUG), 'visible'=> true, 'type' => 'number' ], - [ 'name' => 'apply_to_add', 'title' => __('Add', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'apply_to_update', 'title' => __('Update', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'send_email', 'title' => __('Email', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], - [ 'name' => 'enabled', 'title' => __('Enabled', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'title', 'title' => esc_html__('Title', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'category', 'title' => esc_html__('Category', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'gain_loss', 'title' => esc_html__('Gain / Loss', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'value', 'title' => esc_html__('Value', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + //[ 'name' => 'max_awards', 'title' => esc_html__('Max. Awards', WE_LS_SLUG), 'visible'=> true, 'type' => 'number' ], + [ 'name' => 'apply_to_add', 'title' => esc_html__('Add', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'apply_to_update', 'title' => esc_html__('Update', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'send_email', 'title' => esc_html__('Email', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], + [ 'name' => 'enabled', 'title' => esc_html__('Enabled', WE_LS_SLUG), 'visible'=> true, 'type' => 'text' ], ]; $awards = ws_ls_awards(); @@ -485,7 +485,7 @@ function ws_ls_awards_shortcode_grid( $user_defined_arguments ) { } $arguments = shortcode_atts([ - 'message' => __('No awards', WE_LS_SLUG), + 'message' => esc_html__('No awards', WE_LS_SLUG), 'user-id' => get_current_user_id(), 'thumb-width' => 150, 'thumb-height' => 150 diff --git a/pro-features/plus/barcode-reader.php b/pro-features/plus/barcode-reader.php index 983d7baf..d681ecda 100644 --- a/pro-features/plus/barcode-reader.php +++ b/pro-features/plus/barcode-reader.php @@ -18,7 +18,7 @@ function ws_ls_barcode_reader( $arguments = [] ) { $config = [ 'current-url' => get_permalink(), 'querystring-key-user-id' => $arguments[ 'querystring-key-user-id' ], - 'text-error-loading-cameras' => __( 'Could not load any cameras for the barcode reader. Please ensure you have one or more cameras attached to this device and you are accessing it via https.', WE_LS_SLUG ), + 'text-error-loading-cameras' => esc_html__( 'Could not load any cameras for the barcode reader. Please ensure you have one or more cameras attached to this device and you are accessing it via https.', WE_LS_SLUG ), 'open' => $arguments[ 'open' ] ]; diff --git a/pro-features/plus/bmr.php b/pro-features/plus/bmr.php index c2bed31e..06b2c9d9 100755 --- a/pro-features/plus/bmr.php +++ b/pro-features/plus/bmr.php @@ -36,21 +36,21 @@ function ws_ls_calculate_bmr( $user_id = false, $return_error = true, $bmr_type $gender = ws_ls_user_preferences_get('gender', $user_id ); if( true === empty( $gender ) ) { - return ( $return_error ) ? __('No Gender specified', WE_LS_SLUG) : NULL; + return ( $return_error ) ? esc_html__('No Gender specified', WE_LS_SLUG) : NULL; } // Check if user has DOB - calculate age $age = ws_ls_user_get_age_from_dob( $user_id ); if( true === empty( $age ) ) { - return ( $return_error ) ? __('No Date of Birth specified or too young', WE_LS_SLUG ) : NULL; + return ( $return_error ) ? esc_html__('No Date of Birth specified or too young', WE_LS_SLUG ) : NULL; } //Get height $height = ws_ls_user_preferences_get('height', $user_id); if( true === empty( $height ) ) { - return ( $return_error ) ? __( 'No Height specified', WE_LS_SLUG ) : NULL; + return ( $return_error ) ? esc_html__( 'No Height specified', WE_LS_SLUG ) : NULL; } if ( 'start' === $bmr_type ) { @@ -62,7 +62,7 @@ function ws_ls_calculate_bmr( $user_id = false, $return_error = true, $bmr_type $weight = apply_filters( 'wlt_filters_bmr_weight_raw', $weight, $user_id ); if( true === empty( $weight ) ) { - return ( $return_error ) ? __('No Weight entered', WE_LS_SLUG) : NULL; + return ( $return_error ) ? esc_html__('No Weight entered', WE_LS_SLUG) : NULL; } $bmr = ws_ls_calculate_bmr_raw( $gender, $weight, $height, $age, $user_id ); diff --git a/pro-features/plus/challenge/admin.page.php b/pro-features/plus/challenge/admin.page.php index 604185d7..72dca848 100644 --- a/pro-features/plus/challenge/admin.page.php +++ b/pro-features/plus/challenge/admin.page.php @@ -26,7 +26,7 @@ function ws_ls_challenges_admin_page() { $name = ws_ls_post_value( 'ws-ls-name' ); if( true === empty( $name ) ) { - $error = __( 'Please ensure you enter a name for the challenge.', WE_LS_SLUG ); + $error = esc_html__( 'Please ensure you enter a name for the challenge.', WE_LS_SLUG ); } if( true === empty( $error ) ) { @@ -40,11 +40,11 @@ function ws_ls_challenges_admin_page() { $mode = 'processing'; } else { - $error = __( 'There was an error saving the challenge to the database.', WE_LS_SLUG ); + $error = esc_html__( 'There was an error saving the challenge to the database.', WE_LS_SLUG ); } } } - + ?>
      @@ -58,40 +58,40 @@ function ws_ls_challenges_admin_page() { ?>
      -

      +

      - + -

      - +

      +

      -

      +

      -

      -

      -

      +

      +

      +

      -

      +

      -

      -

      +

      - -

      @@ -100,12 +100,12 @@ function ws_ls_challenges_admin_page() {
      -

      +

      - +

      - + - + - +
      @@ -129,12 +129,12 @@ function ws_ls_challenges_admin_page() {
      -

      +

      - +

      -

      +

      -

      ...

      +

      ...

      - %d %s

      ', $entries_processed, __('entries processed', WE_LS_SLUG ) ); + printf( '

      - %d %s

      ', $entries_processed, esc_html__('entries processed', WE_LS_SLUG ) ); - printf( '', ws_ls_challenge_link( 1, 'processing' ) ); + $redirect_url = ws_ls_challenge_link( 1, 'processing' ); } else { - printf( '', ws_ls_challenge_link( 1, 'list' ) ); + $redirect_url = ws_ls_challenge_link( 1, 'list' ); } + + ws_ls_js_redirect( $redirect_url ); + ?>
      @@ -184,18 +187,18 @@ function ws_ls_challenges_admin_disabled() { ?> } ?>
      -

      +

      - - . + + .

      -

      -

      - +

      +

      +

      diff --git a/pro-features/plus/challenge/db.php b/pro-features/plus/challenge/db.php index 7b367769..1d05a293 100644 --- a/pro-features/plus/challenge/db.php +++ b/pro-features/plus/challenge/db.php @@ -232,7 +232,7 @@ function ws_ls_age_ranges_array_map_to_string( $element ) { $text = ( true === empty( $element[ 'min' ] ) ) ? 0 : $element[ 'min' ]; - $text .= ( true === empty( $element[ 'max' ] ) ) ? '+' : sprintf( ' %s %d', __( 'to', WE_LS_SLUG ), $element[ 'max' ] ); + $text .= ( true === empty( $element[ 'max' ] ) ) ? '+' : sprintf( ' %s %d', esc_html__( 'to', WE_LS_SLUG ), $element[ 'max' ] ); return ( '0+' === $text ) ? '' : $text; } @@ -370,8 +370,13 @@ function ws_ls_challenges_stats( $challenge_id ) { $challenge_id = (int) $challenge_id; - $stats[ 'count' ] = $wpdb->get_var( 'SELECT count( user_id ) FROM ' . $wpdb->prefix . WE_LS_MYSQL_CHALLENGES_DATA . ' WHERE challenge_id = ' . $challenge_id ); - $stats[ 'processed' ] = $wpdb->get_var( 'SELECT count( user_id ) FROM ' . $wpdb->prefix . WE_LS_MYSQL_CHALLENGES_DATA . ' WHERE last_processed is not NULL and challenge_id = ' . $challenge_id ); + // This is here for people that "check code" but don't actually read code to check it's safe!! + $sql = $wpdb->prepare( 'SELECT count( user_id ) FROM ' . $wpdb->prefix . WE_LS_MYSQL_CHALLENGES_DATA . ' WHERE challenge_id = %d', $challenge_id ); + $stats[ 'count' ] = $wpdb->get_var( $sql ); + + $sql = $wpdb->prepare( 'SELECT count( user_id ) FROM ' . $wpdb->prefix . WE_LS_MYSQL_CHALLENGES_DATA . ' WHERE last_processed is not NULL and challenge_id = %d', $challenge_id ); + $stats[ 'processed' ] = $wpdb->get_var( $sql ); + $stats[ 'to-be-processed' ] = $stats[ 'count' ] - $stats[ 'processed' ]; return $stats; diff --git a/pro-features/plus/challenge/functions.php b/pro-features/plus/challenge/functions.php index e5bb86ea..b5ff0b95 100644 --- a/pro-features/plus/challenge/functions.php +++ b/pro-features/plus/challenge/functions.php @@ -178,13 +178,13 @@ function ws_ls_challenges_table() { - - - - - - - + + + + + + + @@ -219,10 +219,10 @@ function ws_ls_challenges_table() { ws_ls_iso_date_into_correct_format( $challenge[ 'end_date' ] ), $stats[ 'count' ], $stats[ 'to-be-processed' ], - ( 1 === (int) $challenge[ 'enabled' ] ) ? __( 'No', WE_LS_SLUG ) : __( 'Yes', WE_LS_SLUG ), - __( 'View challenge data', WE_LS_SLUG ), - __( 'Close challenge', WE_LS_SLUG ), - __( 'Delete challenge', WE_LS_SLUG ), + ( 1 === (int) $challenge[ 'enabled' ] ) ? esc_html__( 'No', WE_LS_SLUG ) : esc_html__( 'Yes', WE_LS_SLUG ), + esc_html__( 'View challenge data', WE_LS_SLUG ), + esc_html__( 'Close challenge', WE_LS_SLUG ), + esc_html__( 'Delete challenge', WE_LS_SLUG ), ws_ls_challenge_link( $challenge[ 'id' ] ), ws_ls_challenge_link( $challenge[ 'id' ], 'close' ), ws_ls_challenge_link( $challenge[ 'id' ], 'delete' ) @@ -243,11 +243,11 @@ function ws_ls_challenges_table() { function ws_ls_challenges_entry_columns_defaults() { return [ - 'weight_diff' => [ 'title' => __( 'Total Weight Loss', WE_LS_SLUG ), 'type' => 'text' ], - 'bmi_diff' => [ 'title' => __( 'BMI Change', WE_LS_SLUG ) ], - 'weight_percentage' => [ 'title' => __( '% Body Weight', WE_LS_SLUG ) ], - 'count_mt_entries' => [ 'title' => __( 'Meal Tracker Streaks (Days)', WE_LS_SLUG ) ], - 'count_wt_entries_week' => [ 'title' => __( 'Weight Tracker Streaks (Weeks)', WE_LS_SLUG ) ] + 'weight_diff' => [ 'title' => esc_html__( 'Total Weight Loss', WE_LS_SLUG ), 'type' => 'text' ], + 'bmi_diff' => [ 'title' => esc_html__( 'BMI Change', WE_LS_SLUG ) ], + 'weight_percentage' => [ 'title' => esc_html__( '% Body Weight', WE_LS_SLUG ) ], + 'count_mt_entries' => [ 'title' => esc_html__( 'Meal Tracker Streaks (Days)', WE_LS_SLUG ) ], + 'count_wt_entries_week' => [ 'title' => esc_html__( 'Weight Tracker Streaks (Weeks)', WE_LS_SLUG ) ] ]; } @@ -315,7 +315,7 @@ function ws_ls_challenges_view_entries( $args ) { ]); if ( true === empty( $args[ 'id' ] ) ) { - return __( 'Please specify an ID e.g. [wt-challenges id="1"]'); + return esc_html__( 'Please specify an ID e.g. [wt-challenges id="1"]'); } $data = ws_ls_challenges_data( $args ); @@ -331,7 +331,7 @@ function ws_ls_challenges_view_entries( $args ) { ', - __( 'Name', WE_LS_SLUG ) + esc_html__( 'Name', WE_LS_SLUG ) ); foreach ( $columns as $key => $details ) { @@ -427,9 +427,9 @@ function ws_ls_challenges_view_entries( $args ) {
      %s
      ', $html_columns, - __( 'Sum', WE_LS_SLUG ), + esc_html__( 'Sum', WE_LS_SLUG ), $html_sums, - __( 'Average', WE_LS_SLUG ), + esc_html__( 'Average', WE_LS_SLUG ), $html_averages ); } @@ -461,37 +461,37 @@ function ws_ls_challenges_show_filters() { } // Gender - $html .= ws_ls_form_field_select( [ 'key' => 'gender', 'label' => __( 'Gender', WE_LS_SLUG ) . ':', 'values' => ws_ls_genders(), 'selected' => ws_ls_querystring_value( 'gender', true ) ] ); + $html .= ws_ls_form_field_select( [ 'key' => 'gender', 'label' => esc_html__( 'Gender', WE_LS_SLUG ) . ':', 'values' => ws_ls_genders(), 'selected' => ws_ls_querystring_value( 'gender', true ) ] ); // Age range - $html .= ws_ls_form_field_select( [ 'key' => 'age-range', 'label' => __( 'Age Range', WE_LS_SLUG ) . ':', 'values' => ws_ls_age_ranges( true ), 'selected' => ws_ls_querystring_value( 'age-range', true ) ] ); + $html .= ws_ls_form_field_select( [ 'key' => 'age-range', 'label' => esc_html__( 'Age Range', WE_LS_SLUG ) . ':', 'values' => ws_ls_age_ranges( true ), 'selected' => ws_ls_querystring_value( 'age-range', true ) ] ); // Group if ( true === ws_ls_groups_enabled () ) { - $html .= ws_ls_form_field_select( [ 'key' => 'group-id', 'label' => __( 'Group', WE_LS_SLUG ) . ':', 'values' => ws_ls_challenge_filters_group_select_values(), 'selected' => ws_ls_querystring_value( 'group-id', true ) ] ); + $html .= ws_ls_form_field_select( [ 'key' => 'group-id', 'label' => esc_html__( 'Group', WE_LS_SLUG ) . ':', 'values' => ws_ls_challenge_filters_group_select_values(), 'selected' => ws_ls_querystring_value( 'group-id', true ) ] ); } // Optin if ( true === is_admin() ) { $html .= ws_ls_form_field_select( [ 'key' => 'opt-in', - 'label' => __( 'Opted in', WE_LS_SLUG ) . ':', + 'label' => esc_html__( 'Opted in', WE_LS_SLUG ) . ':', 'values' => [ - 1 => __( 'Opted in', WE_LS_SLUG ), - 0 => __( 'Everyone', WE_LS_SLUG ) + 1 => esc_html__( 'Opted in', WE_LS_SLUG ), + 0 => esc_html__( 'Everyone', WE_LS_SLUG ) ], 'selected' => ws_ls_querystring_value( 'opt-in', true ) ] ); $html .= ws_ls_form_field_select( [ 'key' => 'min-wt-entries', - 'label' => __( 'Min. Weight Entries', WE_LS_SLUG ) . ':', + 'label' => esc_html__( 'Min. Weight Entries', WE_LS_SLUG ) . ':', 'values' => [ - 2 => __( 'Two or more', WE_LS_SLUG ), - 1 => __( 'One or more', WE_LS_SLUG ) + 2 => esc_html__( 'Two or more', WE_LS_SLUG ), + 1 => esc_html__( 'One or more', WE_LS_SLUG ) ], 'selected' => ws_ls_querystring_value( 'min-wt-entries', true ) ] ); } - $html .= sprintf( '', __( 'Filter', WE_LS_SLUG ) ); + $html .= sprintf( '', esc_html__( 'Filter', WE_LS_SLUG ) ); return $html; } diff --git a/pro-features/plus/challenge/hooks.php b/pro-features/plus/challenge/hooks.php index 90231e41..c32b1320 100644 --- a/pro-features/plus/challenge/hooks.php +++ b/pro-features/plus/challenge/hooks.php @@ -35,9 +35,9 @@ function ws_ls_challenges_hook_settings_form_opt_in( $html, $user_id ) { $current_value = ws_ls_user_preferences_get( 'challenge_opt_in', $user_id ); $html .= ws_ls_form_field_select( [ 'key' => 'ws-ls-challenge-opt-in', - 'label' => __( 'Challenges', WE_LS_SLUG ), - 'values' => [ 'no' => __( 'No - Do not opt me into any challenges', WE_LS_SLUG ), - 'yes' => __( 'Yes - Opt me into challenges!', WE_LS_SLUG ) ], + 'label' => esc_html__( 'Challenges', WE_LS_SLUG ), + 'values' => [ 'no' => esc_html__( 'No - Do not opt me into any challenges', WE_LS_SLUG ), + 'yes' => esc_html__( 'Yes - Opt me into challenges!', WE_LS_SLUG ) ], 'selected' => ( '1' === $current_value ) ? 'yes' : 'no', 'uikit' => true ] ); diff --git a/pro-features/plus/challenge/shortcodes.php b/pro-features/plus/challenge/shortcodes.php index ce1c9445..c1db8cda 100644 --- a/pro-features/plus/challenge/shortcodes.php +++ b/pro-features/plus/challenge/shortcodes.php @@ -25,8 +25,8 @@ function ws_ls_challenges_shortcodes_opt_in( $user_defined_arguments ) { ws_ls_set_user_preference_simple( 'challenge_opt_in', ( 'yes' === $get_optin ) ? 1 : 0 ); return sprintf( '

      %1$s %2$s.

      ', - __( 'You have been', WE_LS_SLUG ), - ( 'yes' === $get_optin ) ? __( 'opted into all challenges', WE_LS_SLUG ) : __( 'opted out of all challenges', WE_LS_SLUG ) + esc_html__( 'You have been', WE_LS_SLUG ), + ( 'yes' === $get_optin ) ? esc_html__( 'opted into all challenges', WE_LS_SLUG ) : esc_html__( 'opted out of all challenges', WE_LS_SLUG ) ); } @@ -49,9 +49,9 @@ function ws_ls_challenges_shortcodes_opt_in( $user_defined_arguments ) { ', esc_url( $current_url ), '?opt-in=yes', - __( 'Opt in', WE_LS_SLUG ), + esc_html__( 'Opt in', WE_LS_SLUG ), '?opt-in=no', - __( 'Opt Out', WE_LS_SLUG ) + esc_html__( 'Opt Out', WE_LS_SLUG ) ); } add_shortcode( 'wlt-challenges-optin', 'ws_ls_challenges_shortcodes_opt_in' ); @@ -86,8 +86,8 @@ function ws_ls_challenges_view_entries_guide() { } printf( '

      %1$s

      %2$s

      ', - __( 'Display data on the front end ', WE_LS_SLUG ), - __( 'If you wish to display this table on a page or post, use the following shortcode: ', WE_LS_SLUG ) + esc_html__( 'Display data on the front end ', WE_LS_SLUG ), + esc_html__( 'If you wish to display this table on a page or post, use the following shortcode: ', WE_LS_SLUG ) ); $arguments = ''; diff --git a/pro-features/plus/harris-benedict.php b/pro-features/plus/harris-benedict.php index 05ad34ba..f3cc6dc4 100644 --- a/pro-features/plus/harris-benedict.php +++ b/pro-features/plus/harris-benedict.php @@ -79,7 +79,7 @@ function ws_ls_harris_benedict_calculate_calories_raw( $bmr, $gender, $activity_ // -------------------------------------------------- // We have activity level and bmr, calculate daily calories. - $calorie_intake['maintain'] = ['total' => round($activity_level * $bmr, 2), 'label' => __( 'Maintain', WE_LS_SLUG )]; + $calorie_intake['maintain'] = ['total' => round($activity_level * $bmr, 2), 'label' => esc_html__( 'Maintain', WE_LS_SLUG )]; // Filter total $calorie_intake['maintain']['total'] = apply_filters( 'wlt-filter-calories-maintain', $calorie_intake['maintain']['total'], $bmr, $activity_level, $user_id ); @@ -101,7 +101,7 @@ function ws_ls_harris_benedict_calculate_calories_raw( $bmr, $gender, $activity_ $calories_to_lose = ws_ls_harris_benedict_safety_cap_check_raw( $gender, $calories_to_lose ); - $calorie_intake['lose'] = ['total' => $calories_to_lose, 'label' => __( 'Lose', WE_LS_SLUG ) ] ; // lose weight (1 to 2lbs per week) + $calorie_intake['lose'] = ['total' => $calories_to_lose, 'label' => esc_html__( 'Lose', WE_LS_SLUG ) ] ; // lose weight (1 to 2lbs per week) // Filter lose total $calorie_intake['lose']['total'] = apply_filters( 'wlt-filter-calories-lose', $calorie_intake['lose']['total'], $bmr, $activity_level, $calories_to_lose, $user_id, $gender ); @@ -112,7 +112,7 @@ function ws_ls_harris_benedict_calculate_calories_raw( $bmr, $gender, $activity_ $calories_to_gain = $calorie_intake['maintain']['total'] + ws_ls_harris_benedict_filter_calories_to_add( $calorie_intake['maintain']['total'], $gender ); - $calorie_intake['gain'] = [ 'total' => $calories_to_gain, 'label' => __( 'Gain', WE_LS_SLUG ) ] ; + $calorie_intake['gain'] = [ 'total' => $calories_to_gain, 'label' => esc_html__( 'Gain', WE_LS_SLUG ) ] ; // Filter lose total $calorie_intake['gain']['total'] = apply_filters( 'wlt-filter-calories-gain', $calorie_intake['gain']['total'], $bmr, $activity_level, $calories_to_gain, $user_id, $gender ); @@ -203,7 +203,7 @@ function ws_ls_harris_benedict_render_table( $user_id, $missing_data_text = fals $calories = ws_ls_harris_benedict_calculate_calories( $user_id ); } - $missing_data_text = ( false === $missing_data_text ) ? __('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ) : $missing_data_text; + $missing_data_text = ( false === $missing_data_text ) ? esc_html__('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ) : $missing_data_text; if(false === empty($calories)) { @@ -220,11 +220,11 @@ function ws_ls_harris_benedict_render_table( $user_id, $missing_data_text = fals (false === empty($additional_css_class)) ? esc_attr($additional_css_class) . ' ' : '', false === is_admin() ? 'ws-ls-harris-benedict' : 'widefat', true === $email ? 'cellpadding="10" border="1"' : '', - __( 'Total', WE_LS_SLUG ), - __( 'Breakfast', WE_LS_SLUG ), - __( 'Lunch', WE_LS_SLUG ), - __( 'Dinner', WE_LS_SLUG ), - __( 'Snacks', WE_LS_SLUG ), + esc_html__( 'Total', WE_LS_SLUG ), + esc_html__( 'Breakfast', WE_LS_SLUG ), + esc_html__( 'Lunch', WE_LS_SLUG ), + esc_html__( 'Dinner', WE_LS_SLUG ), + esc_html__( 'Snacks', WE_LS_SLUG ), ws_ls_harris_benedict_meal_ratio_get( 'breakfast' ), ws_ls_harris_benedict_meal_ratio_get( 'lunch' ), ws_ls_harris_benedict_meal_ratio_get( 'dinner' ), @@ -289,13 +289,13 @@ function ws_ls_harris_benedict_render_table( $user_id, $missing_data_text = fals

      %8$s: %9$s - %1$d%3$s to %2$d%3$s - %5$s %6$s%7$s.

      ', $range[ 'from' ], $range[ 'to' ], - __( 'kcal', WE_LS_SLUG ), + esc_html__( 'kcal', WE_LS_SLUG ), $calories['maintain']['total'], - __( 'Subtract', WE_LS_SLUG ), + esc_html__( 'Subtract', WE_LS_SLUG ), esc_html( $range[ 'amount' ] ), - 'fixed' === $range[ 'unit' ] ? __( 'kcals of total calories to maintain weight', WE_LS_SLUG ) : __( '% of total calories required to maintain weight', WE_LS_SLUG ), - __( 'Rule applied for suggested weight loss', WE_LS_SLUG ), - ( false === empty( $gender ) ) ? $gender : __( 'Everyone', WE_LS_SLUG ) + 'fixed' === $range[ 'unit' ] ? esc_html__( 'kcals of total calories to maintain weight', WE_LS_SLUG ) : esc_html__( '% of total calories required to maintain weight', WE_LS_SLUG ), + esc_html__( 'Rule applied for suggested weight loss', WE_LS_SLUG ), + ( false === empty( $gender ) ) ? $gender : esc_html__( 'Everyone', WE_LS_SLUG ) ); } } @@ -312,13 +312,13 @@ function ws_ls_harris_benedict_render_table( $user_id, $missing_data_text = fals

      %8$s: %9$s - %1$d%3$s to %2$d%3$s - %5$s %6$s%7$s.

      ', $range[ 'from' ], $range[ 'to' ], - __( 'kcal', WE_LS_SLUG ), + esc_html__( 'kcal', WE_LS_SLUG ), $calories['maintain']['total'], - __( 'Add', WE_LS_SLUG ), + esc_html__( 'Add', WE_LS_SLUG ), esc_html( $range[ 'amount' ] ), - 'fixed' === $range[ 'unit' ] ? __( 'kcals of total calories to maintain weight', WE_LS_SLUG ) : __( '% of total calories required to maintain weight', WE_LS_SLUG ), - __( 'Rule applied for suggested weight gain', WE_LS_SLUG ), - ( false === empty( $gender ) ) ? $gender : __( 'Everyone', WE_LS_SLUG ) + 'fixed' === $range[ 'unit' ] ? esc_html__( 'kcals of total calories to maintain weight', WE_LS_SLUG ) : esc_html__( '% of total calories required to maintain weight', WE_LS_SLUG ), + esc_html__( 'Rule applied for suggested weight gain', WE_LS_SLUG ), + ( false === empty( $gender ) ) ? $gender : esc_html__( 'Everyone', WE_LS_SLUG ) ); } } @@ -350,7 +350,7 @@ function ws_ls_shortcode_harris_benedict( $user_defined_arguments ) { } $arguments = shortcode_atts( [ - 'error-message' => __('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ), + 'error-message' => esc_html__('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ), 'user-id' => false, 'add-unit' => false, 'progress' => 'maintain', // 'maintain', 'lose', 'gain', 'auto' @@ -385,7 +385,7 @@ function ws_ls_shortcode_harris_benedict( $user_defined_arguments ) { $display_value = ws_ls_calculate_percentage_of_number( $calorie_intake[ $progress ][ $type ], $arguments[ 'percentage' ] ); if ( true === $arguments[ 'add-unit' ] ) { - $display_value .= __( 'kcal', WE_LS_SLUG ); + $display_value .= esc_html__( 'kcal', WE_LS_SLUG ); } return esc_html( $display_value ); @@ -408,7 +408,7 @@ function ws_ls_shortcode_harris_benedict_table( $user_defined_arguments ) { } $arguments = shortcode_atts( [ 'css-class' => '', - 'error-message' => __('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ), + 'error-message' => esc_html__('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG ), 'user-id' => false, 'disable-jquery' => false ], @@ -457,12 +457,12 @@ function ws_ls_display_calorie_cap_raw( $gender ) { $calorie_cap = $is_female ? ws_ls_harris_benedict_setting( 'ws-ls-female-cal-cap' ) : ws_ls_harris_benedict_setting( 'ws-ls-male-cal-cap' ); return sprintf('%s %s %s. %s %s.', - ($is_female) ? __('Female', WE_LS_SLUG ) : __('Male', WE_LS_SLUG ), - __('calories for weight loss are capped at ', WE_LS_SLUG ), + ($is_female) ? esc_html__('Female', WE_LS_SLUG ) : esc_html__('Male', WE_LS_SLUG ), + esc_html__('calories for weight loss are capped at ', WE_LS_SLUG ), ws_ls_round_number( $calorie_cap ), - __('This can be modified within ', WE_LS_SLUG ), + esc_html__('This can be modified within ', WE_LS_SLUG ), ws_ls_get_link_to_settings(), - __('settings', WE_LS_SLUG ) + esc_html__('settings', WE_LS_SLUG ) ); } diff --git a/pro-features/plus/macronutrient-calculator.php b/pro-features/plus/macronutrient-calculator.php index 86d8df73..894d16e6 100644 --- a/pro-features/plus/macronutrient-calculator.php +++ b/pro-features/plus/macronutrient-calculator.php @@ -125,7 +125,7 @@ function ws_ls_macro_render_table($user_id, $missing_data_text = false, $additio $macros = ws_ls_macro_calculate( $user_id ); } - $missing_data_text = ( false === $missing_data_text ) ? __('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG) : $missing_data_text; + $missing_data_text = ( false === $missing_data_text ) ? esc_html__('Please ensure all relevant data to calculate calorie intake has been entered i.e. Activity Level, Date of Birth, Current Weight, Gender and Height.', WE_LS_SLUG) : $missing_data_text; if ( false === empty( $macros ) ) { @@ -154,11 +154,11 @@ function ws_ls_macro_render_table($user_id, $missing_data_text = false, $additio ', ws_ls_get_macro_name( $key ), ws_ls_round_number( $macros[$key]['calories'] ), - __('Total', WE_LS_SLUG), - __('Breakfast', WE_LS_SLUG), - __('Lunch', WE_LS_SLUG), - __('Dinner', WE_LS_SLUG), - __('Snacks', WE_LS_SLUG) + esc_html__('Total', WE_LS_SLUG), + esc_html__('Breakfast', WE_LS_SLUG), + esc_html__('Lunch', WE_LS_SLUG), + esc_html__('Dinner', WE_LS_SLUG), + esc_html__('Snacks', WE_LS_SLUG) ); // Protein @@ -170,7 +170,7 @@ function ws_ls_macro_render_table($user_id, $missing_data_text = false, $additio
      %s %s
      %s %s
      %s %s
      ' . __('Name', WE_LS_SLUG) . '' . __('Weight Difference', WE_LS_SLUG) . '' . esc_html__('Name', WE_LS_SLUG) . '' . esc_html__('Weight Difference', WE_LS_SLUG) . '+/-' . __('No of entries', WE_LS_SLUG) . '' . esc_html__('No of entries', WE_LS_SLUG) . '