Unified · Split
| @@ -263,6 +263,13 @@ · 262 unmodified lines | |||
| 263 | <None Include="PostBuildSetup.bat" /> | 263 | <None Include="PostBuildSetup.bat" /> |
| 264 | </ItemGroup> | 264 | </ItemGroup> |
| 265 | <ItemGroup> | 265 | <ItemGroup> |
| 266 | <EmbeddedResource Include="Shared\Scripts\knockout.mapping.js" /> | ||
| 267 | <EmbeddedResource Include="Shared\Scripts\gsf.web.client.js" /> | ||
| 268 | <EmbeddedResource Include="Shared\Scripts\gsf.web.knockout.js" /> | ||
| 269 | <EmbeddedResource Include="Shared\Scripts\gsf.web.primeui.js" /> | ||
| 270 | <EmbeddedResource Include="Shared\Scripts\js.cookie.js" /> | ||
| 271 | <EmbeddedResource Include="Shared\Scripts\knockout.reactor.js" /> | ||
| 272 | <EmbeddedResource Include="Shared\Scripts\knockout.observableDictionary.js" /> | ||
| 266 | <EmbeddedResource Include="Security\Scripts\core.js" /> | 273 | <EmbeddedResource Include="Security\Scripts\core.js" /> |
| 267 | <EmbeddedResource Include="Security\Scripts\enc-base64.js" /> | 274 | <EmbeddedResource Include="Security\Scripts\enc-base64.js" /> |
| 268 | <EmbeddedResource Include="Security\Scripts\ntlm.js" /> | 275 | <EmbeddedResource Include="Security\Scripts\ntlm.js" /> |
| @@ -1,3 +1,8 @@ | |||
| 1 | /******************************************************************************************************\ | ||
| 2 | !WARNING: The location for this resource has been moved! | ||
| 3 | New resource path is "/@GSF/Web/Shared/Scripts/", please update scripts to use this new location | ||
| 4 | \******************************************************************************************************/ | ||
| 5 | |||
| 1 | /** | 6 | /** |
| 2 | * @license AngularJS v1.5.8 | 7 | * @license AngularJS v1.5.8 |
| 3 | * (c) 2010-2016 Google, Inc. http://angularjs.org | 8 | * (c) 2010-2016 Google, Inc. http://angularjs.org |
| @@ -1,4 +1,9 @@ | |||
| 1 | //****************************************************************************************************** | 1 | /******************************************************************************************************\ |
| 2 | !WARNING: The location for this resource has been moved! | ||
| 3 | New resource path is "/@GSF/Web/Shared/Scripts/", please update scripts to use this new location | ||
| 4 | \******************************************************************************************************/ | ||
| 5 | |||
| 6 | //****************************************************************************************************** | ||
| 2 | // gsf.web.client.js - Gbtc | 7 | // gsf.web.client.js - Gbtc |
| 3 | // | 8 | // |
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | 9 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. |
| @@ -1,4 +1,9 @@ | |||
| 1 | //****************************************************************************************************** | 1 | /******************************************************************************************************\ |
| 2 | !WARNING: The location for this resource has been moved! | ||
| 3 | New resource path is "/@GSF/Web/Shared/Scripts/", please update scripts to use this new location | ||
| 4 | \******************************************************************************************************/ | ||
| 5 | |||
| 6 | //****************************************************************************************************** | ||
| 2 | // gsf.web.knockout.js - Gbtc | 7 | // gsf.web.knockout.js - Gbtc |
| 3 | // | 8 | // |
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | 9 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. |
| @@ -1,4 +1,9 @@ | |||
| 1 | //****************************************************************************************************** | 1 | /******************************************************************************************************\ |
| 2 | !WARNING: The location for this resource has been moved! | ||
| 3 | New resource path is "/@GSF/Web/Shared/Scripts/", please update scripts to use this new location | ||
| 4 | \******************************************************************************************************/ | ||
| 5 | |||
| 6 | //****************************************************************************************************** | ||
| 2 | // gsf.web.primeui.js - Gbtc | 7 | // gsf.web.primeui.js - Gbtc |
| 3 | // | 8 | // |
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | 9 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. |
| @@ -1,4 +1,9 @@ | |||
| 1 | /*! | 1 | /******************************************************************************************************\ |
| 2 | !WARNING: The location for this resource has been moved! | ||
| 3 | New resource path is "/@GSF/Web/Shared/Scripts/", please update scripts to use this new location | ||
| 4 | \******************************************************************************************************/ | ||
| 5 | |||
| 6 | /*! | ||
| 2 | * JavaScript Cookie v2.1.0 | 7 | * JavaScript Cookie v2.1.0 |
| 3 | * https://github.com/js-cookie/js-cookie | 8 | * https://github.com/js-cookie/js-cookie |
| 4 | * | 9 | * |
| @@ -1,3 +1,8 @@ | |||
| 1 | /******************************************************************************************************\ | ||
| 2 | !WARNING: The location for this resource has been moved! | ||
| 3 | New resource path is "/@GSF/Web/Shared/Scripts/", please update scripts to use this new location | ||
| 4 | \******************************************************************************************************/ | ||
| 5 | |||
| 1 | // Deep observer plugin for Knockout http://knockoutjs.com/ | 6 | // Deep observer plugin for Knockout http://knockoutjs.com/ |
| 2 | // (c) Ziad Jeeroburkhan | 7 | // (c) Ziad Jeeroburkhan |
| 3 | // License: MIT (http://www.opensource.org/licenses/mit-license.php) | 8 | // License: MIT (http://www.opensource.org/licenses/mit-license.php) |
| @@ -1 +1,6 @@ | |||
| 1 | /******************************************************************************************************\ | ||
| 2 | !WARNING: The location for this resource has been moved! | ||
| 3 | New resource path is "/@GSF/Web/Shared/Scripts/", please update scripts to use this new location | ||
| 4 | \******************************************************************************************************/ | ||
| 5 | |||
| 1 | ko.subscribable.fn.watch=function(a,b,c,d){var e=typeof a;return"boolean"===e||"undefined"===e?ko.watch(this,{enabled:a!==!1}):"function"!==e||ko.isSubscribable(a)?ko.watch(a,b,c,d||this):ko.watch(this,b||{},a,d||this),this},ko.watch=function(a,b,c,d){function e(f,g,h,i,j){if(f&&0!==b.depth&&(-1===b.depth||h.length<(b.depth||1))){if(b.watchedOnly&&!f.watchable&&f!=a)return;if((b.enabled===!1||b.enabled===!0)&&(f.watchable=b.enabled),f.watchable===!1)return;if(b.seal===!0&&(f.watchable=!1),f===g||ko.utils.arrayIndexOf(h,f)>-1)return;var k=[].concat(h,g&&g!==a?g:[]);if(b.hide&&("object"==typeof b.hide?b.hide===f:ko.utils.arrayIndexOf(b.hide,f)>-1))return;if(ko.isSubscribable(f)){if(c&&(i===!0||!b.beforeWatch||b.beforeWatch.call(d,k,f)!==!1)){if("function"==typeof f.pop)return f.subscribe(function(a){ko.utils.arrayForEach(a,function(a){var b=c.call(d,k,f,a);void 0!==b&&d(b),a.moved||setTimeout(function(){e(a.value,j?null:f,k,"deleted"===a.status)},0)})},void 0,"arrayChange"),e(f(),j?null:f,k,i,!0),!0;if(i===!0){var l=f.H||f._subscriptions;if(l){if(l.change)for(var m=l.change.length-1;m>=0;m--)l.change[m]._watcher===d&&l.change[m].dispose();if(l.beforeChange&&b.keepOldValues>0)for(var m=l.beforeChange.length-1;m>=0;m--)l.beforeChange[m]._watcher===d&&l.beforeChange[m].dispose()}e(f(),j?null:f,k,!0,!0)}else{if(b.enabled===!0&&f.watchable===!1)return;(b.keepOldValues>0||b.mutable)&&(f.subscribe(function(a){if(b.keepOldValues>0){var c=f.oldValues?f.oldValues:f.oldValues=[];for(c.unshift(a);c.length>b.keepOldValues;)c.pop()}b.mutable&&"object"==typeof a&&e(a,j?null:f,k,!0,!0)},null,"beforeChange")._watcher=d),f.subscribe(function(){if(f.watchable!==!1){var a=c.call(d,k,f);void 0!==a&&d(a),b.mutable&&"object"==typeof f()&&e(f(),j?null:f,k)}},null,"change")._watcher=d,b.hideWrappedValues!==!0&&e(f(),j?null:f,k)}}}else switch(Object.prototype.toString.call(f)){case"[object Object]":return ko.utils.objectForEach(f,function(a,c){if(b.wrap){var g=Object.prototype.toString.call(c);"[object Function]"!==g&&"[object Object]"!==g&&(b.beforeWrap&&b.beforeWrap.call(d,k,f,c)===!1||(c=f[a]="[object Array]"===g?ko.observableArray(c):ko.observable(c)))}var h=e(c,j?null:f,k,i);h&&b.tagParentsWithName===!0&&!c._fieldName&&(c._fieldName=a)}),!0;case"[object Array]":if(b.hideArrays!==!0)for(var m=0;m<f.length;m++)e(f[m],j?null:f,k,i);return!0}}}return"function"==typeof b&&(d=d||c,c=b,b={}),d=d||this,"function"!=typeof a||ko.isSubscribable(a)?void e(a,null,[]):ko.computed(a,c,b)};window.foo = "1.3.0"; | 6 | ko.subscribable.fn.watch=function(a,b,c,d){var e=typeof a;return"boolean"===e||"undefined"===e?ko.watch(this,{enabled:a!==!1}):"function"!==e||ko.isSubscribable(a)?ko.watch(a,b,c,d||this):ko.watch(this,b||{},a,d||this),this},ko.watch=function(a,b,c,d){function e(f,g,h,i,j){if(f&&0!==b.depth&&(-1===b.depth||h.length<(b.depth||1))){if(b.watchedOnly&&!f.watchable&&f!=a)return;if((b.enabled===!1||b.enabled===!0)&&(f.watchable=b.enabled),f.watchable===!1)return;if(b.seal===!0&&(f.watchable=!1),f===g||ko.utils.arrayIndexOf(h,f)>-1)return;var k=[].concat(h,g&&g!==a?g:[]);if(b.hide&&("object"==typeof b.hide?b.hide===f:ko.utils.arrayIndexOf(b.hide,f)>-1))return;if(ko.isSubscribable(f)){if(c&&(i===!0||!b.beforeWatch||b.beforeWatch.call(d,k,f)!==!1)){if("function"==typeof f.pop)return f.subscribe(function(a){ko.utils.arrayForEach(a,function(a){var b=c.call(d,k,f,a);void 0!==b&&d(b),a.moved||setTimeout(function(){e(a.value,j?null:f,k,"deleted"===a.status)},0)})},void 0,"arrayChange"),e(f(),j?null:f,k,i,!0),!0;if(i===!0){var l=f.H||f._subscriptions;if(l){if(l.change)for(var m=l.change.length-1;m>=0;m--)l.change[m]._watcher===d&&l.change[m].dispose();if(l.beforeChange&&b.keepOldValues>0)for(var m=l.beforeChange.length-1;m>=0;m--)l.beforeChange[m]._watcher===d&&l.beforeChange[m].dispose()}e(f(),j?null:f,k,!0,!0)}else{if(b.enabled===!0&&f.watchable===!1)return;(b.keepOldValues>0||b.mutable)&&(f.subscribe(function(a){if(b.keepOldValues>0){var c=f.oldValues?f.oldValues:f.oldValues=[];for(c.unshift(a);c.length>b.keepOldValues;)c.pop()}b.mutable&&"object"==typeof a&&e(a,j?null:f,k,!0,!0)},null,"beforeChange")._watcher=d),f.subscribe(function(){if(f.watchable!==!1){var a=c.call(d,k,f);void 0!==a&&d(a),b.mutable&&"object"==typeof f()&&e(f(),j?null:f,k)}},null,"change")._watcher=d,b.hideWrappedValues!==!0&&e(f(),j?null:f,k)}}}else switch(Object.prototype.toString.call(f)){case"[object Object]":return ko.utils.objectForEach(f,function(a,c){if(b.wrap){var g=Object.prototype.toString.call(c);"[object Function]"!==g&&"[object Object]"!==g&&(b.beforeWrap&&b.beforeWrap.call(d,k,f,c)===!1||(c=f[a]="[object Array]"===g?ko.observableArray(c):ko.observable(c)))}var h=e(c,j?null:f,k,i);h&&b.tagParentsWithName===!0&&!c._fieldName&&(c._fieldName=a)}),!0;case"[object Array]":if(b.hideArrays!==!0)for(var m=0;m<f.length;m++)e(f[m],j?null:f,k,i);return!0}}}return"function"==typeof b&&(d=d||c,c=b,b={}),d=d||this,"function"!=typeof a||ko.isSubscribable(a)?void e(a,null,[]):ko.computed(a,c,b)};window.foo = "1.3.0"; |
| @@ -1,4 +1,9 @@ | |||
| 1 | // Knockout Observable Dictionary | 1 | /******************************************************************************************************\ |
| 2 | !WARNING: The location for this resource has been moved! | ||
| 3 | New resource path is "/@GSF/Web/Shared/Scripts/", please update scripts to use this new location | ||
| 4 | \******************************************************************************************************/ | ||
| 5 | |||
| 6 | // Knockout Observable Dictionary | ||
| 2 | // (c) James Foster | 7 | // (c) James Foster |
| 3 | // License: MIT (http://www.opensource.org/licenses/mit-license.php) | 8 | // License: MIT (http://www.opensource.org/licenses/mit-license.php) |
| 4 | 9 | ||
| @@ -25,6 +25,22 @@ · 24 unmodified lines | |||
| 25 | // Paged view model base class scripts | 25 | // Paged view model base class scripts |
| 26 | "use strict"; | 26 | "use strict"; |
| 27 | 27 | ||
| 28 | if (typeof jQuery === "undefined") { | ||
| 29 | throw new Error("pageViewModel script requires jQuery - make sure jquery.js is loaded first"); | ||
| 30 | } | ||
| 31 | |||
| 32 | if (typeof Cookies.get === "undefined") { | ||
| 33 | throw new Error("pageViewModel script requires js.cookie - make sure js.cookie.js is loaded first"); | ||
| 34 | } | ||
| 35 | |||
| 36 | if (typeof ko === "undefined") { | ||
| 37 | throw new Error("pageViewModel script requires knockout - make sure knockout.js is loaded first"); | ||
| 38 | } | ||
| 39 | |||
| 40 | if (typeof Dictionary === "undefined") { | ||
| 41 | throw new Error("pageViewModel script requires gsf.web.client - make sure gsf.web.client.js is loaded first"); | ||
| 42 | } | ||
| 43 | |||
| 28 | var RecordMode = { | 44 | var RecordMode = { |
| 29 | View: 0, | 45 | View: 0, |
| 30 | Edit: 1, | 46 | Edit: 1, |
| @@ -42,6 +42,12 @@ · 41 unmodified lines | |||
| 42 | ViewBag.PageControlScripts = new StringBuilder(); | 42 | ViewBag.PageControlScripts = new StringBuilder(); |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | bool includeScripts = false; | ||
| 46 | |||
| 47 | if (ViewBag.IncludeScripts != null) { | ||
| 48 | includeScripts = (bool)ViewBag.IncludeScripts; | ||
| 49 | } | ||
| 50 | |||
| 45 | // Setup page control scripts | 51 | // Setup page control scripts |
| 46 | StringBuilder pageControlScripts = ViewBag.PageControlScripts; | 52 | StringBuilder pageControlScripts = ViewBag.PageControlScripts; |
| 47 | 53 | ||
| @@ -53,22 +59,20 @@ · 5 unmodified lines | |||
| 53 | ViewBag.RecordsTitle ?? "Records" | 59 | ViewBag.RecordsTitle ?? "Records" |
| 54 | ); | 60 | ); |
| 55 | 61 | ||
| 56 | pageControlScripts.Append(string.Format(@" | 62 | if (includeScripts) { |
| 57 | <script src=""{0}""></script> | 63 | pageControlScripts.Append(@" |
| 58 | <script src=""{1}""></script> | 64 | <script src=""/@GSF/Web/Shared/Scripts/knockout.js""></script> |
| 59 | <script src=""{2}""></script> | 65 | <script src=""/@GSF/Web/Shared/Scripts/knockout.mapping.js""></script> |
| 60 | ", | 66 | <script src=""/@GSF/Web/Shared/Scripts/knockout.validation.js""></script> |
| 61 | Url.Content("~/Scripts/knockout-3.4.0.js"), | 67 | <script src=""/@GSF/Web/Shared/Scripts/knockout.reactor.min.js""></script> |
| 62 | Url.Content("~/Scripts/knockout.mapping-latest.js"), | 68 | <script src=""/@GSF/Web/Shared/Scripts/knockout.observableDictionary.js""></script> |
| 63 | Url.Content("~/Scripts/knockout.validation.min.js")) | 69 | <script src=""/@GSF/Web/Shared/Scripts/js.cookie.js""></script> |
| 64 | ); | 70 | <script src=""/@GSF/Web/Shared/Scripts/gsf.web.knockout.js""></script> |
| 71 | <script src=""/@GSF/Web/Model/Scripts/pagedViewModel.js""></script> | ||
| 72 | "); | ||
| 73 | } | ||
| 65 | 74 | ||
| 66 | pageControlScripts.Append(@" | 75 | pageControlScripts.Append(@" |
| 67 | <script src=""/@GSF/Web/Model/Scripts/ko-reactor.min.js""></script> | ||
| 68 | <script src=""/@GSF/Web/Model/Scripts/ko.observableDictionary.js""></script> | ||
| 69 | <script src=""/@GSF/Web/Model/Scripts/js.cookie.js""></script> | ||
| 70 | <script src=""/@GSF/Web/Model/Scripts/gsf.web.knockout.js""></script> | ||
| 71 | <script src=""/@GSF/Web/Model/Scripts/pagedViewModel.js""></script> | ||
| 72 | <script> | 76 | <script> |
| 73 | $(function() { | 77 | $(function() { |
| 74 | $(""#clearSortOrder"").click(function() { | 78 | $(""#clearSortOrder"").click(function() { |
| @@ -28,30 +28,35 @@ namespace GSF.Web.Shared · 27 unmodified lines | |||
| 28 | /// </summary> | 28 | /// </summary> |
| 29 | public static class Resources | 29 | public static class Resources |
| 30 | { | 30 | { |
| 31 | /// <summary> | ||
| 32 | /// Defines embedded resource root path. | ||
| 33 | /// </summary> | ||
| 34 | public const string Root = "/@GSF/Web"; | ||
| 35 | |||
| 31 | /// <summary> | 36 | /// <summary> |
| 32 | /// Gets common set of icon resources that can be included in web page header. | 37 | /// Gets common set of icon resources that can be included in web page header. |
| 33 | /// </summary> | 38 | /// </summary> |
| 34 | public static readonly string HeaderIcons = @" | 39 | public static readonly string HeaderIcons = $@" |
| 35 | <link rel=""apple-touch-icon-precomposed"" sizes=""57x57"" href=""/@GSF/Web/Shared/Images/Icons/apple-touch-icon-57x57.png"" /> | 40 | <link rel=""apple-touch-icon-precomposed"" sizes=""57x57"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-57x57.png"" /> |
| 36 | <link rel=""apple-touch-icon-precomposed"" sizes=""114x114"" href=""/@GSF/Web/Shared/Images/Icons/apple-touch-icon-114x114.png"" /> | 41 | <link rel=""apple-touch-icon-precomposed"" sizes=""114x114"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-114x114.png"" /> |
| 37 | <link rel=""apple-touch-icon-precomposed"" sizes=""72x72"" href=""/@GSF/Web/Shared/Images/Icons/apple-touch-icon-72x72.png"" /> | 42 | <link rel=""apple-touch-icon-precomposed"" sizes=""72x72"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-72x72.png"" /> |
| 38 | <link rel=""apple-touch-icon-precomposed"" sizes=""144x144"" href=""/@GSF/Web/Shared/Images/Icons/apple-touch-icon-144x144.png"" /> | 43 | <link rel=""apple-touch-icon-precomposed"" sizes=""144x144"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-144x144.png"" /> |
| 39 | <link rel=""apple-touch-icon-precomposed"" sizes=""60x60"" href=""/@GSF/Web/Shared/Images/Icons/apple-touch-icon-60x60.png"" /> | 44 | <link rel=""apple-touch-icon-precomposed"" sizes=""60x60"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-60x60.png"" /> |
| 40 | <link rel=""apple-touch-icon-precomposed"" sizes=""120x120"" href=""/@GSF/Web/Shared/Images/Icons/apple-touch-icon-120x120.png"" /> | 45 | <link rel=""apple-touch-icon-precomposed"" sizes=""120x120"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-120x120.png"" /> |
| 41 | <link rel=""apple-touch-icon-precomposed"" sizes=""76x76"" href=""/@GSF/Web/Shared/Images/Icons/apple-touch-icon-76x76.png"" /> | 46 | <link rel=""apple-touch-icon-precomposed"" sizes=""76x76"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-76x76.png"" /> |
| 42 | <link rel=""apple-touch-icon-precomposed"" sizes=""152x152"" href=""/@GSF/Web/Shared/Images/Icons/apple-touch-icon-152x152.png"" /> | 47 | <link rel=""apple-touch-icon-precomposed"" sizes=""152x152"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-152x152.png"" /> |
| 43 | <link rel=""icon"" type=""image/png"" href=""/@GSF/Web/Shared/Images/Icons/favicon-196x196.png"" sizes=""196x196"" /> | 48 | <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-196x196.png"" sizes=""196x196"" /> |
| 44 | <link rel=""icon"" type=""image/png"" href=""/@GSF/Web/Shared/Images/Icons/favicon-96x96.png"" sizes=""96x96"" /> | 49 | <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-96x96.png"" sizes=""96x96"" /> |
| 45 | <link rel=""icon"" type=""image/png"" href=""/@GSF/Web/Shared/Images/Icons/favicon-32x32.png"" sizes=""32x32"" /> | 50 | <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-32x32.png"" sizes=""32x32"" /> |
| 46 | <link rel=""icon"" type=""image/png"" href=""/@GSF/Web/Shared/Images/Icons/favicon-16x16.png"" sizes=""16x16"" /> | 51 | <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-16x16.png"" sizes=""16x16"" /> |
| 47 | <link rel=""icon"" type=""image/png"" href=""/@GSF/Web/Shared/Images/Icons/favicon-128.png"" sizes=""128x128"" /> | 52 | <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-128.png"" sizes=""128x128"" /> |
| 48 | <link rel=""shortcut icon"" href=""/@GSF/Web/Shared/Images/Icons/favicon.ico"" /> | 53 | <link rel=""shortcut icon"" href=""{Root}/Shared/Images/Icons/favicon.ico"" /> |
| 49 | <meta name=""msapplication-TileColor"" content=""#AABBAA"" /> | 54 | <meta name=""msapplication-TileColor"" content=""#AABBAA"" /> |
| 50 | <meta name=""msapplication-TileImage"" content=""/@GSF/Web/Shared/Images/Icons/mstile-144x144.png"" /> | 55 | <meta name=""msapplication-TileImage"" content=""{Root}/Shared/Images/Icons/mstile-144x144.png"" /> |
| 51 | <meta name=""msapplication-square70x70logo"" content=""/@GSF/Web/Shared/Images/Icons/mstile-70x70.png"" /> | 56 | <meta name=""msapplication-square70x70logo"" content=""{Root}/Shared/Images/Icons/mstile-70x70.png"" /> |
| 52 | <meta name=""msapplication-square150x150logo"" content=""/@GSF/Web/Shared/Images/Icons/mstile-150x150.png"" /> | 57 | <meta name=""msapplication-square150x150logo"" content=""{Root}/Shared/Images/Icons/mstile-150x150.png"" /> |
| 53 | <meta name=""msapplication-wide310x150logo"" content=""/@GSF/Web/Shared/Images/Icons/mstile-310x150.png"" /> | 58 | <meta name=""msapplication-wide310x150logo"" content=""{Root}/Shared/Images/Icons/mstile-310x150.png"" /> |
| 54 | <meta name=""msapplication-square310x310logo"" content=""/@GSF/Web/Shared/Images/Icons/mstile-310x310.png""/> | 59 | <meta name=""msapplication-square310x310logo"" content=""{Root}/Shared/Images/Icons/mstile-310x310.png""/> |
| 55 | ".FixForwardSpacing(); | 60 | ".FixForwardSpacing(0); |
| 56 | } | 61 | } |
| 57 | } | 62 | } |
| @@ -0,0 +1,895 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // gsf.web.client.js - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may | ||
| 9 | // not use this file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 01/26/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | // ReSharper disable NativeTypePrototypeExtending | ||
| 24 | // ReSharper disable UndeclaredGlobalVariableUsing | ||
| 25 | |||
| 26 | // Grid Solutions Framework Core Web Client Script Functions | ||
| 27 | "use strict"; | ||
| 28 | |||
| 29 | if (typeof jQuery === "undefined") { | ||
| 30 | throw new Error("gsf.web.client script requires jQuery - make sure jquery.js is loaded first"); | ||
| 31 | } | ||
| 32 | |||
| 33 | const isIE = detectIE(); | ||
| 34 | var textMetrics; | ||
| 35 | |||
| 36 | $(function() { | ||
| 37 | // Create a canvas object that will be used for text metrics calculations | ||
| 38 | $("<canvas id=\"textMetricsCanvas\" height=\"1px\" width=\"1px\" style=\"visibility: hidden\"></canvas>").appendTo("body"); | ||
| 39 | |||
| 40 | // Get text metrics canvas context | ||
| 41 | textMetrics = document.getElementById("textMetricsCanvas").getContext("2d"); | ||
| 42 | }); | ||
| 43 | |||
| 44 | // Miscellaneous functions | ||
| 45 | function notNull(value, nonNullValue) { | ||
| 46 | return value || (nonNullValue || ""); | ||
| 47 | } | ||
| 48 | |||
| 49 | function detectIE() { | ||
| 50 | const ua = window.navigator.userAgent; | ||
| 51 | const msie = ua.indexOf("MSIE "); | ||
| 52 | |||
| 53 | if (msie > 0) { | ||
| 54 | // IE 10 or older => return version number | ||
| 55 | return parseInt(ua.substring(msie + 5, ua.indexOf(".", msie)), 10); | ||
| 56 | } | ||
| 57 | |||
| 58 | const trident = ua.indexOf("Trident/"); | ||
| 59 | |||
| 60 | if (trident > 0) { | ||
| 61 | // IE 11 => return version number | ||
| 62 | const rv = ua.indexOf("rv:"); | ||
| 63 | return parseInt(ua.substring(rv + 3, ua.indexOf(".", rv)), 10); | ||
| 64 | } | ||
| 65 | |||
| 66 | const edge = ua.indexOf("Edge/"); | ||
| 67 | |||
| 68 | if (edge > 0) { | ||
| 69 | // Edge (IE 12+) => return version number | ||
| 70 | return parseInt(ua.substring(edge + 5, ua.indexOf(".", edge)), 10); | ||
| 71 | } | ||
| 72 | |||
| 73 | // Other browser | ||
| 74 | return false; | ||
| 75 | } | ||
| 76 | |||
| 77 | function isNumber(val) { | ||
| 78 | return !isNaN(parseFloat(val)) && isFinite(val); | ||
| 79 | } | ||
| 80 | |||
| 81 | function toHex(val, leftPadding) { | ||
| 82 | if (leftPadding === undefined) | ||
| 83 | leftPadding = 4; | ||
| 84 | |||
| 85 | const isNegative = val < 0; | ||
| 86 | return (isNegative ? "-" : "") + "0x" + Math.abs(val).toString(16).padLeft(leftPadding, "0").toUpperCase(); | ||
| 87 | } | ||
| 88 | |||
| 89 | function isBool(val) { | ||
| 90 | if (typeof value === "boolean") | ||
| 91 | return true; | ||
| 92 | |||
| 93 | const lval = val.toString().toLowerCase(); | ||
| 94 | return lval === "true" || lval === "false"; | ||
| 95 | } | ||
| 96 | |||
| 97 | function getBool(val) { | ||
| 98 | const num = +val; | ||
| 99 | return !isNaN(num) ? !!num : !!String(val).toLowerCase().replace(false, ""); | ||
| 100 | } | ||
| 101 | |||
| 102 | function getParameterByName(name, url) { | ||
| 103 | if (!url) | ||
| 104 | url = window.location.href; | ||
| 105 | |||
| 106 | name = name.replace(/[\[\]]/g, "\\$&"); | ||
| 107 | |||
| 108 | const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), results = regex.exec(url); | ||
| 109 | |||
| 110 | if (!results) | ||
| 111 | return null; | ||
| 112 | |||
| 113 | if (!results[2]) | ||
| 114 | return ""; | ||
| 115 | |||
| 116 | return decodeURIComponent(results[2].replace(/\+/g, " ")); | ||
| 117 | } | ||
| 118 | |||
| 119 | function clearCachedCredentials(securedUrl, successCallback) { | ||
| 120 | if (isIE) { | ||
| 121 | document.execCommand("ClearAuthenticationCache", "false"); | ||
| 122 | |||
| 123 | if (successCallback) | ||
| 124 | successCallback(true); | ||
| 125 | } else { | ||
| 126 | const xhr = new XMLHttpRequest(); | ||
| 127 | |||
| 128 | xhr.open("GET", securedUrl, true); | ||
| 129 | |||
| 130 | xhr.setRequestHeader("Content-type", "application/json"); | ||
| 131 | |||
| 132 | // Send in an invalid set of credentials, i.e., base64 encoded _logout:_logout: | ||
| 133 | xhr.setRequestHeader("Authorization", "Basic X2xvZ291dDpfbG9nb3V0"); | ||
| 134 | |||
| 135 | if (successCallback) | ||
| 136 | xhr.onreadystatechange = function() { | ||
| 137 | if (this.readyState === XMLHttpRequest.DONE) | ||
| 138 | successCallback(this.status === 401); | ||
| 139 | }; | ||
| 140 | |||
| 141 | xhr.send(); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | // Number functions | ||
| 146 | Number.prototype.truncate = function() { | ||
| 147 | if (typeof Math.trunc != "function") | ||
| 148 | return parseInt(this.toString()); | ||
| 149 | |||
| 150 | return Math.trunc(this); | ||
| 151 | } | ||
| 152 | |||
| 153 | Number.prototype.padLeft = function(totalWidth, paddingChar) { | ||
| 154 | return this.truncate().toString().padLeft(totalWidth, paddingChar || "0"); | ||
| 155 | } | ||
| 156 | |||
| 157 | Number.prototype.padRight = function(totalWidth, paddingChar) { | ||
| 158 | return this.truncate().toString().padRight(totalWidth, paddingChar || "0"); | ||
| 159 | } | ||
| 160 | |||
| 161 | // Array functions | ||
| 162 | |||
| 163 | // Combines a dictionary of key-value pairs into a string. Values will be escaped within startValueDelimiter and endValueDelimiter | ||
| 164 | // to contain nested key/value pair expressions like the following: "normalKVP=-1; nestedKVP={p1=true; p2=0.001}" when either the | ||
| 165 | // parameterDelimiter or the keyValueDelimiter are detected in the value of the key/value pair. | ||
| 166 | function joinKeyValuePairs (source, parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) { | ||
| 167 | if (!parameterDelimiter) | ||
| 168 | parameterDelimiter = ";"; | ||
| 169 | |||
| 170 | if (!keyValueDelimiter) | ||
| 171 | keyValueDelimiter = "="; | ||
| 172 | |||
| 173 | if (!startValueDelimiter) | ||
| 174 | startValueDelimiter = "{"; | ||
| 175 | |||
| 176 | if (!endValueDelimiter) | ||
| 177 | endValueDelimiter = "}"; | ||
| 178 | |||
| 179 | const values = []; | ||
| 180 | |||
| 181 | for (let key in source) { | ||
| 182 | if (source.hasOwnProperty(key)) { | ||
| 183 | let value = source[key]; | ||
| 184 | |||
| 185 | if (isBool(value)) | ||
| 186 | value = value.toString().toLowerCase(); | ||
| 187 | else | ||
| 188 | value = value ? value.toString() : ""; | ||
| 189 | |||
| 190 | if (value.indexOf(parameterDelimiter) >= 0 || value.indexOf(keyValueDelimiter) >= 0) | ||
| 191 | value = startValueDelimiter + value + endValueDelimiter; | ||
| 192 | |||
| 193 | values.push(key + keyValueDelimiter + value); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | return values.join(parameterDelimiter + " "); | ||
| 198 | } | ||
| 199 | |||
| 200 | Array.prototype.joinKeyValuePairs = function(parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) { | ||
| 201 | return joinKeyValuePairs(this, parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter); | ||
| 202 | }; | ||
| 203 | |||
| 204 | Array.prototype.forEachWithDelay = function(iterationCallback, timeout, completedCallback, thisArg) { | ||
| 205 | var index = 0, | ||
| 206 | count = this.length, | ||
| 207 | self = this, | ||
| 208 | next = function() { | ||
| 209 | if (self[index]) | ||
| 210 | iterationCallback.call(thisArg || self, self[index], index, self); | ||
| 211 | |||
| 212 | index++; | ||
| 213 | |||
| 214 | if (index < count) | ||
| 215 | setTimeout(next, timeout); | ||
| 216 | else | ||
| 217 | completedCallback.call(thisArg || self, self); | ||
| 218 | }; | ||
| 219 | |||
| 220 | next(); | ||
| 221 | }; | ||
| 222 | |||
| 223 | if (!Array.prototype.any) { | ||
| 224 | |||
| 225 | if (Array.prototype.some) { | ||
| 226 | Array.prototype.any = Array.prototype.some; | ||
| 227 | } | ||
| 228 | else { | ||
| 229 | Array.prototype.any = function(callback, thisArg) { | ||
| 230 | var args; | ||
| 231 | |||
| 232 | if (this == null) | ||
| 233 | throw new TypeError("this is null or not defined"); | ||
| 234 | |||
| 235 | const array = Object(this); | ||
| 236 | const length = array.length >>> 0; // Convert array length to positive integer | ||
| 237 | |||
| 238 | if (typeof callback !== "function") | ||
| 239 | throw new TypeError(); | ||
| 240 | |||
| 241 | if (arguments.length > 1) | ||
| 242 | args = thisArg; | ||
| 243 | else | ||
| 244 | args = undefined; | ||
| 245 | |||
| 246 | var index = 0; | ||
| 247 | |||
| 248 | while (index < length) { | ||
| 249 | if (index in array) { | ||
| 250 | const element = array[index]; | ||
| 251 | |||
| 252 | if (callback.call(args, element, index, array)) | ||
| 253 | return true; | ||
| 254 | } | ||
| 255 | |||
| 256 | index++; | ||
| 257 | } | ||
| 258 | |||
| 259 | return false; | ||
| 260 | } | ||
| 261 | } | ||
| 262 | } | ||
| 263 | |||
| 264 | // Represents a dictionary style class with case-insensitive keys | ||
| 265 | function Dictionary(source) { | ||
| 266 | const self = this; | ||
| 267 | |||
| 268 | if (!source) | ||
| 269 | source = []; | ||
| 270 | |||
| 271 | self._keys = []; | ||
| 272 | self._values = []; | ||
| 273 | |||
| 274 | self.count = function() { | ||
| 275 | var size = 0; | ||
| 276 | |||
| 277 | for (let property in self._values) { | ||
| 278 | if (self._values.hasOwnProperty(property)) | ||
| 279 | size++; | ||
| 280 | } | ||
| 281 | |||
| 282 | return size; | ||
| 283 | } | ||
| 284 | |||
| 285 | self.keys = function() { | ||
| 286 | const keys = []; | ||
| 287 | |||
| 288 | for (let property in self._keys) { | ||
| 289 | if (self._keys.hasOwnProperty(property)) | ||
| 290 | keys.push(self._keys[property]); | ||
| 291 | } | ||
| 292 | |||
| 293 | return keys; | ||
| 294 | } | ||
| 295 | |||
| 296 | self.values = function() { | ||
| 297 | const values = []; | ||
| 298 | |||
| 299 | for (let property in self._values) { | ||
| 300 | if (self._keys.hasOwnProperty(property)) | ||
| 301 | values.push(self._values[property]); | ||
| 302 | } | ||
| 303 | |||
| 304 | return values; | ||
| 305 | } | ||
| 306 | |||
| 307 | self.get = function(key) { | ||
| 308 | return self._values[String(key).toLowerCase()]; | ||
| 309 | } | ||
| 310 | |||
| 311 | self.set = function(key, value) { | ||
| 312 | const lkey = String(key).toLowerCase(); | ||
| 313 | |||
| 314 | if (!self._keys[lkey] || self._keys[lkey].toLowerCase() !== key) | ||
| 315 | self._keys[lkey] = key; | ||
| 316 | |||
| 317 | if (isBool(value)) | ||
| 318 | self._values[lkey] = getBool(value); | ||
| 319 | else | ||
| 320 | self._values[lkey] = value; | ||
| 321 | } | ||
| 322 | |||
| 323 | self.remove = function(key) { | ||
| 324 | const lkey = String(key).toLowerCase(); | ||
| 325 | delete self._keys[lkey]; | ||
| 326 | delete self._values[lkey]; | ||
| 327 | } | ||
| 328 | |||
| 329 | self.containsKey = function(key) { | ||
| 330 | const lkey = String(key).toLowerCase(); | ||
| 331 | |||
| 332 | for (let property in self._values) { | ||
| 333 | if (self._values.hasOwnProperty(property) && property === lkey) | ||
| 334 | return true; | ||
| 335 | } | ||
| 336 | |||
| 337 | return false; | ||
| 338 | } | ||
| 339 | |||
| 340 | self.containsValue = function(value) { | ||
| 341 | for (let property in self._values) { | ||
| 342 | if (self._values.hasOwnProperty(property) && self._values[property] === value) | ||
| 343 | return true; | ||
| 344 | } | ||
| 345 | |||
| 346 | return false; | ||
| 347 | } | ||
| 348 | |||
| 349 | self.clear = function() { | ||
| 350 | self._keys = []; | ||
| 351 | self._values = []; | ||
| 352 | } | ||
| 353 | |||
| 354 | self.joinKeyValuePairs = function(parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter) { | ||
| 355 | const keyValuePairs = []; | ||
| 356 | |||
| 357 | for (let property in self._values) { | ||
| 358 | if (self._values.hasOwnProperty(property)) | ||
| 359 | keyValuePairs[self._keys[property]] = self._values[property]; | ||
| 360 | } | ||
| 361 | |||
| 362 | return keyValuePairs.joinKeyValuePairs(parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter); | ||
| 363 | } | ||
| 364 | |||
| 365 | self.pushAll = function(source) { | ||
| 366 | for (let property in source) | ||
| 367 | if (source.hasOwnProperty(property)) | ||
| 368 | self.set(property, source[property]); | ||
| 369 | } | ||
| 370 | |||
| 371 | self.toObservableDictionary = function(useLowerKeys) { | ||
| 372 | // See ko.observableDictionary.js | ||
| 373 | const observableDictionary = new ko.observableDictionary(); | ||
| 374 | |||
| 375 | for (let property in self._values) { | ||
| 376 | if (self._values.hasOwnProperty(property)) | ||
| 377 | observableDictionary.push(useLowerKeys ? property : self._keys[property], self._values[property]); | ||
| 378 | } | ||
| 379 | |||
| 380 | return observableDictionary; | ||
| 381 | } | ||
| 382 | |||
| 383 | self.updateObservableDictionary = function(observableDictionary, useLowerKeys) { | ||
| 384 | for (let property in self._values) { | ||
| 385 | if (self._values.hasOwnProperty(property)) | ||
| 386 | observableDictionary.set(useLowerKeys ? property : self._keys[property], self._values[property]); | ||
| 387 | } | ||
| 388 | } | ||
| 389 | |||
| 390 | // Construction | ||
| 391 | if (source instanceof Dictionary) { | ||
| 392 | for (let property in source._values) | ||
| 393 | if (source._values.hasOwnProperty(property)) | ||
| 394 | self.set(source._keys[property], source._values[property]); | ||
| 395 | } | ||
| 396 | else { | ||
| 397 | for (let property in source) { | ||
| 398 | if (source.hasOwnProperty(property)) | ||
| 399 | self.set(property, source[property]); | ||
| 400 | } | ||
| 401 | } | ||
| 402 | } | ||
| 403 | |||
| 404 | Dictionary.fromObservableDictionary = function(observableDictionary) { | ||
| 405 | const dictionary = new Dictionary(); | ||
| 406 | dictionary.pushAll(observableDictionary.toJSON()); | ||
| 407 | return dictionary; | ||
| 408 | } | ||
| 409 | |||
| 410 | // String functions | ||
| 411 | function isEmpty(str) { | ||
| 412 | return !str || String(str).length === 0; | ||
| 413 | } | ||
| 414 | |||
| 415 | String.prototype.trimLeft = String.prototype.trimLeft || function () { | ||
| 416 | return this.replace(/^\s+/,""); | ||
| 417 | } | ||
| 418 | |||
| 419 | String.prototype.truncate = function(limit) { | ||
| 420 | const text = this.trim(); | ||
| 421 | |||
| 422 | if (text.length > limit) | ||
| 423 | return text.substr(0, limit - 3) + "..."; | ||
| 424 | |||
| 425 | return text; | ||
| 426 | } | ||
| 427 | |||
| 428 | String.prototype.replaceAll = function(findText, replaceWith, ignoreCase) { | ||
| 429 | return this.replace( | ||
| 430 | new RegExp(findText.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g, "\\$&"), (ignoreCase ? "gi" : "g")), | ||
| 431 | (typeof replaceWith == "string") ? replaceWith.replace(/\$/g, "$$$$") : replaceWith); | ||
| 432 | } | ||
| 433 | |||
| 434 | if (!String.prototype.endsWith) { | ||
| 435 | String.prototype.endsWith = function(searchString, position) { | ||
| 436 | const subjectString = this.toString() ; | ||
| 437 | |||
| 438 | if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) | ||
| 439 | position = subjectString.length; | ||
| 440 | |||
| 441 | position -= searchString.length; | ||
| 442 | |||
| 443 | const lastIndex = subjectString.lastIndexOf(searchString, position); | ||
| 444 | |||
| 445 | return lastIndex !== -1 && lastIndex === position; | ||
| 446 | }; | ||
| 447 | } | ||
| 448 | |||
| 449 | if (!String.format) { | ||
| 450 | String.format = function(format) { | ||
| 451 | var text = format; | ||
| 452 | |||
| 453 | for (let i = 1; i < arguments.length; i++) | ||
| 454 | text = text.replaceAll("{" + (i - 1) + "}", arguments[i]); | ||
| 455 | |||
| 456 | return text; | ||
| 457 | }; | ||
| 458 | } | ||
| 459 | |||
| 460 | String.prototype.padLeft = function(totalWidth, paddingChar) { | ||
| 461 | if (totalWidth > this.length) | ||
| 462 | return Array(totalWidth - this.length + 1).join(paddingChar || " ") + this; | ||
| 463 | |||
| 464 | return this; | ||
| 465 | } | ||
| 466 | |||
| 467 | String.prototype.padRight = function(totalWidth, paddingChar) { | ||
| 468 | if (totalWidth > this.length) | ||
| 469 | return this + Array(totalWidth - this.length + 1).join(paddingChar || " "); | ||
| 470 | |||
| 471 | return this; | ||
| 472 | } | ||
| 473 | |||
| 474 | String.prototype.countOccurrences = function(searchString) { | ||
| 475 | return (this.split(searchString).length - 1); | ||
| 476 | } | ||
| 477 | |||
| 478 | if (!String.prototype.startsWith) { | ||
| 479 | String.prototype.startsWith = function(searchString, position) { | ||
| 480 | position = position || 0; | ||
| 481 | return this.substr(position, searchString.length) === searchString; | ||
| 482 | } | ||
| 483 | } | ||
| 484 | |||
| 485 | String.prototype.regexEncode = function() { | ||
| 486 | return "\\u" + this.charCodeAt(0).toString(16).padLeft(4, "0"); | ||
| 487 | } | ||
| 488 | |||
| 489 | // Returns a Dictionary of the parsed key/value pair expressions from a string. Parameter pairs are delimited by keyValueDelimiter | ||
| 490 | // and multiple pairs separated by parameterDelimiter. Supports encapsulated nested expressions. | ||
| 491 | String.prototype.parseKeyValuePairs = function(parameterDelimiter, keyValueDelimiter, startValueDelimiter, endValueDelimiter, ignoreDuplicateKeys) { | ||
| 492 | if (!parameterDelimiter) | ||
| 493 | parameterDelimiter = ";"; | ||
| 494 | |||
| 495 | if (!keyValueDelimiter) | ||
| 496 | keyValueDelimiter = "="; | ||
| 497 | |||
| 498 | if (!startValueDelimiter) | ||
| 499 | startValueDelimiter = "{"; | ||
| 500 | |||
| 501 | if (!endValueDelimiter) | ||
| 502 | endValueDelimiter = "}"; | ||
| 503 | |||
| 504 | if (ignoreDuplicateKeys === undefined) | ||
| 505 | ignoreDuplicateKeys = true; | ||
| 506 | |||
| 507 | if (parameterDelimiter === keyValueDelimiter || | ||
| 508 | parameterDelimiter === startValueDelimiter || | ||
| 509 | parameterDelimiter === endValueDelimiter || | ||
| 510 | keyValueDelimiter === startValueDelimiter || | ||
| 511 | keyValueDelimiter === endValueDelimiter || | ||
| 512 | startValueDelimiter === endValueDelimiter) | ||
| 513 | throw "All delimiters must be unique"; | ||
| 514 | |||
| 515 | const escapedParameterDelimiter = parameterDelimiter.regexEncode(); | ||
| 516 | const escapedKeyValueDelimiter = keyValueDelimiter.regexEncode(); | ||
| 517 | const escapedStartValueDelimiter = startValueDelimiter.regexEncode(); | ||
| 518 | const escapedEndValueDelimiter = endValueDelimiter.regexEncode(); | ||
| 519 | const backslashDelimiter = "\\".regexEncode(); | ||
| 520 | |||
| 521 | var keyValuePairs = new Dictionary(); | ||
| 522 | var escapedValue = []; | ||
| 523 | var valueEscaped = false; | ||
| 524 | var delimiterDepth = 0; | ||
| 525 | |||
| 526 | // Escape any parameter or key/value delimiters within tagged value sequences | ||
| 527 | // For example, the following string: | ||
| 528 | // "normalKVP=-1; nestedKVP={p1=true; p2=false}") | ||
| 529 | // would be encoded as: | ||
| 530 | // "normalKVP=-1; nestedKVP=p1\\u003dtrue\\u003b p2\\u003dfalse") | ||
| 531 | for (let i = 0; i < this.length; i++) { | ||
| 532 | var character = this[i]; | ||
| 533 | |||
| 534 | if (character === startValueDelimiter) { | ||
| 535 | if (!valueEscaped) { | ||
| 536 | valueEscaped = true; | ||
| 537 | continue; // Don't add tag start delimiter to final value | ||
| 538 | } | ||
| 539 | |||
| 540 | // Handle nested delimiters | ||
| 541 | delimiterDepth++; | ||
| 542 | } | ||
| 543 | |||
| 544 | if (character === endValueDelimiter) { | ||
| 545 | if (valueEscaped) { | ||
| 546 | if (delimiterDepth > 0) { | ||
| 547 | // Handle nested delimiters | ||
| 548 | delimiterDepth--; | ||
| 549 | } | ||
| 550 | else { | ||
| 551 | valueEscaped = false; | ||
| 552 | continue; // Don't add tag stop delimiter to final value | ||
| 553 | } | ||
| 554 | } | ||
| 555 | else { | ||
| 556 | throw "Failed to parse key/value pairs: invalid delimiter mismatch. Encountered end value delimiter \"" + endValueDelimiter + "\" before start value delimiter \"" + startValueDelimiter + "\"."; | ||
| 557 | } | ||
| 558 | } | ||
| 559 | |||
| 560 | if (valueEscaped) { | ||
| 561 | // Escape any delimiter characters inside nested key/value pair | ||
| 562 | if (character === parameterDelimiter) | ||
| 563 | escapedValue.push(escapedParameterDelimiter); | ||
| 564 | else if (character === keyValueDelimiter) | ||
| 565 | escapedValue.push(escapedKeyValueDelimiter); | ||
| 566 | else if (character === startValueDelimiter) | ||
| 567 | escapedValue.push(escapedStartValueDelimiter); | ||
| 568 | else if (character === endValueDelimiter) | ||
| 569 | escapedValue.push(escapedEndValueDelimiter); | ||
| 570 | else if (character === "\\") | ||
| 571 | escapedValue.push(backslashDelimiter); | ||
| 572 | else | ||
| 573 | escapedValue.push(character); | ||
| 574 | } | ||
| 575 | else { | ||
| 576 | if (character === "\\") | ||
| 577 | escapedValue.push(backslashDelimiter); | ||
| 578 | else | ||
| 579 | escapedValue.push(character); | ||
| 580 | } | ||
| 581 | } | ||
| 582 | |||
| 583 | if (delimiterDepth !== 0 || valueEscaped) { | ||
| 584 | // If value is still escaped, tagged expression was not terminated | ||
| 585 | if (valueEscaped) | ||
| 586 | delimiterDepth = 1; | ||
| 587 | |||
| 588 | throw "Failed to parse key/value pairs: invalid delimiter mismatch. Encountered more " + | ||
| 589 | (delimiterDepth > 0 ? "start value delimiters \"" + startValueDelimiter + "\"" : "end value delimiters \"" + endValueDelimiter + "\"") + " than " + | ||
| 590 | (delimiterDepth < 0 ? "start value delimiters \"" + startValueDelimiter + "\"" : "end value delimiters \"" + endValueDelimiter + "\"") + "."; | ||
| 591 | } | ||
| 592 | |||
| 593 | // Parse key/value pairs from escaped value | ||
| 594 | var pairs = escapedValue.join("").split(parameterDelimiter); | ||
| 595 | |||
| 596 | for (let i = 0; i < pairs.length; i++) { | ||
| 597 | // Separate key from value | ||
| 598 | var elements = pairs[i].split(keyValueDelimiter); | ||
| 599 | |||
| 600 | if (elements.length === 2) { | ||
| 601 | // Get key | ||
| 602 | var key = elements[0].trim(); | ||
| 603 | |||
| 604 | // Get unescaped value | ||
| 605 | var unescapedValue = elements[1].trim(). | ||
| 606 | replaceAll(escapedParameterDelimiter, parameterDelimiter). | ||
| 607 | replaceAll(escapedKeyValueDelimiter, keyValueDelimiter). | ||
| 608 | replaceAll(escapedStartValueDelimiter, startValueDelimiter). | ||
| 609 | replaceAll(escapedEndValueDelimiter, endValueDelimiter). | ||
| 610 | replaceAll(backslashDelimiter, "\\"); | ||
| 611 | |||
| 612 | // Add key/value pair to dictionary | ||
| 613 | if (ignoreDuplicateKeys) { | ||
| 614 | // Add or replace key elements with unescaped value | ||
| 615 | keyValuePairs.set(key, unescapedValue); | ||
| 616 | } | ||
| 617 | else { | ||
| 618 | // Add key elements with unescaped value throwing an exception for encountered duplicate keys | ||
| 619 | if (keyValuePairs.containsKey(key)) | ||
| 620 | throw "Failed to parse key/value pairs: duplicate key encountered. Key \"" + key + "\" is not unique within the string: \"" + this + "\""; | ||
| 621 | |||
| 622 | keyValuePairs.set(key, unescapedValue); | ||
| 623 | } | ||
| 624 | } | ||
| 625 | } | ||
| 626 | |||
| 627 | return keyValuePairs; | ||
| 628 | } | ||
| 629 | |||
| 630 | // Renders URLs and e-mail addresses as clickable links | ||
| 631 | function renderHotLinks(sourceText, target) { | ||
| 632 | if (target === undefined) | ||
| 633 | target = "_blank"; | ||
| 634 | |||
| 635 | sourceText = sourceText.toString(); | ||
| 636 | |||
| 637 | var replacedText; | ||
| 638 | |||
| 639 | // URLs starting with http://, https://, or ftp:// | ||
| 640 | const replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; | ||
| 641 | replacedText = sourceText.replace(replacePattern1, "<a href=\"$1\" target=\"" + target + "\">$1</a>"); | ||
| 642 | |||
| 643 | // URLs starting with "www." without // before it | ||
| 644 | const replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; | ||
| 645 | replacedText = replacedText.replace(replacePattern2, "$1<a href=\"http://$2\" target=\"" + target + "\">$2</a>"); | ||
| 646 | |||
| 647 | // Change e-mail addresses to mailto: links | ||
| 648 | const replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; | ||
| 649 | replacedText = replacedText.replace(replacePattern3, "<a href=\"mailto:$1\">$1</a>"); | ||
| 650 | |||
| 651 | return replacedText; | ||
| 652 | } | ||
| 653 | |||
| 654 | // Date Functions | ||
| 655 | Date.prototype.addDays = function(days) { | ||
| 656 | return new Date(this.setDate(this.getDate() + days)); | ||
| 657 | } | ||
| 658 | |||
| 659 | Date.prototype.toUTC = function() { | ||
| 660 | this.setMinutes(this.getMinutes() - this.getTimezoneOffset()); | ||
| 661 | return this; | ||
| 662 | } | ||
| 663 | |||
| 664 | Date.prototype.daysBetween = function(startDate) { | ||
| 665 | const millisecondsPerDay = 24 * 60 * 60 * 1000; | ||
| 666 | return (this.toUTC() - startDate.toUTC()) / millisecondsPerDay; | ||
| 667 | } | ||
| 668 | |||
| 669 | String.prototype.toDate = function() { | ||
| 670 | return new Date(Date.parse(this)); | ||
| 671 | }; | ||
| 672 | |||
| 673 | String.prototype.formatDate = function(format, utc) { | ||
| 674 | return formatDate(this.toDate(), format, utc); | ||
| 675 | }; | ||
| 676 | |||
| 677 | Date.prototype.formatDate = function(format, utc) { | ||
| 678 | return formatDate(this, format, utc); | ||
| 679 | }; | ||
| 680 | |||
| 681 | function formatDate(date, format, utc) { | ||
| 682 | if (typeof date === "string") | ||
| 683 | return formatDate(date.toDate(), format, utc); | ||
| 684 | |||
| 685 | if (format === undefined && DateTimeFormat != undefined) | ||
| 686 | format = DateTimeFormat; | ||
| 687 | |||
| 688 | if (utc === undefined) | ||
| 689 | utc = true; | ||
| 690 | |||
| 691 | var MMMM = ["\x00", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; | ||
| 692 | var MMM = ["\x01", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; | ||
| 693 | var dddd = ["\x02", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; | ||
| 694 | var ddd = ["\x03", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; | ||
| 695 | |||
| 696 | function ii(i, len) { | ||
| 697 | var ss = i + ""; | ||
| 698 | len = len || 2; | ||
| 699 | while (ss.length < len) ss = "0" + ss; | ||
| 700 | return ss; | ||
| 701 | } | ||
| 702 | |||
| 703 | var y = utc ? date.getUTCFullYear() : date.getFullYear(); | ||
| 704 | format = format.replace(/(^|[^\\])yyyy+/g, "$1" + y); | ||
| 705 | format = format.replace(/(^|[^\\])yy/g, "$1" + y.toString().substr(2, 2)); | ||
| 706 | format = format.replace(/(^|[^\\])y/g, "$1" + y); | ||
| 707 | |||
| 708 | var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1; | ||
| 709 | format = format.replace(/(^|[^\\])MMMM+/g, "$1" + MMMM[0]); | ||
| 710 | format = format.replace(/(^|[^\\])MMM/g, "$1" + MMM[0]); | ||
| 711 | format = format.replace(/(^|[^\\])MM/g, "$1" + ii(M)); | ||
| 712 | format = format.replace(/(^|[^\\])M/g, "$1" + M); | ||
| 713 | |||
| 714 | var d = utc ? date.getUTCDate() : date.getDate(); | ||
| 715 | format = format.replace(/(^|[^\\])dddd+/g, "$1" + dddd[0]); | ||
| 716 | format = format.replace(/(^|[^\\])ddd/g, "$1" + ddd[0]); | ||
| 717 | format = format.replace(/(^|[^\\])dd/g, "$1" + ii(d)); | ||
| 718 | format = format.replace(/(^|[^\\])d/g, "$1" + d); | ||
| 719 | |||
| 720 | var H = utc ? date.getUTCHours() : date.getHours(); | ||
| 721 | format = format.replace(/(^|[^\\])HH+/g, "$1" + ii(H)); | ||
| 722 | format = format.replace(/(^|[^\\])H/g, "$1" + H); | ||
| 723 | |||
| 724 | var h = H > 12 ? H - 12 : H === 0 ? 12 : H; | ||
| 725 | format = format.replace(/(^|[^\\])hh+/g, "$1" + ii(h)); | ||
| 726 | format = format.replace(/(^|[^\\])h/g, "$1" + h); | ||
| 727 | |||
| 728 | var m = utc ? date.getUTCMinutes() : date.getMinutes(); | ||
| 729 | format = format.replace(/(^|[^\\])mm+/g, "$1" + ii(m)); | ||
| 730 | format = format.replace(/(^|[^\\])m/g, "$1" + m); | ||
| 731 | |||
| 732 | var s = utc ? date.getUTCSeconds() : date.getSeconds(); | ||
| 733 | format = format.replace(/(^|[^\\])ss+/g, "$1" + ii(s)); | ||
| 734 | format = format.replace(/(^|[^\\])s/g, "$1" + s); | ||
| 735 | |||
| 736 | var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds(); | ||
| 737 | format = format.replace(/(^|[^\\])fff+/g, "$1" + ii(f, 3)); | ||
| 738 | f = Math.round(f / 10); | ||
| 739 | format = format.replace(/(^|[^\\])ff/g, "$1" + ii(f)); | ||
| 740 | f = Math.round(f / 10); | ||
| 741 | format = format.replace(/(^|[^\\])f/g, "$1" + f); | ||
| 742 | |||
| 743 | var T = H < 12 ? "AM" : "PM"; | ||
| 744 | format = format.replace(/(^|[^\\])TT+/g, "$1" + T); | ||
| 745 | format = format.replace(/(^|[^\\])T/g, "$1" + T.charAt(0)); | ||
| 746 | |||
| 747 | var t = T.toLowerCase(); | ||
| 748 | format = format.replace(/(^|[^\\])tt+/g, "$1" + t); | ||
| 749 | format = format.replace(/(^|[^\\])t/g, "$1" + t.charAt(0)); | ||
| 750 | |||
| 751 | var tz = -date.getTimezoneOffset(); | ||
| 752 | var K = utc || !tz ? "Z" : tz > 0 ? "+" : "-"; | ||
| 753 | if (!utc) { | ||
| 754 | tz = Math.abs(tz); | ||
| 755 | var tzHrs = Math.floor(tz / 60); | ||
| 756 | var tzMin = tz % 60; | ||
| 757 | K += ii(tzHrs) + ":" + ii(tzMin); | ||
| 758 | } | ||
| 759 | |||
| 760 | format = format.replace(/(^|[^\\])K/g, "$1" + K); | ||
| 761 | var day = (utc ? date.getUTCDay() : date.getDay()) + 1; | ||
| 762 | |||
| 763 | format = format.replace(new RegExp(dddd[0], "g"), dddd[day]); | ||
| 764 | format = format.replace(new RegExp(ddd[0], "g"), ddd[day]); | ||
| 765 | |||
| 766 | format = format.replace(new RegExp(MMMM[0], "g"), MMMM[M]); | ||
| 767 | format = format.replace(new RegExp(MMM[0], "g"), MMM[M]); | ||
| 768 | |||
| 769 | format = format.replace(/\\(.)/g, "$1"); | ||
| 770 | |||
| 771 | return format; | ||
| 772 | }; | ||
| 773 | |||
| 774 | // jQuery extensions | ||
| 775 | $.fn.enable = function() { | ||
| 776 | return this.each(function() { | ||
| 777 | this.disabled = false; | ||
| 778 | }); | ||
| 779 | } | ||
| 780 | |||
| 781 | $.fn.disable = function() { | ||
| 782 | return this.each(function() { | ||
| 783 | this.disabled = true; | ||
| 784 | }); | ||
| 785 | } | ||
| 786 | |||
| 787 | $.fn.visible = function() { | ||
| 788 | return this.each(function() { | ||
| 789 | this.css("visibility", "visible"); | ||
| 790 | }); | ||
| 791 | } | ||
| 792 | |||
| 793 | $.fn.invisible = function() { | ||
| 794 | return this.each(function() { | ||
| 795 | this.css("visibility", "hidden"); | ||
| 796 | }); | ||
| 797 | } | ||
| 798 | |||
| 799 | $.fn.readCookie = function(name) { | ||
| 800 | this.val(Cookies.get(name)); | ||
| 801 | } | ||
| 802 | |||
| 803 | $.fn.writeCookie = function(name, expiration) { | ||
| 804 | if (expiration) // Date or number of days | ||
| 805 | Cookies.set(name, this.val(), { expires: expiration }); | ||
| 806 | else | ||
| 807 | Cookies.set(name, this.val()); | ||
| 808 | } | ||
| 809 | |||
| 810 | $.fn.removeCookie = function(name) { | ||
| 811 | Cookies.remove(name); | ||
| 812 | } | ||
| 813 | |||
| 814 | $.fn.paddingHeight = function() { | ||
| 815 | return this.outerHeight(true) - this.height(); | ||
| 816 | } | ||
| 817 | |||
| 818 | $.fn.paddingWidth = function() { | ||
| 819 | return this.outerWidth(true) - this.width(); | ||
| 820 | } | ||
| 821 | |||
| 822 | // Call function once at start-up to auto-size drop-down to selected option contents | ||
| 823 | $.fn.autoSizeSelect = function() { | ||
| 824 | return this.each(function() { | ||
| 825 | $(this).change(function() { | ||
| 826 | const arrowWidth = 30; | ||
| 827 | const $this = $(this); | ||
| 828 | |||
| 829 | // Create test element | ||
| 830 | const text = $this.find("option:selected").text(); | ||
| 831 | const $test = $("<span>").html(text); | ||
| 832 | |||
| 833 | // Add to body, get width, and get out | ||
| 834 | $test.appendTo("body"); | ||
| 835 | const width = $test.width(); | ||
| 836 | $test.remove(); | ||
| 837 | |||
| 838 | // Set select width | ||
| 839 | $this.width(width + arrowWidth); | ||
| 840 | }).change(); | ||
| 841 | }); | ||
| 842 | } | ||
| 843 | |||
| 844 | // Cell truncations should only be used with .table-cell-hard-wrap style | ||
| 845 | $.fn.truncateToWidth = function(text, rows) { | ||
| 846 | if (isEmpty(text)) | ||
| 847 | return ""; | ||
| 848 | |||
| 849 | if (!rows) | ||
| 850 | rows = 1; | ||
| 851 | |||
| 852 | textMetrics.font = this.css("font"); | ||
| 853 | |||
| 854 | var targetWidth = this.innerWidth(); | ||
| 855 | |||
| 856 | if (rows > 1) | ||
| 857 | targetWidth *= ((isIE ? 0.45 : 0.75) * rows); | ||
| 858 | |||
| 859 | if (targetWidth > 30) { | ||
| 860 | let textWidth = textMetrics.measureText(text).width; | ||
| 861 | let limit = Math.min(text.length, Math.ceil(targetWidth / (textWidth / text.length))); | ||
| 862 | |||
| 863 | while (textWidth > targetWidth && limit > 3) { | ||
| 864 | limit--; | ||
| 865 | text = text.truncate(limit); | ||
| 866 | textWidth = textMetrics.measureText(text).width; | ||
| 867 | } | ||
| 868 | } | ||
| 869 | else { | ||
| 870 | text = text.charAt(0) + "..."; | ||
| 871 | } | ||
| 872 | |||
| 873 | return text; | ||
| 874 | } | ||
| 875 | |||
| 876 | // The following target arrays of promises | ||
| 877 | $.fn.whenAny = function() { | ||
| 878 | var finish = $.Deferred(); | ||
| 879 | |||
| 880 | if (this.length === 0) | ||
| 881 | finish.resolve(); | ||
| 882 | else | ||
| 883 | $.each(this, function(index, deferred) { | ||
| 884 | deferred.done(finish.resolve); | ||
| 885 | }); | ||
| 886 | |||
| 887 | return finish.promise(); | ||
| 888 | } | ||
| 889 | |||
| 890 | $.fn.whenAll = function() { | ||
| 891 | if (this.length > 0) | ||
| 892 | return $.when.apply($, this); | ||
| 893 | |||
| 894 | return $.Deferred().resolve().promise(); | ||
| 895 | } | ||
| @@ -0,0 +1,167 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // gsf.web.knockout.js - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 07/22/2016 - Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | // ReSharper disable UndeclaredGlobalVariableUsing | ||
| 24 | |||
| 25 | // Grid Solutions Framework Core Web Knockout Extension Script Functions | ||
| 26 | "use strict"; | ||
| 27 | |||
| 28 | // Field functions | ||
| 29 | function fieldHasValue(field, required) { | ||
| 30 | if (field == null) | ||
| 31 | return true; | ||
| 32 | |||
| 33 | if (required === undefined) | ||
| 34 | required = false; | ||
| 35 | |||
| 36 | var isValid = true; | ||
| 37 | |||
| 38 | if (required) { | ||
| 39 | if (typeof field == "function") | ||
| 40 | isValid = !isEmpty(field()); | ||
| 41 | else | ||
| 42 | isValid = !isEmpty(field); | ||
| 43 | } | ||
| 44 | |||
| 45 | return isValid; | ||
| 46 | } | ||
| 47 | |||
| 48 | function fieldIsValid(field, required) { | ||
| 49 | var isValid = fieldHasValue(field, required); | ||
| 50 | |||
| 51 | if (isValid && typeof field.isValid == "function") | ||
| 52 | isValid = field.isValid(); | ||
| 53 | |||
| 54 | return isValid; | ||
| 55 | } | ||
| 56 | |||
| 57 | // Custom knockout binding handers | ||
| 58 | ko.bindingHandlers.numeric = { | ||
| 59 | init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { | ||
| 60 | $(element).on("keydown", function(event) { | ||
| 61 | // Allow backspace, delete, tab, escape, and enter | ||
| 62 | if (event.keyCode === 46 || event.keyCode === 8 || event.keyCode === 9 || event.keyCode === 27 || event.keyCode === 13 || | ||
| 63 | // Ctrl+A | ||
| 64 | (event.keyCode === 65 && event.ctrlKey) || | ||
| 65 | // E + - . , | ||
| 66 | (event.keyCode === 69 || event.keyCode === 107 || event.keyCode === 109 || event.keyCode === 110 || event.keyCode === 190 || event.keyCode === 188) || | ||
| 67 | // home, end, left, right, up, down | ||
| 68 | (event.keyCode >= 35 && event.keyCode <= 40)) { | ||
| 69 | return; | ||
| 70 | } | ||
| 71 | else { | ||
| 72 | // If value is not a number, stop key-press | ||
| 73 | if (event.shiftKey || (event.keyCode < 48 || event.keyCode > 57) && (event.keyCode < 96 || event.keyCode > 105)) { | ||
| 74 | event.preventDefault(); | ||
| 75 | } | ||
| 76 | } | ||
| 77 | }); | ||
| 78 | } | ||
| 79 | }; | ||
| 80 | |||
| 81 | ko.bindingHandlers.integer = { | ||
| 82 | init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { | ||
| 83 | $(element).on("keydown", function(event) { | ||
| 84 | // Allow backspace, delete, tab, escape, and enter | ||
| 85 | if (event.keyCode === 46 || event.keyCode === 8 || event.keyCode === 9 || event.keyCode === 27 || event.keyCode === 13 || | ||
| 86 | // Ctrl+A | ||
| 87 | (event.keyCode === 65 && event.ctrlKey) || | ||
| 88 | // home, end, left, right, up, down | ||
| 89 | (event.keyCode >= 35 && event.keyCode <= 40)) { | ||
| 90 | return; | ||
| 91 | } | ||
| 92 | else { | ||
| 93 | // If value is not a number, stop key-press | ||
| 94 | if (event.shiftKey || (event.keyCode < 48 || event.keyCode > 57) && (event.keyCode < 96 || event.keyCode > 105)) { | ||
| 95 | event.preventDefault(); | ||
| 96 | } | ||
| 97 | } | ||
| 98 | }); | ||
| 99 | } | ||
| 100 | }; | ||
| 101 | |||
| 102 | // Restrict binding to upper case letters - binding target specification required | ||
| 103 | ko.bindingHandlers.upperCase = { | ||
| 104 | init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { | ||
| 105 | $(element).on("input", function(event) { | ||
| 106 | const start = this.selectionStart; | ||
| 107 | const end = this.selectionEnd; | ||
| 108 | valueAccessor()(this.value = this.value.toUpperCase()); | ||
| 109 | this.setSelectionRange(start, end); | ||
| 110 | }); | ||
| 111 | } | ||
| 112 | }; | ||
| 113 | |||
| 114 | // Restrict binding to lower case letters - binding target specification required | ||
| 115 | ko.bindingHandlers.lowerCase = { | ||
| 116 | init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { | ||
| 117 | $(element).on("input", function(event) { | ||
| 118 | const start = this.selectionStart; | ||
| 119 | const end = this.selectionEnd; | ||
| 120 | valueAccessor()(this.value = this.value.toLowerCase()); | ||
| 121 | this.setSelectionRange(start, end); | ||
| 122 | }); | ||
| 123 | } | ||
| 124 | }; | ||
| 125 | |||
| 126 | // Restrict binding to upper case letters, numbers, '!', '-', '@', '#', '_' , '.' or '$' - binding target specification required | ||
| 127 | ko.bindingHandlers.acronym = { | ||
| 128 | init: function(element, valueAccessor, allBindings, viewModel, bindingContext) { | ||
| 129 | $(element).on("keydown", function(event) { | ||
| 130 | // Allow backspace, delete, tab, escape, and enter | ||
| 131 | if (event.keyCode === 46 || event.keyCode === 8 || event.keyCode === 9 || event.keyCode === 27 || event.keyCode === 13 || | ||
| 132 | // Ctrl+A | ||
| 133 | (event.keyCode === 65 && event.ctrlKey) || | ||
| 134 | // home, end, left, right, up, down | ||
| 135 | (event.keyCode >= 35 && event.keyCode <= 40)) { | ||
| 136 | return; | ||
| 137 | } | ||
| 138 | else { | ||
| 139 | // If value is not a number or letter, stop key-press | ||
| 140 | if ((event.keyCode < 48 || event.keyCode > 57) && (event.keyCode < 96 || event.keyCode > 105) && (event.keyCode < 65 || event.keyCode > 90) && (event.keyCode < 189 || event.keyCode > 190)) { | ||
| 141 | event.preventDefault(); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | }).on("input", function(event) { | ||
| 145 | const start = this.selectionStart; | ||
| 146 | const end = this.selectionEnd; | ||
| 147 | valueAccessor()(this.value = this.value.toUpperCase()); | ||
| 148 | this.setSelectionRange(start, end); | ||
| 149 | }); | ||
| 150 | } | ||
| 151 | }; | ||
| 152 | |||
| 153 | // Handle special validation error message location when using bootstrap add-on fields | ||
| 154 | ko.bindingHandlers.validationCore = { | ||
| 155 | init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { | ||
| 156 | const parent = $(element).parent().closest(".input-group"); | ||
| 157 | const message = document.createElement("em"); | ||
| 158 | message.className = "validationMessage small"; | ||
| 159 | |||
| 160 | if (parent.length > 0) | ||
| 161 | $(parent).after(message); | ||
| 162 | else | ||
| 163 | $(element).after(message); | ||
| 164 | |||
| 165 | ko.applyBindingsToNode(message, { validationMessage: valueAccessor() }); | ||
| 166 | } | ||
| 167 | }; | ||
| @@ -0,0 +1,110 @@ | |||
| 1 | //****************************************************************************************************** | ||
| 2 | // gsf.web.primeui.js - Gbtc | ||
| 3 | // | ||
| 4 | // Copyright © 2016, Grid Protection Alliance. All Rights Reserved. | ||
| 5 | // | ||
| 6 | // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See | ||
| 7 | // the NOTICE file distributed with this work for additional information regarding copyright ownership. | ||
| 8 | // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this | ||
| 9 | // file except in compliance with the License. You may obtain a copy of the License at: | ||
| 10 | // | ||
| 11 | // http://opensource.org/licenses/MIT | ||
| 12 | // | ||
| 13 | // Unless agreed to in writing, the subject software distributed under the License is distributed on an | ||
| 14 | // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the | ||
| 15 | // License for the specific language governing permissions and limitations. | ||
| 16 | // | ||
| 17 | // Code Modification History: | ||
| 18 | // ---------------------------------------------------------------------------------------------------- | ||
| 19 | // 04/08/2016 - J. Ritchie Carroll | ||
| 20 | // Generated original version of source code. | ||
| 21 | // | ||
| 22 | //****************************************************************************************************** | ||
| 23 | // ReSharper disable UndeclaredGlobalVariableUsing | ||
| 24 | |||
| 25 | // Grid Solutions Framework Core Web PrimeUI Extension Script Functions | ||
| 26 | "use strict"; | ||
| 27 | |||
| 28 | // Get access to PrimeUI widget, e.g.: var autoCompletePanel = getPrimeUIWidget($("#myInputText"), "puiautocomplete").panel | ||
| 29 | function getPrimeUIWidget(jqueryElement, widgetType) { | ||
| 30 | return jqueryElement.data("primeui-" + widgetType); | ||
| 31 | } | ||
| 32 | |||
| 33 | // Adds auto-complete lookup functionality to an input field element associated with a paged-view model | ||
| 34 | function initializeAutoCompleteLookupField(fieldName, loadRecordsHubFunction, isObservable, addInputGroup, addShowAllDropDown, limit) { | ||
| 35 | if (isObservable === undefined) | ||
| 36 | isObservable = true; | ||
| 37 | |||
| 38 | if (addInputGroup === undefined) | ||
| 39 | addInputGroup = true; | ||
| 40 | |||
| 41 | if (addShowAllDropDown === undefined) | ||
| 42 | addShowAllDropDown = true; | ||
| 43 | |||
| 44 | if (limit === undefined) | ||
| 45 | limit = 50; | ||
| 46 | |||
| 47 | const inputFieldID = "input" + fieldName; | ||
| 48 | const inputField = $("#" + inputFieldID); | ||
| 49 | |||
| 50 | // Turn off browser remembered field values when doing auto-complete lookups | ||
| 51 | inputField.attr("autocomplete", "off"); | ||
| 52 | |||
| 53 | if (addInputGroup) | ||
| 54 | inputField.wrap("<div class=\"input-group\"></div>"); | ||
| 55 | |||
| 56 | if (addShowAllDropDown && viewModel.canEdit()) { | ||
| 57 | // Insert drop-down button after input field | ||
| 58 | inputField.after("<span id=\"" + inputFieldID + "ShowAll\" class=\"input-group-addon\" data-bind=\"style: {'cursor': ($root.recordMode()===RecordMode.View ? 'not-allowed' : 'pointer')}, visible: $root.recordMode()!==RecordMode.View\"><i class=\"glyphicon glyphicon-triangle-bottom\"></i></span>"); | ||
| 59 | |||
| 60 | const inputFieldDropDown = $("#" + inputFieldID + "ShowAll"); | ||
| 61 | |||
| 62 | // Set on-click handler for drop-down button | ||
| 63 | inputFieldDropDown.click(function() { | ||
| 64 | // Search all records when user clicks drop-down button | ||
| 65 | if (viewModel.dataHubIsConnected() && viewModel.recordMode() !== RecordMode.View) | ||
| 66 | inputField.puiautocomplete("search", ""); | ||
| 67 | }); | ||
| 68 | |||
| 69 | // Apply data bindings to newly added drop-down button | ||
| 70 | ko.applyBindings(viewModel, inputFieldDropDown[0]); | ||
| 71 | } | ||
| 72 | |||
| 73 | inputField.puiautocomplete({ | ||
| 74 | effect: "fade", | ||
| 75 | effectSpeed: "fast", | ||
| 76 | forceSelection: true, | ||
| 77 | completeSource: function(request, response) { | ||
| 78 | const self = this; | ||
| 79 | |||
| 80 | if (viewModel.dataHubIsConnected()) { | ||
| 81 | loadRecordsHubFunction(request.query, limit).done(function(records) { | ||
| 82 | if (limit > 0 && records.length >= limit) { | ||
| 83 | records.push({ id: null, label: "Search results truncated..." }); | ||
| 84 | } | ||
| 85 | |||
| 86 | response.call(self, records); | ||
| 87 | |||
| 88 | // Set z-index of panel to be above pop-up dialog | ||
| 89 | self.panel.css("z-index", $("#addNewEditDialog").css("z-index") + 1); | ||
| 90 | |||
| 91 | // Auto-size panel to full width of input field area (including input group buttons) | ||
| 92 | setTimeout(function() { self.panel.width(inputField.parent().parent().width()); }, 25); | ||
| 93 | }); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | }); | ||
| 97 | |||
| 98 | if (isObservable) { | ||
| 99 | inputField.puiautocomplete({ | ||
| 100 | select: function(event, item) { | ||
| 101 | // Make sure knockout sees any selection - it can't always pickup non-user initiated changes | ||
| 102 | if (item) | ||
| 103 | viewModel.currentRecord()[fieldName](item.text()); | ||
| 104 | } | ||
| 105 | }); | ||
| 106 | } | ||
| 107 | |||
| 108 | // Override jQuery UI display style added by puiautocomplete (messes with bootstrap form-element formatting) | ||
| 109 | inputField.parent().css("display", "inline"); | ||
| 110 | } | ||
| @@ -0,0 +1,145 @@ | |||
| 1 | /*! | ||
| 2 | * JavaScript Cookie v2.1.0 | ||
| 3 | * https://github.com/js-cookie/js-cookie | ||
| 4 | * | ||
| 5 | * Copyright 2006, 2015 Klaus Hartl & Fagner Brack | ||
| 6 | * Released under the MIT license | ||
| 7 | */ | ||
| 8 | (function (factory) { | ||
| 9 | if (typeof define === 'function' && define.amd) { | ||
| 10 | define(factory); | ||
| 11 | } else if (typeof exports === 'object') { | ||
| 12 | module.exports = factory(); | ||
| 13 | } else { | ||
| 14 | var _OldCookies = window.Cookies; | ||
| 15 | var api = window.Cookies = factory(); | ||
| 16 | api.noConflict = function () { | ||
| 17 | window.Cookies = _OldCookies; | ||
| 18 | return api; | ||
| 19 | }; | ||
| 20 | } | ||
| 21 | }(function () { | ||
| 22 | function extend() { | ||
| 23 | var i = 0; | ||
| 24 | var result = {}; | ||
| 25 | for (; i < arguments.length; i++) { | ||
| 26 | var attributes = arguments[i]; | ||
| 27 | for (var key in attributes) { | ||
| 28 | result[key] = attributes[key]; | ||
| 29 | } | ||
| 30 | } | ||
| 31 | return result; | ||
| 32 | } | ||
| 33 | |||
| 34 | function init(converter) { | ||
| 35 | function api(key, value, attributes) { | ||
| 36 | var result; | ||
| 37 | |||
| 38 | // Write | ||
| 39 | |||
| 40 | if (arguments.length > 1) { | ||
| 41 | attributes = extend({ | ||
| 42 | path: '/' | ||
| 43 | }, api.defaults, attributes); | ||
| 44 | |||
| 45 | if (typeof attributes.expires === 'number') { | ||
| 46 | var expires = new Date(); | ||
| 47 | expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5); | ||
| 48 | attributes.expires = expires; | ||
| 49 | } | ||
| 50 | |||
| 51 | try { | ||
| 52 | result = JSON.stringify(value); | ||
| 53 | if (/^[\{\[]/.test(result)) { | ||
| 54 | value = result; | ||
| 55 | } | ||
| 56 | } catch (e) { } | ||
| 57 | |||
| 58 | if (!converter.write) { | ||
| 59 | value = encodeURIComponent(String(value)) | ||
| 60 | .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); | ||
| 61 | } else { | ||
| 62 | value = converter.write(value, key); | ||
| 63 | } | ||
| 64 | |||
| 65 | key = encodeURIComponent(String(key)); | ||
| 66 | key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent); | ||
| 67 | key = key.replace(/[\(\)]/g, escape); | ||
| 68 | |||
| 69 | return (document.cookie = [ | ||
| 70 | key, '=', value, | ||
| 71 | attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE | ||
| 72 | attributes.path && '; path=' + attributes.path, | ||
| 73 | attributes.domain && '; domain=' + attributes.domain, | ||
| 74 | attributes.secure ? '; secure' : '' | ||
| 75 | ].join('')); | ||
| 76 | } | ||
| 77 | |||
| 78 | // Read | ||
| 79 | |||
| 80 | if (!key) { | ||
| 81 | result = {}; | ||
| 82 | } | ||
| 83 | |||
| 84 | // To prevent the for loop in the first place assign an empty array | ||
| 85 | // in case there are no cookies at all. Also prevents odd result when | ||
| 86 | // calling "get()" | ||
| 87 | var cookies = document.cookie ? document.cookie.split('; ') : []; | ||
| 88 | var rdecode = /(%[0-9A-Z]{2})+/g; | ||
| 89 | var i = 0; | ||
| 90 | |||
| 91 | for (; i < cookies.length; i++) { | ||
| 92 | var parts = cookies[i].split('='); | ||
| 93 | var name = parts[0].replace(rdecode, decodeURIComponent); | ||
| 94 | var cookie = parts.slice(1).join('='); | ||
| 95 | |||
| 96 | if (cookie.charAt(0) === '"') { | ||
| 97 | cookie = cookie.slice(1, -1); | ||
| 98 | } | ||
| 99 | |||
| 100 | try { | ||
| 101 | cookie = converter.read ? | ||
| 102 | converter.read(cookie, name) : converter(cookie, name) || | ||
| 103 | cookie.replace(rdecode, decodeURIComponent); | ||
| 104 | |||
| 105 | if (this.json) { | ||
| 106 | try { | ||
| 107 | cookie = JSON.parse(cookie); | ||
| 108 | } catch (e) { } | ||
| 109 | } | ||
| 110 | |||
| 111 | if (key === name) { | ||
| 112 | result = cookie; | ||
| 113 | break; | ||
| 114 | } | ||
| 115 | |||
| 116 | if (!key) { | ||
| 117 | result[name] = cookie; | ||
| 118 | } | ||
| 119 | } catch (e) { } | ||
| 120 | } | ||
| 121 | |||
| 122 | return result; | ||
| 123 | } | ||
| 124 | |||
| 125 | api.get = api.set = api; | ||
| 126 | api.getJSON = function () { | ||
| 127 | return api.apply({ | ||
| 128 | json: true | ||
| 129 | }, [].slice.call(arguments)); | ||
| 130 | }; | ||
| 131 | api.defaults = {}; | ||
| 132 | |||
| 133 | api.remove = function (key, attributes) { | ||
| 134 | api(key, '', extend(attributes, { | ||
| 135 | expires: -1 | ||
| 136 | })); | ||
| 137 | }; | ||
| 138 | |||
| 139 | api.withConverter = init; | ||
| 140 | |||
| 141 | return api; | ||
| 142 | } | ||
| 143 | |||
| 144 | return init(function () { }); | ||
| 145 | })); | ||
| @@ -0,0 +1,21 @@ | |||
| 1 | /// Knockout Mapping plugin v2.4.0 | ||
| 2 | /// (c) 2013 Steven Sanderson, Roy Jacobs - http://knockoutjs.com/ | ||
| 3 | /// License: MIT (http://www.opensource.org/licenses/mit-license.php) | ||
| 4 | (function(d){"function"===typeof require&&"object"===typeof exports&&"object"===typeof module?d(require("knockout"),exports):"function"===typeof define&&define.amd?define(["knockout","exports"],d):d(ko,ko.mapping={})})(function(d,f){function y(b,c){var a,e;for(e in c)if(c.hasOwnProperty(e)&&c[e])if(a=f.getType(b[e]),e&&b[e]&&"array"!==a&&"string"!==a)y(b[e],c[e]);else if("array"===f.getType(b[e])&&"array"===f.getType(c[e])){a=b;for(var d=e,l=b[e],n=c[e],t={},g=l.length-1;0<=g;--g)t[l[g]]=l[g];for(g= | ||
| 5 | n.length-1;0<=g;--g)t[n[g]]=n[g];l=[];n=void 0;for(n in t)l.push(t[n]);a[d]=l}else b[e]=c[e]}function E(b,c){var a={};y(a,b);y(a,c);return a}function z(b,c){for(var a=E({},b),d=L.length-1;0<=d;d--){var f=L[d];a[f]&&(a[""]instanceof Object||(a[""]={}),a[""][f]=a[f],delete a[f])}c&&(a.ignore=h(c.ignore,a.ignore),a.include=h(c.include,a.include),a.copy=h(c.copy,a.copy),a.observe=h(c.observe,a.observe));a.ignore=h(a.ignore,j.ignore);a.include=h(a.include,j.include);a.copy=h(a.copy,j.copy);a.observe=h(a.observe, | ||
| 6 | j.observe);a.mappedProperties=a.mappedProperties||{};a.copiedProperties=a.copiedProperties||{};return a}function h(b,c){"array"!==f.getType(b)&&(b="undefined"===f.getType(b)?[]:[b]);"array"!==f.getType(c)&&(c="undefined"===f.getType(c)?[]:[c]);return d.utils.arrayGetDistinctValues(b.concat(c))}function F(b,c,a,e,k,l,n){var t="array"===f.getType(d.utils.unwrapObservable(c));l=l||"";if(f.isMapped(b)){var g=d.utils.unwrapObservable(b)[p];a=E(g,a)}var j=n||k,h=function(){return a[e]&&a[e].create instanceof | ||
| 7 | Function},x=function(b){var f=G,g=d.dependentObservable;d.dependentObservable=function(a,b,c){c=c||{};a&&"object"==typeof a&&(c=a);var e=c.deferEvaluation,M=!1;c.deferEvaluation=!0;a=new H(a,b,c);if(!e){var g=a,e=d.dependentObservable;d.dependentObservable=H;a=d.isWriteableObservable(g);d.dependentObservable=e;a=H({read:function(){M||(d.utils.arrayRemoveItem(f,g),M=!0);return g.apply(g,arguments)},write:a&&function(a){return g(a)},deferEvaluation:!0});f.push(a)}return a};d.dependentObservable.fn= | ||
| 8 | H.fn;d.computed=d.dependentObservable;b=d.utils.unwrapObservable(k)instanceof Array?a[e].create({data:b||c,parent:j,skip:N}):a[e].create({data:b||c,parent:j});d.dependentObservable=g;d.computed=d.dependentObservable;return b},u=function(){return a[e]&&a[e].update instanceof Function},v=function(b,f){var g={data:f||c,parent:j,target:d.utils.unwrapObservable(b)};d.isWriteableObservable(b)&&(g.observable=b);return a[e].update(g)};if(n=I.get(c))return n;e=e||"";if(t){var t=[],s=!1,m=function(a){return a}; | ||
| 9 | a[e]&&a[e].key&&(m=a[e].key,s=!0);d.isObservable(b)||(b=d.observableArray([]),b.mappedRemove=function(a){var c="function"==typeof a?a:function(b){return b===m(a)};return b.remove(function(a){return c(m(a))})},b.mappedRemoveAll=function(a){var c=C(a,m);return b.remove(function(a){return-1!=d.utils.arrayIndexOf(c,m(a))})},b.mappedDestroy=function(a){var c="function"==typeof a?a:function(b){return b===m(a)};return b.destroy(function(a){return c(m(a))})},b.mappedDestroyAll=function(a){var c=C(a,m);return b.destroy(function(a){return-1!= | ||
| 10 | d.utils.arrayIndexOf(c,m(a))})},b.mappedIndexOf=function(a){var c=C(b(),m);a=m(a);return d.utils.arrayIndexOf(c,a)},b.mappedCreate=function(a){if(-1!==b.mappedIndexOf(a))throw Error("There already is an object with the key that you specified.");var c=h()?x(a):a;u()&&(a=v(c,a),d.isWriteableObservable(c)?c(a):c=a);b.push(c);return c});n=C(d.utils.unwrapObservable(b),m).sort();g=C(c,m);s&&g.sort();s=d.utils.compareArrays(n,g);n={};var J,A=d.utils.unwrapObservable(c),y={},z=!0,g=0;for(J=A.length;g<J;g++){var r= | ||
| 11 | m(A[g]);if(void 0===r||r instanceof Object){z=!1;break}y[r]=A[g]}var A=[],B=0,g=0;for(J=s.length;g<J;g++){var r=s[g],q,w=l+"["+g+"]";switch(r.status){case "added":var D=z?y[r.value]:K(d.utils.unwrapObservable(c),r.value,m);q=F(void 0,D,a,e,b,w,k);h()||(q=d.utils.unwrapObservable(q));w=O(d.utils.unwrapObservable(c),D,n);q===N?B++:A[w-B]=q;n[w]=!0;break;case "retained":D=z?y[r.value]:K(d.utils.unwrapObservable(c),r.value,m);q=K(b,r.value,m);F(q,D,a,e,b,w,k);w=O(d.utils.unwrapObservable(c),D,n);A[w]= | ||
| 12 | q;n[w]=!0;break;case "deleted":q=K(b,r.value,m)}t.push({event:r.status,item:q})}b(A);a[e]&&a[e].arrayChanged&&d.utils.arrayForEach(t,function(b){a[e].arrayChanged(b.event,b.item)})}else if(P(c)){b=d.utils.unwrapObservable(b);if(!b){if(h())return s=x(),u()&&(s=v(s)),s;if(u())return v(s);b={}}u()&&(b=v(b));I.save(c,b);if(u())return b;Q(c,function(e){var f=l.length?l+"."+e:e;if(-1==d.utils.arrayIndexOf(a.ignore,f))if(-1!=d.utils.arrayIndexOf(a.copy,f))b[e]=c[e];else if("object"!=typeof c[e]&&"array"!= | ||
| 13 | typeof c[e]&&0<a.observe.length&&-1==d.utils.arrayIndexOf(a.observe,f))b[e]=c[e],a.copiedProperties[f]=!0;else{var g=I.get(c[e]),k=F(b[e],c[e],a,e,b,f,b),g=g||k;if(0<a.observe.length&&-1==d.utils.arrayIndexOf(a.observe,f))b[e]=g(),a.copiedProperties[f]=!0;else{if(d.isWriteableObservable(b[e]))b[e](d.utils.unwrapObservable(g));else g=void 0===b[e]?g:d.utils.unwrapObservable(g),b[e]=g;a.mappedProperties[f]=!0}}})}else switch(f.getType(c)){case "function":u()?d.isWriteableObservable(c)?(c(v(c)),b=c): | ||
| 14 | b=v(c):b=c;break;default:if(d.isWriteableObservable(b))return q=u()?v(b):d.utils.unwrapObservable(c),b(q),q;h()||u();b=h()?x():d.observable(d.utils.unwrapObservable(c));u()&&b(v(b))}return b}function O(b,c,a){for(var e=0,d=b.length;e<d;e++)if(!0!==a[e]&&b[e]===c)return e;return null}function R(b,c){var a;c&&(a=c(b));"undefined"===f.getType(a)&&(a=b);return d.utils.unwrapObservable(a)}function K(b,c,a){b=d.utils.unwrapObservable(b);for(var e=0,f=b.length;e<f;e++){var l=b[e];if(R(l,a)===c)return l}throw Error("When calling ko.update*, the key '"+ | ||
| 15 | c+"' was not found!");}function C(b,c){return d.utils.arrayMap(d.utils.unwrapObservable(b),function(a){return c?R(a,c):a})}function Q(b,c){if("array"===f.getType(b))for(var a=0;a<b.length;a++)c(a);else for(a in b)c(a)}function P(b){var c=f.getType(b);return("object"===c||"array"===c)&&null!==b}function T(){var b=[],c=[];this.save=function(a,e){var f=d.utils.arrayIndexOf(b,a);0<=f?c[f]=e:(b.push(a),c.push(e))};this.get=function(a){a=d.utils.arrayIndexOf(b,a);return 0<=a?c[a]:void 0}}function S(){var b= | ||
| 16 | {},c=function(a){var c;try{c=a}catch(d){c="$$$"}a=b[c];void 0===a&&(a=new T,b[c]=a);return a};this.save=function(a,b){c(a).save(a,b)};this.get=function(a){return c(a).get(a)}}var p="__ko_mapping__",H=d.dependentObservable,B=0,G,I,L=["create","update","key","arrayChanged"],N={},x={include:["_destroy"],ignore:[],copy:[],observe:[]},j=x;f.isMapped=function(b){return(b=d.utils.unwrapObservable(b))&&b[p]};f.fromJS=function(b){if(0==arguments.length)throw Error("When calling ko.fromJS, pass the object you want to convert."); | ||
| 17 | try{B++||(G=[],I=new S);var c,a;2==arguments.length&&(arguments[1][p]?a=arguments[1]:c=arguments[1]);3==arguments.length&&(c=arguments[1],a=arguments[2]);a&&(c=E(c,a[p]));c=z(c);var e=F(a,b,c);a&&(e=a);if(!--B)for(;G.length;){var d=G.pop();d&&d()}e[p]=E(e[p],c);return e}catch(f){throw B=0,f;}};f.fromJSON=function(b){var c=d.utils.parseJson(b);arguments[0]=c;return f.fromJS.apply(this,arguments)};f.updateFromJS=function(){throw Error("ko.mapping.updateFromJS, use ko.mapping.fromJS instead. Please note that the order of parameters is different!"); | ||
| 18 | };f.updateFromJSON=function(){throw Error("ko.mapping.updateFromJSON, use ko.mapping.fromJSON instead. Please note that the order of parameters is different!");};f.toJS=function(b,c){j||f.resetDefaultOptions();if(0==arguments.length)throw Error("When calling ko.mapping.toJS, pass the object you want to convert.");if("array"!==f.getType(j.ignore))throw Error("ko.mapping.defaultOptions().ignore should be an array.");if("array"!==f.getType(j.include))throw Error("ko.mapping.defaultOptions().include should be an array."); | ||
| 19 | if("array"!==f.getType(j.copy))throw Error("ko.mapping.defaultOptions().copy should be an array.");c=z(c,b[p]);return f.visitModel(b,function(a){return d.utils.unwrapObservable(a)},c)};f.toJSON=function(b,c){var a=f.toJS(b,c);return d.utils.stringifyJson(a)};f.defaultOptions=function(){if(0<arguments.length)j=arguments[0];else return j};f.resetDefaultOptions=function(){j={include:x.include.slice(0),ignore:x.ignore.slice(0),copy:x.copy.slice(0)}};f.getType=function(b){if(b&&"object"===typeof b){if(b.constructor=== | ||
| 20 | Date)return"date";if(b.constructor===Array)return"array"}return typeof b};f.visitModel=function(b,c,a){a=a||{};a.visitedObjects=a.visitedObjects||new S;var e,k=d.utils.unwrapObservable(b);if(P(k))a=z(a,k[p]),c(b,a.parentName),e="array"===f.getType(k)?[]:{};else return c(b,a.parentName);a.visitedObjects.save(b,e);var l=a.parentName;Q(k,function(b){if(!(a.ignore&&-1!=d.utils.arrayIndexOf(a.ignore,b))){var j=k[b],g=a,h=l||"";"array"===f.getType(k)?l&&(h+="["+b+"]"):(l&&(h+="."),h+=b);g.parentName=h; | ||
| 21 | if(!(-1===d.utils.arrayIndexOf(a.copy,b)&&-1===d.utils.arrayIndexOf(a.include,b)&&k[p]&&k[p].mappedProperties&&!k[p].mappedProperties[b]&&k[p].copiedProperties&&!k[p].copiedProperties[b]&&"array"!==f.getType(k)))switch(f.getType(d.utils.unwrapObservable(j))){case "object":case "array":case "undefined":g=a.visitedObjects.get(j);e[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:e[b]=c(j,a.parentName)}}});return e}}); | ||
| @@ -0,0 +1,224 @@ | |||
| 1 | // Knockout Observable Dictionary | ||
| 2 | // (c) James Foster | ||
| 3 | // License: MIT (http://www.opensource.org/licenses/mit-license.php) | ||
| 4 | |||
| 5 | (function () { | ||
| 6 | function DictionaryItem(key, value, dictionary) { | ||
| 7 | var observableKey = new ko.observable(key); | ||
| 8 | |||
| 9 | this.value = new ko.observable(value); | ||
| 10 | this.key = new ko.computed({ | ||
| 11 | read: observableKey, | ||
| 12 | write: function (newKey) { | ||
| 13 | var current = observableKey(); | ||
| 14 | |||
| 15 | if (current == newKey) return; | ||
| 16 | |||
| 17 | // no two items are allowed to share the same key. | ||
| 18 | dictionary.remove(newKey); | ||
| 19 | |||
| 20 | observableKey(newKey); | ||
| 21 | } | ||
| 22 | }); | ||
| 23 | } | ||
| 24 | |||
| 25 | ko.observableDictionary = function (dictionary, keySelector, valueSelector) { | ||
| 26 | var result = {}; | ||
| 27 | |||
| 28 | result.items = new ko.observableArray(); | ||
| 29 | |||
| 30 | result._wrappers = {}; | ||
| 31 | result._keySelector = keySelector || function (value, key) { return key; }; | ||
| 32 | result._valueSelector = valueSelector || function (value) { return value; }; | ||
| 33 | |||
| 34 | if (typeof keySelector == 'string') result._keySelector = function (value) { return value[keySelector]; }; | ||
| 35 | if (typeof valueSelector == 'string') result._valueSelector = function (value) { return value[valueSelector]; }; | ||
| 36 | |||
| 37 | ko.utils.extend(result, ko.observableDictionary['fn']); | ||
| 38 | |||
| 39 | result.pushAll(dictionary); | ||
| 40 | |||
| 41 | return result; | ||
| 42 | }; | ||
| 43 | |||
| 44 | ko.observableDictionary['fn'] = { | ||
| 45 | remove: function (valueOrPredicate) { | ||
| 46 | var predicate = valueOrPredicate; | ||
| 47 | |||
| 48 | if (valueOrPredicate instanceof DictionaryItem) { | ||
| 49 | predicate = function (item) { | ||
| 50 | return item.key() === valueOrPredicate.key(); | ||
| 51 | }; | ||
| 52 | } | ||
| 53 | else if (typeof valueOrPredicate != "function") { | ||
| 54 | predicate = function (item) { | ||
| 55 | return item.key() === valueOrPredicate; | ||
| 56 | }; | ||
| 57 | } | ||
| 58 | |||
| 59 | ko.observableArray['fn'].remove.call(this.items, predicate); | ||
| 60 | }, | ||
| 61 | |||
| 62 | push: function (key, value) { | ||
| 63 | var item = null; | ||
| 64 | |||
| 65 | if (key instanceof DictionaryItem) { | ||
| 66 | // handle the case where only a DictionaryItem is passed in | ||
| 67 | item = key; | ||
| 68 | value = key.value(); | ||
| 69 | key = key.key(); | ||
| 70 | } | ||
| 71 | |||
| 72 | if (value === undefined) { | ||
| 73 | value = this._valueSelector(key); | ||
| 74 | key = this._keySelector(value); | ||
| 75 | } | ||
| 76 | else { | ||
| 77 | value = this._valueSelector(value); | ||
| 78 | } | ||
| 79 | |||
| 80 | var current = this.get(key, false); | ||
| 81 | if (current) { | ||
| 82 | // update existing value | ||
| 83 | current(value); | ||
| 84 | return current; | ||
| 85 | } | ||
| 86 | |||
| 87 | if (!item) { | ||
| 88 | item = new DictionaryItem(key, value, this); | ||
| 89 | } | ||
| 90 | |||
| 91 | ko.observableArray['fn'].push.call(this.items, item); | ||
| 92 | |||
| 93 | return value; | ||
| 94 | }, | ||
| 95 | |||
| 96 | pushAll: function (dictionary) { | ||
| 97 | var self = this; | ||
| 98 | var items = self.items(); | ||
| 99 | |||
| 100 | if (dictionary instanceof Array) { | ||
| 101 | $.each(dictionary, function (index, item) { | ||
| 102 | var key = self._keySelector(item, index); | ||
| 103 | var value = self._valueSelector(item); | ||
| 104 | items.push(new DictionaryItem(key, value, self)); | ||
| 105 | }); | ||
| 106 | } | ||
| 107 | else { | ||
| 108 | for (var prop in dictionary) { | ||
| 109 | if (dictionary.hasOwnProperty(prop)) { | ||
| 110 | var item = dictionary[prop]; | ||
| 111 | var key = self._keySelector(item, prop); | ||
| 112 | var value = self._valueSelector(item); | ||
| 113 | items.push(new DictionaryItem(key, value, self)); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | self.items.valueHasMutated(); | ||
| 119 | }, | ||
| 120 | |||
| 121 | sort: function (method) { | ||
| 122 | if (method === undefined) { | ||
| 123 | method = function (a, b) { | ||
| 124 | return defaultComparison(a.key(), b.key()); | ||
| 125 | }; | ||
| 126 | } | ||
| 127 | |||
| 128 | return ko.observableArray['fn'].sort.call(this.items, method); | ||
| 129 | }, | ||
| 130 | |||
| 131 | indexOf: function (key) { | ||
| 132 | if (key instanceof DictionaryItem) { | ||
| 133 | return ko.observableArray['fn'].indexOf.call(this.items, key); | ||
| 134 | } | ||
| 135 | |||
| 136 | var underlyingArray = this.items(); | ||
| 137 | for (var index = 0; index < underlyingArray.length; index++) { | ||
| 138 | if (underlyingArray[index].key() == key) | ||
| 139 | return index; | ||
| 140 | } | ||
| 141 | return -1; | ||
| 142 | }, | ||
| 143 | |||
| 144 | get: function (key, wrap) { | ||
| 145 | if (wrap == false) | ||
| 146 | return getValue(key, this.items()); | ||
| 147 | |||
| 148 | var wrapper = this._wrappers[key]; | ||
| 149 | |||
| 150 | if (wrapper == null) { | ||
| 151 | wrapper = this._wrappers[key] = new ko.computed({ | ||
| 152 | read: function () { | ||
| 153 | var value = getValue(key, this.items()); | ||
| 154 | return value ? value() : null; | ||
| 155 | }, | ||
| 156 | write: function (newValue) { | ||
| 157 | var value = getValue(key, this.items()); | ||
| 158 | |||
| 159 | if (value) | ||
| 160 | value(newValue); | ||
| 161 | else | ||
| 162 | this.push(key, newValue); | ||
| 163 | } | ||
| 164 | }, this); | ||
| 165 | } | ||
| 166 | |||
| 167 | return wrapper; | ||
| 168 | }, | ||
| 169 | |||
| 170 | set: function (key, value) { | ||
| 171 | return this.push(key, value); | ||
| 172 | }, | ||
| 173 | |||
| 174 | keys: function () { | ||
| 175 | return ko.utils.arrayMap(this.items(), function (item) { return item.key(); }); | ||
| 176 | }, | ||
| 177 | |||
| 178 | values: function () { | ||
| 179 | return ko.utils.arrayMap(this.items(), function (item) { return item.value(); }); | ||
| 180 | }, | ||
| 181 | |||
| 182 | removeAll: function () { | ||
| 183 | this.items.removeAll(); | ||
| 184 | }, | ||
| 185 | |||
| 186 | toJSON: function () { | ||
| 187 | var result = {}; | ||
| 188 | var items = ko.utils.unwrapObservable(this.items); | ||
| 189 | |||
| 190 | ko.utils.arrayForEach(items, function (item) { | ||
| 191 | var key = ko.utils.unwrapObservable(item.key); | ||
| 192 | var value = ko.utils.unwrapObservable(item.value); | ||
| 193 | |||
| 194 | result[key] = value; | ||
| 195 | }); | ||
| 196 | |||
| 197 | return result; | ||
| 198 | } | ||
| 199 | }; | ||
| 200 | |||
| 201 | function getValue(key, items) { | ||
| 202 | var found = ko.utils.arrayFirst(items, function (item) { | ||
| 203 | return item.key() == key; | ||
| 204 | }); | ||
| 205 | return found ? found.value : null; | ||
| 206 | } | ||
| 207 | })(); | ||
| 208 | |||
| 209 | |||
| 210 | // Utility methods | ||
| 211 | // --------------------------------------------- | ||
| 212 | function isNumeric(n) { | ||
| 213 | return !isNaN(parseFloat(n)) && isFinite(n); | ||
| 214 | } | ||
| 215 | |||
| 216 | function defaultComparison(a, b) { | ||
| 217 | if (isNumeric(a) && isNumeric(b)) return a - b; | ||
| 218 | |||
| 219 | a = a.toString(); | ||
| 220 | b = b.toString(); | ||
| 221 | |||
| 222 | return a == b ? 0 : (a < b ? -1 : 1); | ||
| 223 | } | ||
| 224 | // --------------------------------------------- | ||
| @@ -0,0 +1,247 @@ | |||
| 1 | // Deep observer plugin for Knockout http://knockoutjs.com/ | ||
| 2 | // (c) Ziad Jeeroburkhan | ||
| 3 | // License: MIT (http://www.opensource.org/licenses/mit-license.php) | ||
| 4 | // Version 1.3.0 | ||
| 5 | |||
| 6 | ko.subscribable.fn['watch'] = function (targetOrEvaluatorCallback, options, evaluatorCallback, context) { | ||
| 7 | /// <summary> | ||
| 8 | /// Track and manage changes within the chained observable down to any given level. | ||
| 9 | /// </summary> | ||
| 10 | /// <param name="options" type="object"> | ||
| 11 | /// false -> Disables tracking on the chained observable. | ||
| 12 | /// { depth: 2 } -> Track all nested subscribables down to the 2nd level(default is 1).<br/> | ||
| 13 | /// { depth: -1 } -> Track all nested subscribables.<br/> | ||
| 14 | /// { hide: [...] } -> Property or array of properties to be ignored.<br/> | ||
| 15 | /// { hideArrays: true } -> Ignore all nested arrays.<br/> | ||
| 16 | /// { hideWrappedValues: true } -> Ignore observables wrapped under yet another parent observable.<br/> | ||
| 17 | /// { mutable: true } -> Dynamically adapt to changes made to the target structure through its own subscribables.<br/> | ||
| 18 | /// { watchedOnly: true } -> Watch only subscribables tagged with .watch().<br/> | ||
| 19 | /// { beforeWatch: function(parents, child) {...} } -> Function called prior to creating a subscription. Returning false aborts the operation and ignores its children.<br/> | ||
| 20 | /// { wrap: true } -> Wrap all fields into observables. This happens on the fly for new array items(or child objects when mutable is set to true).<br/> | ||
| 21 | /// { beforeWrap: function(parents, field, value) {...} } -> Function called prior to wrapping a value into an observable. Returning false leaves it as it is.<br/> | ||
| 22 | /// { tagParentsWithName: true } -> Add the property '_fieldName' under each parent for easy identification.<br/> | ||
| 23 | /// { keepOldValues: 3 } -> Keep the last three values for each subscribable under the property 'oldValues'.<br/> | ||
| 24 | /// { seal: true } -> Prevent any subsequent watcher from watching down the target tree.<br/> | ||
| 25 | /// </param> | ||
| 26 | /// <param name="evaluatorCallback" type="function"> | ||
| 27 | /// The callback function called during changes. Any return value is assigned to the chained observable. | ||
| 28 | /// </param> | ||
| 29 | |||
| 30 | var targetType = typeof targetOrEvaluatorCallback; | ||
| 31 | |||
| 32 | if (targetType === 'boolean' || targetType === 'undefined') { | ||
| 33 | // Turn on or off the watcher for the specified target along with any of its children. | ||
| 34 | ko.watch(this, { enabled: targetOrEvaluatorCallback !== false }); | ||
| 35 | } else if (targetType === 'function' && !ko.isSubscribable(targetOrEvaluatorCallback)) { | ||
| 36 | // Target the chained subscribable itself if no target subscribable or object was passed. | ||
| 37 | ko.watch(this, options || {}, targetOrEvaluatorCallback, context || this); | ||
| 38 | } else { | ||
| 39 | ko.watch(targetOrEvaluatorCallback, options, evaluatorCallback, context || this); | ||
| 40 | } | ||
| 41 | |||
| 42 | return this; | ||
| 43 | }; | ||
| 44 | |||
| 45 | ko['watch'] = function (target, options, evaluatorCallback, context) { | ||
| 46 | /// <summary> | ||
| 47 | /// Track and manage changes within a specific target object down to any given level. | ||
| 48 | /// </summary> | ||
| 49 | /// <param name="target"> | ||
| 50 | /// An object or function containing targeted subscribables or the subscribable itself. | ||
| 51 | /// </param> | ||
| 52 | /// <param name="options" type="object"> | ||
| 53 | /// { depth: 2 } -> Track all nested subscribables down to the 2nd level(default is 1).<br/> | ||
| 54 | /// { depth: -1 } -> Track all nested subscribables.<br/> | ||
| 55 | /// { hide: [...] } -> Property or array of properties to be ignored.<br/> | ||
| 56 | /// { hideArrays: true } -> Ignore all nested arrays.<br/> | ||
| 57 | /// { hideWrappedValues: true } -> Ignore observables wrapped under yet another parent observable.<br/> | ||
| 58 | /// { mutable: true } -> Dynamically adapt to changes made to the target structure through its own subscribables.<br/> | ||
| 59 | /// { watchedOnly: true } -> Watch only subscribables tagged with .watch().<br/> | ||
| 60 | /// { beforeWatch: function(parents, child) {...} } -> Function called prior to creating a subscription. Returning false aborts the operation and ignores its children.<br/> | ||
| 61 | /// { wrap: true } -> Wrap all fields into observables. This happens on the fly for new array items(or child objects when mutable is set to true).<br/> | ||
| 62 | /// { beforeWrap: function(parents, field, value) {...} } -> Function called prior to wrapping a value into an observable. Returning false leaves it as it is.<br/> | ||
| 63 | /// { tagParentsWithName: true } -> Add the property '_fieldName' under each parent for easy identification.<br/> | ||
| 64 | /// { keepOldValues: 3 } -> Keep the last three values for each subscribable under the property 'oldValues'.<br/> | ||
| 65 | /// { seal: true } -> Prevent any subsequent watcher from watching down the target tree.<br/> | ||
| 66 | /// </param> | ||
| 67 | /// <param name="evaluatorCallback" type="function"> | ||
| 68 | /// The callback function called during changes. | ||
| 69 | /// </param> | ||
| 70 | |||
| 71 | if (typeof options === 'function') { | ||
| 72 | context = context || evaluatorCallback; | ||
| 73 | evaluatorCallback = options; | ||
| 74 | options = {}; | ||
| 75 | } | ||
| 76 | |||
| 77 | context = context || this; | ||
| 78 | |||
| 79 | function watchChildren(child, parent, grandParents, unwatch, keepOffParentList) { | ||
| 80 | |||
| 81 | if (child && options.depth !== 0 && (options.depth === -1 || grandParents.length < (options.depth || 1))) { | ||
| 82 | |||
| 83 | // Proceed on watched children only when in watched-only mode. | ||
| 84 | if (options.watchedOnly && !child.watchable && child != target) | ||
| 85 | return; | ||
| 86 | |||
| 87 | // Setting the target as false prevents it from being watched later on. | ||
| 88 | if (options.enabled === false || options.enabled === true) | ||
| 89 | child.watchable = options.enabled; | ||
| 90 | |||
| 91 | // Ignore watch-disabled objects. | ||
| 92 | if (child.watchable === false) | ||
| 93 | return; | ||
| 94 | |||
| 95 | // Prevent subsequent watchers from watching the target when sealed. | ||
| 96 | if (options.seal === true) | ||
| 97 | child.watchable = false; | ||
| 98 | |||
| 99 | // Bypass circular references. | ||
| 100 | if (child === parent || ko.utils.arrayIndexOf(grandParents, child) > -1) | ||
| 101 | return; | ||
| 102 | |||
| 103 | // Merge parents. Using a fresh array so it is not referenced in the next recursion if any. | ||
| 104 | var parents = [].concat(grandParents, parent && parent !== target ? parent : []); | ||
| 105 | |||
| 106 | // Ignore hidden objects. Also applies to any of their children. | ||
| 107 | if (options.hide) | ||
| 108 | if (typeof options.hide === 'object' | ||
| 109 | ? options.hide === child | ||
| 110 | : ko.utils.arrayIndexOf(options.hide, child) > -1) | ||
| 111 | return; | ||
| 112 | |||
| 113 | if (ko.isSubscribable(child)) { | ||
| 114 | if (evaluatorCallback) { | ||
| 115 | if (unwatch === true || !options.beforeWatch || options.beforeWatch.call(context, parents, child) !== false) { | ||
| 116 | if (typeof child.pop === 'function') { | ||
| 117 | // Child is an observable array. Watch all item changes within it. | ||
| 118 | child.subscribe(function (changes) { | ||
| 119 | ko.utils.arrayForEach(changes, function (item) { | ||
| 120 | |||
| 121 | var returnValue = evaluatorCallback.call(context, parents, child, item); | ||
| 122 | if (returnValue !== undefined) | ||
| 123 | context(returnValue); | ||
| 124 | |||
| 125 | if (!item.moved) { | ||
| 126 | // Deleted or brand new item. Unwatch or watch it. | ||
| 127 | setTimeout(function () { | ||
| 128 | watchChildren(item.value, (keepOffParentList ? null : child), parents, item.status === 'deleted'); | ||
| 129 | }, 0); | ||
| 130 | } | ||
| 131 | }); | ||
| 132 | }, undefined, 'arrayChange'); | ||
| 133 | |||
| 134 | watchChildren(child(), (keepOffParentList ? null : child), parents, unwatch, true); | ||
| 135 | |||
| 136 | return true; | ||
| 137 | |||
| 138 | } else { | ||
| 139 | |||
| 140 | if (unwatch === true) { | ||
| 141 | // A subscribable was removed from an array or mutable object. | ||
| 142 | // Clean up all its change subscriptions through either H or _subscriptions | ||
| 143 | // depending on whether the Knockout code is minified or not. | ||
| 144 | var subsc = child.H || child._subscriptions; | ||
| 145 | |||
| 146 | if (subsc) { | ||
| 147 | if (subsc.change) | ||
| 148 | for (var i = subsc.change.length - 1; i >= 0; i--) | ||
| 149 | if (subsc.change[i]._watcher === context) | ||
| 150 | subsc.change[i].dispose(); | ||
| 151 | |||
| 152 | if (subsc.beforeChange && options.keepOldValues > 0) | ||
| 153 | // Also clean up any before-change subscriptions used for tracking old values. | ||
| 154 | for (var i = subsc.beforeChange.length - 1; i >= 0; i--) | ||
| 155 | if (subsc.beforeChange[i]._watcher === context) | ||
| 156 | subsc.beforeChange[i].dispose(); | ||
| 157 | } | ||
| 158 | |||
| 159 | watchChildren(child(), (keepOffParentList ? null : child), parents, true, true); | ||
| 160 | |||
| 161 | } else { | ||
| 162 | |||
| 163 | if (options.enabled === true && child.watchable === false) | ||
| 164 | // Only waking up an existing watcher. Let's not add another. | ||
| 165 | return; | ||
| 166 | |||
| 167 | if (options.keepOldValues > 0 || options.mutable) { | ||
| 168 | child.subscribe(function (oldValue) { | ||
| 169 | if (options.keepOldValues > 0) { | ||
| 170 | // Add old value to history list before every update. | ||
| 171 | var values = (child['oldValues'] | ||
| 172 | ? child['oldValues'] | ||
| 173 | : child['oldValues'] = []); | ||
| 174 | |||
| 175 | values.unshift(oldValue); | ||
| 176 | |||
| 177 | while (values.length > options.keepOldValues) | ||
| 178 | values.pop(); | ||
| 179 | } | ||
| 180 | |||
| 181 | if (options.mutable && typeof oldValue === 'object') | ||
| 182 | // Clean up all subscriptions for the old child object. | ||
| 183 | watchChildren(oldValue, (keepOffParentList ? null : child), parents, true, true); | ||
| 184 | |||
| 185 | }, null, 'beforeChange')._watcher = context; | ||
| 186 | } | ||
| 187 | |||
| 188 | child.subscribe(function () { | ||
| 189 | if (child.watchable !== false) { | ||
| 190 | var returnValue = evaluatorCallback.call(context, parents, child); | ||
| 191 | |||
| 192 | if (returnValue !== undefined) | ||
| 193 | context(returnValue); | ||
| 194 | |||
| 195 | if (options.mutable && typeof child() === 'object') | ||
| 196 | // Watch the new comer object. | ||
| 197 | watchChildren(child(), (keepOffParentList ? null : child), parents); | ||
| 198 | } | ||
| 199 | |||
| 200 | }, null, 'change')._watcher = context; | ||
| 201 | |||
| 202 | if (options.hideWrappedValues !== true) | ||
| 203 | watchChildren(child(), (keepOffParentList ? null : child), parents); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | } else { | ||
| 210 | switch (Object.prototype.toString.call(child)) { | ||
| 211 | case '[object Object]': | ||
| 212 | ko.utils.objectForEach(child, function (property, sub) { | ||
| 213 | if (options.wrap) { | ||
| 214 | // Wrap simple objects and arrays into observables. | ||
| 215 | var type = Object.prototype.toString.call(sub); | ||
| 216 | if (type !== '[object Function]' && type !== '[object Object]') { | ||
| 217 | if (!options.beforeWrap || options.beforeWrap.call(context, parents, child, sub) !== false) { | ||
| 218 | sub = child[property] = type === '[object Array]' | ||
| 219 | ? ko.observableArray(sub) | ||
| 220 | : ko.observable(sub); | ||
| 221 | } | ||
| 222 | } | ||
| 223 | } | ||
| 224 | |||
| 225 | var subHasChildren = watchChildren(sub, (keepOffParentList ? null : child), parents, unwatch); | ||
| 226 | |||
| 227 | if (subHasChildren && options.tagParentsWithName === true && !sub['_fieldName']) | ||
| 228 | sub['_fieldName'] = property; | ||
| 229 | }); | ||
| 230 | return true; | ||
| 231 | |||
| 232 | case '[object Array]': | ||
| 233 | if (options.hideArrays !== true) | ||
| 234 | for (var i = 0; i < child.length; i++) | ||
| 235 | watchChildren(child[i], (keepOffParentList ? null : child), parents, unwatch); | ||
| 236 | return true; | ||
| 237 | } | ||
| 238 | } | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | // Use a computed when targeting a non-watchable function. | ||
| 243 | if (typeof target === 'function' && !ko.isSubscribable(target)) | ||
| 244 | return ko.computed(target, evaluatorCallback, options); | ||
| 245 | |||
| 246 | watchChildren(target, null, []); | ||
| 247 | }; | ||