From 7807c0366aaa30a7ef68531362d4123a0c11539f Mon Sep 17 00:00:00 2001 From: Aaron Opell Date: Tue, 10 Mar 2020 11:59:12 -0700 Subject: [PATCH 1/9] Minor manifest updates --- manifest.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index f33f5274..7fa04e06 100644 --- a/manifest.json +++ b/manifest.json @@ -2,11 +2,10 @@ "manifest_version": 2, "name": "Schoology Plus", "short_name": "Schoology+", - "description": "Schoology Plus enhances your LAUSD Schoology experience with numerous interface improvements", + "description": "Schoology Plus enhances your Schoology experience with numerous interface improvements", "applications": { "gecko": { - "id": "schoology.plus@aopell.me", - "update_url": "https://aopell.me/SchoologyPlus/update.json" + "id": "schoology.plus@aopell.me" } }, "version": "6.0", From 73a355712cf5199dd541c328c3b609e8f13549d3 Mon Sep 17 00:00:00 2001 From: Aaron Opell Date: Tue, 10 Mar 2020 14:08:35 -0700 Subject: [PATCH 2/9] Documentation updates --- README.md | 4 ++-- themes/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 229d2cac..7980af2b 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ [![Chrome Download](https://img.shields.io/chrome-web-store/v/fbfppoaockpecjpbdmldojdehdpepfef.svg?label=chrome%20download)](https://chrome.google.com/webstore/detail/schoology-plus/fbfppoaockpecjpbdmldojdehdpepfef) [![Chrome Web Store Users](https://img.shields.io/chrome-web-store/users/fbfppoaockpecjpbdmldojdehdpepfef.svg)](https://chrome.google.com/webstore/detail/schoology-plus/fbfppoaockpecjpbdmldojdehdpepfef) [![Chrome Web Store Rating](https://img.shields.io/chrome-web-store/rating/fbfppoaockpecjpbdmldojdehdpepfef.svg)](https://chrome.google.com/webstore/detail/schoology-plus/fbfppoaockpecjpbdmldojdehdpepfef) -[![Firefox Download](https://img.shields.io/badge/dynamic/json.svg?label=firefox%20download&url=https%3A%2F%2Faopell.me%2FSchoologyPlus%2Fupdate.json&query=%24.addons%5B%27schoology.plus%40aopell.me%27%5D.updates%5B0%5D.version&colorB=orange)](http://aopell.me/SchoologyPlus/firefox-download.html) +[![Mozilla Add-on](https://img.shields.io/amo/v/schoology-plus?color=orange&label=firefox%20download)](https://addons.mozilla.org/en-US/firefox/addon/schoology-plus/) [![Discord](https://img.shields.io/discord/526898202495025172.svg?color=7289da&label=discord)](https://aopell.github.io/SchoologyPlus/discord.html) [![Changelog](https://img.shields.io/github/release/aopell/SchoologyPlus.svg?label=changelog&colorB=lightgrey)](https://aopell.me/SchoologyPlus/changelog) [![Download for Chrome](https://developer.chrome.com/webstore/images/ChromeWebStore_Badge_v2_206x58.png)](https://chrome.google.com/webstore/detail/schoology-plus/fbfppoaockpecjpbdmldojdehdpepfef) -[Download for Firefox](http://aopell.me/SchoologyPlus/firefox-download.html) +[Download for Firefox](https://addons.mozilla.org/en-US/firefox/addon/schoology-plus/) #### Contents - [Features](#features) diff --git a/themes/README.md b/themes/README.md index 13b4d7b8..51a396af 100644 --- a/themes/README.md +++ b/themes/README.md @@ -109,7 +109,7 @@ A Schoology Plus theme has the following format and components (each component w |Key|`hue` |Value Type|`number`| |Description|The theme's HSL color hue, used to color the interface by modifying the saturation and lightness values. The default Schoology Plus theme uses hue 210, and this value will be used by default if no color definitions are present. -|Value Restrictions|Hues are integers between 0 and 359, however decimal numbers still work and numbers over 359 are subject to a modus of 360 (i.e. actual hue value will be `providedHueValue % 360`). +|Value Restrictions|Hues are integers between 0 and 359, however decimal numbers still work and numbers over 359 are subject to a modulus of 360 (i.e. actual hue value will be `providedHueValue % 360`). |Special Notes|N/A| **Example** ```json @@ -243,7 +243,7 @@ A Schoology Plus theme has the following format and components (each component w |Default Value|Schoology Plus default course icon set |Description|An array of two-key objects, where the key `regex` is a regular expression and the key `url` is an image URL to be used as an icon for courses with names matching the regular expression. |Value Restrictions|An array of objects where all values of `regex` keys are valid regular expressions and all values of `url` keys are direct image links. Images should be square and at least `32x32` in size, but this is not required. -|Special Notes|Course names are checked against regular expressions in array order, meaning the regexes in the objects with lower indecies in the array are checked first *in a non-case-sensitive manner*. If no regular expression matches a specific course, Schoology Plus will fallback to the default Schoology Plus icon set. If you want to prevent this behavior, add an entry such as `".": "https://example.com/my-image.png"` that will match all course titles. +|Special Notes|Course names are checked against regular expressions in array order, meaning the regexes in the objects with lower indices in the array are checked first *in a non-case-sensitive manner*. If no regular expression matches a specific course, Schoology Plus will fallback to the default Schoology Plus icon set. If you want to prevent this behavior, add an entry such as `{ regex: ".", url: "https://example.com/my-image.png" }` that will match all course titles. **Example** ```json "icons": [ From 8bc586923569895f06c9b890250fcd405cd17bc2 Mon Sep 17 00:00:00 2001 From: Aaron Opell Date: Sat, 21 Mar 2020 18:41:27 -0700 Subject: [PATCH 3/9] Fixed issue where some text was white --- css/all.css | 6 ++++-- js/version-specific.js | 10 ---------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/css/all.css b/css/all.css index cea82376..a0609b90 100644 --- a/css/all.css +++ b/css/all.css @@ -507,7 +507,8 @@ body .grading-groups-list .grading-group, body #grading-group- span.ajax-post-comment, .period-row .title, -.category-row .title { +.category-row .title, +._2mWUT a { color: var(--hover-color) !important; } @@ -518,6 +519,7 @@ body a.link-btn { footer li a, header li a, .splus-modal-footer-text a, -body a.link-btn.active { +body a.link-btn.active, +a._3_bfp { color: white !important; } \ No newline at end of file diff --git a/js/version-specific.js b/js/version-specific.js index 65fb8aa7..83c09983 100644 --- a/js/version-specific.js +++ b/js/version-specific.js @@ -183,16 +183,6 @@ let migrationsTo = { new Date(2019, 1 /* February - don't you just love JavaScript */, 14) ) ]); - }, - "5.7": function(currentVersion, previousVersion) { - saveBroadcasts([ - createBroadcast( - 570, - 'Leave a review for Schoology Plus!', - '
Do you love Schoology Plus?
If so, we\'d really appreciate if you\'d leave us a review on the Chrome Web Store!

Click here to visit the page for Schoology Plus on the Chrome Web Store
', - new Date(2019, 11 /* December */, 11) - ) - ]); } }; From a2a7f64faf9fd02ed3b4d9e21ad223a402fe1fad Mon Sep 17 00:00:00 2001 From: Aaron Opell Date: Sat, 21 Mar 2020 18:47:38 -0700 Subject: [PATCH 4/9] Made 'Restore Defaults' red again --- css/all.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/css/all.css b/css/all.css index a0609b90..b299b7b4 100644 --- a/css/all.css +++ b/css/all.css @@ -254,7 +254,7 @@ video.easter-egg { } .restore-defaults { - color: red; + color: red !important; margin-top: 10px; font-weight: normal; } From 14c3a74e424c5cd9175baefa9c038c4b779e4e92 Mon Sep 17 00:00:00 2001 From: Aaron Opell Date: Mon, 23 Mar 2020 16:20:07 -0700 Subject: [PATCH 5/9] Added DOMPurify per recommendation of Mozilla --- js/background.js | 2 +- lib/js/purify.min.js | 3 +++ manifest.json | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 lib/js/purify.min.js diff --git a/js/background.js b/js/background.js index 9ae2707b..7a918a59 100644 --- a/js/background.js +++ b/js/background.js @@ -241,7 +241,7 @@ function loadAssignmentNotifications(storageContent) { timeModified = true; } let div = document.querySelector("div") || document.body.appendChild(document.createElement("div")); - div.innerHTML = response.output; + div.innerHTML = DOMPurify.sanitize(response.output); let notifications = div.querySelectorAll(".edge-sentence"); let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; let totalAssignments = 0; diff --git a/lib/js/purify.min.js b/lib/js/purify.min.js new file mode 100644 index 00000000..f7554110 --- /dev/null +++ b/lib/js/purify.min.js @@ -0,0 +1,3 @@ +/*! DOMPurify | (c) Cure53 and other contributors | github.com/cure53/DOMPurify/blob/master/LICENSE */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.DOMPurify=t()}(this,function(){"use strict";var n=Object.hasOwnProperty,i=Object.setPrototypeOf,a=Object.isFrozen,be=Object.keys,Te=Object.freeze,e=Object.seal,t="undefined"!=typeof Reflect&&Reflect,l=t.apply,o=t.construct;l=l||function(e,t,r){return e.apply(t,r)},Te=Te||function(e){return e},e=e||function(e){return e},o=o||function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t/gm),Ye=e(/^data-[\-\w.\u00B7-\uFFFF]/),Xe=e(/^aria-[\-\w]+$/),$e=e(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),Je=e(/^(?:\w+script|data):/i),Qe=e(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g),Ze="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function et(e){if(Array.isArray(e)){for(var t=0,r=Array(e.length);t

').querySelector("svg img")&&(a=!0)}catch(e){}}(),function(){try{var e=O("</title><img>");De(/<\/title/,e.querySelector("title").innerHTML)&&(l=!0)}catch(e){}}());function fe(e){return x.call(e.ownerDocument||e,e,n.SHOW_ELEMENT|n.SHOW_COMMENT|n.SHOW_TEXT,function(){return n.FILTER_ACCEPT},!1)}function pe(e){return"object"===(void 0===p?"undefined":Ze(p))?e instanceof p:e&&"object"===(void 0===e?"undefined":Ze(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName}function me(e,t,r){k[e]&&Ae(k[e],function(e){e.call(d,t,r,ue)})}function ye(e){var t,r=void 0;if(me("beforeSanitizeElements",e,null),!((t=e)instanceof m||t instanceof y||"string"==typeof t.nodeName&&"string"==typeof t.textContent&&"function"==typeof t.removeChild&&t.attributes instanceof i&&"function"==typeof t.removeAttribute&&"function"==typeof t.setAttribute&&"string"==typeof t.namespaceURI))return _(e),1;var n=Me(e.nodeName);if(me("uponSanitizeElement",e,{tagName:n,allowedTags:I}),("svg"===n||"math"===n)&&0!==e.querySelectorAll("p, br").length)return _(e),1;if(I[n]&&!W[n])return"noscript"===n&&De(/<\/noscript/i,e.innerHTML)||"noembed"===n&&De(/<\/noembed/i,e.innerHTML)?(_(e),1):(!V||e.firstElementChild||e.content&&e.content.firstElementChild||!De(/</g,e.textContent)||(Ee(d.removed,{element:e.cloneNode()}),e.innerHTML?e.innerHTML=Ne(e.innerHTML,/</g,"<"):e.innerHTML=Ne(e.textContent,/</g,"<")),Y&&3===e.nodeType&&(r=e.textContent,r=Ne(r,w," "),r=Ne(r,D," "),e.textContent!==r&&(Ee(d.removed,{element:e.cloneNode()}),e.textContent=r)),me("afterSanitizeElements",e,null),0);if(ne&&!ae[n]&&"function"==typeof e.insertAdjacentHTML)try{var o=e.innerHTML;e.insertAdjacentHTML("AfterEnd",b?b.createHTML(o):o)}catch(e){}return _(e),1}function ge(e,t,r){if(re&&("id"===t||"name"===t)&&(r in c||r in de))return!1;if(!(G&&De(R,t)||q&&De(H,t))){if(!U[t]||B[t])return!1;if(!ce[t]&&!De(z,Ne(r,F,""))&&("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==Oe(r,"data:")||!le[e])&&(!K||De(C,Ne(r,F,"")))&&r)return!1}return!0}function he(e){var t=void 0,r=void 0,n=void 0,o=void 0,i=void 0;me("beforeSanitizeAttributes",e,null);var a=e.attributes;if(a){var l={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:U};for(i=a.length;i--;){var c=(t=a[i]).name,s=t.namespaceURI;if(r=we(t.value),n=Me(c),l.attrName=n,l.attrValue=r,l.keepAttr=!0,l.forceKeepAttr=void 0,me("uponSanitizeAttribute",e,l),r=l.attrValue,!l.forceKeepAttr){if("name"===n&&"IMG"===e.nodeName&&a.id)o=a.id,a=ke(a,[]),N("id",e),N(c,e),xe(a,o)>i&&e.setAttribute("id",o.value);else{if("INPUT"===e.nodeName&&"type"===n&&"file"===r&&l.keepAttr&&(U[n]||!B[n]))continue;"id"===c&&e.setAttribute(c,""),N(c,e)}if(l.keepAttr)if(V&&De(/\/>/i,r))N(c,e);else if(De(/svg|math/i,e.namespaceURI)&&De(Re("</("+Se(be(ae),"|")+")","i"),r))N(c,e);else{Y&&(r=Ne(r,w," "),r=Ne(r,D," "));var u=e.nodeName.toLowerCase();if(ge(u,n,r))try{s?e.setAttributeNS(s,c,r):e.setAttribute(c,r),Le(d.removed)}catch(e){}}}}me("afterSanitizeAttributes",e,null)}}function ve(e){var t=void 0,r=fe(e);for(me("beforeSanitizeShadowDOM",e,null);t=r.nextNode();)me("uponSanitizeShadowNode",t,null),ye(t)||(t.content instanceof f&&ve(t.content),he(t));me("afterSanitizeShadowDOM",e,null)}return d.sanitize=function(e,t){var r=void 0,n=void 0,o=void 0,i=void 0,a=void 0;if("string"!=typeof(e=e||"\x3c!--\x3e")&&!pe(e)){if("function"!=typeof e.toString)throw He("toString is not a function");if("string"!=typeof(e=e.toString()))throw He("dirty is not a string, aborting")}if(!d.isSupported){if("object"===Ze(s.toStaticHTML)||"function"==typeof s.toStaticHTML){if("string"==typeof e)return s.toStaticHTML(e);if(pe(e))return s.toStaticHTML(e.outerHTML)}return e}if($||M(t),d.removed=[],"string"==typeof e&&(oe=!1),!oe)if(e instanceof p)1===(n=(r=O("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===n.nodeName||"HTML"===n.nodeName?r=n:r.appendChild(n);else{if(!Q&&!Y&&!X&&te&&-1===e.indexOf("<"))return b?b.createHTML(e):e;if(!(r=O(e)))return Q?null:T}r&&J&&_(r.firstChild);for(var l=fe(oe?e:r);o=l.nextNode();)3===o.nodeType&&o===i||ye(o)||(o.content instanceof f&&ve(o.content),he(o),i=o);if(i=null,oe)return e;if(Q){if(Z)for(a=L.call(r.ownerDocument);r.firstChild;)a.appendChild(r.firstChild);else a=r;return ee&&(a=E.call(u,a,!0)),a}var c=X?r.outerHTML:r.innerHTML;return Y&&(c=Ne(c,w," "),c=Ne(c,D," ")),b&&te?b.createHTML(c):c},d.setConfig=function(e){M(e),$=!0},d.clearConfig=function(){ue=null,$=!1},d.isValidAttribute=function(e,t,r){ue||M({});var n=Me(e),o=Me(t);return ge(n,o,r)},d.addHook=function(e,t){"function"==typeof t&&(k[e]=k[e]||[],Ee(k[e],t))},d.removeHook=function(e){k[e]&&Le(k[e])},d.removeHooks=function(e){k[e]&&(k[e]=[])},d.removeAllHooks=function(){k={}},d}()}); +//# sourceMappingURL=purify.min.js.map diff --git a/manifest.json b/manifest.json index 7fa04e06..7fd7dfce 100644 --- a/manifest.json +++ b/manifest.json @@ -46,6 +46,7 @@ "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", "background": { "scripts": [ + "lib/js/purify.min.js", "js/background.js" ], "persistent": true From 479780f4132e53d09add9cf55bd6479b58ab588d Mon Sep 17 00:00:00 2001 From: Aaron Opell <aaron.opell@live.com> Date: Mon, 23 Mar 2020 17:24:13 -0700 Subject: [PATCH 6/9] Removed usafe-eval; updated blackslist --- css/all.css | 2 +- js/all.js | 2 +- lib/js/jquery.tipsy.min.js | 3 +- lib/js/roundslider.js | 1613 ++++++++++++++++++++++++++++++++++++ lib/js/roundslider.min.js | 2 - manifest.json | 15 +- theme-editor.html | 2 +- 7 files changed, 1631 insertions(+), 8 deletions(-) create mode 100644 lib/js/roundslider.js delete mode 100644 lib/js/roundslider.min.js diff --git a/css/all.css b/css/all.css index b299b7b4..aa3e68bd 100644 --- a/css/all.css +++ b/css/all.css @@ -273,7 +273,7 @@ input[type=text].setting-item { } .close-button { - color: red; + color: red !important; font-weight: normal; font-size: 20px; } diff --git a/js/all.js b/js/all.js index 7f447e61..a5d96938 100644 --- a/js/all.js +++ b/js/all.js @@ -18,7 +18,7 @@ // Check Schoology domain { - const BLACKLISTED_DOMAINS = ["asset-cdn.schoology.com", "www.schoology.com", "schoology.com"]; + const BLACKLISTED_DOMAINS = ["asset-cdn.schoology.com", "ui.schoology.com", "www.schoology.com", "schoology.com"]; let dd = Setting.getValue("defaultDomain"); if (dd !== window.location.host && !BLACKLISTED_DOMAINS.includes(window.location.host)) { diff --git a/lib/js/jquery.tipsy.min.js b/lib/js/jquery.tipsy.min.js index ca0c1903..a35dcae5 100644 --- a/lib/js/jquery.tipsy.min.js +++ b/lib/js/jquery.tipsy.min.js @@ -4,5 +4,4 @@ // released under the MIT license // NOTE: no need to include CSS, Schoology does it for us -!function(t,e,i){function s(t,e){return"function"==typeof t?t.call(e):t}function o(t){for(;t=t.parentNode;)if(t==document)return!0;return!1}function n(t){return"object"==typeof HTMLElement?t instanceof HTMLElement:t&&"object"==typeof t&&1===t.nodeType&&"string"==typeof t.nodeName}function l(){return"tipsyuid"+h++}function a(e,i){this.$element=t(e),this.options=i,this.enabled=!0,this.fixTitle()}var h=0;a.prototype={show:function(){if(o(this.$element[0])&&(!n(this.$element)||this.$element.is(":visible"))){var e;if(this.enabled&&(e=this.getTitle())){var i=this.tip();i.find(".tipsy-inner"+this.options.theme)[this.options.html?"html":"text"](e),i[0].className="tipsy"+this.options.theme,this.options.className&&i.addClass(s(this.options.className,this.$element[0])),i.remove().css({top:0,left:0,visibility:"hidden",display:"block"}).prependTo(document.body);var a=t.extend({},this.$element.offset());a=this.$element.parents("svg").size()>0?t.extend(a,this.$element[0].getBBox()):t.extend(a,{width:this.$element[0].offsetWidth||0,height:this.$element[0].offsetHeight||0});var h,f=i[0].offsetWidth,r=i[0].offsetHeight,p=s(this.options.gravity,this.$element[0]);switch(p.charAt(0)){case"n":h={top:a.top+a.height+this.options.offset,left:a.left+a.width/2-f/2};break;case"s":h={top:a.top-r-this.options.offset,left:a.left+a.width/2-f/2};break;case"e":h={top:a.top+a.height/2-r/2,left:a.left-f-this.options.offset};break;case"w":h={top:a.top+a.height/2-r/2,left:a.left+a.width+this.options.offset}}if(2==p.length&&("w"==p.charAt(1)?h.left=a.left+a.width/2-15:h.left=a.left+a.width/2-f+15),i.css(h).addClass("tipsy-"+p+this.options.theme),i.find(".tipsy-arrow"+this.options.theme)[0].className="tipsy-arrow"+this.options.theme+" tipsy-arrow-"+p.charAt(0)+this.options.theme,this.options.fade?(this.options.shadow&&t(".tipsy-inner").css({"box-shadow":"0px 0px "+this.options.shadowBlur+"px "+this.options.shadowSpread+"px rgba(0, 0, 0, "+this.options.shadowOpacity+")","-webkit-box-shadow":"0px 0px "+this.options.shadowBlur+"px "+this.options.shadowSpread+"px rgba(0, 0, 0, "+this.options.shadowOpacity+")"}),i.stop().css({opacity:0,display:"block",visibility:"visible"}).animate({opacity:this.options.opacity},this.options.fadeInTime)):i.css({visibility:"visible",opacity:this.options.opacity}),this.options.aria){var d=l();i.attr("id",d),this.$element.attr("aria-describedby",d)}}}},hide:function(){this.options.fade?this.tip().stop().fadeOut(this.options.fadeOutTime,function(){t(this).remove()}):this.tip().remove(),this.options.aria&&this.$element.removeAttr("aria-describedby")},fixTitle:function(){var t=this.$element,e=s(this.options.id,this.$element[0]);(t.prop("title")||"string"!=typeof t.prop("original-title"))&&(t.prop("original-title",t.prop("title")||"").removeAttr("title"),t.attr("aria-describedby",e),t.attr("tabindex")===i&&t.attr("tabindex",0))},getTitle:function(){var t,e=this.$element,i=this.options;return this.fixTitle(),"string"==typeof i.title?t=e.prop("title"==i.title?"original-title":i.title):"function"==typeof i.title&&(t=i.title.call(e[0])),t=(""+t).replace(/(^\s*|\s*$)/,""),t||i.fallback},tip:function(){var e=s(this.options.id,this.$element[0]);return this.$tip||(this.$tip=t('<div class="tipsy'+this.options.theme+'" id="'+e+'" role="tooltip"></div>').html('<div class="tipsy-arrow'+this.options.theme+'"></div><div class="tipsy-inner'+this.options.theme+'"></div>').attr("role","tooltip"),this.$tip.data("tipsy-pointee",this.$element[0])),this.$tip},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled}},t.fn.tipsy=function(e){function i(i){var s=t.data(i,"tipsy");return s||(s=new a(i,t.fn.tipsy.elementOptions(i,e)),t.data(i,"tipsy",s)),s}function s(){if(t.fn.tipsy.enabled===!0){var s=i(this);s.hoverState="in",0===e.delayIn?s.show():(s.fixTitle(),setTimeout(function(){"in"==s.hoverState&&o(s.$element)&&s.show()},e.delayIn))}}function n(){var t=i(this);t.hoverState="out",0===e.delayOut?t.hide():setTimeout(function(){"out"!=t.hoverState&&t.$element&&t.$element.is(":visible")||t.hide()},e.delayOut)}if(t.fn.tipsy.enable(),e===!0)return this.data("tipsy");if("string"==typeof e){var l=this.data("tipsy");return l&&l[e](),this}if(e=t.extend({},t.fn.tipsy.defaults,e),e.theme=e.theme&&""!==e.theme?"-"+e.theme:"",e.live||this.each(function(){i(this)}),"manual"!=e.trigger)if(e.live&&e.live!==!0)"focus"!=e.trigger&&(t(this).on("mouseenter",e.live,s),t(this).on("mouseleave",e.live,n)),"blur"!=e.trigger&&(t(this).on("focus",e.live,s),t(this).on("blur",e.live,n));else{if(e.live&&!t.live)throw"Since jQuery 1.9, pass selector as live argument. eg. $(document).tipsy({live: 'a.live'});";var h=e.live?"live":"bind";"focus"!=e.trigger&&this[h]("mouseenter",s)[h]("mouseleave",n),"blur"!=e.trigger&&this[h]("focus",s)[h]("blur",n)}return this},t.fn.tipsy.defaults={aria:!1,className:null,id:"tipsy",delayIn:0,delayOut:0,fade:!1,fadeInTime:400,fadeOutTime:400,shadow:!1,shadowBlur:8,shadowOpacity:1,shadowSpread:0,fallback:"",gravity:"n",html:!1,live:!1,offset:0,opacity:.8,title:"title",trigger:"interactive",theme:""},t.fn.tipsy.revalidate=function(){t(".tipsy").each(function(){var e=t.data(this,"tipsy-pointee");e&&o(e)||t(this).remove()})},t.fn.tipsy.enable=function(){t.fn.tipsy.enabled=!0},t.fn.tipsy.disable=function(){t.fn.tipsy.enabled=!1},t.fn.tipsy.elementOptions=function(e,i){return t.metadata?t.extend({},i,t(e).metadata()):i},t.fn.tipsy.autoNS=function(){return t(this).offset().top>t(document).scrollTop()+t(e).height()/2?"s":"n"},t.fn.tipsy.autoWE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"e":"w"},t.fn.tipsy.autoNWNE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"ne":"nw"},t.fn.tipsy.autoSWSE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"se":"sw"},t.fn.tipsy.autoBounds=function(i,s,o){return function(){var n={ns:o[0],ew:o.length>1?o[1]:!1},l=t(document).scrollTop()+i,a=t(document).scrollLeft()+s,h=t(this);return h.offset().top<l&&(n.ns="n"),h.offset().left<a&&(n.ew="w"),t(e).width()+t(document).scrollLeft()-h.offset().left<s&&(n.ew="e"),t(e).height()+t(document).scrollTop()-h.offset().top<i&&(n.ns="s"),n.ns+(n.ew?n.ew:"")}},t.fn.tipsy.autoBounds2=function(i,s){return function(){var o={},n=t(document).scrollTop()+i,l=t(document).scrollLeft()+i,a=t(this);return s.length>1?(o.ns=s[0],o.ew=s[1]):"e"==s[0]||"w"==s[0]?o.ew=s[0]:o.ns=s[0],a.offset().top<n&&(o.ns="n"),a.offset().left<l&&(o.ew="w"),t(e).width()+t(document).scrollLeft()-(a.offset().left+a.width())<i&&(o.ew="e"),t(e).height()+t(document).scrollTop()-(a.offset().top+a.height())<i&&(o.ns="s"),o.ns?o.ns+(o.ew?o.ew:""):o.ew}}}(jQuery,window); -//# sourceMappingURL=./jquery.tipsy.min.js.map \ No newline at end of file +!function(t,e,i){function s(t,e){return"function"==typeof t?t.call(e):t}function o(t){for(;t=t.parentNode;)if(t==document)return!0;return!1}function n(t){return"object"==typeof HTMLElement?t instanceof HTMLElement:t&&"object"==typeof t&&1===t.nodeType&&"string"==typeof t.nodeName}function l(){return"tipsyuid"+h++}function a(e,i){this.$element=t(e),this.options=i,this.enabled=!0,this.fixTitle()}var h=0;a.prototype={show:function(){if(o(this.$element[0])&&(!n(this.$element)||this.$element.is(":visible"))){var e;if(this.enabled&&(e=this.getTitle())){var i=this.tip();i.find(".tipsy-inner"+this.options.theme)[this.options.html?"html":"text"](e),i[0].className="tipsy"+this.options.theme,this.options.className&&i.addClass(s(this.options.className,this.$element[0])),i.remove().css({top:0,left:0,visibility:"hidden",display:"block"}).prependTo(document.body);var a=t.extend({},this.$element.offset());a=this.$element.parents("svg").size()>0?t.extend(a,this.$element[0].getBBox()):t.extend(a,{width:this.$element[0].offsetWidth||0,height:this.$element[0].offsetHeight||0});var h,f=i[0].offsetWidth,r=i[0].offsetHeight,p=s(this.options.gravity,this.$element[0]);switch(p.charAt(0)){case"n":h={top:a.top+a.height+this.options.offset,left:a.left+a.width/2-f/2};break;case"s":h={top:a.top-r-this.options.offset,left:a.left+a.width/2-f/2};break;case"e":h={top:a.top+a.height/2-r/2,left:a.left-f-this.options.offset};break;case"w":h={top:a.top+a.height/2-r/2,left:a.left+a.width+this.options.offset}}if(2==p.length&&("w"==p.charAt(1)?h.left=a.left+a.width/2-15:h.left=a.left+a.width/2-f+15),i.css(h).addClass("tipsy-"+p+this.options.theme),i.find(".tipsy-arrow"+this.options.theme)[0].className="tipsy-arrow"+this.options.theme+" tipsy-arrow-"+p.charAt(0)+this.options.theme,this.options.fade?(this.options.shadow&&t(".tipsy-inner").css({"box-shadow":"0px 0px "+this.options.shadowBlur+"px "+this.options.shadowSpread+"px rgba(0, 0, 0, "+this.options.shadowOpacity+")","-webkit-box-shadow":"0px 0px "+this.options.shadowBlur+"px "+this.options.shadowSpread+"px rgba(0, 0, 0, "+this.options.shadowOpacity+")"}),i.stop().css({opacity:0,display:"block",visibility:"visible"}).animate({opacity:this.options.opacity},this.options.fadeInTime)):i.css({visibility:"visible",opacity:this.options.opacity}),this.options.aria){var d=l();i.attr("id",d),this.$element.attr("aria-describedby",d)}}}},hide:function(){this.options.fade?this.tip().stop().fadeOut(this.options.fadeOutTime,function(){t(this).remove()}):this.tip().remove(),this.options.aria&&this.$element.removeAttr("aria-describedby")},fixTitle:function(){var t=this.$element,e=s(this.options.id,this.$element[0]);(t.prop("title")||"string"!=typeof t.prop("original-title"))&&(t.prop("original-title",t.prop("title")||"").removeAttr("title"),t.attr("aria-describedby",e),t.attr("tabindex")===i&&t.attr("tabindex",0))},getTitle:function(){var t,e=this.$element,i=this.options;return this.fixTitle(),"string"==typeof i.title?t=e.prop("title"==i.title?"original-title":i.title):"function"==typeof i.title&&(t=i.title.call(e[0])),t=(""+t).replace(/(^\s*|\s*$)/,""),t||i.fallback},tip:function(){var e=s(this.options.id,this.$element[0]);return this.$tip||(this.$tip=t('<div class="tipsy'+this.options.theme+'" id="'+e+'" role="tooltip"></div>').html('<div class="tipsy-arrow'+this.options.theme+'"></div><div class="tipsy-inner'+this.options.theme+'"></div>').attr("role","tooltip"),this.$tip.data("tipsy-pointee",this.$element[0])),this.$tip},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled}},t.fn.tipsy=function(e){function i(i){var s=t.data(i,"tipsy");return s||(s=new a(i,t.fn.tipsy.elementOptions(i,e)),t.data(i,"tipsy",s)),s}function s(){if(t.fn.tipsy.enabled===!0){var s=i(this);s.hoverState="in",0===e.delayIn?s.show():(s.fixTitle(),setTimeout(function(){"in"==s.hoverState&&o(s.$element)&&s.show()},e.delayIn))}}function n(){var t=i(this);t.hoverState="out",0===e.delayOut?t.hide():setTimeout(function(){"out"!=t.hoverState&&t.$element&&t.$element.is(":visible")||t.hide()},e.delayOut)}if(t.fn.tipsy.enable(),e===!0)return this.data("tipsy");if("string"==typeof e){var l=this.data("tipsy");return l&&l[e](),this}if(e=t.extend({},t.fn.tipsy.defaults,e),e.theme=e.theme&&""!==e.theme?"-"+e.theme:"",e.live||this.each(function(){i(this)}),"manual"!=e.trigger)if(e.live&&e.live!==!0)"focus"!=e.trigger&&(t(this).on("mouseenter",e.live,s),t(this).on("mouseleave",e.live,n)),"blur"!=e.trigger&&(t(this).on("focus",e.live,s),t(this).on("blur",e.live,n));else{if(e.live&&!t.live)throw"Since jQuery 1.9, pass selector as live argument. eg. $(document).tipsy({live: 'a.live'});";var h=e.live?"live":"bind";"focus"!=e.trigger&&this[h]("mouseenter",s)[h]("mouseleave",n),"blur"!=e.trigger&&this[h]("focus",s)[h]("blur",n)}return this},t.fn.tipsy.defaults={aria:!1,className:null,id:"tipsy",delayIn:0,delayOut:0,fade:!1,fadeInTime:400,fadeOutTime:400,shadow:!1,shadowBlur:8,shadowOpacity:1,shadowSpread:0,fallback:"",gravity:"n",html:!1,live:!1,offset:0,opacity:.8,title:"title",trigger:"interactive",theme:""},t.fn.tipsy.revalidate=function(){t(".tipsy").each(function(){var e=t.data(this,"tipsy-pointee");e&&o(e)||t(this).remove()})},t.fn.tipsy.enable=function(){t.fn.tipsy.enabled=!0},t.fn.tipsy.disable=function(){t.fn.tipsy.enabled=!1},t.fn.tipsy.elementOptions=function(e,i){return t.metadata?t.extend({},i,t(e).metadata()):i},t.fn.tipsy.autoNS=function(){return t(this).offset().top>t(document).scrollTop()+t(e).height()/2?"s":"n"},t.fn.tipsy.autoWE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"e":"w"},t.fn.tipsy.autoNWNE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"ne":"nw"},t.fn.tipsy.autoSWSE=function(){return t(this).offset().left>t(document).scrollLeft()+t(e).width()/2?"se":"sw"},t.fn.tipsy.autoBounds=function(i,s,o){return function(){var n={ns:o[0],ew:o.length>1?o[1]:!1},l=t(document).scrollTop()+i,a=t(document).scrollLeft()+s,h=t(this);return h.offset().top<l&&(n.ns="n"),h.offset().left<a&&(n.ew="w"),t(e).width()+t(document).scrollLeft()-h.offset().left<s&&(n.ew="e"),t(e).height()+t(document).scrollTop()-h.offset().top<i&&(n.ns="s"),n.ns+(n.ew?n.ew:"")}},t.fn.tipsy.autoBounds2=function(i,s){return function(){var o={},n=t(document).scrollTop()+i,l=t(document).scrollLeft()+i,a=t(this);return s.length>1?(o.ns=s[0],o.ew=s[1]):"e"==s[0]||"w"==s[0]?o.ew=s[0]:o.ns=s[0],a.offset().top<n&&(o.ns="n"),a.offset().left<l&&(o.ew="w"),t(e).width()+t(document).scrollLeft()-(a.offset().left+a.width())<i&&(o.ew="e"),t(e).height()+t(document).scrollTop()-(a.offset().top+a.height())<i&&(o.ns="s"),o.ns?o.ns+(o.ew?o.ew:""):o.ew}}}(jQuery,window); \ No newline at end of file diff --git a/lib/js/roundslider.js b/lib/js/roundslider.js new file mode 100644 index 00000000..da140f87 --- /dev/null +++ b/lib/js/roundslider.js @@ -0,0 +1,1613 @@ +/*! + * roundSlider v1.4.0 | (c) 2015-2020, Soundar + * MIT license | http://roundsliderui.com/licence.html + */ + +(function ($, window, undefined) { + "use strict"; + /*jslint nomen: true */ + + var pluginName = "roundSlider"; + + // The plugin initialization + $.fn[pluginName] = function (options) { + return CreateRoundSlider.call(this, options, arguments); + }; + + RoundSlider.prototype = { + + pluginName: pluginName, + version: "1.4.0", + + // after the control initialization the updated default values + // are merged into the options + options: {}, + + // holds the current roundSlider element + control: null, + + // default properties of the plugin. while add a new property, + // that type should be included in the "_props:" for validation + defaults: { + min: 0, + max: 100, + step: 1, + value: null, + radius: 85, + width: 18, + handleSize: "+0", + startAngle: 0, + endAngle: "+360", + animation: true, + showTooltip: true, + editableTooltip: true, + readOnly: false, + disabled: false, + keyboardAction: true, + mouseScrollAction: false, + lineCap: "butt", + sliderType: "default", + circleShape: "full", + handleShape: "round", + + // SVG related properties + svgMode: false, + borderWidth: 1, + borderColor: null, + pathColor: null, + rangeColor: null, + + // events + beforeCreate: null, + create: null, + start: null, + drag: null, + change: null, + stop: null, + tooltipFormat: null + }, + keys: { // key codes for + UP: 38, // up arrow + DOWN: 40, // down arrow + LEFT: 37, // left arrow + RIGHT: 39 // right arrow + }, + _props: function () { + return { + numberType: ["min", "max", "step", "radius", "width", "borderWidth", "startAngle"], + booleanType: ["animation", "showTooltip", "editableTooltip", "readOnly", "disabled", + "keyboardAction", "mouseScrollAction", "svgMode"], + stringType: ["sliderType", "circleShape", "handleShape", "lineCap"] + }; + }, + + _init: function () { + if (this.options.svgMode) { + var EMPTY_FUNCTION = function () { }; + this._appendSeperator = EMPTY_FUNCTION; + this._refreshSeperator = EMPTY_FUNCTION; + this._updateSeperator = EMPTY_FUNCTION; + this._appendOverlay = EMPTY_FUNCTION; + this._checkOverlay = EMPTY_FUNCTION; + this._updateWidth = EMPTY_FUNCTION; + } + + this._isBrowserSupport = this._isBrowserSupported(); + this._isKO = false; + this._isAngular = false; + if (this.control.is("input")) { + this._isInputType = true; + this._hiddenField = this.control; + this.control = this.$createElement("div"); + this.control.insertAfter(this._hiddenField); + this.options.value = this._hiddenField.val() || this.options.value; + var that = this; + this._checkKO() && setTimeout(function () { that._checkKO(); }, 1); + this._checkAngular(); + } + this._bindOnDrag = false; + var _updateOn = this._dataElement().attr("data-updateon"); + if (typeof _updateOn == "string") { if (_updateOn == "drag") this._bindOnDrag = true; } + else if (this._isAngular) this._bindOnDrag = true; + + this._onInit(); + }, + _onInit: function () { + this._initialize(); + this._update(); + this._render(); + }, + _initialize: function () { + var browserName = this.browserName = this.getBrowserName(); + if (browserName) this.control.addClass("rs-" + browserName); + if (!this._isBrowserSupport) return; + this._isReadOnly = false; + this._checkDataType(); + this._refreshCircleShape(); + }, + _render: function () { + this.container = this.$createElement("div.rs-container"); + this.innerContainer = this.$createElement("div.rs-inner-container"); + this.container.append(this.innerContainer); + var $rootCSS = "rs-control " + (this.options.svgMode ? "rs-svg-mode" : "rs-classic-mode"); + this.control.addClass($rootCSS).empty().append(this.container); + + if (this._isBrowserSupport) { + this._createLayers(); + this._createOtherLayers(); + this._setContainerClass(); + this._setRadius(); + this._setProperties(); + this._setValue(); + this._updateTooltipPos(); + this._bindControlEvents("_bind"); + } + else { + var msg = this.$createElement("div.rs-msg"); + msg.html(typeof this._throwError === "function" ? this._throwError() : this._throwError); + this.control.empty().addClass("rs-error").append(msg); + if (this._isInputType) this.control.append(this._dataElement()); + } + }, + _update: function () { + this._validateSliderType(); + this._updateStartEnd(); + this._validateStartEnd(); + this._handle1 = this._handle2 = this._handleDefaults(); + this._analyzeModelValue(); + this._validateModelValue(); + }, + _createLayers: function () { + if (this.options.svgMode) { + this._createSVGElements(); + this._setSVGAttributes(); + this._setSVGStyles(); + this._moveSliderRange(true); + return; + } + + this.block = this.$createElement("div.rs-block rs-outer rs-border"); + this.innerContainer.append(this.block); + + var padd = this.options.width, start = this._start, path; + path = this.$createElement("div.rs-path rs-transition"); + + if (this._rangeSlider || this._showRange) { + this.block1 = path.clone().addClass("rs-range-color").rsRotate(start); + this.block2 = path.clone().addClass("rs-range-color").css("opacity", "0").rsRotate(start); + this.block3 = path.clone().addClass("rs-path-color").rsRotate(start); + this.block4 = path.addClass("rs-path-color").css({ "opacity": "1", "z-index": "1" }).rsRotate(start - 180); + + this.block.append(this.block1, this.block2, this.block3, this.block4).addClass("rs-split"); + } + else this.block.append(path.addClass("rs-path-color")); + + this.lastBlock = this.$createElement("span.rs-block").css({ "padding": padd }); + this.innerBlock = this.$createElement("div.rs-inner rs-bg-color rs-border"); + this.lastBlock.append(this.innerBlock); + this.block.append(this.lastBlock); + }, + _createOtherLayers: function () { + this._appendHandle(); + this._appendSeperator(); // non SVG mode only + this._appendOverlay(); // non SVG mode only + this._appendHiddenField(); + }, + _setProperties: function () { + this._updatePre(); + this._setHandleShape(); + this._addAnimation(); + this._appendTooltip(); + if (!this.options.showTooltip) this._removeTooltip(); + if (this.options.disabled) this.disable(); + else if (this.options.readOnly) this._readOnly(true); + if (this.options.mouseScrollAction) this._bindScrollEvents("_bind"); + }, + _updatePre: function () { + this._prechange = this._predrag = this.options.value; + }, + _setValue: function () { + if (this._rangeSlider) { + this._setHandleValue(1); + this._setHandleValue(2); + } + else { + if (this._showRange) this._setHandleValue(1); + var index = (this.options.sliderType == "default") ? (this._active || 1) : parseFloat(this.bar.children().attr("index")); + this._setHandleValue(index); + } + }, + _appendTooltip: function () { + if (this.container.children(".rs-tooltip").length !== 0) return; + this.tooltip = this.$createElement("span.rs-tooltip rs-tooltip-text"); + this.container.append(this.tooltip); + this._tooltipEditable(); + this._updateTooltip(); + }, + _removeTooltip: function () { + if (this.container.children(".rs-tooltip").length == 0) return; + this.tooltip && this.tooltip.remove(); + }, + _tooltipEditable: function () { + var o = this.options, tooltip = this.tooltip, hook; + if (!tooltip || !o.showTooltip) return; + + if (o.editableTooltip) { + tooltip.addClass("edit"); + hook = "_bind"; + } + else { + tooltip.removeClass("edit"); + hook = "_unbind"; + } + this[hook](tooltip, "click", this._editTooltip); + }, + _editTooltip: function (e) { + var tooltip = this.tooltip; + if (!tooltip.hasClass("edit") || this._isReadOnly) return; + var border = parseFloat(tooltip.css("border-left-width")) * 2; + var input = this.input = this.$createElement("input.rs-input rs-tooltip-text").css({ + height: tooltip.outerHeight() - border, + width: tooltip.outerWidth() - border + }); + tooltip.html(input).removeClass("edit").addClass("hover"); + + input.focus().val(this._getTooltipValue(true)); + + this._bind(input, "blur", this._focusOut); + this._bind(input, "change", this._focusOut); + }, + _focusOut: function (e) { + if (e.type == "change") { + var val = this.input.val().replace("-", ","); + if (val[0] == ",") { + val = "-" + val.slice(1).replace("-", ","); + } + this.options.value = val; + this._analyzeModelValue(); + this._validateModelValue(); + this._setValue(); + this.input.val(this._getTooltipValue(true)); + } + else { + this.tooltip.addClass("edit").removeClass("hover"); + this._updateTooltip(); + } + this._raiseEvent("change"); + }, + _setHandleShape: function () { + var type = this.options.handleShape, allHandles = this._handles(); + allHandles.removeClass("rs-handle-dot rs-handle-square"); + if (type == "dot") allHandles.addClass("rs-handle-dot"); + else if (type == "square") allHandles.addClass("rs-handle-square"); + else this.options.handleShape = "round"; + }, + _setHandleValue: function (index) { + this._active = index; + var handle = this["_handle" + index]; + if (this.options.sliderType != "min-range") this.bar = this._activeHandleBar(); + this._changeSliderValue(handle.value, handle.angle); + }, + _setAnimation: function () { + if (this.options.animation) this._addAnimation(); + else this._removeAnimation(); + }, + _addAnimation: function () { + if (this.options.animation) this.control.addClass("rs-animation"); + }, + _removeAnimation: function () { + this.control.removeClass("rs-animation"); + }, + _setContainerClass: function () { + var circleShape = this.options.circleShape; + if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0) { + this.container.addClass("full " + circleShape); + } + else { + this.container.addClass(circleShape.split("-").join(" ")); + } + }, + _setRadius: function () { + var o = this.options, r = o.radius, d = r * 2, circleShape = o.circleShape; + var extraSize = 0, actualHeight, actualWidth; + var height = actualHeight = d, width = actualWidth = d; + + // whenever the radius changes, before update the container size + // check for the lineCap also, since that will make some additional size + // also, based on that need to align the handle bars + var isFullCircle = (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0); + if (o.svgMode && !isFullCircle) { + var handleBars = this._handleBars(); + if (o.lineCap != "none") { + extraSize = (o.lineCap === "butt") ? (o.borderWidth / 2) : ((o.width / 2) + o.borderWidth); + if (circleShape.indexOf("bottom") != -1) { + handleBars.css("margin-top", extraSize + 'px'); + } + if (circleShape.indexOf("right") != -1) { + handleBars.css("margin-right", -extraSize + 'px'); + } + } + else { + // when lineCap none, then remove the styles that was set previously for the other lineCap props + $.each(handleBars, function (i, bar) { + bar.style.removeProperty("margin-top"); + bar.style.removeProperty("margin-right"); + }); + } + } + + if (circleShape.indexOf("half") === 0) { + switch (circleShape) { + case "half-top": + case "half-bottom": + height = r; actualHeight = r + extraSize; + break; + case "half-left": + case "half-right": + width = r; actualWidth = r + extraSize; + break; + } + } + else if (circleShape.indexOf("quarter") === 0) { + height = width = r; + actualHeight = actualWidth = r + extraSize; + } + + this.container.css({ "height": height, "width": width }); + this.control.css({ "height": actualHeight, "width": actualWidth }); + + // when needed, then only we can set the styles through script, otherwise CSS styles applicable + if (extraSize !== 0) this.innerContainer.css({ "height": actualHeight, "width": actualWidth }); + else this.innerContainer.removeAttr("style"); + + if (o.svgMode) { + this.svgContainer.height(d).width(d); + this.svgContainer.children("svg").height(d).width(d); + } + }, + _border: function (seperator) { + if (seperator) return parseFloat(this._startLine.children().css("border-bottom-width")); + if (this.options.svgMode) return this.options.borderWidth * 2; + return parseFloat(this.block.css("border-top-width")) * 2; + }, + _appendHandle: function () { + if (this._rangeSlider || !this._showRange) this._createHandle(1); + if (this._rangeSlider || this._showRange) this._createHandle(2); + }, + _appendSeperator: function () { + this._startLine = this._addSeperator(this._start, "rs-start"); + this._endLine = this._addSeperator(this._start + this._end, "rs-end"); + this._refreshSeperator(); + }, + _addSeperator: function (pos, cls) { + var line = this.$createElement("span.rs-seperator rs-border"), width = this.options.width, _border = this._border(); + var lineWrap = this.$createElement("span.rs-bar rs-transition " + cls).append(line).rsRotate(pos); + this.container.append(lineWrap); + return lineWrap; + }, + _refreshSeperator: function () { + var bars = this._startLine.add(this._endLine), seperators = bars.children().removeAttr("style"); + var o = this.options, width = o.width, _border = this._border(), size = width + _border; + if (o.lineCap == "round" && o.circleShape != "full") { + bars.addClass("rs-rounded"); + seperators.css({ width: size, height: (size / 2) + 1 }); + this._startLine.children().css("margin-top", -1).addClass(o.sliderType == "min-range" ? "rs-range-color" : "rs-path-color"); + this._endLine.children().css("margin-top", size / -2).addClass("rs-path-color"); + } + else { + bars.removeClass("rs-rounded"); + seperators.css({ "width": size, "margin-top": this._border(true) / -2 }).removeClass("rs-range-color rs-path-color"); + } + }, + _updateSeperator: function () { + this._startLine.rsRotate(this._start); + this._endLine.rsRotate(this._start + this._end); + }, + _createHandle: function (index) { + var handle = this.$createElement("div.rs-handle rs-move"), o = this.options, hs; + if ((hs = o.handleShape) != "round") handle.addClass("rs-handle-" + hs); + handle.attr({ "index": index, "tabIndex": "0" }); + + var id = this._dataElement()[0].id, id = id ? id + "_" : ""; + var label = id + "handle" + (o.sliderType == "range" ? "_" + (index == 1 ? "start" : "end") : ""); + handle.attr({ "role": "slider", "aria-label": label }); // WAI-ARIA support + + var bar = this.$createElement("div.rs-bar rs-transition").css("z-index", "7").append(handle).rsRotate(this._start); + bar.addClass(o.sliderType == "range" && index == 2 ? "rs-second" : "rs-first"); + this.container.append(bar); + this._refreshHandle(); + + this.bar = bar; + this._active = index; + if (index != 1 && index != 2) this["_handle" + index] = this._handleDefaults(); + this._bind(handle, "focus", this._handleFocus); + this._bind(handle, "blur", this._handleBlur); + return handle; + }, + _refreshHandle: function () { + var o = this.options, hSize = o.handleSize, width = o.width, h, w, isSquare = true, isNumber = this.isNumber; + if (typeof hSize === "string" && isNumber(hSize)) { + if (hSize.charAt(0) === "+" || hSize.charAt(0) === "-") { + try { hSize = width + parseFloat(hSize.charAt(0) + Math.abs(parseFloat(hSize))); } + catch (e) { console.warn(e); } + } + else if (hSize.indexOf(",")) { + var s = hSize.split(","); + if (isNumber(s[0]) && isNumber(s[1])) w = parseFloat(s[0]), h = parseFloat(s[1]), isSquare = false; + } + } + if (isSquare) h = w = isNumber(hSize) ? parseFloat(hSize) : width; + var diff = (width + this._border() - w) / 2; + this._handles().css({ height: h, width: w, "margin": -h / 2 + "px 0 0 " + diff + "px" }); + }, + _handleDefaults: function () { + var min = this.options.min; + return { angle: this._valueToAngle(min), value: min }; + }, + _handleBars: function () { + return this.container.children("div.rs-bar"); + }, + _handles: function () { + return this._handleBars().find(".rs-handle"); + }, + _activeHandleBar: function (index) { + index = (index != undefined) ? index : this._active; + return $(this._handleBars()[index - 1]); + }, + _handleArgs: function (index) { + index = (index != undefined) ? index : this._active; + var _handle = this["_handle" + index]; + return { + element: this._activeHandleBar(index).children(), + index: index, + isActive: index == this._active, + value: _handle ? _handle.value : null, + angle: _handle ? _handle.angle : null + }; + }, + _dataElement: function () { + return this._isInputType ? this._hiddenField : this.control; + }, + _raiseEvent: function (event) { + var preValue = this["_pre" + event], currentValue = this.options.value; + if (preValue !== currentValue) { + this["_pre" + event] = currentValue; + if (event == "change") this._updatePre(); + this._updateTooltip(); + if ((event == "change") || (this._bindOnDrag && event == "drag")) this._updateHidden(); + return this._raise(event, { value: currentValue, preValue: preValue, "handle": this._handleArgs() }); + } + }, + + // Events handlers + _elementDown: function (e) { + if (this._isReadOnly) return; + var $target = $(e.target); + + if ($target.hasClass("rs-handle")) { + this._handleDown(e); + } + else { + var point = this._getXY(e), center = this._getCenterPoint(); + var distance = this._getDistance(point, center); + var block = this.block || this.svgContainer; + var outerDistance = block.outerWidth() / 2; + var innerDistance = outerDistance - (this.options.width + this._border()); + + if (distance >= innerDistance && distance <= outerDistance) { + var handle = this.control.find(".rs-handle.rs-focus"), angle, value; + if (handle.length !== 0) { + // here, some handle was in already focused state, and user clicked on the slider path + // so this will make the handle unfocus, to avoid that we can prevent this event + e.preventDefault(); + } + + var d = this._getAngleValue(point, center); + angle = d.angle, value = d.value; + + if (this._rangeSlider) { + if (handle.length == 1) { + var active = parseFloat(handle.attr("index")); + if (!this._invertRange) { + if (active == 1 && angle > this._handle2.angle) active = 2; + else if (active == 2 && angle < this._handle1.angle) active = 1; + } + this._active = active; + } + else this._active = (this._handle2.angle - angle) < (angle - this._handle1.angle) ? 2 : 1; + this.bar = this._activeHandleBar(); + } + + this._changeSliderValue(value, angle); + this._raiseEvent("change"); + } + } + }, + _handleDown: function (e) { + e.preventDefault(); + var $target = $(e.target); + $target.focus(); + this._removeAnimation(); + this._bindMouseEvents("_bind"); + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + this._handles().removeClass("rs-move"); + this._raise("start", { value: this.options.value, "handle": this._handleArgs() }); + }, + _handleMove: function (e) { + e.preventDefault(); + var point = this._getXY(e), center = this._getCenterPoint(); + var d = this._getAngleValue(point, center, true), angle, value; + angle = d.angle, value = d.value; + + this._changeSliderValue(value, angle); + this._raiseEvent("drag"); + }, + _handleUp: function (e) { + this._handles().addClass("rs-move"); + this._bindMouseEvents("_unbind"); + this._addAnimation(); + this._raiseEvent("change"); + this._raise("stop", { value: this.options.value, "handle": this._handleArgs() }); + }, + _handleFocus: function (e) { + if (this._isReadOnly) return; + var $target = $(e.target); + this._handles().removeClass("rs-focus"); + $target.addClass("rs-focus"); + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + if (this.options.keyboardAction) { + this._bindKeyboardEvents("_unbind"); + this._bindKeyboardEvents("_bind"); + } + + // updates the class for change z-index + this.control.find("div.rs-bar").css("z-index", "7"); + this.bar.css("z-index", "8"); + }, + _handleBlur: function (e) { + this._handles().removeClass("rs-focus"); + if (this.options.keyboardAction) this._bindKeyboardEvents("_unbind"); + }, + _handleKeyDown: function (e) { + if (this._isReadOnly) return; + var key = e.keyCode, keyCodes = this.keys; + + if (key == 27) // if Esc key pressed then hanldes will be focused out + this._handles().blur(); + + if (!(key >= 35 && key <= 40)) return; // if not valid keys, then return + if (key >= 37 && key <= 40) this._removeAnimation(); + + var h = this["_handle" + this._active], val, ang; + + e.preventDefault(); + if (key == keyCodes.UP || key == keyCodes.RIGHT) // Up || Right Key + val = this._round(this._limitValue(h.value + this.options.step)); + else if (key == keyCodes.DOWN || key == keyCodes.LEFT) // Down || Left Key + val = this._round(this._limitValue(h.value - this._getMinusStep(h.value))); + else if (key == 36) // Home Key + val = this._getKeyValue("Home"); + else if (key == 35) // End Key + val = this._getKeyValue("End"); + + ang = this._valueToAngle(val); + this._changeSliderValue(val, ang); + this._raiseEvent("drag"); + }, + _handleKeyUp: function (e) { + this._addAnimation(); + this._raiseEvent("change"); + }, + _getMinusStep: function (val) { + var o = this.options, min = o.min, max = o.max, step = o.step; + if (val == max) { + var remain = (max - min) % step; + return remain == 0 ? step : remain; + } + return step; + }, + _getKeyValue: function (key) { + var o = this.options, min = o.min, max = o.max; + if (this._rangeSlider) { + if (key == "Home") return (this._active == 1) ? min : this._handle1.value; + else return (this._active == 1) ? this._handle2.value : max; + } + return (key == "Home") ? min : max; + }, + _elementScroll: function (event) { + if (this._isReadOnly) return; + event.preventDefault(); + var e = event.originalEvent || event, h, val, ang, delta; + delta = e.wheelDelta ? e.wheelDelta / 60 : (e.detail ? -e.detail / 2 : 0); + if (delta == 0) return; + + this._updateActiveHandle(event); + h = this["_handle" + this._active]; + val = h.value + (delta > 0 ? this.options.step : -this._getMinusStep(h.value)); + val = this._limitValue(val); + ang = this._valueToAngle(val); + + this._removeAnimation(); + this._changeSliderValue(val, ang); + this._raiseEvent("change"); + this._addAnimation(); + }, + _updateActiveHandle: function (e) { + var $target = $(e.target); + if ($target.hasClass("rs-handle") && $target.parent().parent()[0] == this.control[0]) { + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + } + if (!this.bar.find(".rs-handle").hasClass("rs-focus")) this.bar.find(".rs-handle").focus(); + }, + + // Events binding + _bindControlEvents: function (hook) { + this[hook](this.control, "mousedown", this._elementDown); + this[hook](this.control, "touchstart", this._elementDown); + }, + _bindScrollEvents: function (hook) { + this[hook](this.control, "mousewheel", this._elementScroll); + this[hook](this.control, "DOMMouseScroll", this._elementScroll); + }, + _bindMouseEvents: function (hook) { + this[hook]($(document), "mousemove", this._handleMove); + this[hook]($(document), "mouseup", this._handleUp); + this[hook]($(document), "mouseleave", this._handleUp); + + // *** for Touch support *** // + this[hook]($(document), "touchmove", this._handleMove); + this[hook]($(document), "touchend", this._handleUp); + this[hook]($(document), "touchcancel", this._handleUp); + }, + _bindKeyboardEvents: function (hook) { + this[hook]($(document), "keydown", this._handleKeyDown); + this[hook]($(document), "keyup", this._handleKeyUp); + }, + + // internal methods + _changeSliderValue: function (value, angle) { + var oAngle = this._oriAngle(angle), lAngle = this._limitAngle(angle); + if (!this._rangeSlider && !this._showRange) { + + this["_handle" + this._active] = { angle: angle, value: value }; + this.options.value = value; + this.bar.rsRotate(lAngle); + this._updateARIA(value); + } + else if ((this._active == 1 && oAngle <= this._oriAngle(this._handle2.angle)) || + (this._active == 2 && oAngle >= this._oriAngle(this._handle1.angle)) || this._invertRange) { + + this["_handle" + this._active] = { angle: angle, value: value }; + this.options.value = this._rangeSlider ? this._handle1.value + "," + this._handle2.value : value; + this.bar.rsRotate(lAngle); + this._updateARIA(value); + + if (this.options.svgMode) { + this._moveSliderRange(); + return; + } + + // classic DIV handling + var dAngle = this._oriAngle(this._handle2.angle) - this._oriAngle(this._handle1.angle), o2 = "1", o3 = "0"; + if (dAngle <= 180 && !(dAngle < 0 && dAngle > -180)) o2 = "0", o3 = "1"; + this.block2.css("opacity", o2); + this.block3.css("opacity", o3); + + (this._active == 1 ? this.block4 : this.block2).rsRotate(lAngle - 180); + (this._active == 1 ? this.block1 : this.block3).rsRotate(lAngle); + } + }, + + // SVG related functionalities + _createSVGElements: function () { + var svgEle = this.$createSVG("svg"); + var PATH = "path.rs-transition "; + var pathAttr = { fill: "transparent" }; + + this.$path = this.$createSVG(PATH + "rs-path", pathAttr); + this.$range = this._showRange ? this.$createSVG(PATH + "rs-range", pathAttr) : null; + this.$border = this.$createSVG(PATH + "rs-border", pathAttr); + this.$append(svgEle, [this.$path, this.$range, this.$border]); + + this.svgContainer = this.$createElement("div.rs-svg-container").append(svgEle); + this.innerContainer.append(this.svgContainer); + }, + _setSVGAttributes: function () { + var o = this.options, radius = o.radius, + border = o.borderWidth, width = o.width, + lineCap = o.lineCap; + var outerRadius = radius - (border / 2), + innerRadius = outerRadius - width - border; + var startAngle = this._start, + totalAngle = this._end, + endAngle = startAngle + totalAngle; + + // draw the path for border element + var border_d = this.$drawPath(radius, outerRadius, startAngle, endAngle, innerRadius, lineCap); + this.$setAttribute(this.$border, { + "d": border_d + }); + // and set the border width + $(this.$border).css("stroke-width", border); + + var pathRadius = radius - border - (width / 2); + this.svgPathLength = this.$getArcLength(pathRadius, totalAngle); + var d = this.$drawPath(radius, pathRadius, startAngle, endAngle); + var attr = { "d": d, "stroke-width": width, "stroke-linecap": lineCap }; + + // draw the path for slider path element + this.$setAttribute(this.$path, attr); + + if (this._showRange) { + // draw the path for slider range element + this.$setAttribute(this.$range, attr); + + // there was a small bug when lineCap was round/square, this will solve that + if (lineCap == "round" || lineCap == "square") this.$range.setAttribute("stroke-dashoffset", "0.01"); + else this.$range.removeAttribute("stroke-dashoffset"); + } + }, + _setSVGStyles: function () { + var o = this.options, + borderColor = o.borderColor, + pathColor = o.pathColor, + rangeColor = o.rangeColor; + + if (borderColor) { + $(this.$border).css("stroke", borderColor); + } + + if (pathColor) { + $(this.$path).css("stroke", pathColor); + } + + if (this._showRange && rangeColor) { + $(this.$range).css("stroke", rangeColor); + } + }, + _moveSliderRange: function (isInit) { + if (!this._showRange) return; + + var startAngle = this._start, + totalAngle = this._end; + var handle1Angle = this._handle1.angle - startAngle, + handle2Angle = this._handle2.angle - startAngle; + if (isInit) handle1Angle = handle2Angle = 0; + var dashArray = []; + + if (handle1Angle <= handle2Angle) { + // starting the dashArray from 0 means normal range, otherwise it's invert range + // so when handle1 value is smaller then it's a normal range selection only + dashArray.push(0); + } + else { + // when handle1 value is larger then it's a invert range selection, also swap the values + var temp = handle1Angle; + handle1Angle = handle2Angle; + handle2Angle = temp; + } + + var handle1Distance = (handle1Angle / totalAngle) * this.svgPathLength; + dashArray.push(handle1Distance); + + var handle2Distance = ((handle2Angle - handle1Angle) / totalAngle) * this.svgPathLength; + dashArray.push(handle2Distance, this.svgPathLength); + + this.$range.style.strokeDasharray = dashArray.join(" "); + }, + _isPropsRelatedToSVG: function (property) { + var svgRelatedProps = ["radius", "borderWidth", "width", "lineCap", "startAngle", "endAngle"]; + return this._hasProperty(property, svgRelatedProps); + }, + _isPropsRelatedToSVGStyles: function (property) { + var svgStylesRelatedProps = ["borderColor", "pathColor", "rangeColor"]; + return this._hasProperty(property, svgStylesRelatedProps); + }, + _hasProperty: function (property, list) { + if (typeof property == "string") { + return (list.indexOf(property) !== -1); + } + else { + var allProperties = Object.keys(property); + return allProperties.some(function (prop) { + return (list.indexOf(prop) !== -1); + }); + } + }, + + // WAI-ARIA support + _updateARIA: function (value) { + var o = this.options, min = o.min, max = o.max; + this.bar.children().attr({ "aria-valuenow": value }); + if (o.sliderType == "range") { + var handles = this._handles(); + handles.eq(0).attr({ "aria-valuemin": min }); + handles.eq(1).attr({ "aria-valuemax": max }); + + if (this._active == 1) handles.eq(1).attr({ "aria-valuemin": value }); + else handles.eq(0).attr({ "aria-valuemax": value }); + } + else this.bar.children().attr({ "aria-valuemin": min, "aria-valuemax": max }); + }, + // Listener for KO binding + _checkKO: function () { + var _data = this._dataElement().data("bind"); + if (typeof _data == "string" && typeof ko == "object") { + var _vm = ko.dataFor(this._dataElement()[0]); + if (typeof _vm == "undefined") return true; + var _all = _data.split(","), _handler; + for (var i = 0; i < _all.length; i++) { + var d = _all[i].split(":"); + if ($.trim(d[0]) == "value") { + _handler = $.trim(d[1]); + break; + } + } + if (_handler) { + this._isKO = true; + ko.computed(function () { this.option("value", _vm[_handler]()); }, this); + } + } + }, + // Listener for Angular binding + _checkAngular: function () { + if (typeof angular == "object" && typeof angular.element == "function") { + this._ngName = this._dataElement().attr("ng-model"); + if (typeof this._ngName == "string") { + this._isAngular = true; var that = this; + this._scope().$watch(this._ngName, function (newValue, oldValue) { that.option("value", newValue); }); + } + } + }, + _scope: function () { + return angular.element(this._dataElement()).scope(); + }, + _getDistance: function (p1, p2) { + return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); + }, + _getXY: function (e) { + if (e.type.indexOf("mouse") == -1) e = (e.originalEvent || e).changedTouches[0]; + return { x: e.pageX, y: e.pageY }; + }, + _getCenterPoint: function () { + var block = this.block || this.svgContainer; + var offset = block.offset(), center; + center = { + x: offset.left + (block.outerWidth() / 2), + y: offset.top + (block.outerHeight() / 2) + }; + return center; + }, + _getAngleValue: function (point, center, isDrag) { + var deg = Math.atan2(point.y - center.y, center.x - point.x); + var angle = (-deg / (Math.PI / 180)); + if (angle < this._start) angle += 360; + angle = this._checkAngle(angle, isDrag); + return this._processStepByAngle(angle); + }, + _checkAngle: function (angle, isDrag) { + var o_angle = this._oriAngle(angle), + preAngle = this["_handle" + this._active].angle, + o_preAngle = this._oriAngle(preAngle); + + if (o_angle > this._end) { + if (!isDrag) return preAngle; + angle = this._start + (o_preAngle <= this._end - o_preAngle ? 0 : this._end); + } + else if (isDrag) { + var d = this._handleDragDistance; + if (this.isNumber(d)) if (Math.abs(o_angle - o_preAngle) > d) return preAngle; + } + return angle; + }, + _processStepByAngle: function (angle) { + var value = this._angleToValue(angle); + return this._processStepByValue(value); + }, + _processStepByValue: function (value) { + var o = this.options, min = o.min, max = o.max, step = o.step, isMinHigher = (min > max); + var remain, currVal, nextVal, preVal, newVal, ang; + + step = (isMinHigher ? -step : step); + remain = (value - min) % step; + + currVal = value - remain; + nextVal = this._limitValue(currVal + step); + preVal = this._limitValue(currVal - step); + + if (!isMinHigher) { + if (value >= currVal) newVal = (value - currVal < nextVal - value) ? currVal : nextVal; + else newVal = (currVal - value > value - preVal) ? currVal : preVal; + } + else { + if (value <= currVal) newVal = (currVal - value < value - nextVal) ? currVal : nextVal; + else newVal = (value - currVal > preVal - value) ? currVal : preVal; + } + newVal = this._round(newVal), ang = this._valueToAngle(newVal); + return { value: newVal, angle: ang }; + }, + _round: function (val) { + var s = this.options.step.toString().split("."); + return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val); + }, + _oriAngle: function (angle) { + var ang = angle - this._start; + if (ang < 0) ang += 360; + return ang; + }, + _limitAngle: function (angle) { + if (angle > 360 + this._start) angle -= 360; + if (angle < this._start) angle += 360; + return angle; + }, + _limitValue: function (value) { + var o = this.options, min = o.min, max = o.max, isMinHigher = (min > max); + if ((!isMinHigher && value < min) || (isMinHigher && value > min)) value = min; + if ((!isMinHigher && value > max) || (isMinHigher && value < max)) value = max; + return value; + }, + _angleToValue: function (angle) { + var o = this.options, min = o.min, max = o.max, value; + value = (this._oriAngle(angle) / this._end) * (max - min) + min; + return value; + }, + _valueToAngle: function (value) { + var o = this.options, min = o.min, max = o.max, angle; + angle = (((value - min) / (max - min)) * this._end) + this._start; + return angle; + }, + _appendHiddenField: function () { + this._hiddenField = this._hiddenField || this.$createElement("input"); + this._hiddenField.attr({ + "type": "hidden", "name": this._dataElement()[0].id || "" + }); + this.control.append(this._hiddenField); + this._updateHidden(); + }, + _updateHidden: function () { + var val = this.options.value; + this._hiddenField.val(val); + if (this._isKO || this._isAngular) this._hiddenField.trigger("change"); + if (this._isAngular) this._scope()[this._ngName] = val; + }, + _updateTooltip: function () { + if (this.tooltip && !this.tooltip.hasClass("hover")) + this.tooltip.html(this._getTooltipValue()); + this._updateTooltipPos(); + }, + _updateTooltipPos: function () { + this.tooltip && this.tooltip.css(this._getTooltipPos()); + }, + _getTooltipPos: function () { + var circleShape = this.options.circleShape, pos; + var tooltipHeight = this.tooltip.outerHeight(), tooltipWidth = this.tooltip.outerWidth(); + + if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0) { + return { + "margin-top": -tooltipHeight / 2, + "margin-left": -tooltipWidth / 2 + }; + } + else if (circleShape.indexOf("half") != -1) { + switch (circleShape) { + case "half-top": + case "half-bottom": + pos = { "margin-left": -tooltipWidth / 2 }; break; + case "half-left": + case "half-right": + pos = { "margin-top": -tooltipHeight / 2 }; break; + } + return pos; + } + return {}; + }, + _getTooltipValue: function (isNormal) { + var value = this.options.value; + if (this._rangeSlider) { + var p = value.split(","); + if (isNormal) return p[0] + " - " + p[1]; + return this._tooltipValue(p[0], 1) + " - " + this._tooltipValue(p[1], 2); + } + if (isNormal) return value; + return this._tooltipValue(value); + }, + _tooltipValue: function (value, index) { + var returnValue = this._raise("tooltipFormat", { value: value, "handle": this._handleArgs(index) }); + return (returnValue != null && typeof returnValue !== "boolean") ? returnValue : value; + }, + _validateStartAngle: function () { + var start = this.options.startAngle; + start = (this.isNumber(start) ? parseFloat(start) : 0) % 360; + if (start < 0) start += 360; + this.options.startAngle = start; + return start; + }, + _validateEndAngle: function () { + var o = this.options, start = o.startAngle, end = o.endAngle; + if (typeof end === "string" && this.isNumber(end) && (end.charAt(0) === "+" || end.charAt(0) === "-")) { + try { end = start + parseFloat(end.charAt(0) + Math.abs(parseFloat(end))); } + catch (e) { console.warn(e); } + } + end = (this.isNumber(end) ? parseFloat(end) : 360) % 360; + if (end <= start) end += 360; + return end; + }, + _refreshCircleShape: function () { + var circleShape = this.options.circleShape; + var allCircelShapes = ["half-top", "half-bottom", "half-left", "half-right", + "quarter-top-left", "quarter-top-right", "quarter-bottom-right", "quarter-bottom-left", + "pie", "custom-half", "custom-quarter"]; + var shape_codes = ["h1", "h2", "h3", "h4", "q1", "q2", "q3", "q4", "3/4", "ch", "cq"]; + + if (allCircelShapes.indexOf(circleShape) == -1) { + var index = shape_codes.indexOf(circleShape); + if (index != -1) circleShape = allCircelShapes[index]; + else if (circleShape == "half") circleShape = "half-top"; + else if (circleShape == "quarter") circleShape = "quarter-top-left"; + else circleShape = "full"; + } + this.options.circleShape = circleShape; + }, + _appendOverlay: function () { + var shape = this.options.circleShape; + if (shape == "pie") + this._checkOverlay(".rs-overlay", 270); + else if (shape == "custom-half" || shape == "custom-quarter") { + this._checkOverlay(".rs-overlay1", 180); + if (shape == "custom-quarter") + this._checkOverlay(".rs-overlay2", this._end); + } + }, + _checkOverlay: function (cls, angle) { + var overlay = this.container.children(cls); + if (overlay.length == 0) { + overlay = this.$createElement("div" + cls + " rs-transition rs-bg-color"); + this.container.append(overlay); + } + overlay.rsRotate(this._start + angle); + }, + _checkDataType: function () { + var m = this.options, i, prop, value, props = this._props(); + // to check number datatype + for (i in props.numberType) { + prop = props.numberType[i], value = m[prop]; + if (!this.isNumber(value)) m[prop] = this.defaults[prop]; + else m[prop] = parseFloat(value); + } + // to check input string + for (i in props.booleanType) { + prop = props.booleanType[i], value = m[prop]; + m[prop] = (value == "false") ? false : !!value; + } + // to check boolean datatype + for (i in props.stringType) { + prop = props.stringType[i], value = m[prop]; + m[prop] = ("" + value).toLowerCase(); + } + }, + _validateSliderType: function () { + var type = this.options.sliderType.toLowerCase(); + this._rangeSlider = this._showRange = false; + if (type == "range") this._rangeSlider = this._showRange = true; + else if (type.indexOf("min") != -1) { + this._showRange = true; + type = "min-range"; + } + else type = "default"; + this.options.sliderType = type; + }, + _updateStartEnd: function () { + var o = this.options, circle = o.circleShape, startAngle = o.startAngle, endAngle = o.endAngle; + + if (circle != "full") { + if (circle.indexOf("quarter") != -1) endAngle = "+90"; + else if (circle.indexOf("half") != -1) endAngle = "+180"; + else if (circle == "pie") endAngle = "+270"; + this.options.endAngle = endAngle; + + if (circle == "quarter-top-left" || circle == "half-top") startAngle = 0; + else if (circle == "quarter-top-right" || circle == "half-right") startAngle = 90; + else if (circle == "quarter-bottom-right" || circle == "half-bottom") startAngle = 180; + else if (circle == "quarter-bottom-left" || circle == "half-left") startAngle = 270; + this.options.startAngle = startAngle; + } + }, + _validateStartEnd: function () { + this._start = this._validateStartAngle(); + this._end = this._validateEndAngle(); + + var add = (this._start < this._end) ? 0 : 360; + this._end += add - this._start; + }, + _analyzeModelValue: function () { + var o = this.options, val = o.value, min = o.min, max = o.max, + lastValue, newValue, isNumber = this.isNumber; + if (val instanceof Array) val = val.toString(); + var valueIsString = (typeof val == "string"); + + var parts = valueIsString ? val.split(",") : [val]; + + if (this._rangeSlider) { + if (valueIsString) { + if (parts.length >= 2) newValue = (isNumber(parts[0]) ? parts[0] : min) + "," + + (isNumber(parts[1]) ? parts[1] : max); + else newValue = isNumber(parts[0]) ? min + "," + parts[0] : min + "," + min; + } + else newValue = isNumber(val) ? min + "," + val : min + "," + min; + } + else { + if (valueIsString) lastValue = parts.pop(), newValue = isNumber(lastValue) ? parseFloat(lastValue) : min; + else newValue = isNumber(val) ? parseFloat(val) : min; + } + this.options.value = newValue; + }, + _validateModelValue: function () { + var o = this.options, val = o.value; + if (this._rangeSlider) { + var parts = val.split(","), val1 = parseFloat(parts[0]), val2 = parseFloat(parts[1]); + val1 = this._limitValue(val1); + val2 = this._limitValue(val2); + if (!this._invertRange) { + var min = o.min, max = o.max; + var isMinHigher = (min > max); + if (isMinHigher) { + if (val1 < val2) val1 = val2; + } else { + if (val1 > val2) val2 = val1; + } + } + + this._handle1 = this._processStepByValue(val1); + this._handle2 = this._processStepByValue(val2); + this.options.value = this._handle1.value + "," + this._handle2.value; + } + else { + var index = this._showRange ? 2 : (this._active || 1); + this["_handle" + index] = this._processStepByValue(this._limitValue(val)); + if (this._showRange) this._handle1 = this._handleDefaults(); + this.options.value = this["_handle" + index].value; + } + }, + + // common core methods + $createElement: function (tag) { + var t = tag.split('.'); + return $(document.createElement(t[0])).addClass(t[1] || ""); + }, + $createSVG: function (tag, attr) { + var t = tag.split('.'); + var svgEle = document.createElementNS("http://www.w3.org/2000/svg", t[0]); + if (t[1]) { + svgEle.setAttribute("class", t[1]); + } + if (attr) { + this.$setAttribute(svgEle, attr); + } + return svgEle; + }, + $setAttribute: function (ele, attr) { + for (var key in attr) { + var val = attr[key]; + if (key === "class") { + var prev = ele.getAttribute('class'); + if (prev) val += " " + prev; + } + ele.setAttribute(key, val); + } + return ele; + }, + $append: function (parent, children) { + children.forEach(function (element) { + element && parent.appendChild(element); + }); + return parent; + }, + isNumber: function (number) { + number = parseFloat(number); + return typeof number === "number" && !isNaN(number); + }, + getBrowserName: function () { + var browserName = "", ua = window.navigator.userAgent; + if ((!!window.opr && !!opr.addons) || !!window.opera || ua.indexOf(' OPR/') >= 0) browserName = "opera"; + else if (typeof InstallTrigger !== 'undefined') browserName = "firefox"; + else if (ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0) browserName = "ie"; + else if (!!window.StyleMedia) browserName = "edge"; + else if (ua.indexOf('Safari') != -1 && ua.indexOf('Chrome') == -1) browserName = "safari"; + else if ((!!window.chrome && !!window.chrome.webstore) || (ua.indexOf('Chrome') != -1)) browserName = "chrome"; + return browserName; + }, + _isBrowserSupported: function () { + var properties = ["borderRadius", "WebkitBorderRadius", "MozBorderRadius", + "OBorderRadius", "msBorderRadius", "KhtmlBorderRadius"]; + for (var i = 0; i < properties.length; i++) { + if (document.body.style[properties[i]] !== undefined) return true; + } + }, + _throwError: function () { + return "This browser doesn't support the border-radious property."; + }, + _raise: function (event, args) { + var o = this.options, fn = o[event], val = true; + args = args || { value: o.value }; + args["id"] = this.id; + args["control"] = this.control; + args["options"] = o; + if (fn) { + args["type"] = event; + if (typeof fn === "string") fn = window[fn]; + if ($.isFunction(fn)) { + val = fn.call(this, args); + val = val === false ? false : val; + } + } + this.control.trigger($.Event(event, args)); + return val; + }, + _bind: function (element, _event, handler) { + $(element).bind(_event, $.proxy(handler, this)); + }, + _unbind: function (element, _event, handler) { + $(element).unbind(_event, $.proxy(handler, this)); + }, + _getInstance: function () { + return $.data(this._dataElement()[0], pluginName); + }, + _saveInstanceOnElement: function () { + $.data(this.control[0], pluginName, this); + }, + _saveInstanceOnID: function () { + var id = this.id; + if (id && typeof window[id] !== "undefined") + window[id] = this; + }, + _removeData: function () { + var control = this._dataElement()[0]; + $.removeData && $.removeData(control, pluginName); + if (control.id && typeof window[control.id]["_init"] === "function") + delete window[control.id]; + }, + _destroyControl: function () { + if (this._isInputType) this._dataElement().insertAfter(this.control).attr("type", "text"); + this.control.empty().removeClass("rs-control").height("").width(""); + this._removeAnimation(); + this._bindControlEvents("_unbind"); + }, + + // methods to dynamic options updation (through option) + _updateWidth: function () { + this.lastBlock.css("padding", this.options.width); + }, + _readOnly: function (bool) { + this._isReadOnly = bool; + this.container.removeClass("rs-readonly"); + if (bool) this.container.addClass("rs-readonly"); + }, + + // get & set for the properties + _get: function (property) { + return this.options[property]; + }, + _set: function (property, value, forceSet) { + var props = this._props(); + if ($.inArray(property, props.numberType) != -1) { // to check number datatype + if (!this.isNumber(value)) return; + value = parseFloat(value); + } + else if ($.inArray(property, props.booleanType) != -1) { // to check boolean datatype + value = (value == "false") ? false : !!value; + } + else if ($.inArray(property, props.stringType) != -1) { // to check input string + value = value.toLowerCase(); + } + + if (!forceSet && this.options[property] == value) return; + this.options[property] = value; + switch (property) { + case "startAngle": + case "endAngle": + this._validateStartEnd(); + this._updateSeperator(); // non SVG mode only + this._appendOverlay(); // non SVG mode only + case "min": + case "max": + case "step": + case "value": + this._analyzeModelValue(); + this._validateModelValue(); + this._setValue(); + this._updatePre(); + this._updateHidden(); + this._updateTooltip(); + break; + case "radius": + this._setRadius(); + this._updateTooltipPos(); + break; + case "width": + this._removeAnimation(); + this._updateWidth(); // non SVG mode only + this._setRadius(); + this._refreshHandle(); + this._updateTooltipPos(); + this._addAnimation(); + this._refreshSeperator(); // non SVG mode only + break; + case "borderWidth": + this._setRadius(); + this._refreshHandle(); + break; + case "handleSize": + this._refreshHandle(); + break; + case "handleShape": + this._setHandleShape(); + break; + case "animation": + this._setAnimation(); + break; + case "showTooltip": + this.options.showTooltip ? this._appendTooltip() : this._removeTooltip(); + break; + case "editableTooltip": + this._tooltipEditable(); + this._updateTooltipPos(); + break; + case "disabled": + this.options.disabled ? this.disable() : this.enable(); + break; + case "readOnly": + this.options.readOnly ? this._readOnly(true) : (!this.options.disabled && this._readOnly(false)); + break; + case "mouseScrollAction": + this._bindScrollEvents(this.options.mouseScrollAction ? "_bind" : "_unbind"); + break; + case "lineCap": + this._setRadius(); + this._refreshSeperator(); // non SVG mode only + break; + case "circleShape": + this._refreshCircleShape(); + if (this.options.circleShape == "full") { + this.options.startAngle = 0; + this.options.endAngle = "+360"; + } + case "sliderType": + this._destroyControl(); + this._onInit(); + break; + case "svgMode": + var $control = this.control, $options = this.options; + this.destroy(); + $control[pluginName]($options); + break; + } + return this; + }, + + // public methods + option: function (property, value) { + if (!this._getInstance() || !this._isBrowserSupport) return; + if ($.isPlainObject(property)) { + if (property["min"] !== undefined || property["max"] !== undefined) { + if (property["min"] !== undefined) { + this.options.min = property["min"]; + delete property["min"]; + } + if (property["max"] !== undefined) { + this.options.max = property["max"]; + delete property["max"]; + } + var val = this.options.value; + if (property["value"] !== undefined) { + val = property["value"] + delete property["value"]; + } + this._set("value", val, true); + } + for (var prop in property) { + this._set(prop, property[prop]); + } + } + else if (property && typeof property == "string") { + if (value === undefined) return this._get(property); + this._set(property, value); + } + + // whenever the properties set dynamically, check for SVG mode. also check + // any of the property was related to SVG. If yes, then redraw the SVG path + if (this.options.svgMode && property) { + if (this._isPropsRelatedToSVG(property)) { + this._setSVGAttributes(); + this._moveSliderRange(); + } + if (this._isPropsRelatedToSVGStyles(property)) { + this._setSVGStyles(); + } + } + + return this; + }, + getValue: function (index) { + if (this.options.sliderType == "range" && this.isNumber(index)) { + var i = parseFloat(index); + if (i == 1 || i == 2) + return this["_handle" + i].value; + } + return this._get("value"); + }, + setValue: function (value, index) { + if (this.isNumber(value)) { + if (this.isNumber(index)) { + var sliderType = this.options.sliderType; + if (sliderType == "range") { + var i = parseFloat(index), val = parseFloat(value); + if (i == 1) value = val + "," + this._handle2.value; + else if (i == 2) value = this._handle1.value + "," + val; + } + else if (sliderType == "default") this._active = index; + } + this._set("value", value); + } + }, + disable: function () { + this.options.disabled = true; + this.container.addClass("rs-disabled"); + this._readOnly(true); + }, + enable: function () { + this.options.disabled = false; + this.container.removeClass("rs-disabled"); + if (!this.options.readOnly) this._readOnly(false); + }, + destroy: function () { + if (!this._getInstance()) return; + this._destroyControl(); + this._removeData(); + if (this._isInputType) this.control.remove(); + } + }; + + $.fn.rsRotate = function (degree) { + var control = this, rotation = "rotate(" + degree + "deg)"; + control.css('-webkit-transform', rotation); + control.css('-moz-transform', rotation); + control.css('-ms-transform', rotation); + control.css('-o-transform', rotation); + control.css('transform', rotation); + return control; + } + + // The plugin constructor + function RoundSlider(control, options) { + this.id = control.id; + this.control = $(control); + + // the options value holds the updated defaults value + this.options = $.extend({}, this.defaults, options); + } + + // The plugin wrapper, prevents multiple instantiations + function CreateRoundSlider(options, args) { + + for (var i = 0; i < this.length; i++) { + var that = this[i], instance = $.data(that, pluginName); + if (!instance) { + var _this = new RoundSlider(that, options); + _this._saveInstanceOnElement(); + _this._saveInstanceOnID(); + + if (_this._raise("beforeCreate") !== false) { + _this._init(); + _this._raise("create"); + } + else _this._removeData(); + } + else if ($.isPlainObject(options)) { + if (typeof instance.option === "function") instance.option(options); + else if (that.id && window[that.id] && typeof window[that.id].option === "function") { + window[that.id].option(options); + } + } + else if (typeof options === "string") { + if (typeof instance[options] === "function") { + if ((options === "option" || options.indexOf("get") === 0) && args[2] === undefined) { + return instance[options](args[1]); + } + instance[options](args[1], args[2]); + } + } + } + return this; + } + + // ### SVG related logic + RoundSlider.prototype.$polarToCartesian = function (centerXY, radius, angleInDegrees) { + var angleInRadians = (angleInDegrees - 180) * Math.PI / 180; + + return [ + centerXY + (radius * Math.cos(angleInRadians)), + centerXY + (radius * Math.sin(angleInRadians)) + ].join(" "); + } + + RoundSlider.prototype.$drawArc = function (centerXY, radius, startAngle, endAngle, isOuter) { + var isCircle = (endAngle - startAngle == 360); + var largeArcFlag = Math.abs(startAngle - endAngle) <= 180 ? "0" : "1"; + var isClockwise = true; + var outerDirection = isClockwise ? 1 : 0; + var innerDirection = isClockwise ? 0 : 1; + var direction = isOuter ? outerDirection : innerDirection; + var _endAngle = isOuter ? endAngle : startAngle; + + var path = []; + + // if it is a perfect circle then draw two half circles, otherwise draw arc + if (isCircle) { + var midAngle = (startAngle + endAngle) / 2; + var midPoint = this.$polarToCartesian(centerXY, radius, midAngle); + var endPoint = this.$polarToCartesian(centerXY, radius, _endAngle); + path.push( + "A", 1, 1, 0, 0, direction, midPoint, + "A", 1, 1, 0, 0, direction, endPoint + ); + } + else { + var endPoint = this.$polarToCartesian(centerXY, radius, _endAngle); + path.push( + "A", radius, radius, 0, largeArcFlag, direction, endPoint + ); + } + + return path.join(" "); + } + + RoundSlider.prototype.$drawPath = function (centerXY, outerRadius, startAngle, endAngle, innerRadius, lineCap) { + var outerStart = this.$polarToCartesian(centerXY, outerRadius, startAngle); + var outerArc = this.$drawArc(centerXY, outerRadius, startAngle, endAngle, true); // draw outer circle + + var d = [ + "M " + outerStart, + outerArc + ]; + + if (innerRadius) { + var innerEnd = this.$polarToCartesian(centerXY, innerRadius, endAngle); + var innerArc = this.$drawArc(centerXY, innerRadius, startAngle, endAngle, false); // draw inner circle + + if (lineCap == "none") { + d.push( + "M " + innerEnd, + innerArc + ); + } + else if (lineCap == "round") { + d.push( + "A 1, 1, 0, 0, 1, " + innerEnd, + innerArc, + "A 1, 1, 0, 0, 1, " + outerStart + ); + } + else if (lineCap == "butt" || lineCap == "square") { + d.push( + "L " + innerEnd, + innerArc, + "L " + outerStart, + "Z" + ); + } + } + return d.join(" "); + } + + RoundSlider.prototype.$getArcLength = function (radius, degree = 360) { + // when degree not provided we can consider that arc as a complete circle + // circle's arc length formula => 2πR(Θ/360) + return 2 * Math.PI * radius * (degree / 360); + } + + $.fn[pluginName].prototype = RoundSlider.prototype; + +})(jQuery, window); \ No newline at end of file diff --git a/lib/js/roundslider.min.js b/lib/js/roundslider.min.js deleted file mode 100644 index c1524533..00000000 --- a/lib/js/roundslider.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! roundSlider v1.3.3 | (c) 2015-2019, Soundar | MIT license | http://roundsliderui.com/licence.html */ -;(function($,window,undefined){"use strict";function RoundSlider(n,t){this.id=n.id,this.control=$(n),this.options=$.extend({},this.defaults,t)}function CreateRoundSlider(n,t){for(var i,r,u,f=0;f<this.length;f++)if(i=this[f],r=$.data(i,pluginName),r){if($.isPlainObject(n))typeof r.option=="function"?r.option(n):i.id&&window[i.id]&&typeof window[i.id].option=="function"&&window[i.id].option(n);else if(typeof n=="string"&&typeof r[n]=="function"){if((n==="option"||n.indexOf("get")===0)&&t[2]===undefined)return r[n](t[1]);r[n](t[1],t[2])}}else u=new RoundSlider(i,n),u._saveInstanceOnElement(),u._saveInstanceOnID(),u._raise("beforeCreate")!==!1?(u._init(),u._raise("create")):u._removeData();return this}var pluginName="roundSlider";$.fn[pluginName]=function(n){return CreateRoundSlider.call(this,n,arguments)},RoundSlider.prototype={pluginName:pluginName,version:"1.3.3",options:{},control:null,defaults:{min:0,max:100,step:1,value:null,radius:85,width:18,handleSize:"+0",startAngle:0,endAngle:"+360",animation:!0,showTooltip:!0,editableTooltip:!0,readOnly:!1,disabled:!1,keyboardAction:!0,mouseScrollAction:!1,lineCap:"square",sliderType:"default",circleShape:"full",handleShape:"round",beforeCreate:null,create:null,start:null,drag:null,change:null,stop:null,tooltipFormat:null},keys:{UP:38,DOWN:40,LEFT:37,RIGHT:39},_props:function(){return{numberType:["min","max","step","radius","width","startAngle"],booleanType:["animation","showTooltip","editableTooltip","readOnly","disabled","keyboardAction","mouseScrollAction"],stringType:["sliderType","circleShape","handleShape","lineCap"]}},_init:function(){var t,n;this._isBrowserSupport=this._isBrowserSupported(),this._isKO=!1,this._isAngular=!1,this.control.is("input")&&(this._isInputType=!0,this._hiddenField=this.control,this.control=this.$createElement("div"),this.control.insertAfter(this._hiddenField),this.options.value=this._hiddenField.val()||this.options.value,t=this,this._checkKO()&&setTimeout(function(){t._checkKO()},1),this._checkAngular()),this._bindOnDrag=!1,n=this._dataElement().attr("data-updateon"),typeof n=="string"?n=="drag"&&(this._bindOnDrag=!0):this._isAngular&&(this._bindOnDrag=!0),this._onInit()},_onInit:function(){this._initialize(),this._update(),this._render()},_initialize:function(){var n=this.browserName=this.getBrowserName();(n&&this.control.addClass("rs-"+n),this._isBrowserSupport)&&(this._isReadOnly=!1,this._checkDataType(),this._refreshCircleShape())},_render:function(){if(this.container=this.$createElement("div.rs-container"),this.innerContainer=this.$createElement("div.rs-inner-container"),this.block=this.$createElement("div.rs-block rs-outer rs-border"),this.container.append(this.innerContainer.append(this.block)),this.control.addClass("rs-control").empty().append(this.container),this._setRadius(),this._isBrowserSupport)this._createLayers(),this._setProperties(),this._setValue(),this._updateTooltipPos(),this._bindControlEvents("_bind");else{var n=this.$createElement("div.rs-msg");n.html(typeof this._throwError=="function"?this._throwError():this._throwError),this.control.empty().addClass("rs-error").append(n),this._isInputType&&this.control.append(this._dataElement())}},_update:function(){this._validateSliderType(),this._updateStartEnd(),this._validateStartEnd(),this._handle1=this._handle2=this._handleDefaults(),this._analyzeModelValue(),this._validateModelValue()},_createLayers:function(){var i=this.options.width,t=this._start,n;n=this.$createElement("div.rs-path rs-transition"),this._rangeSlider||this._showRange?(this.block1=n.clone().addClass("rs-range-color").rsRotate(t),this.block2=n.clone().addClass("rs-range-color").css("opacity","0").rsRotate(t),this.block3=n.clone().addClass("rs-path-color").rsRotate(t),this.block4=n.addClass("rs-path-color").css({opacity:"1","z-index":"1"}).rsRotate(t-180),this.block.append(this.block1,this.block2,this.block3,this.block4).addClass("rs-split")):this.block.append(n.addClass("rs-path-color")),this.lastBlock=this.$createElement("span.rs-block").css({padding:i}),this.innerBlock=this.$createElement("div.rs-inner rs-bg-color rs-border"),this.lastBlock.append(this.innerBlock),this.block.append(this.lastBlock),this._appendHandle(),this._appendOverlay(),this._appendHiddenField()},_setProperties:function(){this._updatePre(),this._setHandleShape(),this._addAnimation(),this._appendTooltip(),this.options.showTooltip||this._removeTooltip(),this.options.disabled?this.disable():this.options.readOnly&&this._readOnly(!0),this.options.mouseScrollAction&&this._bindScrollEvents("_bind")},_updatePre:function(){this._prechange=this._predrag=this.options.value},_setValue:function(){if(this._rangeSlider)this._setHandleValue(1),this._setHandleValue(2);else{this._showRange&&this._setHandleValue(1);var n=this.options.sliderType=="default"?this._active||1:parseFloat(this.bar.children().attr("index"));this._setHandleValue(n)}},_appendTooltip:function(){this.container.children(".rs-tooltip").length===0&&(this.tooltip=this.$createElement("span.rs-tooltip rs-tooltip-text"),this.container.append(this.tooltip),this._tooltipEditable(),this._updateTooltip())},_removeTooltip:function(){this.container.children(".rs-tooltip").length!=0&&this.tooltip&&this.tooltip.remove()},_tooltipEditable:function(){var i=this.options,n=this.tooltip,t;n&&i.showTooltip&&(i.editableTooltip?(n.addClass("edit"),t="_bind"):(n.removeClass("edit"),t="_unbind"),this[t](n,"click",this._editTooltip))},_editTooltip:function(){var n=this.tooltip,i,t;n.hasClass("edit")&&!this._isReadOnly&&(i=parseFloat(n.css("border-left-width"))*2,t=this.input=this.$createElement("input.rs-input rs-tooltip-text").css({height:n.outerHeight()-i,width:n.outerWidth()-i}),n.html(t).removeClass("edit").addClass("hover"),t.focus().val(this._getTooltipValue(!0)),this._bind(t,"blur",this._focusOut),this._bind(t,"change",this._focusOut))},_focusOut:function(n){if(n.type=="change"){var t=this.input.val().replace("-",",");t[0]==","&&(t="-"+t.slice(1).replace("-",",")),this.options.value=t,this._analyzeModelValue(),this._validateModelValue(),this._setValue(),this.input.val(this._getTooltipValue(!0))}else this.tooltip.addClass("edit").removeClass("hover"),this._updateTooltip();this._raiseEvent("change")},_setHandleShape:function(){var t=this.options.handleShape,n=this._handles();n.removeClass("rs-handle-dot rs-handle-square"),t=="dot"?n.addClass("rs-handle-dot"):t=="square"?n.addClass("rs-handle-square"):this.options.handleShape="round"},_setHandleValue:function(n){this._active=n;var t=this["_handle"+n];this.options.sliderType!="min-range"&&(this.bar=this._activeHandleBar()),this._changeSliderValue(t.value,t.angle)},_setAnimation:function(){this.options.animation?this._addAnimation():this._removeAnimation()},_addAnimation:function(){this.options.animation&&this.control.addClass("rs-animation")},_removeAnimation:function(){this.control.removeClass("rs-animation")},_setRadius:function(){var t=this.options.radius,i=t*2,n=this.options.circleShape,r=i,u=i,f,e;if(this.container.removeClass().addClass("rs-container"),n.indexOf("half")===0){switch(n){case"half-top":case"half-bottom":r=t,u=i;break;case"half-left":case"half-right":r=i,u=t}this.container.addClass(n.replace("half-","")+" half")}else n.indexOf("quarter")===0?(r=u=t,f=n.split("-"),this.container.addClass(f[0]+" "+f[1]+" "+f[2])):this.container.addClass("full "+n);e={height:r,width:u},this.control.css(e),this.container.css(e)},_border:function(n){return n?parseFloat(this._startLine.children().css("border-bottom-width")):parseFloat(this.block.css("border-top-width"))*2},_appendHandle:function(){(this._rangeSlider||!this._showRange)&&this._createHandle(1),(this._rangeSlider||this._showRange)&&this._createHandle(2),this._startLine=this._addSeperator(this._start,"rs-start"),this._endLine=this._addSeperator(this._start+this._end,"rs-end"),this._refreshSeperator()},_addSeperator:function(n,t){var r=this.$createElement("span.rs-seperator rs-border"),u=this.options.width,f=this._border(),i=this.$createElement("span.rs-bar rs-transition "+t).append(r).rsRotate(n);return this.container.append(i),i},_refreshSeperator:function(){var i=this._startLine.add(this._endLine),r=i.children().removeAttr("style"),n=this.options,u=n.width,f=this._border(),t=u+f;n.lineCap=="round"&&n.circleShape!="full"?(i.addClass("rs-rounded"),r.css({width:t,height:t/2+1}),this._startLine.children().css("margin-top",-1).addClass(n.sliderType=="min-range"?"rs-range-color":"rs-path-color"),this._endLine.children().css("margin-top",t/-2).addClass("rs-path-color")):(i.removeClass("rs-rounded"),r.css({width:t,"margin-top":this._border(!0)/-2}).removeClass("rs-range-color rs-path-color"))},_updateSeperator:function(){this._startLine.rsRotate(this._start),this._endLine.rsRotate(this._start+this._end)},_createHandle:function(n){var t=this.$createElement("div.rs-handle rs-move"),u=this.options,f,r;(f=u.handleShape)!="round"&&t.addClass("rs-handle-"+f),t.attr({index:n,tabIndex:"0"});var i=this._dataElement()[0].id,i=i?i+"_":"",e=i+"handle"+(u.sliderType=="range"?"_"+(n==1?"start":"end"):"");return t.attr({role:"slider","aria-label":e}),r=this.$createElement("div.rs-bar rs-transition").css("z-index","7").append(t).rsRotate(this._start),r.addClass(u.sliderType=="range"&&n==2?"rs-second":"rs-first"),this.container.append(r),this._refreshHandle(),this.bar=r,this._active=n,n!=1&&n!=2&&(this["_handle"+n]=this._handleDefaults()),this._bind(t,"focus",this._handleFocus),this._bind(t,"blur",this._handleBlur),t},_refreshHandle:function(){var o=this.options,hSize=o.handleSize,width=o.width,h,w,isSquare=!0,isNumber=this.isNumber,s,diff;if(typeof hSize=="string"&&isNumber(hSize))if(hSize.charAt(0)==="+"||hSize.charAt(0)==="-")try{hSize=eval(width+hSize.charAt(0)+Math.abs(parseFloat(hSize)))}catch(e){console.warn(e)}else hSize.indexOf(",")&&(s=hSize.split(","),isNumber(s[0])&&isNumber(s[1])&&(w=parseFloat(s[0]),h=parseFloat(s[1]),isSquare=!1));isSquare&&(h=w=isNumber(hSize)?parseFloat(hSize):width),diff=(width+this._border()-w)/2,this._handles().css({height:h,width:w,margin:-h/2+"px 0 0 "+diff+"px"})},_handleDefaults:function(){var n=this.options.min;return{angle:this._valueToAngle(n),value:n}},_handles:function(){return this.container.children("div.rs-bar").find(".rs-handle")},_activeHandleBar:function(n){return n=n!=undefined?n:this._active,$(this.container.children("div.rs-bar")[n-1])},_handleArgs:function(n){n=n!=undefined?n:this._active;var t=this["_handle"+n];return{element:this._activeHandleBar(n).children(),index:n,isActive:n==this._active,value:t?t.value:null,angle:t?t.angle:null}},_dataElement:function(){return this._isInputType?this._hiddenField:this.control},_raiseEvent:function(n){var i=this["_pre"+n],t=this.options.value;if(i!==t)return this["_pre"+n]=t,n=="change"&&this._updatePre(),this._updateTooltip(),(n=="change"||this._bindOnDrag&&n=="drag")&&this._updateHidden(),this._raise(n,{value:t,preValue:i,handle:this._handleArgs()})},_elementDown:function(n){var u,t,h,i,r;if(!this._isReadOnly)if(u=$(n.target),u.hasClass("rs-handle"))this._handleDown(n);else{var f=this._getXY(n),e=this._getCenterPoint(),o=this._getDistance(f,e),s=this.block.outerWidth()/2,c=s-(this.options.width+this._border());o>=c&&o<=s&&(n.preventDefault(),t=this.control.find(".rs-handle.rs-focus"),this.control.attr("tabindex","0").focus().removeAttr("tabindex"),r=this._getAngleValue(f,e),h=r.angle,i=r.value,this._rangeSlider&&(t=this.control.find(".rs-handle.rs-focus"),this._active=t.length==1?parseFloat(t.attr("index")):this._handle2.value-i<i-this._handle1.value?2:1,this.bar=this._activeHandleBar()),this._changeSliderValue(i,h),this._raiseEvent("change"))}},_handleDown:function(n){n.preventDefault();var t=$(n.target);t.focus(),this._removeAnimation(),this._bindMouseEvents("_bind"),this.bar=t.parent(),this._active=parseFloat(t.attr("index")),this._handles().removeClass("rs-move"),this._raise("start",{value:this.options.value,handle:this._handleArgs()})},_handleMove:function(n){n.preventDefault();var u=this._getXY(n),f=this._getCenterPoint(),t=this._getAngleValue(u,f,!0),i,r;i=t.angle,r=t.value,this._changeSliderValue(r,i),this._raiseEvent("drag")},_handleUp:function(){this._handles().addClass("rs-move"),this._bindMouseEvents("_unbind"),this._addAnimation(),this._raiseEvent("change"),this._raise("stop",{value:this.options.value,handle:this._handleArgs()})},_handleFocus:function(n){if(!this._isReadOnly){var t=$(n.target);this._handles().removeClass("rs-focus"),t.addClass("rs-focus"),this.bar=t.parent(),this._active=parseFloat(t.attr("index")),this.options.keyboardAction&&(this._bindKeyboardEvents("_unbind"),this._bindKeyboardEvents("_bind")),this.control.find("div.rs-bar").css("z-index","7"),this.bar.css("z-index","8")}},_handleBlur:function(){this._handles().removeClass("rs-focus"),this.options.keyboardAction&&this._bindKeyboardEvents("_unbind")},_handleKeyDown:function(n){var t,r,u,i,f;this._isReadOnly||(t=n.keyCode,r=this.keys,t==27&&this._handles().blur(),t>=35&&t<=40)&&(t>=37&&t<=40&&this._removeAnimation(),u=this["_handle"+this._active],n.preventDefault(),t==r.UP||t==r.RIGHT?i=this._round(this._limitValue(u.value+this.options.step)):t==r.DOWN||t==r.LEFT?i=this._round(this._limitValue(u.value-this._getMinusStep(u.value))):t==36?i=this._getKeyValue("Home"):t==35&&(i=this._getKeyValue("End")),f=this._valueToAngle(i),this._changeSliderValue(i,f),this._raiseEvent("drag"))},_handleKeyUp:function(){this._addAnimation(),this._raiseEvent("change")},_getMinusStep:function(n){var t=this.options,f=t.min,u=t.max,i=t.step,r;return n==u?(r=(u-f)%i,r==0?i:r):i},_getKeyValue:function(n){var t=this.options,i=t.min,r=t.max;return this._rangeSlider?n=="Home"?this._active==1?i:this._handle1.value:this._active==1?this._handle2.value:r:n=="Home"?i:r},_elementScroll:function(n){if(!this._isReadOnly){n.preventDefault();var i=n.originalEvent||n,r,t,f,u;(u=i.wheelDelta?i.wheelDelta/60:i.detail?-i.detail/2:0,u!=0)&&(this._updateActiveHandle(n),r=this["_handle"+this._active],t=r.value+(u>0?this.options.step:-this._getMinusStep(r.value)),t=this._limitValue(t),f=this._valueToAngle(t),this._removeAnimation(),this._changeSliderValue(t,f),this._raiseEvent("change"),this._addAnimation())}},_updateActiveHandle:function(n){var t=$(n.target);t.hasClass("rs-handle")&&t.parent().parent()[0]==this.control[0]&&(this.bar=t.parent(),this._active=parseFloat(t.attr("index"))),this.bar.find(".rs-handle").hasClass("rs-focus")||this.bar.find(".rs-handle").focus()},_bindControlEvents:function(n){this[n](this.control,"mousedown",this._elementDown),this[n](this.control,"touchstart",this._elementDown)},_bindScrollEvents:function(n){this[n](this.control,"mousewheel",this._elementScroll),this[n](this.control,"DOMMouseScroll",this._elementScroll)},_bindMouseEvents:function(n){this[n]($(document),"mousemove",this._handleMove),this[n]($(document),"mouseup",this._handleUp),this[n]($(document),"mouseleave",this._handleUp),this[n]($(document),"touchmove",this._handleMove),this[n]($(document),"touchend",this._handleUp),this[n]($(document),"touchcancel",this._handleUp)},_bindKeyboardEvents:function(n){this[n]($(document),"keydown",this._handleKeyDown),this[n]($(document),"keyup",this._handleKeyUp)},_changeSliderValue:function(n,t){var u=this._oriAngle(t),i=this._limitAngle(t);if(this._rangeSlider||this._showRange){if(this._active==1&&u<=this._oriAngle(this._handle2.angle)||this._active==2&&u>=this._oriAngle(this._handle1.angle)||this._invertRange){this["_handle"+this._active]={angle:t,value:n},this.options.value=this._rangeSlider?this._handle1.value+","+this._handle2.value:n,this.bar.rsRotate(i),this._updateARIA(n);var r=this._oriAngle(this._handle2.angle)-this._oriAngle(this._handle1.angle),f="1",e="0";r<=180&&!(r<0&&r>-180)&&(f="0",e="1"),this.block2.css("opacity",f),this.block3.css("opacity",e),(this._active==1?this.block4:this.block2).rsRotate(i-180),(this._active==1?this.block1:this.block3).rsRotate(i)}}else this["_handle"+this._active]={angle:t,value:n},this.options.value=n,this.bar.rsRotate(i),this._updateARIA(n)},_updateARIA:function(n){var i=this.options,r=i.min,u=i.max,t;this.bar.children().attr({"aria-valuenow":n}),i.sliderType=="range"?(t=this._handles(),t.eq(0).attr({"aria-valuemin":r}),t.eq(1).attr({"aria-valuemax":u}),this._active==1?t.eq(1).attr({"aria-valuemin":n}):t.eq(0).attr({"aria-valuemax":n})):this.bar.children().attr({"aria-valuemin":r,"aria-valuemax":u})},_checkKO:function(){var f=this._dataElement().data("bind"),t,i,r,n,u;if(typeof f=="string"&&typeof ko=="object"){if(t=ko.dataFor(this._dataElement()[0]),typeof t=="undefined")return!0;for(i=f.split(","),n=0;n<i.length;n++)if(u=i[n].split(":"),$.trim(u[0])=="value"){r=$.trim(u[1]);break}r&&(this._isKO=!0,ko.computed(function(){this.option("value",t[r]())},this))}},_checkAngular:function(){if(typeof angular=="object"&&typeof angular.element=="function"&&(this._ngName=this._dataElement().attr("ng-model"),typeof this._ngName=="string")){this._isAngular=!0;var n=this;this._scope().$watch(this._ngName,function(t){n.option("value",t)})}},_scope:function(){return angular.element(this._dataElement()).scope()},_getDistance:function(n,t){return Math.sqrt((n.x-t.x)*(n.x-t.x)+(n.y-t.y)*(n.y-t.y))},_getXY:function(n){return n.type.indexOf("mouse")==-1&&(n=(n.originalEvent||n).changedTouches[0]),{x:n.pageX,y:n.pageY}},_getCenterPoint:function(){var n=this.block.offset();return{x:n.left+this.block.outerWidth()/2,y:n.top+this.block.outerHeight()/2}},_getAngleValue:function(n,t,i){var u=Math.atan2(n.y-t.y,t.x-n.x),r=-u/(Math.PI/180);return r<this._start&&(r+=360),r=this._checkAngle(r,i),this._processStepByAngle(r)},_checkAngle:function(n,t){var f=this._oriAngle(n),i=this["_handle"+this._active].angle,r=this._oriAngle(i),u;if(f>this._end){if(!t)return i;n=this._start+(r<=this._end-r?0:this._end)}else if(t&&(u=this._handleDragDistance,this.isNumber(u)&&Math.abs(f-r)>u))return i;return n},_processStepByAngle:function(n){var t=this._angleToValue(n);return this._processStepByValue(t)},_processStepByValue:function(n){var e=this.options,o=e.min,l=e.max,i=e.step,s=o>l,h,t,r,u,f,c;return i=s?-i:i,h=(n-o)%i,t=n-h,r=this._limitValue(t+i),u=this._limitValue(t-i),f=s?n<=t?t-n<n-r?t:r:n-t>u-n?t:u:n>=t?n-t<r-n?t:r:t-n>n-u?t:u,f=this._round(f),c=this._valueToAngle(f),{value:f,angle:c}},_round:function(n){var t=this.options.step.toString().split(".");return t[1]?parseFloat(n.toFixed(t[1].length)):Math.round(n)},_oriAngle:function(n){var t=n-this._start;return t<0&&(t+=360),t},_limitAngle:function(n){return n>360+this._start&&(n-=360),n<this._start&&(n+=360),n},_limitValue:function(n){var u=this.options,t=u.min,i=u.max,r=t>i;return(!r&&n<t||r&&n>t)&&(n=t),(!r&&n>i||r&&n<i)&&(n=i),n},_angleToValue:function(n){var t=this.options,i=t.min,r=t.max;return this._oriAngle(n)/this._end*(r-i)+i},_valueToAngle:function(n){var t=this.options,i=t.min,r=t.max;return(n-i)/(r-i)*this._end+this._start},_appendHiddenField:function(){this._hiddenField=this._hiddenField||this.$createElement("input"),this._hiddenField.attr({type:"hidden",name:this._dataElement()[0].id||""}),this.control.append(this._hiddenField),this._updateHidden()},_updateHidden:function(){var n=this.options.value;this._hiddenField.val(n),(this._isKO||this._isAngular)&&this._hiddenField.trigger("change"),this._isAngular&&(this._scope()[this._ngName]=n)},_updateTooltip:function(){this.tooltip&&!this.tooltip.hasClass("hover")&&this.tooltip.html(this._getTooltipValue()),this._updateTooltipPos()},_updateTooltipPos:function(){this.tooltip&&this.tooltip.css(this._getTooltipPos())},_getTooltipPos:function(){var n=this.options.circleShape,t,i=this.tooltip.outerHeight(),r=this.tooltip.outerWidth();if(n=="full"||n=="pie"||n.indexOf("custom")===0)return{"margin-top":-i/2,"margin-left":-r/2};if(n.indexOf("half")!=-1){switch(n){case"half-top":case"half-bottom":t={"margin-left":-r/2};break;case"half-left":case"half-right":t={"margin-top":-i/2}}return t}return{}},_getTooltipValue:function(n){var i=this.options.value,t;return this._rangeSlider?(t=i.split(","),n)?t[0]+" - "+t[1]:this._tooltipValue(t[0],1)+" - "+this._tooltipValue(t[1],2):n?i:this._tooltipValue(i)},_tooltipValue:function(n,t){var i=this._raise("tooltipFormat",{value:n,handle:this._handleArgs(t)});return i!=null&&typeof i!="boolean"?i:n},_validateStartAngle:function(){var n=this.options.startAngle;return n=(this.isNumber(n)?parseFloat(n):0)%360,n<0&&(n+=360),this.options.startAngle=n,n},_validateEndAngle:function(){var o=this.options,start=o.startAngle,end=o.endAngle;if(typeof end=="string"&&this.isNumber(end)&&(end.charAt(0)==="+"||end.charAt(0)==="-"))try{end=eval(start+end.charAt(0)+Math.abs(parseFloat(end)))}catch(e){console.warn(e)}return end=(this.isNumber(end)?parseFloat(end):360)%360,end<=start&&(end+=360),end},_refreshCircleShape:function(){var n=this.options.circleShape,i=["half-top","half-bottom","half-left","half-right","quarter-top-left","quarter-top-right","quarter-bottom-right","quarter-bottom-left","pie","custom-half","custom-quarter"],t;i.indexOf(n)==-1&&(t=["h1","h2","h3","h4","q1","q2","q3","q4","3/4","ch","cq"].indexOf(n),n=t!=-1?i[t]:n=="half"?"half-top":n=="quarter"?"quarter-top-left":"full"),this.options.circleShape=n},_appendOverlay:function(){var n=this.options.circleShape;n=="pie"?this._checkOverlay(".rs-overlay",270):(n=="custom-half"||n=="custom-quarter")&&(this._checkOverlay(".rs-overlay1",180),n=="custom-quarter"&&this._checkOverlay(".rs-overlay2",this._end))},_checkOverlay:function(n,t){var i=this.container.children(n);i.length==0&&(i=this.$createElement("div"+n+" rs-transition rs-bg-color"),this.container.append(i)),i.rsRotate(this._start+t)},_checkDataType:function(){var i=this.options,r,n,t,u=this._props();for(r in u.numberType)n=u.numberType[r],t=i[n],i[n]=this.isNumber(t)?parseFloat(t):this.defaults[n];for(r in u.booleanType)n=u.booleanType[r],t=i[n],i[n]=t=="false"?!1:!!t;for(r in u.stringType)n=u.stringType[r],t=i[n],i[n]=(""+t).toLowerCase()},_validateSliderType:function(){var n=this.options.sliderType.toLowerCase();this._rangeSlider=this._showRange=!1,n=="range"?this._rangeSlider=this._showRange=!0:n.indexOf("min")!=-1?(this._showRange=!0,n="min-range"):n="default",this.options.sliderType=n},_updateStartEnd:function(){var r=this.options,n=r.circleShape,t=r.startAngle,i=r.endAngle;n!="full"&&(n.indexOf("quarter")!=-1?i="+90":n.indexOf("half")!=-1?i="+180":n=="pie"&&(i="+270"),this.options.endAngle=i,n=="quarter-top-left"||n=="half-top"?t=0:n=="quarter-top-right"||n=="half-right"?t=90:n=="quarter-bottom-right"||n=="half-bottom"?t=180:(n=="quarter-bottom-left"||n=="half-left")&&(t=270),this.options.startAngle=t)},_validateStartEnd:function(){this._start=this._validateStartAngle(),this._end=this._validateEndAngle();var n=this._start<this._end?0:360;this._end+=n-this._start},_analyzeModelValue:function(){var f=this.options,n=f.value,t=f.min,s=f.max,e,u,r=this.isNumber,o=typeof n=="string",i;n instanceof Array&&(n=n.toString()),i=o?n.split(","):[n],this._rangeSlider?u=o?i.length>=2?(r(i[0])?i[0]:t)+","+(r(i[1])?i[1]:s):r(i[0])?t+","+i[0]:t+","+t:r(n)?t+","+n:t+","+t:o?(e=i.pop(),u=r(e)?parseFloat(e):t):u=r(n)?parseFloat(n):t,this.options.value=u},_validateModelValue:function(){var r=this.options.value,i;if(this._rangeSlider){var u=r.split(","),n=parseFloat(u[0]),t=parseFloat(u[1]);n=this._limitValue(n),t=this._limitValue(t),this._invertRange||n>t&&(t=n),this._handle1=this._processStepByValue(n),this._handle2=this._processStepByValue(t),this.options.value=this._handle1.value+","+this._handle2.value}else i=this._showRange?2:this._active||1,this["_handle"+i]=this._processStepByValue(this._limitValue(r)),this._showRange&&(this._handle1=this._handleDefaults()),this.options.value=this["_handle"+i].value},$createElement:function(n){var t=n.split(".");return $(document.createElement(t[0])).addClass(t[1]||"")},isNumber:function(n){return n=parseFloat(n),typeof n=="number"&&!isNaN(n)},getBrowserName:function(){var n="",t=window.navigator.userAgent;return!!window.opr&&!!opr.addons||!!window.opera||t.indexOf(" OPR/")>=0?n="opera":typeof InstallTrigger!="undefined"?n="firefox":t.indexOf("MSIE ")>0||t.indexOf("Trident/")>0?n="ie":window.StyleMedia?n="edge":t.indexOf("Safari")!=-1&&t.indexOf("Chrome")==-1?n="safari":(!window.chrome||!window.chrome.webstore)&&t.indexOf("Chrome")==-1||(n="chrome"),n},_isBrowserSupported:function(){for(var t=["borderRadius","WebkitBorderRadius","MozBorderRadius","OBorderRadius","msBorderRadius","KhtmlBorderRadius"],n=0;n<t.length;n++)if(document.body.style[t[n]]!==undefined)return!0},_throwError:function(){return"This browser doesn't support the border-radious property."},_raise:function(n,t){var u=this.options,i=u[n],r=!0;return t=t||{value:u.value},t.id=this.id,t.control=this.control,t.options=u,i&&(t.type=n,typeof i=="string"&&(i=window[i]),$.isFunction(i)&&(r=i.call(this,t),r=r===!1?!1:r)),this.control.trigger($.Event(n,t)),r},_bind:function(n,t,i){$(n).bind(t,$.proxy(i,this))},_unbind:function(n,t,i){$(n).unbind(t,$.proxy(i,this))},_getInstance:function(){return $.data(this._dataElement()[0],pluginName)},_saveInstanceOnElement:function(){$.data(this.control[0],pluginName,this)},_saveInstanceOnID:function(){var n=this.id;n&&typeof window[n]!="undefined"&&(window[n]=this)},_removeData:function(){var n=this._dataElement()[0];$.removeData&&$.removeData(n,pluginName),n.id&&typeof window[n.id]._init=="function"&&delete window[n.id]},_destroyControl:function(){this._isInputType&&this._dataElement().insertAfter(this.control).attr("type","text"),this.control.empty().removeClass("rs-control").height("").width(""),this._removeAnimation(),this._bindControlEvents("_unbind")},_updateWidth:function(){this.lastBlock.css("padding",this.options.width),this._refreshHandle()},_readOnly:function(n){this._isReadOnly=n,this.container.removeClass("rs-readonly"),n&&this.container.addClass("rs-readonly")},_get:function(n){return this.options[n]},_set:function(n,t){var i=this._props();if($.inArray(n,i.numberType)!=-1){if(!this.isNumber(t))return;t=parseFloat(t)}else $.inArray(n,i.booleanType)!=-1?t=t=="false"?!1:!!t:$.inArray(n,i.stringType)!=-1&&(t=t.toLowerCase());if(this.options[n]!=t){this.options[n]=t;switch(n){case"startAngle":case"endAngle":this._validateStartEnd(),this._updateSeperator(),this._appendOverlay();case"min":case"max":case"step":case"value":this._analyzeModelValue(),this._validateModelValue(),this._setValue(),this._updatePre(),this._updateHidden(),this._updateTooltip();break;case"radius":this._setRadius(),this._updateTooltipPos();break;case"width":this._removeAnimation(),this._updateWidth(),this._updateTooltipPos(),this._addAnimation(),this._refreshSeperator();break;case"handleSize":this._refreshHandle();break;case"handleShape":this._setHandleShape();break;case"animation":this._setAnimation();break;case"showTooltip":this.options.showTooltip?this._appendTooltip():this._removeTooltip();break;case"editableTooltip":this._tooltipEditable(),this._updateTooltipPos();break;case"disabled":this.options.disabled?this.disable():this.enable();break;case"readOnly":this.options.readOnly?this._readOnly(!0):!this.options.disabled&&this._readOnly(!1);break;case"mouseScrollAction":this._bindScrollEvents(this.options.mouseScrollAction?"_bind":"_unbind");break;case"lineCap":this._refreshSeperator();break;case"circleShape":this._refreshCircleShape(),this.options.circleShape=="full"&&(this.options.startAngle=0,this.options.endAngle="+360");case"sliderType":this._destroyControl(),this._onInit()}return this}},option:function(n,t){if(this._getInstance()&&this._isBrowserSupport){if($.isPlainObject(n)){(n.min!==undefined||n.max!==undefined)&&(n.min!==undefined&&(this.options.min=n.min,delete n.min),n.max!==undefined&&(this.options.max=n.max,delete n.max),n.value==undefined&&this._set("value",this.options.value));for(var i in n)this._set(i,n[i])}else if(n&&typeof n=="string"){if(t===undefined)return this._get(n);this._set(n,t)}return this}},getValue:function(n){if(this.options.sliderType=="range"&&this.isNumber(n)){var t=parseFloat(n);if(t==1||t==2)return this["_handle"+t].value}return this._get("value")},setValue:function(n,t){var i,r,u;this.isNumber(n)&&(this.isNumber(t)&&(i=this.options.sliderType,i=="range"?(r=parseFloat(t),u=parseFloat(n),r==1?n=u+","+this._handle2.value:r==2&&(n=this._handle1.value+","+u)):i=="default"&&(this._active=t)),this._set("value",n))},disable:function(){this.options.disabled=!0,this.container.addClass("rs-disabled"),this._readOnly(!0)},enable:function(){this.options.disabled=!1,this.container.removeClass("rs-disabled"),this.options.readOnly||this._readOnly(!1)},destroy:function(){this._getInstance()&&(this._destroyControl(),this._removeData(),this._isInputType&&this.control.remove())}},$.fn.rsRotate=function(n){var t=this,i="rotate("+n+"deg)";return t.css("-webkit-transform",i),t.css("-moz-transform",i),t.css("-ms-transform",i),t.css("-o-transform",i),t.css("transform",i),t},$.fn[pluginName].prototype=RoundSlider.prototype})(jQuery,window); \ No newline at end of file diff --git a/manifest.json b/manifest.json index 7fd7dfce..05b5b6f3 100644 --- a/manifest.json +++ b/manifest.json @@ -43,7 +43,7 @@ "default-icons.html", "lib/data/schoology-reorder-ui-langprops.json" ], - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", + "content_security_policy": "script-src 'self'; object-src 'self'", "background": { "scripts": [ "lib/js/purify.min.js", @@ -108,6 +108,19 @@ ], "run_at": "document_end" }, + { + "matches": [ + "https://lms.lausd.net/course/*/materials*", + "https://*.schoology.com/course/*/materials*" + ], + "css": [ + "css/materials.css" + ], + "js": [ + "lib/js/pdf.min.js" + ], + "run_at": "document_start" + }, { "matches": [ "https://lms.lausd.net/course/*/materials*", diff --git a/theme-editor.html b/theme-editor.html index bd1fa5b1..3244647a 100644 --- a/theme-editor.html +++ b/theme-editor.html @@ -561,7 +561,7 @@ <h6 style="line-height: 24px; vertical-align: middle;"><i </main> <script src="/lib/js/materialize.min.js"></script> <script src="/lib/js/jquery-ui.min.js"></script> - <script src="/lib/js/roundslider.min.js"></script> + <script src="/lib/js/roundslider.js"></script> <script src="/js/icons.js"></script> <script src="/js/theme-model.js"></script> <script src="/js/default-themes.js"></script> From 0b25bffb247788410d762012f90c88aa2597b269 Mon Sep 17 00:00:00 2001 From: Aaron Opell <aaron.opell@live.com> Date: Mon, 23 Mar 2020 20:38:14 -0700 Subject: [PATCH 7/9] Added privacy policy --- PRIVACY.md | 31 + lib/js/roundslider.js | 3224 ++++++++++++++++++++--------------------- 2 files changed, 1643 insertions(+), 1612 deletions(-) create mode 100644 PRIVACY.md diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 00000000..9fff13ec --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,31 @@ +# Privacy Policy - Schoology Plus + +## Introduction + +It is Schoology Plus's policy to respect your privacy regarding any information we may collect while operating our browser extension. This Privacy Policy applies to the Schoology Plus browser extension for Chrome, Firefox, and Microsoft Edge (hereinafter, "us", "we", "the Extension", or "Schoology Plus"). We respect your privacy and are committed to protecting personally identifiable information accessible to us through the Extension. We have adopted this privacy policy ("Privacy Policy") to explain what information may be collected by our Extension, how we use this information, and under what circumstances we may disclose the information to third parties. This Privacy Policy applies only to information we collect through the Extension and does not apply to our collection of information from other sources. + +## Non Personally Identifying Information + +Like most website operators, Schoology Plus collects non-personally-identifying information of the sort that web browsers and servers typically make available, such as the browser type, language preference, referring site, and the date and time of each user request. Schoology Plus's purpose in collecting non-personally identifying information is to better understand how Schoology Plus's users use the Extension. From time to time, Schoology Plus may release non-personally-identifying information in the aggregate, e.g., by publishing a report on trends in the usage of the Extension. + +Schoology Plus uses Google Analytics to collect information for these purposes. If you would like to opt-out of tracking, you can install the [Google Analytics Opt-out Add-on](https://chrome.google.com/webstore/detail/google-analytics-opt-out/fllaojicojecljbmefodhfapmkghcbnh?hl=en) or another tracking-prevention extension. + +## Personally Identifying Information + +By using Schoology Plus, you grant us access to view and modify the contents of the Schoology website in your web browser. However, we DO NOT harvest or collect any information that can personally identify you longer than absolutely necessary to perform calculations or page enhancements. This information (including, but not limited to: school name, grades, and assignment details) is only modified locally and never leaves your computer. + +## Aggregated Statistics + +Schoology Plus may collect statistics about the behavior of users. Schoology Plus may display this information publicly or provide it to others. However, Schoology Plus does not disclose your personally-identifying information. + +## Cookies + +To enrich and perfect your online experience, Schoology Plus uses "Cookies", similar technologies and services provided by others to display personalized content and store your preferences on your computer. A cookie is a string of information that a website stores on a user's computer, and that the user's browser provides to the website each time the user returns. Schoology Plus uses cookies to help us identify and track users' extension preferences. Schoology Plus users who do not wish to have cookies placed on their computers should set their browsers to refuse cookies before using the Extension, with the drawback that certain features of Schoology Plus may not function properly without the aid of cookies. By continuing to use the Extension without changing your cookie settings, you hereby acknowledge and agree to Schoology Plus's use of cookies. + +## Privacy Policy Changes + +Although most changes are likely to be minor, Schoology Plus may change its Privacy Policy from time to time, and in Schoology Plus's sole discretion. Schoology Plus encourages users to frequently check this page for any changes to its Privacy Policy. Your continued use of the Extension after any change in this Privacy Policy will constitute your acceptance of such change. Any major changes will be prominently announced. + +## Disclaimer + +Schoology Plus is not affiliated with Schoology Inc. or the Los Angeles Unified School District. Schoology, the SCHOOLOGY® wordmark, and the S logo are registered and unregistered trademarks of Schoology, Inc. in the United States. All product names, logos, and brands are property of their respective owners. \ No newline at end of file diff --git a/lib/js/roundslider.js b/lib/js/roundslider.js index da140f87..b6241084 100644 --- a/lib/js/roundslider.js +++ b/lib/js/roundslider.js @@ -1,1613 +1,1613 @@ -/*! - * roundSlider v1.4.0 | (c) 2015-2020, Soundar - * MIT license | http://roundsliderui.com/licence.html - */ - -(function ($, window, undefined) { - "use strict"; - /*jslint nomen: true */ - - var pluginName = "roundSlider"; - - // The plugin initialization - $.fn[pluginName] = function (options) { - return CreateRoundSlider.call(this, options, arguments); - }; - - RoundSlider.prototype = { - - pluginName: pluginName, - version: "1.4.0", - - // after the control initialization the updated default values - // are merged into the options - options: {}, - - // holds the current roundSlider element - control: null, - - // default properties of the plugin. while add a new property, - // that type should be included in the "_props:" for validation - defaults: { - min: 0, - max: 100, - step: 1, - value: null, - radius: 85, - width: 18, - handleSize: "+0", - startAngle: 0, - endAngle: "+360", - animation: true, - showTooltip: true, - editableTooltip: true, - readOnly: false, - disabled: false, - keyboardAction: true, - mouseScrollAction: false, - lineCap: "butt", - sliderType: "default", - circleShape: "full", - handleShape: "round", - - // SVG related properties - svgMode: false, - borderWidth: 1, - borderColor: null, - pathColor: null, - rangeColor: null, - - // events - beforeCreate: null, - create: null, - start: null, - drag: null, - change: null, - stop: null, - tooltipFormat: null - }, - keys: { // key codes for - UP: 38, // up arrow - DOWN: 40, // down arrow - LEFT: 37, // left arrow - RIGHT: 39 // right arrow - }, - _props: function () { - return { - numberType: ["min", "max", "step", "radius", "width", "borderWidth", "startAngle"], - booleanType: ["animation", "showTooltip", "editableTooltip", "readOnly", "disabled", - "keyboardAction", "mouseScrollAction", "svgMode"], - stringType: ["sliderType", "circleShape", "handleShape", "lineCap"] - }; - }, - - _init: function () { - if (this.options.svgMode) { - var EMPTY_FUNCTION = function () { }; - this._appendSeperator = EMPTY_FUNCTION; - this._refreshSeperator = EMPTY_FUNCTION; - this._updateSeperator = EMPTY_FUNCTION; - this._appendOverlay = EMPTY_FUNCTION; - this._checkOverlay = EMPTY_FUNCTION; - this._updateWidth = EMPTY_FUNCTION; - } - - this._isBrowserSupport = this._isBrowserSupported(); - this._isKO = false; - this._isAngular = false; - if (this.control.is("input")) { - this._isInputType = true; - this._hiddenField = this.control; - this.control = this.$createElement("div"); - this.control.insertAfter(this._hiddenField); - this.options.value = this._hiddenField.val() || this.options.value; - var that = this; - this._checkKO() && setTimeout(function () { that._checkKO(); }, 1); - this._checkAngular(); - } - this._bindOnDrag = false; - var _updateOn = this._dataElement().attr("data-updateon"); - if (typeof _updateOn == "string") { if (_updateOn == "drag") this._bindOnDrag = true; } - else if (this._isAngular) this._bindOnDrag = true; - - this._onInit(); - }, - _onInit: function () { - this._initialize(); - this._update(); - this._render(); - }, - _initialize: function () { - var browserName = this.browserName = this.getBrowserName(); - if (browserName) this.control.addClass("rs-" + browserName); - if (!this._isBrowserSupport) return; - this._isReadOnly = false; - this._checkDataType(); - this._refreshCircleShape(); - }, - _render: function () { - this.container = this.$createElement("div.rs-container"); - this.innerContainer = this.$createElement("div.rs-inner-container"); - this.container.append(this.innerContainer); - var $rootCSS = "rs-control " + (this.options.svgMode ? "rs-svg-mode" : "rs-classic-mode"); - this.control.addClass($rootCSS).empty().append(this.container); - - if (this._isBrowserSupport) { - this._createLayers(); - this._createOtherLayers(); - this._setContainerClass(); - this._setRadius(); - this._setProperties(); - this._setValue(); - this._updateTooltipPos(); - this._bindControlEvents("_bind"); - } - else { - var msg = this.$createElement("div.rs-msg"); - msg.html(typeof this._throwError === "function" ? this._throwError() : this._throwError); - this.control.empty().addClass("rs-error").append(msg); - if (this._isInputType) this.control.append(this._dataElement()); - } - }, - _update: function () { - this._validateSliderType(); - this._updateStartEnd(); - this._validateStartEnd(); - this._handle1 = this._handle2 = this._handleDefaults(); - this._analyzeModelValue(); - this._validateModelValue(); - }, - _createLayers: function () { - if (this.options.svgMode) { - this._createSVGElements(); - this._setSVGAttributes(); - this._setSVGStyles(); - this._moveSliderRange(true); - return; - } - - this.block = this.$createElement("div.rs-block rs-outer rs-border"); - this.innerContainer.append(this.block); - - var padd = this.options.width, start = this._start, path; - path = this.$createElement("div.rs-path rs-transition"); - - if (this._rangeSlider || this._showRange) { - this.block1 = path.clone().addClass("rs-range-color").rsRotate(start); - this.block2 = path.clone().addClass("rs-range-color").css("opacity", "0").rsRotate(start); - this.block3 = path.clone().addClass("rs-path-color").rsRotate(start); - this.block4 = path.addClass("rs-path-color").css({ "opacity": "1", "z-index": "1" }).rsRotate(start - 180); - - this.block.append(this.block1, this.block2, this.block3, this.block4).addClass("rs-split"); - } - else this.block.append(path.addClass("rs-path-color")); - - this.lastBlock = this.$createElement("span.rs-block").css({ "padding": padd }); - this.innerBlock = this.$createElement("div.rs-inner rs-bg-color rs-border"); - this.lastBlock.append(this.innerBlock); - this.block.append(this.lastBlock); - }, - _createOtherLayers: function () { - this._appendHandle(); - this._appendSeperator(); // non SVG mode only - this._appendOverlay(); // non SVG mode only - this._appendHiddenField(); - }, - _setProperties: function () { - this._updatePre(); - this._setHandleShape(); - this._addAnimation(); - this._appendTooltip(); - if (!this.options.showTooltip) this._removeTooltip(); - if (this.options.disabled) this.disable(); - else if (this.options.readOnly) this._readOnly(true); - if (this.options.mouseScrollAction) this._bindScrollEvents("_bind"); - }, - _updatePre: function () { - this._prechange = this._predrag = this.options.value; - }, - _setValue: function () { - if (this._rangeSlider) { - this._setHandleValue(1); - this._setHandleValue(2); - } - else { - if (this._showRange) this._setHandleValue(1); - var index = (this.options.sliderType == "default") ? (this._active || 1) : parseFloat(this.bar.children().attr("index")); - this._setHandleValue(index); - } - }, - _appendTooltip: function () { - if (this.container.children(".rs-tooltip").length !== 0) return; - this.tooltip = this.$createElement("span.rs-tooltip rs-tooltip-text"); - this.container.append(this.tooltip); - this._tooltipEditable(); - this._updateTooltip(); - }, - _removeTooltip: function () { - if (this.container.children(".rs-tooltip").length == 0) return; - this.tooltip && this.tooltip.remove(); - }, - _tooltipEditable: function () { - var o = this.options, tooltip = this.tooltip, hook; - if (!tooltip || !o.showTooltip) return; - - if (o.editableTooltip) { - tooltip.addClass("edit"); - hook = "_bind"; - } - else { - tooltip.removeClass("edit"); - hook = "_unbind"; - } - this[hook](tooltip, "click", this._editTooltip); - }, - _editTooltip: function (e) { - var tooltip = this.tooltip; - if (!tooltip.hasClass("edit") || this._isReadOnly) return; - var border = parseFloat(tooltip.css("border-left-width")) * 2; - var input = this.input = this.$createElement("input.rs-input rs-tooltip-text").css({ - height: tooltip.outerHeight() - border, - width: tooltip.outerWidth() - border - }); - tooltip.html(input).removeClass("edit").addClass("hover"); - - input.focus().val(this._getTooltipValue(true)); - - this._bind(input, "blur", this._focusOut); - this._bind(input, "change", this._focusOut); - }, - _focusOut: function (e) { - if (e.type == "change") { - var val = this.input.val().replace("-", ","); - if (val[0] == ",") { - val = "-" + val.slice(1).replace("-", ","); - } - this.options.value = val; - this._analyzeModelValue(); - this._validateModelValue(); - this._setValue(); - this.input.val(this._getTooltipValue(true)); - } - else { - this.tooltip.addClass("edit").removeClass("hover"); - this._updateTooltip(); - } - this._raiseEvent("change"); - }, - _setHandleShape: function () { - var type = this.options.handleShape, allHandles = this._handles(); - allHandles.removeClass("rs-handle-dot rs-handle-square"); - if (type == "dot") allHandles.addClass("rs-handle-dot"); - else if (type == "square") allHandles.addClass("rs-handle-square"); - else this.options.handleShape = "round"; - }, - _setHandleValue: function (index) { - this._active = index; - var handle = this["_handle" + index]; - if (this.options.sliderType != "min-range") this.bar = this._activeHandleBar(); - this._changeSliderValue(handle.value, handle.angle); - }, - _setAnimation: function () { - if (this.options.animation) this._addAnimation(); - else this._removeAnimation(); - }, - _addAnimation: function () { - if (this.options.animation) this.control.addClass("rs-animation"); - }, - _removeAnimation: function () { - this.control.removeClass("rs-animation"); - }, - _setContainerClass: function () { - var circleShape = this.options.circleShape; - if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0) { - this.container.addClass("full " + circleShape); - } - else { - this.container.addClass(circleShape.split("-").join(" ")); - } - }, - _setRadius: function () { - var o = this.options, r = o.radius, d = r * 2, circleShape = o.circleShape; - var extraSize = 0, actualHeight, actualWidth; - var height = actualHeight = d, width = actualWidth = d; - - // whenever the radius changes, before update the container size - // check for the lineCap also, since that will make some additional size - // also, based on that need to align the handle bars - var isFullCircle = (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0); - if (o.svgMode && !isFullCircle) { - var handleBars = this._handleBars(); - if (o.lineCap != "none") { - extraSize = (o.lineCap === "butt") ? (o.borderWidth / 2) : ((o.width / 2) + o.borderWidth); - if (circleShape.indexOf("bottom") != -1) { - handleBars.css("margin-top", extraSize + 'px'); - } - if (circleShape.indexOf("right") != -1) { - handleBars.css("margin-right", -extraSize + 'px'); - } - } - else { - // when lineCap none, then remove the styles that was set previously for the other lineCap props - $.each(handleBars, function (i, bar) { - bar.style.removeProperty("margin-top"); - bar.style.removeProperty("margin-right"); - }); - } - } - - if (circleShape.indexOf("half") === 0) { - switch (circleShape) { - case "half-top": - case "half-bottom": - height = r; actualHeight = r + extraSize; - break; - case "half-left": - case "half-right": - width = r; actualWidth = r + extraSize; - break; - } - } - else if (circleShape.indexOf("quarter") === 0) { - height = width = r; - actualHeight = actualWidth = r + extraSize; - } - - this.container.css({ "height": height, "width": width }); - this.control.css({ "height": actualHeight, "width": actualWidth }); - - // when needed, then only we can set the styles through script, otherwise CSS styles applicable - if (extraSize !== 0) this.innerContainer.css({ "height": actualHeight, "width": actualWidth }); - else this.innerContainer.removeAttr("style"); - - if (o.svgMode) { - this.svgContainer.height(d).width(d); - this.svgContainer.children("svg").height(d).width(d); - } - }, - _border: function (seperator) { - if (seperator) return parseFloat(this._startLine.children().css("border-bottom-width")); - if (this.options.svgMode) return this.options.borderWidth * 2; - return parseFloat(this.block.css("border-top-width")) * 2; - }, - _appendHandle: function () { - if (this._rangeSlider || !this._showRange) this._createHandle(1); - if (this._rangeSlider || this._showRange) this._createHandle(2); - }, - _appendSeperator: function () { - this._startLine = this._addSeperator(this._start, "rs-start"); - this._endLine = this._addSeperator(this._start + this._end, "rs-end"); - this._refreshSeperator(); - }, - _addSeperator: function (pos, cls) { - var line = this.$createElement("span.rs-seperator rs-border"), width = this.options.width, _border = this._border(); - var lineWrap = this.$createElement("span.rs-bar rs-transition " + cls).append(line).rsRotate(pos); - this.container.append(lineWrap); - return lineWrap; - }, - _refreshSeperator: function () { - var bars = this._startLine.add(this._endLine), seperators = bars.children().removeAttr("style"); - var o = this.options, width = o.width, _border = this._border(), size = width + _border; - if (o.lineCap == "round" && o.circleShape != "full") { - bars.addClass("rs-rounded"); - seperators.css({ width: size, height: (size / 2) + 1 }); - this._startLine.children().css("margin-top", -1).addClass(o.sliderType == "min-range" ? "rs-range-color" : "rs-path-color"); - this._endLine.children().css("margin-top", size / -2).addClass("rs-path-color"); - } - else { - bars.removeClass("rs-rounded"); - seperators.css({ "width": size, "margin-top": this._border(true) / -2 }).removeClass("rs-range-color rs-path-color"); - } - }, - _updateSeperator: function () { - this._startLine.rsRotate(this._start); - this._endLine.rsRotate(this._start + this._end); - }, - _createHandle: function (index) { - var handle = this.$createElement("div.rs-handle rs-move"), o = this.options, hs; - if ((hs = o.handleShape) != "round") handle.addClass("rs-handle-" + hs); - handle.attr({ "index": index, "tabIndex": "0" }); - - var id = this._dataElement()[0].id, id = id ? id + "_" : ""; - var label = id + "handle" + (o.sliderType == "range" ? "_" + (index == 1 ? "start" : "end") : ""); - handle.attr({ "role": "slider", "aria-label": label }); // WAI-ARIA support - - var bar = this.$createElement("div.rs-bar rs-transition").css("z-index", "7").append(handle).rsRotate(this._start); - bar.addClass(o.sliderType == "range" && index == 2 ? "rs-second" : "rs-first"); - this.container.append(bar); - this._refreshHandle(); - - this.bar = bar; - this._active = index; - if (index != 1 && index != 2) this["_handle" + index] = this._handleDefaults(); - this._bind(handle, "focus", this._handleFocus); - this._bind(handle, "blur", this._handleBlur); - return handle; - }, - _refreshHandle: function () { - var o = this.options, hSize = o.handleSize, width = o.width, h, w, isSquare = true, isNumber = this.isNumber; - if (typeof hSize === "string" && isNumber(hSize)) { - if (hSize.charAt(0) === "+" || hSize.charAt(0) === "-") { - try { hSize = width + parseFloat(hSize.charAt(0) + Math.abs(parseFloat(hSize))); } - catch (e) { console.warn(e); } - } - else if (hSize.indexOf(",")) { - var s = hSize.split(","); - if (isNumber(s[0]) && isNumber(s[1])) w = parseFloat(s[0]), h = parseFloat(s[1]), isSquare = false; - } - } - if (isSquare) h = w = isNumber(hSize) ? parseFloat(hSize) : width; - var diff = (width + this._border() - w) / 2; - this._handles().css({ height: h, width: w, "margin": -h / 2 + "px 0 0 " + diff + "px" }); - }, - _handleDefaults: function () { - var min = this.options.min; - return { angle: this._valueToAngle(min), value: min }; - }, - _handleBars: function () { - return this.container.children("div.rs-bar"); - }, - _handles: function () { - return this._handleBars().find(".rs-handle"); - }, - _activeHandleBar: function (index) { - index = (index != undefined) ? index : this._active; - return $(this._handleBars()[index - 1]); - }, - _handleArgs: function (index) { - index = (index != undefined) ? index : this._active; - var _handle = this["_handle" + index]; - return { - element: this._activeHandleBar(index).children(), - index: index, - isActive: index == this._active, - value: _handle ? _handle.value : null, - angle: _handle ? _handle.angle : null - }; - }, - _dataElement: function () { - return this._isInputType ? this._hiddenField : this.control; - }, - _raiseEvent: function (event) { - var preValue = this["_pre" + event], currentValue = this.options.value; - if (preValue !== currentValue) { - this["_pre" + event] = currentValue; - if (event == "change") this._updatePre(); - this._updateTooltip(); - if ((event == "change") || (this._bindOnDrag && event == "drag")) this._updateHidden(); - return this._raise(event, { value: currentValue, preValue: preValue, "handle": this._handleArgs() }); - } - }, - - // Events handlers - _elementDown: function (e) { - if (this._isReadOnly) return; - var $target = $(e.target); - - if ($target.hasClass("rs-handle")) { - this._handleDown(e); - } - else { - var point = this._getXY(e), center = this._getCenterPoint(); - var distance = this._getDistance(point, center); - var block = this.block || this.svgContainer; - var outerDistance = block.outerWidth() / 2; - var innerDistance = outerDistance - (this.options.width + this._border()); - - if (distance >= innerDistance && distance <= outerDistance) { - var handle = this.control.find(".rs-handle.rs-focus"), angle, value; - if (handle.length !== 0) { - // here, some handle was in already focused state, and user clicked on the slider path - // so this will make the handle unfocus, to avoid that we can prevent this event - e.preventDefault(); - } - - var d = this._getAngleValue(point, center); - angle = d.angle, value = d.value; - - if (this._rangeSlider) { - if (handle.length == 1) { - var active = parseFloat(handle.attr("index")); - if (!this._invertRange) { - if (active == 1 && angle > this._handle2.angle) active = 2; - else if (active == 2 && angle < this._handle1.angle) active = 1; - } - this._active = active; - } - else this._active = (this._handle2.angle - angle) < (angle - this._handle1.angle) ? 2 : 1; - this.bar = this._activeHandleBar(); - } - - this._changeSliderValue(value, angle); - this._raiseEvent("change"); - } - } - }, - _handleDown: function (e) { - e.preventDefault(); - var $target = $(e.target); - $target.focus(); - this._removeAnimation(); - this._bindMouseEvents("_bind"); - this.bar = $target.parent(); - this._active = parseFloat($target.attr("index")); - this._handles().removeClass("rs-move"); - this._raise("start", { value: this.options.value, "handle": this._handleArgs() }); - }, - _handleMove: function (e) { - e.preventDefault(); - var point = this._getXY(e), center = this._getCenterPoint(); - var d = this._getAngleValue(point, center, true), angle, value; - angle = d.angle, value = d.value; - - this._changeSliderValue(value, angle); - this._raiseEvent("drag"); - }, - _handleUp: function (e) { - this._handles().addClass("rs-move"); - this._bindMouseEvents("_unbind"); - this._addAnimation(); - this._raiseEvent("change"); - this._raise("stop", { value: this.options.value, "handle": this._handleArgs() }); - }, - _handleFocus: function (e) { - if (this._isReadOnly) return; - var $target = $(e.target); - this._handles().removeClass("rs-focus"); - $target.addClass("rs-focus"); - this.bar = $target.parent(); - this._active = parseFloat($target.attr("index")); - if (this.options.keyboardAction) { - this._bindKeyboardEvents("_unbind"); - this._bindKeyboardEvents("_bind"); - } - - // updates the class for change z-index - this.control.find("div.rs-bar").css("z-index", "7"); - this.bar.css("z-index", "8"); - }, - _handleBlur: function (e) { - this._handles().removeClass("rs-focus"); - if (this.options.keyboardAction) this._bindKeyboardEvents("_unbind"); - }, - _handleKeyDown: function (e) { - if (this._isReadOnly) return; - var key = e.keyCode, keyCodes = this.keys; - - if (key == 27) // if Esc key pressed then hanldes will be focused out - this._handles().blur(); - - if (!(key >= 35 && key <= 40)) return; // if not valid keys, then return - if (key >= 37 && key <= 40) this._removeAnimation(); - - var h = this["_handle" + this._active], val, ang; - - e.preventDefault(); - if (key == keyCodes.UP || key == keyCodes.RIGHT) // Up || Right Key - val = this._round(this._limitValue(h.value + this.options.step)); - else if (key == keyCodes.DOWN || key == keyCodes.LEFT) // Down || Left Key - val = this._round(this._limitValue(h.value - this._getMinusStep(h.value))); - else if (key == 36) // Home Key - val = this._getKeyValue("Home"); - else if (key == 35) // End Key - val = this._getKeyValue("End"); - - ang = this._valueToAngle(val); - this._changeSliderValue(val, ang); - this._raiseEvent("drag"); - }, - _handleKeyUp: function (e) { - this._addAnimation(); - this._raiseEvent("change"); - }, - _getMinusStep: function (val) { - var o = this.options, min = o.min, max = o.max, step = o.step; - if (val == max) { - var remain = (max - min) % step; - return remain == 0 ? step : remain; - } - return step; - }, - _getKeyValue: function (key) { - var o = this.options, min = o.min, max = o.max; - if (this._rangeSlider) { - if (key == "Home") return (this._active == 1) ? min : this._handle1.value; - else return (this._active == 1) ? this._handle2.value : max; - } - return (key == "Home") ? min : max; - }, - _elementScroll: function (event) { - if (this._isReadOnly) return; - event.preventDefault(); - var e = event.originalEvent || event, h, val, ang, delta; - delta = e.wheelDelta ? e.wheelDelta / 60 : (e.detail ? -e.detail / 2 : 0); - if (delta == 0) return; - - this._updateActiveHandle(event); - h = this["_handle" + this._active]; - val = h.value + (delta > 0 ? this.options.step : -this._getMinusStep(h.value)); - val = this._limitValue(val); - ang = this._valueToAngle(val); - - this._removeAnimation(); - this._changeSliderValue(val, ang); - this._raiseEvent("change"); - this._addAnimation(); - }, - _updateActiveHandle: function (e) { - var $target = $(e.target); - if ($target.hasClass("rs-handle") && $target.parent().parent()[0] == this.control[0]) { - this.bar = $target.parent(); - this._active = parseFloat($target.attr("index")); - } - if (!this.bar.find(".rs-handle").hasClass("rs-focus")) this.bar.find(".rs-handle").focus(); - }, - - // Events binding - _bindControlEvents: function (hook) { - this[hook](this.control, "mousedown", this._elementDown); - this[hook](this.control, "touchstart", this._elementDown); - }, - _bindScrollEvents: function (hook) { - this[hook](this.control, "mousewheel", this._elementScroll); - this[hook](this.control, "DOMMouseScroll", this._elementScroll); - }, - _bindMouseEvents: function (hook) { - this[hook]($(document), "mousemove", this._handleMove); - this[hook]($(document), "mouseup", this._handleUp); - this[hook]($(document), "mouseleave", this._handleUp); - - // *** for Touch support *** // - this[hook]($(document), "touchmove", this._handleMove); - this[hook]($(document), "touchend", this._handleUp); - this[hook]($(document), "touchcancel", this._handleUp); - }, - _bindKeyboardEvents: function (hook) { - this[hook]($(document), "keydown", this._handleKeyDown); - this[hook]($(document), "keyup", this._handleKeyUp); - }, - - // internal methods - _changeSliderValue: function (value, angle) { - var oAngle = this._oriAngle(angle), lAngle = this._limitAngle(angle); - if (!this._rangeSlider && !this._showRange) { - - this["_handle" + this._active] = { angle: angle, value: value }; - this.options.value = value; - this.bar.rsRotate(lAngle); - this._updateARIA(value); - } - else if ((this._active == 1 && oAngle <= this._oriAngle(this._handle2.angle)) || - (this._active == 2 && oAngle >= this._oriAngle(this._handle1.angle)) || this._invertRange) { - - this["_handle" + this._active] = { angle: angle, value: value }; - this.options.value = this._rangeSlider ? this._handle1.value + "," + this._handle2.value : value; - this.bar.rsRotate(lAngle); - this._updateARIA(value); - - if (this.options.svgMode) { - this._moveSliderRange(); - return; - } - - // classic DIV handling - var dAngle = this._oriAngle(this._handle2.angle) - this._oriAngle(this._handle1.angle), o2 = "1", o3 = "0"; - if (dAngle <= 180 && !(dAngle < 0 && dAngle > -180)) o2 = "0", o3 = "1"; - this.block2.css("opacity", o2); - this.block3.css("opacity", o3); - - (this._active == 1 ? this.block4 : this.block2).rsRotate(lAngle - 180); - (this._active == 1 ? this.block1 : this.block3).rsRotate(lAngle); - } - }, - - // SVG related functionalities - _createSVGElements: function () { - var svgEle = this.$createSVG("svg"); - var PATH = "path.rs-transition "; - var pathAttr = { fill: "transparent" }; - - this.$path = this.$createSVG(PATH + "rs-path", pathAttr); - this.$range = this._showRange ? this.$createSVG(PATH + "rs-range", pathAttr) : null; - this.$border = this.$createSVG(PATH + "rs-border", pathAttr); - this.$append(svgEle, [this.$path, this.$range, this.$border]); - - this.svgContainer = this.$createElement("div.rs-svg-container").append(svgEle); - this.innerContainer.append(this.svgContainer); - }, - _setSVGAttributes: function () { - var o = this.options, radius = o.radius, - border = o.borderWidth, width = o.width, - lineCap = o.lineCap; - var outerRadius = radius - (border / 2), - innerRadius = outerRadius - width - border; - var startAngle = this._start, - totalAngle = this._end, - endAngle = startAngle + totalAngle; - - // draw the path for border element - var border_d = this.$drawPath(radius, outerRadius, startAngle, endAngle, innerRadius, lineCap); - this.$setAttribute(this.$border, { - "d": border_d - }); - // and set the border width - $(this.$border).css("stroke-width", border); - - var pathRadius = radius - border - (width / 2); - this.svgPathLength = this.$getArcLength(pathRadius, totalAngle); - var d = this.$drawPath(radius, pathRadius, startAngle, endAngle); - var attr = { "d": d, "stroke-width": width, "stroke-linecap": lineCap }; - - // draw the path for slider path element - this.$setAttribute(this.$path, attr); - - if (this._showRange) { - // draw the path for slider range element - this.$setAttribute(this.$range, attr); - - // there was a small bug when lineCap was round/square, this will solve that - if (lineCap == "round" || lineCap == "square") this.$range.setAttribute("stroke-dashoffset", "0.01"); - else this.$range.removeAttribute("stroke-dashoffset"); - } - }, - _setSVGStyles: function () { - var o = this.options, - borderColor = o.borderColor, - pathColor = o.pathColor, - rangeColor = o.rangeColor; - - if (borderColor) { - $(this.$border).css("stroke", borderColor); - } - - if (pathColor) { - $(this.$path).css("stroke", pathColor); - } - - if (this._showRange && rangeColor) { - $(this.$range).css("stroke", rangeColor); - } - }, - _moveSliderRange: function (isInit) { - if (!this._showRange) return; - - var startAngle = this._start, - totalAngle = this._end; - var handle1Angle = this._handle1.angle - startAngle, - handle2Angle = this._handle2.angle - startAngle; - if (isInit) handle1Angle = handle2Angle = 0; - var dashArray = []; - - if (handle1Angle <= handle2Angle) { - // starting the dashArray from 0 means normal range, otherwise it's invert range - // so when handle1 value is smaller then it's a normal range selection only - dashArray.push(0); - } - else { - // when handle1 value is larger then it's a invert range selection, also swap the values - var temp = handle1Angle; - handle1Angle = handle2Angle; - handle2Angle = temp; - } - - var handle1Distance = (handle1Angle / totalAngle) * this.svgPathLength; - dashArray.push(handle1Distance); - - var handle2Distance = ((handle2Angle - handle1Angle) / totalAngle) * this.svgPathLength; - dashArray.push(handle2Distance, this.svgPathLength); - - this.$range.style.strokeDasharray = dashArray.join(" "); - }, - _isPropsRelatedToSVG: function (property) { - var svgRelatedProps = ["radius", "borderWidth", "width", "lineCap", "startAngle", "endAngle"]; - return this._hasProperty(property, svgRelatedProps); - }, - _isPropsRelatedToSVGStyles: function (property) { - var svgStylesRelatedProps = ["borderColor", "pathColor", "rangeColor"]; - return this._hasProperty(property, svgStylesRelatedProps); - }, - _hasProperty: function (property, list) { - if (typeof property == "string") { - return (list.indexOf(property) !== -1); - } - else { - var allProperties = Object.keys(property); - return allProperties.some(function (prop) { - return (list.indexOf(prop) !== -1); - }); - } - }, - - // WAI-ARIA support - _updateARIA: function (value) { - var o = this.options, min = o.min, max = o.max; - this.bar.children().attr({ "aria-valuenow": value }); - if (o.sliderType == "range") { - var handles = this._handles(); - handles.eq(0).attr({ "aria-valuemin": min }); - handles.eq(1).attr({ "aria-valuemax": max }); - - if (this._active == 1) handles.eq(1).attr({ "aria-valuemin": value }); - else handles.eq(0).attr({ "aria-valuemax": value }); - } - else this.bar.children().attr({ "aria-valuemin": min, "aria-valuemax": max }); - }, - // Listener for KO binding - _checkKO: function () { - var _data = this._dataElement().data("bind"); - if (typeof _data == "string" && typeof ko == "object") { - var _vm = ko.dataFor(this._dataElement()[0]); - if (typeof _vm == "undefined") return true; - var _all = _data.split(","), _handler; - for (var i = 0; i < _all.length; i++) { - var d = _all[i].split(":"); - if ($.trim(d[0]) == "value") { - _handler = $.trim(d[1]); - break; - } - } - if (_handler) { - this._isKO = true; - ko.computed(function () { this.option("value", _vm[_handler]()); }, this); - } - } - }, - // Listener for Angular binding - _checkAngular: function () { - if (typeof angular == "object" && typeof angular.element == "function") { - this._ngName = this._dataElement().attr("ng-model"); - if (typeof this._ngName == "string") { - this._isAngular = true; var that = this; - this._scope().$watch(this._ngName, function (newValue, oldValue) { that.option("value", newValue); }); - } - } - }, - _scope: function () { - return angular.element(this._dataElement()).scope(); - }, - _getDistance: function (p1, p2) { - return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); - }, - _getXY: function (e) { - if (e.type.indexOf("mouse") == -1) e = (e.originalEvent || e).changedTouches[0]; - return { x: e.pageX, y: e.pageY }; - }, - _getCenterPoint: function () { - var block = this.block || this.svgContainer; - var offset = block.offset(), center; - center = { - x: offset.left + (block.outerWidth() / 2), - y: offset.top + (block.outerHeight() / 2) - }; - return center; - }, - _getAngleValue: function (point, center, isDrag) { - var deg = Math.atan2(point.y - center.y, center.x - point.x); - var angle = (-deg / (Math.PI / 180)); - if (angle < this._start) angle += 360; - angle = this._checkAngle(angle, isDrag); - return this._processStepByAngle(angle); - }, - _checkAngle: function (angle, isDrag) { - var o_angle = this._oriAngle(angle), - preAngle = this["_handle" + this._active].angle, - o_preAngle = this._oriAngle(preAngle); - - if (o_angle > this._end) { - if (!isDrag) return preAngle; - angle = this._start + (o_preAngle <= this._end - o_preAngle ? 0 : this._end); - } - else if (isDrag) { - var d = this._handleDragDistance; - if (this.isNumber(d)) if (Math.abs(o_angle - o_preAngle) > d) return preAngle; - } - return angle; - }, - _processStepByAngle: function (angle) { - var value = this._angleToValue(angle); - return this._processStepByValue(value); - }, - _processStepByValue: function (value) { - var o = this.options, min = o.min, max = o.max, step = o.step, isMinHigher = (min > max); - var remain, currVal, nextVal, preVal, newVal, ang; - - step = (isMinHigher ? -step : step); - remain = (value - min) % step; - - currVal = value - remain; - nextVal = this._limitValue(currVal + step); - preVal = this._limitValue(currVal - step); - - if (!isMinHigher) { - if (value >= currVal) newVal = (value - currVal < nextVal - value) ? currVal : nextVal; - else newVal = (currVal - value > value - preVal) ? currVal : preVal; - } - else { - if (value <= currVal) newVal = (currVal - value < value - nextVal) ? currVal : nextVal; - else newVal = (value - currVal > preVal - value) ? currVal : preVal; - } - newVal = this._round(newVal), ang = this._valueToAngle(newVal); - return { value: newVal, angle: ang }; - }, - _round: function (val) { - var s = this.options.step.toString().split("."); - return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val); - }, - _oriAngle: function (angle) { - var ang = angle - this._start; - if (ang < 0) ang += 360; - return ang; - }, - _limitAngle: function (angle) { - if (angle > 360 + this._start) angle -= 360; - if (angle < this._start) angle += 360; - return angle; - }, - _limitValue: function (value) { - var o = this.options, min = o.min, max = o.max, isMinHigher = (min > max); - if ((!isMinHigher && value < min) || (isMinHigher && value > min)) value = min; - if ((!isMinHigher && value > max) || (isMinHigher && value < max)) value = max; - return value; - }, - _angleToValue: function (angle) { - var o = this.options, min = o.min, max = o.max, value; - value = (this._oriAngle(angle) / this._end) * (max - min) + min; - return value; - }, - _valueToAngle: function (value) { - var o = this.options, min = o.min, max = o.max, angle; - angle = (((value - min) / (max - min)) * this._end) + this._start; - return angle; - }, - _appendHiddenField: function () { - this._hiddenField = this._hiddenField || this.$createElement("input"); - this._hiddenField.attr({ - "type": "hidden", "name": this._dataElement()[0].id || "" - }); - this.control.append(this._hiddenField); - this._updateHidden(); - }, - _updateHidden: function () { - var val = this.options.value; - this._hiddenField.val(val); - if (this._isKO || this._isAngular) this._hiddenField.trigger("change"); - if (this._isAngular) this._scope()[this._ngName] = val; - }, - _updateTooltip: function () { - if (this.tooltip && !this.tooltip.hasClass("hover")) - this.tooltip.html(this._getTooltipValue()); - this._updateTooltipPos(); - }, - _updateTooltipPos: function () { - this.tooltip && this.tooltip.css(this._getTooltipPos()); - }, - _getTooltipPos: function () { - var circleShape = this.options.circleShape, pos; - var tooltipHeight = this.tooltip.outerHeight(), tooltipWidth = this.tooltip.outerWidth(); - - if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0) { - return { - "margin-top": -tooltipHeight / 2, - "margin-left": -tooltipWidth / 2 - }; - } - else if (circleShape.indexOf("half") != -1) { - switch (circleShape) { - case "half-top": - case "half-bottom": - pos = { "margin-left": -tooltipWidth / 2 }; break; - case "half-left": - case "half-right": - pos = { "margin-top": -tooltipHeight / 2 }; break; - } - return pos; - } - return {}; - }, - _getTooltipValue: function (isNormal) { - var value = this.options.value; - if (this._rangeSlider) { - var p = value.split(","); - if (isNormal) return p[0] + " - " + p[1]; - return this._tooltipValue(p[0], 1) + " - " + this._tooltipValue(p[1], 2); - } - if (isNormal) return value; - return this._tooltipValue(value); - }, - _tooltipValue: function (value, index) { - var returnValue = this._raise("tooltipFormat", { value: value, "handle": this._handleArgs(index) }); - return (returnValue != null && typeof returnValue !== "boolean") ? returnValue : value; - }, - _validateStartAngle: function () { - var start = this.options.startAngle; - start = (this.isNumber(start) ? parseFloat(start) : 0) % 360; - if (start < 0) start += 360; - this.options.startAngle = start; - return start; - }, - _validateEndAngle: function () { - var o = this.options, start = o.startAngle, end = o.endAngle; - if (typeof end === "string" && this.isNumber(end) && (end.charAt(0) === "+" || end.charAt(0) === "-")) { - try { end = start + parseFloat(end.charAt(0) + Math.abs(parseFloat(end))); } - catch (e) { console.warn(e); } - } - end = (this.isNumber(end) ? parseFloat(end) : 360) % 360; - if (end <= start) end += 360; - return end; - }, - _refreshCircleShape: function () { - var circleShape = this.options.circleShape; - var allCircelShapes = ["half-top", "half-bottom", "half-left", "half-right", - "quarter-top-left", "quarter-top-right", "quarter-bottom-right", "quarter-bottom-left", - "pie", "custom-half", "custom-quarter"]; - var shape_codes = ["h1", "h2", "h3", "h4", "q1", "q2", "q3", "q4", "3/4", "ch", "cq"]; - - if (allCircelShapes.indexOf(circleShape) == -1) { - var index = shape_codes.indexOf(circleShape); - if (index != -1) circleShape = allCircelShapes[index]; - else if (circleShape == "half") circleShape = "half-top"; - else if (circleShape == "quarter") circleShape = "quarter-top-left"; - else circleShape = "full"; - } - this.options.circleShape = circleShape; - }, - _appendOverlay: function () { - var shape = this.options.circleShape; - if (shape == "pie") - this._checkOverlay(".rs-overlay", 270); - else if (shape == "custom-half" || shape == "custom-quarter") { - this._checkOverlay(".rs-overlay1", 180); - if (shape == "custom-quarter") - this._checkOverlay(".rs-overlay2", this._end); - } - }, - _checkOverlay: function (cls, angle) { - var overlay = this.container.children(cls); - if (overlay.length == 0) { - overlay = this.$createElement("div" + cls + " rs-transition rs-bg-color"); - this.container.append(overlay); - } - overlay.rsRotate(this._start + angle); - }, - _checkDataType: function () { - var m = this.options, i, prop, value, props = this._props(); - // to check number datatype - for (i in props.numberType) { - prop = props.numberType[i], value = m[prop]; - if (!this.isNumber(value)) m[prop] = this.defaults[prop]; - else m[prop] = parseFloat(value); - } - // to check input string - for (i in props.booleanType) { - prop = props.booleanType[i], value = m[prop]; - m[prop] = (value == "false") ? false : !!value; - } - // to check boolean datatype - for (i in props.stringType) { - prop = props.stringType[i], value = m[prop]; - m[prop] = ("" + value).toLowerCase(); - } - }, - _validateSliderType: function () { - var type = this.options.sliderType.toLowerCase(); - this._rangeSlider = this._showRange = false; - if (type == "range") this._rangeSlider = this._showRange = true; - else if (type.indexOf("min") != -1) { - this._showRange = true; - type = "min-range"; - } - else type = "default"; - this.options.sliderType = type; - }, - _updateStartEnd: function () { - var o = this.options, circle = o.circleShape, startAngle = o.startAngle, endAngle = o.endAngle; - - if (circle != "full") { - if (circle.indexOf("quarter") != -1) endAngle = "+90"; - else if (circle.indexOf("half") != -1) endAngle = "+180"; - else if (circle == "pie") endAngle = "+270"; - this.options.endAngle = endAngle; - - if (circle == "quarter-top-left" || circle == "half-top") startAngle = 0; - else if (circle == "quarter-top-right" || circle == "half-right") startAngle = 90; - else if (circle == "quarter-bottom-right" || circle == "half-bottom") startAngle = 180; - else if (circle == "quarter-bottom-left" || circle == "half-left") startAngle = 270; - this.options.startAngle = startAngle; - } - }, - _validateStartEnd: function () { - this._start = this._validateStartAngle(); - this._end = this._validateEndAngle(); - - var add = (this._start < this._end) ? 0 : 360; - this._end += add - this._start; - }, - _analyzeModelValue: function () { - var o = this.options, val = o.value, min = o.min, max = o.max, - lastValue, newValue, isNumber = this.isNumber; - if (val instanceof Array) val = val.toString(); - var valueIsString = (typeof val == "string"); - - var parts = valueIsString ? val.split(",") : [val]; - - if (this._rangeSlider) { - if (valueIsString) { - if (parts.length >= 2) newValue = (isNumber(parts[0]) ? parts[0] : min) + "," + - (isNumber(parts[1]) ? parts[1] : max); - else newValue = isNumber(parts[0]) ? min + "," + parts[0] : min + "," + min; - } - else newValue = isNumber(val) ? min + "," + val : min + "," + min; - } - else { - if (valueIsString) lastValue = parts.pop(), newValue = isNumber(lastValue) ? parseFloat(lastValue) : min; - else newValue = isNumber(val) ? parseFloat(val) : min; - } - this.options.value = newValue; - }, - _validateModelValue: function () { - var o = this.options, val = o.value; - if (this._rangeSlider) { - var parts = val.split(","), val1 = parseFloat(parts[0]), val2 = parseFloat(parts[1]); - val1 = this._limitValue(val1); - val2 = this._limitValue(val2); - if (!this._invertRange) { - var min = o.min, max = o.max; - var isMinHigher = (min > max); - if (isMinHigher) { - if (val1 < val2) val1 = val2; - } else { - if (val1 > val2) val2 = val1; - } - } - - this._handle1 = this._processStepByValue(val1); - this._handle2 = this._processStepByValue(val2); - this.options.value = this._handle1.value + "," + this._handle2.value; - } - else { - var index = this._showRange ? 2 : (this._active || 1); - this["_handle" + index] = this._processStepByValue(this._limitValue(val)); - if (this._showRange) this._handle1 = this._handleDefaults(); - this.options.value = this["_handle" + index].value; - } - }, - - // common core methods - $createElement: function (tag) { - var t = tag.split('.'); - return $(document.createElement(t[0])).addClass(t[1] || ""); - }, - $createSVG: function (tag, attr) { - var t = tag.split('.'); - var svgEle = document.createElementNS("http://www.w3.org/2000/svg", t[0]); - if (t[1]) { - svgEle.setAttribute("class", t[1]); - } - if (attr) { - this.$setAttribute(svgEle, attr); - } - return svgEle; - }, - $setAttribute: function (ele, attr) { - for (var key in attr) { - var val = attr[key]; - if (key === "class") { - var prev = ele.getAttribute('class'); - if (prev) val += " " + prev; - } - ele.setAttribute(key, val); - } - return ele; - }, - $append: function (parent, children) { - children.forEach(function (element) { - element && parent.appendChild(element); - }); - return parent; - }, - isNumber: function (number) { - number = parseFloat(number); - return typeof number === "number" && !isNaN(number); - }, - getBrowserName: function () { - var browserName = "", ua = window.navigator.userAgent; - if ((!!window.opr && !!opr.addons) || !!window.opera || ua.indexOf(' OPR/') >= 0) browserName = "opera"; - else if (typeof InstallTrigger !== 'undefined') browserName = "firefox"; - else if (ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0) browserName = "ie"; - else if (!!window.StyleMedia) browserName = "edge"; - else if (ua.indexOf('Safari') != -1 && ua.indexOf('Chrome') == -1) browserName = "safari"; - else if ((!!window.chrome && !!window.chrome.webstore) || (ua.indexOf('Chrome') != -1)) browserName = "chrome"; - return browserName; - }, - _isBrowserSupported: function () { - var properties = ["borderRadius", "WebkitBorderRadius", "MozBorderRadius", - "OBorderRadius", "msBorderRadius", "KhtmlBorderRadius"]; - for (var i = 0; i < properties.length; i++) { - if (document.body.style[properties[i]] !== undefined) return true; - } - }, - _throwError: function () { - return "This browser doesn't support the border-radious property."; - }, - _raise: function (event, args) { - var o = this.options, fn = o[event], val = true; - args = args || { value: o.value }; - args["id"] = this.id; - args["control"] = this.control; - args["options"] = o; - if (fn) { - args["type"] = event; - if (typeof fn === "string") fn = window[fn]; - if ($.isFunction(fn)) { - val = fn.call(this, args); - val = val === false ? false : val; - } - } - this.control.trigger($.Event(event, args)); - return val; - }, - _bind: function (element, _event, handler) { - $(element).bind(_event, $.proxy(handler, this)); - }, - _unbind: function (element, _event, handler) { - $(element).unbind(_event, $.proxy(handler, this)); - }, - _getInstance: function () { - return $.data(this._dataElement()[0], pluginName); - }, - _saveInstanceOnElement: function () { - $.data(this.control[0], pluginName, this); - }, - _saveInstanceOnID: function () { - var id = this.id; - if (id && typeof window[id] !== "undefined") - window[id] = this; - }, - _removeData: function () { - var control = this._dataElement()[0]; - $.removeData && $.removeData(control, pluginName); - if (control.id && typeof window[control.id]["_init"] === "function") - delete window[control.id]; - }, - _destroyControl: function () { - if (this._isInputType) this._dataElement().insertAfter(this.control).attr("type", "text"); - this.control.empty().removeClass("rs-control").height("").width(""); - this._removeAnimation(); - this._bindControlEvents("_unbind"); - }, - - // methods to dynamic options updation (through option) - _updateWidth: function () { - this.lastBlock.css("padding", this.options.width); - }, - _readOnly: function (bool) { - this._isReadOnly = bool; - this.container.removeClass("rs-readonly"); - if (bool) this.container.addClass("rs-readonly"); - }, - - // get & set for the properties - _get: function (property) { - return this.options[property]; - }, - _set: function (property, value, forceSet) { - var props = this._props(); - if ($.inArray(property, props.numberType) != -1) { // to check number datatype - if (!this.isNumber(value)) return; - value = parseFloat(value); - } - else if ($.inArray(property, props.booleanType) != -1) { // to check boolean datatype - value = (value == "false") ? false : !!value; - } - else if ($.inArray(property, props.stringType) != -1) { // to check input string - value = value.toLowerCase(); - } - - if (!forceSet && this.options[property] == value) return; - this.options[property] = value; - switch (property) { - case "startAngle": - case "endAngle": - this._validateStartEnd(); - this._updateSeperator(); // non SVG mode only - this._appendOverlay(); // non SVG mode only - case "min": - case "max": - case "step": - case "value": - this._analyzeModelValue(); - this._validateModelValue(); - this._setValue(); - this._updatePre(); - this._updateHidden(); - this._updateTooltip(); - break; - case "radius": - this._setRadius(); - this._updateTooltipPos(); - break; - case "width": - this._removeAnimation(); - this._updateWidth(); // non SVG mode only - this._setRadius(); - this._refreshHandle(); - this._updateTooltipPos(); - this._addAnimation(); - this._refreshSeperator(); // non SVG mode only - break; - case "borderWidth": - this._setRadius(); - this._refreshHandle(); - break; - case "handleSize": - this._refreshHandle(); - break; - case "handleShape": - this._setHandleShape(); - break; - case "animation": - this._setAnimation(); - break; - case "showTooltip": - this.options.showTooltip ? this._appendTooltip() : this._removeTooltip(); - break; - case "editableTooltip": - this._tooltipEditable(); - this._updateTooltipPos(); - break; - case "disabled": - this.options.disabled ? this.disable() : this.enable(); - break; - case "readOnly": - this.options.readOnly ? this._readOnly(true) : (!this.options.disabled && this._readOnly(false)); - break; - case "mouseScrollAction": - this._bindScrollEvents(this.options.mouseScrollAction ? "_bind" : "_unbind"); - break; - case "lineCap": - this._setRadius(); - this._refreshSeperator(); // non SVG mode only - break; - case "circleShape": - this._refreshCircleShape(); - if (this.options.circleShape == "full") { - this.options.startAngle = 0; - this.options.endAngle = "+360"; - } - case "sliderType": - this._destroyControl(); - this._onInit(); - break; - case "svgMode": - var $control = this.control, $options = this.options; - this.destroy(); - $control[pluginName]($options); - break; - } - return this; - }, - - // public methods - option: function (property, value) { - if (!this._getInstance() || !this._isBrowserSupport) return; - if ($.isPlainObject(property)) { - if (property["min"] !== undefined || property["max"] !== undefined) { - if (property["min"] !== undefined) { - this.options.min = property["min"]; - delete property["min"]; - } - if (property["max"] !== undefined) { - this.options.max = property["max"]; - delete property["max"]; - } - var val = this.options.value; - if (property["value"] !== undefined) { - val = property["value"] - delete property["value"]; - } - this._set("value", val, true); - } - for (var prop in property) { - this._set(prop, property[prop]); - } - } - else if (property && typeof property == "string") { - if (value === undefined) return this._get(property); - this._set(property, value); - } - - // whenever the properties set dynamically, check for SVG mode. also check - // any of the property was related to SVG. If yes, then redraw the SVG path - if (this.options.svgMode && property) { - if (this._isPropsRelatedToSVG(property)) { - this._setSVGAttributes(); - this._moveSliderRange(); - } - if (this._isPropsRelatedToSVGStyles(property)) { - this._setSVGStyles(); - } - } - - return this; - }, - getValue: function (index) { - if (this.options.sliderType == "range" && this.isNumber(index)) { - var i = parseFloat(index); - if (i == 1 || i == 2) - return this["_handle" + i].value; - } - return this._get("value"); - }, - setValue: function (value, index) { - if (this.isNumber(value)) { - if (this.isNumber(index)) { - var sliderType = this.options.sliderType; - if (sliderType == "range") { - var i = parseFloat(index), val = parseFloat(value); - if (i == 1) value = val + "," + this._handle2.value; - else if (i == 2) value = this._handle1.value + "," + val; - } - else if (sliderType == "default") this._active = index; - } - this._set("value", value); - } - }, - disable: function () { - this.options.disabled = true; - this.container.addClass("rs-disabled"); - this._readOnly(true); - }, - enable: function () { - this.options.disabled = false; - this.container.removeClass("rs-disabled"); - if (!this.options.readOnly) this._readOnly(false); - }, - destroy: function () { - if (!this._getInstance()) return; - this._destroyControl(); - this._removeData(); - if (this._isInputType) this.control.remove(); - } - }; - - $.fn.rsRotate = function (degree) { - var control = this, rotation = "rotate(" + degree + "deg)"; - control.css('-webkit-transform', rotation); - control.css('-moz-transform', rotation); - control.css('-ms-transform', rotation); - control.css('-o-transform', rotation); - control.css('transform', rotation); - return control; - } - - // The plugin constructor - function RoundSlider(control, options) { - this.id = control.id; - this.control = $(control); - - // the options value holds the updated defaults value - this.options = $.extend({}, this.defaults, options); - } - - // The plugin wrapper, prevents multiple instantiations - function CreateRoundSlider(options, args) { - - for (var i = 0; i < this.length; i++) { - var that = this[i], instance = $.data(that, pluginName); - if (!instance) { - var _this = new RoundSlider(that, options); - _this._saveInstanceOnElement(); - _this._saveInstanceOnID(); - - if (_this._raise("beforeCreate") !== false) { - _this._init(); - _this._raise("create"); - } - else _this._removeData(); - } - else if ($.isPlainObject(options)) { - if (typeof instance.option === "function") instance.option(options); - else if (that.id && window[that.id] && typeof window[that.id].option === "function") { - window[that.id].option(options); - } - } - else if (typeof options === "string") { - if (typeof instance[options] === "function") { - if ((options === "option" || options.indexOf("get") === 0) && args[2] === undefined) { - return instance[options](args[1]); - } - instance[options](args[1], args[2]); - } - } - } - return this; - } - - // ### SVG related logic - RoundSlider.prototype.$polarToCartesian = function (centerXY, radius, angleInDegrees) { - var angleInRadians = (angleInDegrees - 180) * Math.PI / 180; - - return [ - centerXY + (radius * Math.cos(angleInRadians)), - centerXY + (radius * Math.sin(angleInRadians)) - ].join(" "); - } - - RoundSlider.prototype.$drawArc = function (centerXY, radius, startAngle, endAngle, isOuter) { - var isCircle = (endAngle - startAngle == 360); - var largeArcFlag = Math.abs(startAngle - endAngle) <= 180 ? "0" : "1"; - var isClockwise = true; - var outerDirection = isClockwise ? 1 : 0; - var innerDirection = isClockwise ? 0 : 1; - var direction = isOuter ? outerDirection : innerDirection; - var _endAngle = isOuter ? endAngle : startAngle; - - var path = []; - - // if it is a perfect circle then draw two half circles, otherwise draw arc - if (isCircle) { - var midAngle = (startAngle + endAngle) / 2; - var midPoint = this.$polarToCartesian(centerXY, radius, midAngle); - var endPoint = this.$polarToCartesian(centerXY, radius, _endAngle); - path.push( - "A", 1, 1, 0, 0, direction, midPoint, - "A", 1, 1, 0, 0, direction, endPoint - ); - } - else { - var endPoint = this.$polarToCartesian(centerXY, radius, _endAngle); - path.push( - "A", radius, radius, 0, largeArcFlag, direction, endPoint - ); - } - - return path.join(" "); - } - - RoundSlider.prototype.$drawPath = function (centerXY, outerRadius, startAngle, endAngle, innerRadius, lineCap) { - var outerStart = this.$polarToCartesian(centerXY, outerRadius, startAngle); - var outerArc = this.$drawArc(centerXY, outerRadius, startAngle, endAngle, true); // draw outer circle - - var d = [ - "M " + outerStart, - outerArc - ]; - - if (innerRadius) { - var innerEnd = this.$polarToCartesian(centerXY, innerRadius, endAngle); - var innerArc = this.$drawArc(centerXY, innerRadius, startAngle, endAngle, false); // draw inner circle - - if (lineCap == "none") { - d.push( - "M " + innerEnd, - innerArc - ); - } - else if (lineCap == "round") { - d.push( - "A 1, 1, 0, 0, 1, " + innerEnd, - innerArc, - "A 1, 1, 0, 0, 1, " + outerStart - ); - } - else if (lineCap == "butt" || lineCap == "square") { - d.push( - "L " + innerEnd, - innerArc, - "L " + outerStart, - "Z" - ); - } - } - return d.join(" "); - } - - RoundSlider.prototype.$getArcLength = function (radius, degree = 360) { - // when degree not provided we can consider that arc as a complete circle - // circle's arc length formula => 2πR(Θ/360) - return 2 * Math.PI * radius * (degree / 360); - } - - $.fn[pluginName].prototype = RoundSlider.prototype; - +/*! + * roundSlider v1.4.0 | (c) 2015-2020, Soundar + * MIT license | http://roundsliderui.com/licence.html + */ + +(function ($, window, undefined) { + "use strict"; + /*jslint nomen: true */ + + var pluginName = "roundSlider"; + + // The plugin initialization + $.fn[pluginName] = function (options) { + return CreateRoundSlider.call(this, options, arguments); + }; + + RoundSlider.prototype = { + + pluginName: pluginName, + version: "1.4.0", + + // after the control initialization the updated default values + // are merged into the options + options: {}, + + // holds the current roundSlider element + control: null, + + // default properties of the plugin. while add a new property, + // that type should be included in the "_props:" for validation + defaults: { + min: 0, + max: 100, + step: 1, + value: null, + radius: 85, + width: 18, + handleSize: "+0", + startAngle: 0, + endAngle: "+360", + animation: true, + showTooltip: true, + editableTooltip: true, + readOnly: false, + disabled: false, + keyboardAction: true, + mouseScrollAction: false, + lineCap: "butt", + sliderType: "default", + circleShape: "full", + handleShape: "round", + + // SVG related properties + svgMode: false, + borderWidth: 1, + borderColor: null, + pathColor: null, + rangeColor: null, + + // events + beforeCreate: null, + create: null, + start: null, + drag: null, + change: null, + stop: null, + tooltipFormat: null + }, + keys: { // key codes for + UP: 38, // up arrow + DOWN: 40, // down arrow + LEFT: 37, // left arrow + RIGHT: 39 // right arrow + }, + _props: function () { + return { + numberType: ["min", "max", "step", "radius", "width", "borderWidth", "startAngle"], + booleanType: ["animation", "showTooltip", "editableTooltip", "readOnly", "disabled", + "keyboardAction", "mouseScrollAction", "svgMode"], + stringType: ["sliderType", "circleShape", "handleShape", "lineCap"] + }; + }, + + _init: function () { + if (this.options.svgMode) { + var EMPTY_FUNCTION = function () { }; + this._appendSeperator = EMPTY_FUNCTION; + this._refreshSeperator = EMPTY_FUNCTION; + this._updateSeperator = EMPTY_FUNCTION; + this._appendOverlay = EMPTY_FUNCTION; + this._checkOverlay = EMPTY_FUNCTION; + this._updateWidth = EMPTY_FUNCTION; + } + + this._isBrowserSupport = this._isBrowserSupported(); + this._isKO = false; + this._isAngular = false; + if (this.control.is("input")) { + this._isInputType = true; + this._hiddenField = this.control; + this.control = this.$createElement("div"); + this.control.insertAfter(this._hiddenField); + this.options.value = this._hiddenField.val() || this.options.value; + var that = this; + this._checkKO() && setTimeout(function () { that._checkKO(); }, 1); + this._checkAngular(); + } + this._bindOnDrag = false; + var _updateOn = this._dataElement().attr("data-updateon"); + if (typeof _updateOn == "string") { if (_updateOn == "drag") this._bindOnDrag = true; } + else if (this._isAngular) this._bindOnDrag = true; + + this._onInit(); + }, + _onInit: function () { + this._initialize(); + this._update(); + this._render(); + }, + _initialize: function () { + var browserName = this.browserName = this.getBrowserName(); + if (browserName) this.control.addClass("rs-" + browserName); + if (!this._isBrowserSupport) return; + this._isReadOnly = false; + this._checkDataType(); + this._refreshCircleShape(); + }, + _render: function () { + this.container = this.$createElement("div.rs-container"); + this.innerContainer = this.$createElement("div.rs-inner-container"); + this.container.append(this.innerContainer); + var $rootCSS = "rs-control " + (this.options.svgMode ? "rs-svg-mode" : "rs-classic-mode"); + this.control.addClass($rootCSS).empty().append(this.container); + + if (this._isBrowserSupport) { + this._createLayers(); + this._createOtherLayers(); + this._setContainerClass(); + this._setRadius(); + this._setProperties(); + this._setValue(); + this._updateTooltipPos(); + this._bindControlEvents("_bind"); + } + else { + var msg = this.$createElement("div.rs-msg"); + msg.html(typeof this._throwError === "function" ? this._throwError() : this._throwError); + this.control.empty().addClass("rs-error").append(msg); + if (this._isInputType) this.control.append(this._dataElement()); + } + }, + _update: function () { + this._validateSliderType(); + this._updateStartEnd(); + this._validateStartEnd(); + this._handle1 = this._handle2 = this._handleDefaults(); + this._analyzeModelValue(); + this._validateModelValue(); + }, + _createLayers: function () { + if (this.options.svgMode) { + this._createSVGElements(); + this._setSVGAttributes(); + this._setSVGStyles(); + this._moveSliderRange(true); + return; + } + + this.block = this.$createElement("div.rs-block rs-outer rs-border"); + this.innerContainer.append(this.block); + + var padd = this.options.width, start = this._start, path; + path = this.$createElement("div.rs-path rs-transition"); + + if (this._rangeSlider || this._showRange) { + this.block1 = path.clone().addClass("rs-range-color").rsRotate(start); + this.block2 = path.clone().addClass("rs-range-color").css("opacity", "0").rsRotate(start); + this.block3 = path.clone().addClass("rs-path-color").rsRotate(start); + this.block4 = path.addClass("rs-path-color").css({ "opacity": "1", "z-index": "1" }).rsRotate(start - 180); + + this.block.append(this.block1, this.block2, this.block3, this.block4).addClass("rs-split"); + } + else this.block.append(path.addClass("rs-path-color")); + + this.lastBlock = this.$createElement("span.rs-block").css({ "padding": padd }); + this.innerBlock = this.$createElement("div.rs-inner rs-bg-color rs-border"); + this.lastBlock.append(this.innerBlock); + this.block.append(this.lastBlock); + }, + _createOtherLayers: function () { + this._appendHandle(); + this._appendSeperator(); // non SVG mode only + this._appendOverlay(); // non SVG mode only + this._appendHiddenField(); + }, + _setProperties: function () { + this._updatePre(); + this._setHandleShape(); + this._addAnimation(); + this._appendTooltip(); + if (!this.options.showTooltip) this._removeTooltip(); + if (this.options.disabled) this.disable(); + else if (this.options.readOnly) this._readOnly(true); + if (this.options.mouseScrollAction) this._bindScrollEvents("_bind"); + }, + _updatePre: function () { + this._prechange = this._predrag = this.options.value; + }, + _setValue: function () { + if (this._rangeSlider) { + this._setHandleValue(1); + this._setHandleValue(2); + } + else { + if (this._showRange) this._setHandleValue(1); + var index = (this.options.sliderType == "default") ? (this._active || 1) : parseFloat(this.bar.children().attr("index")); + this._setHandleValue(index); + } + }, + _appendTooltip: function () { + if (this.container.children(".rs-tooltip").length !== 0) return; + this.tooltip = this.$createElement("span.rs-tooltip rs-tooltip-text"); + this.container.append(this.tooltip); + this._tooltipEditable(); + this._updateTooltip(); + }, + _removeTooltip: function () { + if (this.container.children(".rs-tooltip").length == 0) return; + this.tooltip && this.tooltip.remove(); + }, + _tooltipEditable: function () { + var o = this.options, tooltip = this.tooltip, hook; + if (!tooltip || !o.showTooltip) return; + + if (o.editableTooltip) { + tooltip.addClass("edit"); + hook = "_bind"; + } + else { + tooltip.removeClass("edit"); + hook = "_unbind"; + } + this[hook](tooltip, "click", this._editTooltip); + }, + _editTooltip: function (e) { + var tooltip = this.tooltip; + if (!tooltip.hasClass("edit") || this._isReadOnly) return; + var border = parseFloat(tooltip.css("border-left-width")) * 2; + var input = this.input = this.$createElement("input.rs-input rs-tooltip-text").css({ + height: tooltip.outerHeight() - border, + width: tooltip.outerWidth() - border + }); + tooltip.html(input).removeClass("edit").addClass("hover"); + + input.focus().val(this._getTooltipValue(true)); + + this._bind(input, "blur", this._focusOut); + this._bind(input, "change", this._focusOut); + }, + _focusOut: function (e) { + if (e.type == "change") { + var val = this.input.val().replace("-", ","); + if (val[0] == ",") { + val = "-" + val.slice(1).replace("-", ","); + } + this.options.value = val; + this._analyzeModelValue(); + this._validateModelValue(); + this._setValue(); + this.input.val(this._getTooltipValue(true)); + } + else { + this.tooltip.addClass("edit").removeClass("hover"); + this._updateTooltip(); + } + this._raiseEvent("change"); + }, + _setHandleShape: function () { + var type = this.options.handleShape, allHandles = this._handles(); + allHandles.removeClass("rs-handle-dot rs-handle-square"); + if (type == "dot") allHandles.addClass("rs-handle-dot"); + else if (type == "square") allHandles.addClass("rs-handle-square"); + else this.options.handleShape = "round"; + }, + _setHandleValue: function (index) { + this._active = index; + var handle = this["_handle" + index]; + if (this.options.sliderType != "min-range") this.bar = this._activeHandleBar(); + this._changeSliderValue(handle.value, handle.angle); + }, + _setAnimation: function () { + if (this.options.animation) this._addAnimation(); + else this._removeAnimation(); + }, + _addAnimation: function () { + if (this.options.animation) this.control.addClass("rs-animation"); + }, + _removeAnimation: function () { + this.control.removeClass("rs-animation"); + }, + _setContainerClass: function () { + var circleShape = this.options.circleShape; + if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0) { + this.container.addClass("full " + circleShape); + } + else { + this.container.addClass(circleShape.split("-").join(" ")); + } + }, + _setRadius: function () { + var o = this.options, r = o.radius, d = r * 2, circleShape = o.circleShape; + var extraSize = 0, actualHeight, actualWidth; + var height = actualHeight = d, width = actualWidth = d; + + // whenever the radius changes, before update the container size + // check for the lineCap also, since that will make some additional size + // also, based on that need to align the handle bars + var isFullCircle = (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0); + if (o.svgMode && !isFullCircle) { + var handleBars = this._handleBars(); + if (o.lineCap != "none") { + extraSize = (o.lineCap === "butt") ? (o.borderWidth / 2) : ((o.width / 2) + o.borderWidth); + if (circleShape.indexOf("bottom") != -1) { + handleBars.css("margin-top", extraSize + 'px'); + } + if (circleShape.indexOf("right") != -1) { + handleBars.css("margin-right", -extraSize + 'px'); + } + } + else { + // when lineCap none, then remove the styles that was set previously for the other lineCap props + $.each(handleBars, function (i, bar) { + bar.style.removeProperty("margin-top"); + bar.style.removeProperty("margin-right"); + }); + } + } + + if (circleShape.indexOf("half") === 0) { + switch (circleShape) { + case "half-top": + case "half-bottom": + height = r; actualHeight = r + extraSize; + break; + case "half-left": + case "half-right": + width = r; actualWidth = r + extraSize; + break; + } + } + else if (circleShape.indexOf("quarter") === 0) { + height = width = r; + actualHeight = actualWidth = r + extraSize; + } + + this.container.css({ "height": height, "width": width }); + this.control.css({ "height": actualHeight, "width": actualWidth }); + + // when needed, then only we can set the styles through script, otherwise CSS styles applicable + if (extraSize !== 0) this.innerContainer.css({ "height": actualHeight, "width": actualWidth }); + else this.innerContainer.removeAttr("style"); + + if (o.svgMode) { + this.svgContainer.height(d).width(d); + this.svgContainer.children("svg").height(d).width(d); + } + }, + _border: function (seperator) { + if (seperator) return parseFloat(this._startLine.children().css("border-bottom-width")); + if (this.options.svgMode) return this.options.borderWidth * 2; + return parseFloat(this.block.css("border-top-width")) * 2; + }, + _appendHandle: function () { + if (this._rangeSlider || !this._showRange) this._createHandle(1); + if (this._rangeSlider || this._showRange) this._createHandle(2); + }, + _appendSeperator: function () { + this._startLine = this._addSeperator(this._start, "rs-start"); + this._endLine = this._addSeperator(this._start + this._end, "rs-end"); + this._refreshSeperator(); + }, + _addSeperator: function (pos, cls) { + var line = this.$createElement("span.rs-seperator rs-border"), width = this.options.width, _border = this._border(); + var lineWrap = this.$createElement("span.rs-bar rs-transition " + cls).append(line).rsRotate(pos); + this.container.append(lineWrap); + return lineWrap; + }, + _refreshSeperator: function () { + var bars = this._startLine.add(this._endLine), seperators = bars.children().removeAttr("style"); + var o = this.options, width = o.width, _border = this._border(), size = width + _border; + if (o.lineCap == "round" && o.circleShape != "full") { + bars.addClass("rs-rounded"); + seperators.css({ width: size, height: (size / 2) + 1 }); + this._startLine.children().css("margin-top", -1).addClass(o.sliderType == "min-range" ? "rs-range-color" : "rs-path-color"); + this._endLine.children().css("margin-top", size / -2).addClass("rs-path-color"); + } + else { + bars.removeClass("rs-rounded"); + seperators.css({ "width": size, "margin-top": this._border(true) / -2 }).removeClass("rs-range-color rs-path-color"); + } + }, + _updateSeperator: function () { + this._startLine.rsRotate(this._start); + this._endLine.rsRotate(this._start + this._end); + }, + _createHandle: function (index) { + var handle = this.$createElement("div.rs-handle rs-move"), o = this.options, hs; + if ((hs = o.handleShape) != "round") handle.addClass("rs-handle-" + hs); + handle.attr({ "index": index, "tabIndex": "0" }); + + var id = this._dataElement()[0].id, id = id ? id + "_" : ""; + var label = id + "handle" + (o.sliderType == "range" ? "_" + (index == 1 ? "start" : "end") : ""); + handle.attr({ "role": "slider", "aria-label": label }); // WAI-ARIA support + + var bar = this.$createElement("div.rs-bar rs-transition").css("z-index", "7").append(handle).rsRotate(this._start); + bar.addClass(o.sliderType == "range" && index == 2 ? "rs-second" : "rs-first"); + this.container.append(bar); + this._refreshHandle(); + + this.bar = bar; + this._active = index; + if (index != 1 && index != 2) this["_handle" + index] = this._handleDefaults(); + this._bind(handle, "focus", this._handleFocus); + this._bind(handle, "blur", this._handleBlur); + return handle; + }, + _refreshHandle: function () { + var o = this.options, hSize = o.handleSize, width = o.width, h, w, isSquare = true, isNumber = this.isNumber; + if (typeof hSize === "string" && isNumber(hSize)) { + if (hSize.charAt(0) === "+" || hSize.charAt(0) === "-") { + try { hSize = width + parseFloat(hSize.charAt(0) + Math.abs(parseFloat(hSize))); } + catch (e) { console.warn(e); } + } + else if (hSize.indexOf(",")) { + var s = hSize.split(","); + if (isNumber(s[0]) && isNumber(s[1])) w = parseFloat(s[0]), h = parseFloat(s[1]), isSquare = false; + } + } + if (isSquare) h = w = isNumber(hSize) ? parseFloat(hSize) : width; + var diff = (width + this._border() - w) / 2; + this._handles().css({ height: h, width: w, "margin": -h / 2 + "px 0 0 " + diff + "px" }); + }, + _handleDefaults: function () { + var min = this.options.min; + return { angle: this._valueToAngle(min), value: min }; + }, + _handleBars: function () { + return this.container.children("div.rs-bar"); + }, + _handles: function () { + return this._handleBars().find(".rs-handle"); + }, + _activeHandleBar: function (index) { + index = (index != undefined) ? index : this._active; + return $(this._handleBars()[index - 1]); + }, + _handleArgs: function (index) { + index = (index != undefined) ? index : this._active; + var _handle = this["_handle" + index]; + return { + element: this._activeHandleBar(index).children(), + index: index, + isActive: index == this._active, + value: _handle ? _handle.value : null, + angle: _handle ? _handle.angle : null + }; + }, + _dataElement: function () { + return this._isInputType ? this._hiddenField : this.control; + }, + _raiseEvent: function (event) { + var preValue = this["_pre" + event], currentValue = this.options.value; + if (preValue !== currentValue) { + this["_pre" + event] = currentValue; + if (event == "change") this._updatePre(); + this._updateTooltip(); + if ((event == "change") || (this._bindOnDrag && event == "drag")) this._updateHidden(); + return this._raise(event, { value: currentValue, preValue: preValue, "handle": this._handleArgs() }); + } + }, + + // Events handlers + _elementDown: function (e) { + if (this._isReadOnly) return; + var $target = $(e.target); + + if ($target.hasClass("rs-handle")) { + this._handleDown(e); + } + else { + var point = this._getXY(e), center = this._getCenterPoint(); + var distance = this._getDistance(point, center); + var block = this.block || this.svgContainer; + var outerDistance = block.outerWidth() / 2; + var innerDistance = outerDistance - (this.options.width + this._border()); + + if (distance >= innerDistance && distance <= outerDistance) { + var handle = this.control.find(".rs-handle.rs-focus"), angle, value; + if (handle.length !== 0) { + // here, some handle was in already focused state, and user clicked on the slider path + // so this will make the handle unfocus, to avoid that we can prevent this event + e.preventDefault(); + } + + var d = this._getAngleValue(point, center); + angle = d.angle, value = d.value; + + if (this._rangeSlider) { + if (handle.length == 1) { + var active = parseFloat(handle.attr("index")); + if (!this._invertRange) { + if (active == 1 && angle > this._handle2.angle) active = 2; + else if (active == 2 && angle < this._handle1.angle) active = 1; + } + this._active = active; + } + else this._active = (this._handle2.angle - angle) < (angle - this._handle1.angle) ? 2 : 1; + this.bar = this._activeHandleBar(); + } + + this._changeSliderValue(value, angle); + this._raiseEvent("change"); + } + } + }, + _handleDown: function (e) { + e.preventDefault(); + var $target = $(e.target); + $target.focus(); + this._removeAnimation(); + this._bindMouseEvents("_bind"); + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + this._handles().removeClass("rs-move"); + this._raise("start", { value: this.options.value, "handle": this._handleArgs() }); + }, + _handleMove: function (e) { + e.preventDefault(); + var point = this._getXY(e), center = this._getCenterPoint(); + var d = this._getAngleValue(point, center, true), angle, value; + angle = d.angle, value = d.value; + + this._changeSliderValue(value, angle); + this._raiseEvent("drag"); + }, + _handleUp: function (e) { + this._handles().addClass("rs-move"); + this._bindMouseEvents("_unbind"); + this._addAnimation(); + this._raiseEvent("change"); + this._raise("stop", { value: this.options.value, "handle": this._handleArgs() }); + }, + _handleFocus: function (e) { + if (this._isReadOnly) return; + var $target = $(e.target); + this._handles().removeClass("rs-focus"); + $target.addClass("rs-focus"); + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + if (this.options.keyboardAction) { + this._bindKeyboardEvents("_unbind"); + this._bindKeyboardEvents("_bind"); + } + + // updates the class for change z-index + this.control.find("div.rs-bar").css("z-index", "7"); + this.bar.css("z-index", "8"); + }, + _handleBlur: function (e) { + this._handles().removeClass("rs-focus"); + if (this.options.keyboardAction) this._bindKeyboardEvents("_unbind"); + }, + _handleKeyDown: function (e) { + if (this._isReadOnly) return; + var key = e.keyCode, keyCodes = this.keys; + + if (key == 27) // if Esc key pressed then hanldes will be focused out + this._handles().blur(); + + if (!(key >= 35 && key <= 40)) return; // if not valid keys, then return + if (key >= 37 && key <= 40) this._removeAnimation(); + + var h = this["_handle" + this._active], val, ang; + + e.preventDefault(); + if (key == keyCodes.UP || key == keyCodes.RIGHT) // Up || Right Key + val = this._round(this._limitValue(h.value + this.options.step)); + else if (key == keyCodes.DOWN || key == keyCodes.LEFT) // Down || Left Key + val = this._round(this._limitValue(h.value - this._getMinusStep(h.value))); + else if (key == 36) // Home Key + val = this._getKeyValue("Home"); + else if (key == 35) // End Key + val = this._getKeyValue("End"); + + ang = this._valueToAngle(val); + this._changeSliderValue(val, ang); + this._raiseEvent("drag"); + }, + _handleKeyUp: function (e) { + this._addAnimation(); + this._raiseEvent("change"); + }, + _getMinusStep: function (val) { + var o = this.options, min = o.min, max = o.max, step = o.step; + if (val == max) { + var remain = (max - min) % step; + return remain == 0 ? step : remain; + } + return step; + }, + _getKeyValue: function (key) { + var o = this.options, min = o.min, max = o.max; + if (this._rangeSlider) { + if (key == "Home") return (this._active == 1) ? min : this._handle1.value; + else return (this._active == 1) ? this._handle2.value : max; + } + return (key == "Home") ? min : max; + }, + _elementScroll: function (event) { + if (this._isReadOnly) return; + event.preventDefault(); + var e = event.originalEvent || event, h, val, ang, delta; + delta = e.wheelDelta ? e.wheelDelta / 60 : (e.detail ? -e.detail / 2 : 0); + if (delta == 0) return; + + this._updateActiveHandle(event); + h = this["_handle" + this._active]; + val = h.value + (delta > 0 ? this.options.step : -this._getMinusStep(h.value)); + val = this._limitValue(val); + ang = this._valueToAngle(val); + + this._removeAnimation(); + this._changeSliderValue(val, ang); + this._raiseEvent("change"); + this._addAnimation(); + }, + _updateActiveHandle: function (e) { + var $target = $(e.target); + if ($target.hasClass("rs-handle") && $target.parent().parent()[0] == this.control[0]) { + this.bar = $target.parent(); + this._active = parseFloat($target.attr("index")); + } + if (!this.bar.find(".rs-handle").hasClass("rs-focus")) this.bar.find(".rs-handle").focus(); + }, + + // Events binding + _bindControlEvents: function (hook) { + this[hook](this.control, "mousedown", this._elementDown); + this[hook](this.control, "touchstart", this._elementDown); + }, + _bindScrollEvents: function (hook) { + this[hook](this.control, "mousewheel", this._elementScroll); + this[hook](this.control, "DOMMouseScroll", this._elementScroll); + }, + _bindMouseEvents: function (hook) { + this[hook]($(document), "mousemove", this._handleMove); + this[hook]($(document), "mouseup", this._handleUp); + this[hook]($(document), "mouseleave", this._handleUp); + + // *** for Touch support *** // + this[hook]($(document), "touchmove", this._handleMove); + this[hook]($(document), "touchend", this._handleUp); + this[hook]($(document), "touchcancel", this._handleUp); + }, + _bindKeyboardEvents: function (hook) { + this[hook]($(document), "keydown", this._handleKeyDown); + this[hook]($(document), "keyup", this._handleKeyUp); + }, + + // internal methods + _changeSliderValue: function (value, angle) { + var oAngle = this._oriAngle(angle), lAngle = this._limitAngle(angle); + if (!this._rangeSlider && !this._showRange) { + + this["_handle" + this._active] = { angle: angle, value: value }; + this.options.value = value; + this.bar.rsRotate(lAngle); + this._updateARIA(value); + } + else if ((this._active == 1 && oAngle <= this._oriAngle(this._handle2.angle)) || + (this._active == 2 && oAngle >= this._oriAngle(this._handle1.angle)) || this._invertRange) { + + this["_handle" + this._active] = { angle: angle, value: value }; + this.options.value = this._rangeSlider ? this._handle1.value + "," + this._handle2.value : value; + this.bar.rsRotate(lAngle); + this._updateARIA(value); + + if (this.options.svgMode) { + this._moveSliderRange(); + return; + } + + // classic DIV handling + var dAngle = this._oriAngle(this._handle2.angle) - this._oriAngle(this._handle1.angle), o2 = "1", o3 = "0"; + if (dAngle <= 180 && !(dAngle < 0 && dAngle > -180)) o2 = "0", o3 = "1"; + this.block2.css("opacity", o2); + this.block3.css("opacity", o3); + + (this._active == 1 ? this.block4 : this.block2).rsRotate(lAngle - 180); + (this._active == 1 ? this.block1 : this.block3).rsRotate(lAngle); + } + }, + + // SVG related functionalities + _createSVGElements: function () { + var svgEle = this.$createSVG("svg"); + var PATH = "path.rs-transition "; + var pathAttr = { fill: "transparent" }; + + this.$path = this.$createSVG(PATH + "rs-path", pathAttr); + this.$range = this._showRange ? this.$createSVG(PATH + "rs-range", pathAttr) : null; + this.$border = this.$createSVG(PATH + "rs-border", pathAttr); + this.$append(svgEle, [this.$path, this.$range, this.$border]); + + this.svgContainer = this.$createElement("div.rs-svg-container").append(svgEle); + this.innerContainer.append(this.svgContainer); + }, + _setSVGAttributes: function () { + var o = this.options, radius = o.radius, + border = o.borderWidth, width = o.width, + lineCap = o.lineCap; + var outerRadius = radius - (border / 2), + innerRadius = outerRadius - width - border; + var startAngle = this._start, + totalAngle = this._end, + endAngle = startAngle + totalAngle; + + // draw the path for border element + var border_d = this.$drawPath(radius, outerRadius, startAngle, endAngle, innerRadius, lineCap); + this.$setAttribute(this.$border, { + "d": border_d + }); + // and set the border width + $(this.$border).css("stroke-width", border); + + var pathRadius = radius - border - (width / 2); + this.svgPathLength = this.$getArcLength(pathRadius, totalAngle); + var d = this.$drawPath(radius, pathRadius, startAngle, endAngle); + var attr = { "d": d, "stroke-width": width, "stroke-linecap": lineCap }; + + // draw the path for slider path element + this.$setAttribute(this.$path, attr); + + if (this._showRange) { + // draw the path for slider range element + this.$setAttribute(this.$range, attr); + + // there was a small bug when lineCap was round/square, this will solve that + if (lineCap == "round" || lineCap == "square") this.$range.setAttribute("stroke-dashoffset", "0.01"); + else this.$range.removeAttribute("stroke-dashoffset"); + } + }, + _setSVGStyles: function () { + var o = this.options, + borderColor = o.borderColor, + pathColor = o.pathColor, + rangeColor = o.rangeColor; + + if (borderColor) { + $(this.$border).css("stroke", borderColor); + } + + if (pathColor) { + $(this.$path).css("stroke", pathColor); + } + + if (this._showRange && rangeColor) { + $(this.$range).css("stroke", rangeColor); + } + }, + _moveSliderRange: function (isInit) { + if (!this._showRange) return; + + var startAngle = this._start, + totalAngle = this._end; + var handle1Angle = this._handle1.angle - startAngle, + handle2Angle = this._handle2.angle - startAngle; + if (isInit) handle1Angle = handle2Angle = 0; + var dashArray = []; + + if (handle1Angle <= handle2Angle) { + // starting the dashArray from 0 means normal range, otherwise it's invert range + // so when handle1 value is smaller then it's a normal range selection only + dashArray.push(0); + } + else { + // when handle1 value is larger then it's a invert range selection, also swap the values + var temp = handle1Angle; + handle1Angle = handle2Angle; + handle2Angle = temp; + } + + var handle1Distance = (handle1Angle / totalAngle) * this.svgPathLength; + dashArray.push(handle1Distance); + + var handle2Distance = ((handle2Angle - handle1Angle) / totalAngle) * this.svgPathLength; + dashArray.push(handle2Distance, this.svgPathLength); + + this.$range.style.strokeDasharray = dashArray.join(" "); + }, + _isPropsRelatedToSVG: function (property) { + var svgRelatedProps = ["radius", "borderWidth", "width", "lineCap", "startAngle", "endAngle"]; + return this._hasProperty(property, svgRelatedProps); + }, + _isPropsRelatedToSVGStyles: function (property) { + var svgStylesRelatedProps = ["borderColor", "pathColor", "rangeColor"]; + return this._hasProperty(property, svgStylesRelatedProps); + }, + _hasProperty: function (property, list) { + if (typeof property == "string") { + return (list.indexOf(property) !== -1); + } + else { + var allProperties = Object.keys(property); + return allProperties.some(function (prop) { + return (list.indexOf(prop) !== -1); + }); + } + }, + + // WAI-ARIA support + _updateARIA: function (value) { + var o = this.options, min = o.min, max = o.max; + this.bar.children().attr({ "aria-valuenow": value }); + if (o.sliderType == "range") { + var handles = this._handles(); + handles.eq(0).attr({ "aria-valuemin": min }); + handles.eq(1).attr({ "aria-valuemax": max }); + + if (this._active == 1) handles.eq(1).attr({ "aria-valuemin": value }); + else handles.eq(0).attr({ "aria-valuemax": value }); + } + else this.bar.children().attr({ "aria-valuemin": min, "aria-valuemax": max }); + }, + // Listener for KO binding + _checkKO: function () { + var _data = this._dataElement().data("bind"); + if (typeof _data == "string" && typeof ko == "object") { + var _vm = ko.dataFor(this._dataElement()[0]); + if (typeof _vm == "undefined") return true; + var _all = _data.split(","), _handler; + for (var i = 0; i < _all.length; i++) { + var d = _all[i].split(":"); + if ($.trim(d[0]) == "value") { + _handler = $.trim(d[1]); + break; + } + } + if (_handler) { + this._isKO = true; + ko.computed(function () { this.option("value", _vm[_handler]()); }, this); + } + } + }, + // Listener for Angular binding + _checkAngular: function () { + if (typeof angular == "object" && typeof angular.element == "function") { + this._ngName = this._dataElement().attr("ng-model"); + if (typeof this._ngName == "string") { + this._isAngular = true; var that = this; + this._scope().$watch(this._ngName, function (newValue, oldValue) { that.option("value", newValue); }); + } + } + }, + _scope: function () { + return angular.element(this._dataElement()).scope(); + }, + _getDistance: function (p1, p2) { + return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); + }, + _getXY: function (e) { + if (e.type.indexOf("mouse") == -1) e = (e.originalEvent || e).changedTouches[0]; + return { x: e.pageX, y: e.pageY }; + }, + _getCenterPoint: function () { + var block = this.block || this.svgContainer; + var offset = block.offset(), center; + center = { + x: offset.left + (block.outerWidth() / 2), + y: offset.top + (block.outerHeight() / 2) + }; + return center; + }, + _getAngleValue: function (point, center, isDrag) { + var deg = Math.atan2(point.y - center.y, center.x - point.x); + var angle = (-deg / (Math.PI / 180)); + if (angle < this._start) angle += 360; + angle = this._checkAngle(angle, isDrag); + return this._processStepByAngle(angle); + }, + _checkAngle: function (angle, isDrag) { + var o_angle = this._oriAngle(angle), + preAngle = this["_handle" + this._active].angle, + o_preAngle = this._oriAngle(preAngle); + + if (o_angle > this._end) { + if (!isDrag) return preAngle; + angle = this._start + (o_preAngle <= this._end - o_preAngle ? 0 : this._end); + } + else if (isDrag) { + var d = this._handleDragDistance; + if (this.isNumber(d)) if (Math.abs(o_angle - o_preAngle) > d) return preAngle; + } + return angle; + }, + _processStepByAngle: function (angle) { + var value = this._angleToValue(angle); + return this._processStepByValue(value); + }, + _processStepByValue: function (value) { + var o = this.options, min = o.min, max = o.max, step = o.step, isMinHigher = (min > max); + var remain, currVal, nextVal, preVal, newVal, ang; + + step = (isMinHigher ? -step : step); + remain = (value - min) % step; + + currVal = value - remain; + nextVal = this._limitValue(currVal + step); + preVal = this._limitValue(currVal - step); + + if (!isMinHigher) { + if (value >= currVal) newVal = (value - currVal < nextVal - value) ? currVal : nextVal; + else newVal = (currVal - value > value - preVal) ? currVal : preVal; + } + else { + if (value <= currVal) newVal = (currVal - value < value - nextVal) ? currVal : nextVal; + else newVal = (value - currVal > preVal - value) ? currVal : preVal; + } + newVal = this._round(newVal), ang = this._valueToAngle(newVal); + return { value: newVal, angle: ang }; + }, + _round: function (val) { + var s = this.options.step.toString().split("."); + return s[1] ? parseFloat(val.toFixed(s[1].length)) : Math.round(val); + }, + _oriAngle: function (angle) { + var ang = angle - this._start; + if (ang < 0) ang += 360; + return ang; + }, + _limitAngle: function (angle) { + if (angle > 360 + this._start) angle -= 360; + if (angle < this._start) angle += 360; + return angle; + }, + _limitValue: function (value) { + var o = this.options, min = o.min, max = o.max, isMinHigher = (min > max); + if ((!isMinHigher && value < min) || (isMinHigher && value > min)) value = min; + if ((!isMinHigher && value > max) || (isMinHigher && value < max)) value = max; + return value; + }, + _angleToValue: function (angle) { + var o = this.options, min = o.min, max = o.max, value; + value = (this._oriAngle(angle) / this._end) * (max - min) + min; + return value; + }, + _valueToAngle: function (value) { + var o = this.options, min = o.min, max = o.max, angle; + angle = (((value - min) / (max - min)) * this._end) + this._start; + return angle; + }, + _appendHiddenField: function () { + this._hiddenField = this._hiddenField || this.$createElement("input"); + this._hiddenField.attr({ + "type": "hidden", "name": this._dataElement()[0].id || "" + }); + this.control.append(this._hiddenField); + this._updateHidden(); + }, + _updateHidden: function () { + var val = this.options.value; + this._hiddenField.val(val); + if (this._isKO || this._isAngular) this._hiddenField.trigger("change"); + if (this._isAngular) this._scope()[this._ngName] = val; + }, + _updateTooltip: function () { + if (this.tooltip && !this.tooltip.hasClass("hover")) + this.tooltip.html(this._getTooltipValue()); + this._updateTooltipPos(); + }, + _updateTooltipPos: function () { + this.tooltip && this.tooltip.css(this._getTooltipPos()); + }, + _getTooltipPos: function () { + var circleShape = this.options.circleShape, pos; + var tooltipHeight = this.tooltip.outerHeight(), tooltipWidth = this.tooltip.outerWidth(); + + if (circleShape == "full" || circleShape == "pie" || circleShape.indexOf("custom") === 0) { + return { + "margin-top": -tooltipHeight / 2, + "margin-left": -tooltipWidth / 2 + }; + } + else if (circleShape.indexOf("half") != -1) { + switch (circleShape) { + case "half-top": + case "half-bottom": + pos = { "margin-left": -tooltipWidth / 2 }; break; + case "half-left": + case "half-right": + pos = { "margin-top": -tooltipHeight / 2 }; break; + } + return pos; + } + return {}; + }, + _getTooltipValue: function (isNormal) { + var value = this.options.value; + if (this._rangeSlider) { + var p = value.split(","); + if (isNormal) return p[0] + " - " + p[1]; + return this._tooltipValue(p[0], 1) + " - " + this._tooltipValue(p[1], 2); + } + if (isNormal) return value; + return this._tooltipValue(value); + }, + _tooltipValue: function (value, index) { + var returnValue = this._raise("tooltipFormat", { value: value, "handle": this._handleArgs(index) }); + return (returnValue != null && typeof returnValue !== "boolean") ? returnValue : value; + }, + _validateStartAngle: function () { + var start = this.options.startAngle; + start = (this.isNumber(start) ? parseFloat(start) : 0) % 360; + if (start < 0) start += 360; + this.options.startAngle = start; + return start; + }, + _validateEndAngle: function () { + var o = this.options, start = o.startAngle, end = o.endAngle; + if (typeof end === "string" && this.isNumber(end) && (end.charAt(0) === "+" || end.charAt(0) === "-")) { + try { end = start + parseFloat(end.charAt(0) + Math.abs(parseFloat(end))); } + catch (e) { console.warn(e); } + } + end = (this.isNumber(end) ? parseFloat(end) : 360) % 360; + if (end <= start) end += 360; + return end; + }, + _refreshCircleShape: function () { + var circleShape = this.options.circleShape; + var allCircelShapes = ["half-top", "half-bottom", "half-left", "half-right", + "quarter-top-left", "quarter-top-right", "quarter-bottom-right", "quarter-bottom-left", + "pie", "custom-half", "custom-quarter"]; + var shape_codes = ["h1", "h2", "h3", "h4", "q1", "q2", "q3", "q4", "3/4", "ch", "cq"]; + + if (allCircelShapes.indexOf(circleShape) == -1) { + var index = shape_codes.indexOf(circleShape); + if (index != -1) circleShape = allCircelShapes[index]; + else if (circleShape == "half") circleShape = "half-top"; + else if (circleShape == "quarter") circleShape = "quarter-top-left"; + else circleShape = "full"; + } + this.options.circleShape = circleShape; + }, + _appendOverlay: function () { + var shape = this.options.circleShape; + if (shape == "pie") + this._checkOverlay(".rs-overlay", 270); + else if (shape == "custom-half" || shape == "custom-quarter") { + this._checkOverlay(".rs-overlay1", 180); + if (shape == "custom-quarter") + this._checkOverlay(".rs-overlay2", this._end); + } + }, + _checkOverlay: function (cls, angle) { + var overlay = this.container.children(cls); + if (overlay.length == 0) { + overlay = this.$createElement("div" + cls + " rs-transition rs-bg-color"); + this.container.append(overlay); + } + overlay.rsRotate(this._start + angle); + }, + _checkDataType: function () { + var m = this.options, i, prop, value, props = this._props(); + // to check number datatype + for (i in props.numberType) { + prop = props.numberType[i], value = m[prop]; + if (!this.isNumber(value)) m[prop] = this.defaults[prop]; + else m[prop] = parseFloat(value); + } + // to check input string + for (i in props.booleanType) { + prop = props.booleanType[i], value = m[prop]; + m[prop] = (value == "false") ? false : !!value; + } + // to check boolean datatype + for (i in props.stringType) { + prop = props.stringType[i], value = m[prop]; + m[prop] = ("" + value).toLowerCase(); + } + }, + _validateSliderType: function () { + var type = this.options.sliderType.toLowerCase(); + this._rangeSlider = this._showRange = false; + if (type == "range") this._rangeSlider = this._showRange = true; + else if (type.indexOf("min") != -1) { + this._showRange = true; + type = "min-range"; + } + else type = "default"; + this.options.sliderType = type; + }, + _updateStartEnd: function () { + var o = this.options, circle = o.circleShape, startAngle = o.startAngle, endAngle = o.endAngle; + + if (circle != "full") { + if (circle.indexOf("quarter") != -1) endAngle = "+90"; + else if (circle.indexOf("half") != -1) endAngle = "+180"; + else if (circle == "pie") endAngle = "+270"; + this.options.endAngle = endAngle; + + if (circle == "quarter-top-left" || circle == "half-top") startAngle = 0; + else if (circle == "quarter-top-right" || circle == "half-right") startAngle = 90; + else if (circle == "quarter-bottom-right" || circle == "half-bottom") startAngle = 180; + else if (circle == "quarter-bottom-left" || circle == "half-left") startAngle = 270; + this.options.startAngle = startAngle; + } + }, + _validateStartEnd: function () { + this._start = this._validateStartAngle(); + this._end = this._validateEndAngle(); + + var add = (this._start < this._end) ? 0 : 360; + this._end += add - this._start; + }, + _analyzeModelValue: function () { + var o = this.options, val = o.value, min = o.min, max = o.max, + lastValue, newValue, isNumber = this.isNumber; + if (val instanceof Array) val = val.toString(); + var valueIsString = (typeof val == "string"); + + var parts = valueIsString ? val.split(",") : [val]; + + if (this._rangeSlider) { + if (valueIsString) { + if (parts.length >= 2) newValue = (isNumber(parts[0]) ? parts[0] : min) + "," + + (isNumber(parts[1]) ? parts[1] : max); + else newValue = isNumber(parts[0]) ? min + "," + parts[0] : min + "," + min; + } + else newValue = isNumber(val) ? min + "," + val : min + "," + min; + } + else { + if (valueIsString) lastValue = parts.pop(), newValue = isNumber(lastValue) ? parseFloat(lastValue) : min; + else newValue = isNumber(val) ? parseFloat(val) : min; + } + this.options.value = newValue; + }, + _validateModelValue: function () { + var o = this.options, val = o.value; + if (this._rangeSlider) { + var parts = val.split(","), val1 = parseFloat(parts[0]), val2 = parseFloat(parts[1]); + val1 = this._limitValue(val1); + val2 = this._limitValue(val2); + if (!this._invertRange) { + var min = o.min, max = o.max; + var isMinHigher = (min > max); + if (isMinHigher) { + if (val1 < val2) val1 = val2; + } else { + if (val1 > val2) val2 = val1; + } + } + + this._handle1 = this._processStepByValue(val1); + this._handle2 = this._processStepByValue(val2); + this.options.value = this._handle1.value + "," + this._handle2.value; + } + else { + var index = this._showRange ? 2 : (this._active || 1); + this["_handle" + index] = this._processStepByValue(this._limitValue(val)); + if (this._showRange) this._handle1 = this._handleDefaults(); + this.options.value = this["_handle" + index].value; + } + }, + + // common core methods + $createElement: function (tag) { + var t = tag.split('.'); + return $(document.createElement(t[0])).addClass(t[1] || ""); + }, + $createSVG: function (tag, attr) { + var t = tag.split('.'); + var svgEle = document.createElementNS("http://www.w3.org/2000/svg", t[0]); + if (t[1]) { + svgEle.setAttribute("class", t[1]); + } + if (attr) { + this.$setAttribute(svgEle, attr); + } + return svgEle; + }, + $setAttribute: function (ele, attr) { + for (var key in attr) { + var val = attr[key]; + if (key === "class") { + var prev = ele.getAttribute('class'); + if (prev) val += " " + prev; + } + ele.setAttribute(key, val); + } + return ele; + }, + $append: function (parent, children) { + children.forEach(function (element) { + element && parent.appendChild(element); + }); + return parent; + }, + isNumber: function (number) { + number = parseFloat(number); + return typeof number === "number" && !isNaN(number); + }, + getBrowserName: function () { + var browserName = "", ua = window.navigator.userAgent; + if ((!!window.opr && !!opr.addons) || !!window.opera || ua.indexOf(' OPR/') >= 0) browserName = "opera"; + else if (typeof InstallTrigger !== 'undefined') browserName = "firefox"; + else if (ua.indexOf('MSIE ') > 0 || ua.indexOf('Trident/') > 0) browserName = "ie"; + else if (!!window.StyleMedia) browserName = "edge"; + else if (ua.indexOf('Safari') != -1 && ua.indexOf('Chrome') == -1) browserName = "safari"; + else if ((!!window.chrome && !!window.chrome.webstore) || (ua.indexOf('Chrome') != -1)) browserName = "chrome"; + return browserName; + }, + _isBrowserSupported: function () { + var properties = ["borderRadius", "WebkitBorderRadius", "MozBorderRadius", + "OBorderRadius", "msBorderRadius", "KhtmlBorderRadius"]; + for (var i = 0; i < properties.length; i++) { + if (document.body.style[properties[i]] !== undefined) return true; + } + }, + _throwError: function () { + return "This browser doesn't support the border-radious property."; + }, + _raise: function (event, args) { + var o = this.options, fn = o[event], val = true; + args = args || { value: o.value }; + args["id"] = this.id; + args["control"] = this.control; + args["options"] = o; + if (fn) { + args["type"] = event; + if (typeof fn === "string") fn = window[fn]; + if ($.isFunction(fn)) { + val = fn.call(this, args); + val = val === false ? false : val; + } + } + this.control.trigger($.Event(event, args)); + return val; + }, + _bind: function (element, _event, handler) { + $(element).bind(_event, $.proxy(handler, this)); + }, + _unbind: function (element, _event, handler) { + $(element).unbind(_event, $.proxy(handler, this)); + }, + _getInstance: function () { + return $.data(this._dataElement()[0], pluginName); + }, + _saveInstanceOnElement: function () { + $.data(this.control[0], pluginName, this); + }, + _saveInstanceOnID: function () { + var id = this.id; + if (id && typeof window[id] !== "undefined") + window[id] = this; + }, + _removeData: function () { + var control = this._dataElement()[0]; + $.removeData && $.removeData(control, pluginName); + if (control.id && typeof window[control.id]["_init"] === "function") + delete window[control.id]; + }, + _destroyControl: function () { + if (this._isInputType) this._dataElement().insertAfter(this.control).attr("type", "text"); + this.control.empty().removeClass("rs-control").height("").width(""); + this._removeAnimation(); + this._bindControlEvents("_unbind"); + }, + + // methods to dynamic options updation (through option) + _updateWidth: function () { + this.lastBlock.css("padding", this.options.width); + }, + _readOnly: function (bool) { + this._isReadOnly = bool; + this.container.removeClass("rs-readonly"); + if (bool) this.container.addClass("rs-readonly"); + }, + + // get & set for the properties + _get: function (property) { + return this.options[property]; + }, + _set: function (property, value, forceSet) { + var props = this._props(); + if ($.inArray(property, props.numberType) != -1) { // to check number datatype + if (!this.isNumber(value)) return; + value = parseFloat(value); + } + else if ($.inArray(property, props.booleanType) != -1) { // to check boolean datatype + value = (value == "false") ? false : !!value; + } + else if ($.inArray(property, props.stringType) != -1) { // to check input string + value = value.toLowerCase(); + } + + if (!forceSet && this.options[property] == value) return; + this.options[property] = value; + switch (property) { + case "startAngle": + case "endAngle": + this._validateStartEnd(); + this._updateSeperator(); // non SVG mode only + this._appendOverlay(); // non SVG mode only + case "min": + case "max": + case "step": + case "value": + this._analyzeModelValue(); + this._validateModelValue(); + this._setValue(); + this._updatePre(); + this._updateHidden(); + this._updateTooltip(); + break; + case "radius": + this._setRadius(); + this._updateTooltipPos(); + break; + case "width": + this._removeAnimation(); + this._updateWidth(); // non SVG mode only + this._setRadius(); + this._refreshHandle(); + this._updateTooltipPos(); + this._addAnimation(); + this._refreshSeperator(); // non SVG mode only + break; + case "borderWidth": + this._setRadius(); + this._refreshHandle(); + break; + case "handleSize": + this._refreshHandle(); + break; + case "handleShape": + this._setHandleShape(); + break; + case "animation": + this._setAnimation(); + break; + case "showTooltip": + this.options.showTooltip ? this._appendTooltip() : this._removeTooltip(); + break; + case "editableTooltip": + this._tooltipEditable(); + this._updateTooltipPos(); + break; + case "disabled": + this.options.disabled ? this.disable() : this.enable(); + break; + case "readOnly": + this.options.readOnly ? this._readOnly(true) : (!this.options.disabled && this._readOnly(false)); + break; + case "mouseScrollAction": + this._bindScrollEvents(this.options.mouseScrollAction ? "_bind" : "_unbind"); + break; + case "lineCap": + this._setRadius(); + this._refreshSeperator(); // non SVG mode only + break; + case "circleShape": + this._refreshCircleShape(); + if (this.options.circleShape == "full") { + this.options.startAngle = 0; + this.options.endAngle = "+360"; + } + case "sliderType": + this._destroyControl(); + this._onInit(); + break; + case "svgMode": + var $control = this.control, $options = this.options; + this.destroy(); + $control[pluginName]($options); + break; + } + return this; + }, + + // public methods + option: function (property, value) { + if (!this._getInstance() || !this._isBrowserSupport) return; + if ($.isPlainObject(property)) { + if (property["min"] !== undefined || property["max"] !== undefined) { + if (property["min"] !== undefined) { + this.options.min = property["min"]; + delete property["min"]; + } + if (property["max"] !== undefined) { + this.options.max = property["max"]; + delete property["max"]; + } + var val = this.options.value; + if (property["value"] !== undefined) { + val = property["value"] + delete property["value"]; + } + this._set("value", val, true); + } + for (var prop in property) { + this._set(prop, property[prop]); + } + } + else if (property && typeof property == "string") { + if (value === undefined) return this._get(property); + this._set(property, value); + } + + // whenever the properties set dynamically, check for SVG mode. also check + // any of the property was related to SVG. If yes, then redraw the SVG path + if (this.options.svgMode && property) { + if (this._isPropsRelatedToSVG(property)) { + this._setSVGAttributes(); + this._moveSliderRange(); + } + if (this._isPropsRelatedToSVGStyles(property)) { + this._setSVGStyles(); + } + } + + return this; + }, + getValue: function (index) { + if (this.options.sliderType == "range" && this.isNumber(index)) { + var i = parseFloat(index); + if (i == 1 || i == 2) + return this["_handle" + i].value; + } + return this._get("value"); + }, + setValue: function (value, index) { + if (this.isNumber(value)) { + if (this.isNumber(index)) { + var sliderType = this.options.sliderType; + if (sliderType == "range") { + var i = parseFloat(index), val = parseFloat(value); + if (i == 1) value = val + "," + this._handle2.value; + else if (i == 2) value = this._handle1.value + "," + val; + } + else if (sliderType == "default") this._active = index; + } + this._set("value", value); + } + }, + disable: function () { + this.options.disabled = true; + this.container.addClass("rs-disabled"); + this._readOnly(true); + }, + enable: function () { + this.options.disabled = false; + this.container.removeClass("rs-disabled"); + if (!this.options.readOnly) this._readOnly(false); + }, + destroy: function () { + if (!this._getInstance()) return; + this._destroyControl(); + this._removeData(); + if (this._isInputType) this.control.remove(); + } + }; + + $.fn.rsRotate = function (degree) { + var control = this, rotation = "rotate(" + degree + "deg)"; + control.css('-webkit-transform', rotation); + control.css('-moz-transform', rotation); + control.css('-ms-transform', rotation); + control.css('-o-transform', rotation); + control.css('transform', rotation); + return control; + } + + // The plugin constructor + function RoundSlider(control, options) { + this.id = control.id; + this.control = $(control); + + // the options value holds the updated defaults value + this.options = $.extend({}, this.defaults, options); + } + + // The plugin wrapper, prevents multiple instantiations + function CreateRoundSlider(options, args) { + + for (var i = 0; i < this.length; i++) { + var that = this[i], instance = $.data(that, pluginName); + if (!instance) { + var _this = new RoundSlider(that, options); + _this._saveInstanceOnElement(); + _this._saveInstanceOnID(); + + if (_this._raise("beforeCreate") !== false) { + _this._init(); + _this._raise("create"); + } + else _this._removeData(); + } + else if ($.isPlainObject(options)) { + if (typeof instance.option === "function") instance.option(options); + else if (that.id && window[that.id] && typeof window[that.id].option === "function") { + window[that.id].option(options); + } + } + else if (typeof options === "string") { + if (typeof instance[options] === "function") { + if ((options === "option" || options.indexOf("get") === 0) && args[2] === undefined) { + return instance[options](args[1]); + } + instance[options](args[1], args[2]); + } + } + } + return this; + } + + // ### SVG related logic + RoundSlider.prototype.$polarToCartesian = function (centerXY, radius, angleInDegrees) { + var angleInRadians = (angleInDegrees - 180) * Math.PI / 180; + + return [ + centerXY + (radius * Math.cos(angleInRadians)), + centerXY + (radius * Math.sin(angleInRadians)) + ].join(" "); + } + + RoundSlider.prototype.$drawArc = function (centerXY, radius, startAngle, endAngle, isOuter) { + var isCircle = (endAngle - startAngle == 360); + var largeArcFlag = Math.abs(startAngle - endAngle) <= 180 ? "0" : "1"; + var isClockwise = true; + var outerDirection = isClockwise ? 1 : 0; + var innerDirection = isClockwise ? 0 : 1; + var direction = isOuter ? outerDirection : innerDirection; + var _endAngle = isOuter ? endAngle : startAngle; + + var path = []; + + // if it is a perfect circle then draw two half circles, otherwise draw arc + if (isCircle) { + var midAngle = (startAngle + endAngle) / 2; + var midPoint = this.$polarToCartesian(centerXY, radius, midAngle); + var endPoint = this.$polarToCartesian(centerXY, radius, _endAngle); + path.push( + "A", 1, 1, 0, 0, direction, midPoint, + "A", 1, 1, 0, 0, direction, endPoint + ); + } + else { + var endPoint = this.$polarToCartesian(centerXY, radius, _endAngle); + path.push( + "A", radius, radius, 0, largeArcFlag, direction, endPoint + ); + } + + return path.join(" "); + } + + RoundSlider.prototype.$drawPath = function (centerXY, outerRadius, startAngle, endAngle, innerRadius, lineCap) { + var outerStart = this.$polarToCartesian(centerXY, outerRadius, startAngle); + var outerArc = this.$drawArc(centerXY, outerRadius, startAngle, endAngle, true); // draw outer circle + + var d = [ + "M " + outerStart, + outerArc + ]; + + if (innerRadius) { + var innerEnd = this.$polarToCartesian(centerXY, innerRadius, endAngle); + var innerArc = this.$drawArc(centerXY, innerRadius, startAngle, endAngle, false); // draw inner circle + + if (lineCap == "none") { + d.push( + "M " + innerEnd, + innerArc + ); + } + else if (lineCap == "round") { + d.push( + "A 1, 1, 0, 0, 1, " + innerEnd, + innerArc, + "A 1, 1, 0, 0, 1, " + outerStart + ); + } + else if (lineCap == "butt" || lineCap == "square") { + d.push( + "L " + innerEnd, + innerArc, + "L " + outerStart, + "Z" + ); + } + } + return d.join(" "); + } + + RoundSlider.prototype.$getArcLength = function (radius, degree = 360) { + // when degree not provided we can consider that arc as a complete circle + // circle's arc length formula => 2πR(Θ/360) + return 2 * Math.PI * radius * (degree / 360); + } + + $.fn[pluginName].prototype = RoundSlider.prototype; + })(jQuery, window); \ No newline at end of file From a029a280038839d890e9872be39df9be6ed1db5b Mon Sep 17 00:00:00 2001 From: Aaron Opell <aaron.opell@live.com> Date: Mon, 23 Mar 2020 20:45:50 -0700 Subject: [PATCH 8/9] Added privacy policy to s+ settings --- PRIVACY.md | 6 +++++- js/all.js | 2 +- manifest.json | 13 ------------- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/PRIVACY.md b/PRIVACY.md index 9fff13ec..fe73c0b9 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -28,4 +28,8 @@ Although most changes are likely to be minor, Schoology Plus may change its Priv ## Disclaimer -Schoology Plus is not affiliated with Schoology Inc. or the Los Angeles Unified School District. Schoology, the SCHOOLOGY® wordmark, and the S logo are registered and unregistered trademarks of Schoology, Inc. in the United States. All product names, logos, and brands are property of their respective owners. \ No newline at end of file +Schoology Plus is not affiliated with Schoology Inc. or the Los Angeles Unified School District. Schoology, the SCHOOLOGY® wordmark, and the S logo are registered and unregistered trademarks of Schoology, Inc. in the United States. All product names, logos, and brands are property of their respective owners. + +## Contact + +The developers of Schoology Plus can be contacted by email at [schoologyplus@aopell.me](mailto:schoologyplus@aopell.me) or through Discord at [this link](https://aopell.me/SchoologyPlus/discord). \ No newline at end of file diff --git a/js/all.js b/js/all.js index a5d96938..26b7676e 100644 --- a/js/all.js +++ b/js/all.js @@ -67,7 +67,7 @@ bottom.appendChild(createElement("span", ["footer-divider"], { textContent: "|" document.documentElement.style.setProperty("--default-visibility", "visible"); -let verboseModalFooterText = `© Aaron Opell, Glen Husman 2017-2020 | <a href="${getBrowser() == "Chrome" ? `https://chrome.google.com/webstore/detail/${chrome.runtime.id}` : "https://github.com/aopell/SchoologyPlus/releases/latest"}">Schoology Plus v${chrome.runtime.getManifest().version_name || chrome.runtime.getManifest().version}${getBrowser() != "Chrome" || chrome.runtime.getManifest().update_url ? '' : ' dev'}</a> | <a href="https://aopell.github.io/SchoologyPlus/discord.html" title="Get support, report bugs, suggest features, and chat with the Schoology Plus community">Discord Support Server</a> | <a href="https://github.com/aopell/SchoologyPlus">GitHub</a> | <a href="#" id="open-contributors">Contributors</a> | <a href="#" id="open-changelog"> Changelog</a>`; +let verboseModalFooterText = `© Aaron Opell, Glen Husman 2017-2020 | <a href="${getBrowser() == "Chrome" ? `https://chrome.google.com/webstore/detail/${chrome.runtime.id}` : "https://github.com/aopell/SchoologyPlus/releases/latest"}">Schoology Plus v${chrome.runtime.getManifest().version_name || chrome.runtime.getManifest().version}${getBrowser() != "Chrome" || chrome.runtime.getManifest().update_url ? '' : ' dev'}</a> | <a href="https://aopell.github.io/SchoologyPlus/discord.html" title="Get support, report bugs, suggest features, and chat with the Schoology Plus community">Discord Server</a> | <a href="https://github.com/aopell/SchoologyPlus">GitHub</a> | <a href="#" id="open-contributors">Contributors</a> | <a target="_blank" href="https://aopell.me/SchoologyPlus/privacy-policy">Privacy Policy</a> | <a href="#" id="open-changelog"> Changelog</a>`; let modalFooterText = "Schoology Plus"; let frame = document.createElement("iframe"); diff --git a/manifest.json b/manifest.json index 05b5b6f3..95a082fb 100644 --- a/manifest.json +++ b/manifest.json @@ -108,19 +108,6 @@ ], "run_at": "document_end" }, - { - "matches": [ - "https://lms.lausd.net/course/*/materials*", - "https://*.schoology.com/course/*/materials*" - ], - "css": [ - "css/materials.css" - ], - "js": [ - "lib/js/pdf.min.js" - ], - "run_at": "document_start" - }, { "matches": [ "https://lms.lausd.net/course/*/materials*", From c5fdcb9cc5e3593d0de7ea1e92b303bd8deab796 Mon Sep 17 00:00:00 2001 From: Aaron Opell <aaron.opell@live.com> Date: Mon, 23 Mar 2020 20:49:04 -0700 Subject: [PATCH 9/9] Bumped to version 6.1 --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 95a082fb..7fa9881b 100644 --- a/manifest.json +++ b/manifest.json @@ -8,7 +8,7 @@ "id": "schoology.plus@aopell.me" } }, - "version": "6.0", + "version": "6.1", "icons": { "128": "imgs/icon@128.png", "64": "imgs/icon@64.png",