History

GSF.Web: Moved common script resources into GSF/Shared/Scripts - marked existing scripts as obsolete.

J. Ritchie Carroll committed 9 years ago

Commit 230686d1 parent 6d6e24d19 files · +1921 −40Browse files

Unified · Split

19 files
  1. Source/Libraries/GSF.Web/GSF.Web.csproj +7 −0
  2. Source/Libraries/GSF.Web/Model/Scripts/angular1.5.8.js +5 −0
  3. Source/Libraries/GSF.Web/Model/Scripts/gsf.web.client.js +6 −1
  4. Source/Libraries/GSF.Web/Model/Scripts/gsf.web.knockout.js +6 −1
  5. Source/Libraries/GSF.Web/Model/Scripts/gsf.web.primeui.js +6 −1
  6. Source/Libraries/GSF.Web/Model/Scripts/js.cookie.js +6 −1
  7. Source/Libraries/GSF.Web/Model/Scripts/ko-reactor.js +5 −0
  8. Source/Libraries/GSF.Web/Model/Scripts/ko-reactor.min.js +5 −0
  9. Source/Libraries/GSF.Web/Model/Scripts/ko.observableDictionary.js +6 −1
  10. Source/Libraries/GSF.Web/Model/Scripts/pagedViewModel.js +16 −0
  11. Source/Libraries/GSF.Web/Model/Views/PagedViewModel.cshtml +18 −14
  12. Source/Libraries/GSF.Web/Shared/Resources.cs +26 −21
  13. Source/Libraries/GSF.Web/Shared/Scripts/gsf.web.client.js +895 −0
  14. Source/Libraries/GSF.Web/Shared/Scripts/gsf.web.knockout.js +167 −0
  15. Source/Libraries/GSF.Web/Shared/Scripts/gsf.web.primeui.js +110 −0
  16. Source/Libraries/GSF.Web/Shared/Scripts/js.cookie.js +145 −0
  17. Source/Libraries/GSF.Web/Shared/Scripts/knockout.mapping.js +21 −0
  18. Source/Libraries/GSF.Web/Shared/Scripts/knockout.observableDictionary.js +224 −0
  19. Source/Libraries/GSF.Web/Shared/Scripts/knockout.reactor.js +247 −0
Source/Libraries/GSF.Web/GSF.Web.csproj+7−0
@@ -263,6 +263,13 @@ · 262 unmodified lines
263263 <None Include="PostBuildSetup.bat" />
264264 </ItemGroup>
265265 <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" />
266273 <EmbeddedResource Include="Security\Scripts\core.js" />
267274 <EmbeddedResource Include="Security\Scripts\enc-base64.js" />
268275 <EmbeddedResource Include="Security\Scripts\ntlm.js" />
Source/Libraries/GSF.Web/Model/Scripts/angular1.5.8.js+5−0
@@ -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+
16 /**
27 * @license AngularJS v1.5.8
38 * (c) 2010-2016 Google, Inc. http://angularjs.org
Source/Libraries/GSF.Web/Model/Scripts/gsf.web.client.js+6−1
@@ -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+//******************************************************************************************************
27 // gsf.web.client.js - Gbtc
38 //
49 // Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
Source/Libraries/GSF.Web/Model/Scripts/gsf.web.knockout.js+6−1
@@ -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+//******************************************************************************************************
27 // gsf.web.knockout.js - Gbtc
38 //
49 // Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
Source/Libraries/GSF.Web/Model/Scripts/gsf.web.primeui.js+6−1
@@ -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+//******************************************************************************************************
27 // gsf.web.primeui.js - Gbtc
38 //
49 // Copyright © 2016, Grid Protection Alliance. All Rights Reserved.
Source/Libraries/GSF.Web/Model/Scripts/js.cookie.js+6−1
@@ -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+/*!
27 * JavaScript Cookie v2.1.0
38 * https://github.com/js-cookie/js-cookie
49 *
Source/Libraries/GSF.Web/Model/Scripts/ko-reactor.js+5−0
@@ -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+
16 // Deep observer plugin for Knockout http://knockoutjs.com/
27 // (c) Ziad Jeeroburkhan
38 // License: MIT (http://www.opensource.org/licenses/mit-license.php)
Source/Libraries/GSF.Web/Model/Scripts/ko-reactor.min.js+5−0
@@ -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+
16 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";
Source/Libraries/GSF.Web/Model/Scripts/ko.observableDictionary.js+6−1
@@ -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
27 // (c) James Foster
38 // License: MIT (http://www.opensource.org/licenses/mit-license.php)
49
Source/Libraries/GSF.Web/Model/Scripts/pagedViewModel.js+16−0
@@ -25,6 +25,22 @@ · 24 unmodified lines
2525 // Paged view model base class scripts
2626 "use strict";
2727
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+
2844 var RecordMode = {
2945 View: 0,
3046 Edit: 1,
Source/Libraries/GSF.Web/Model/Views/PagedViewModel.cshtml+18−14
@@ -42,6 +42,12 @@ · 41 unmodified lines
4242 ViewBag.PageControlScripts = new StringBuilder();
4343 }
4444
45+ bool includeScripts = false;
46+
47+ if (ViewBag.IncludeScripts != null) {
48+ includeScripts = (bool)ViewBag.IncludeScripts;
49+ }
50+
4551 // Setup page control scripts
4652 StringBuilder pageControlScripts = ViewBag.PageControlScripts;
4753
@@ -53,22 +59,20 @@ · 5 unmodified lines
5359 ViewBag.RecordsTitle ?? "Records"
5460 );
5561
56− pageControlScripts.Append(string.Format(@"
57− <script src=""{0}""></script>
58− <script src=""{1}""></script>
59− <script src=""{2}""></script>
60− ",
61− Url.Content("~/Scripts/knockout-3.4.0.js"),
62− Url.Content("~/Scripts/knockout.mapping-latest.js"),
63− Url.Content("~/Scripts/knockout.validation.min.js"))
64− );
62+ if (includeScripts) {
63+ pageControlScripts.Append(@"
64+ <script src=""/@GSF/Web/Shared/Scripts/knockout.js""></script>
65+ <script src=""/@GSF/Web/Shared/Scripts/knockout.mapping.js""></script>
66+ <script src=""/@GSF/Web/Shared/Scripts/knockout.validation.js""></script>
67+ <script src=""/@GSF/Web/Shared/Scripts/knockout.reactor.min.js""></script>
68+ <script src=""/@GSF/Web/Shared/Scripts/knockout.observableDictionary.js""></script>
69+ <script src=""/@GSF/Web/Shared/Scripts/js.cookie.js""></script>
70+ <script src=""/@GSF/Web/Shared/Scripts/gsf.web.knockout.js""></script>
71+ <script src=""/@GSF/Web/Model/Scripts/pagedViewModel.js""></script>
72+ ");
73+ }
6574
6675 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>
7276 <script>
7377 $(function() {
7478 $(""#clearSortOrder"").click(function() {
Source/Libraries/GSF.Web/Shared/Resources.cs+26−21
@@ -28,30 +28,35 @@ namespace GSF.Web.Shared · 27 unmodified lines
2828 /// </summary>
2929 public static class Resources
3030 {
31+ /// <summary>
32+ /// Defines embedded resource root path.
33+ /// </summary>
34+ public const string Root = "/@GSF/Web";
35+
3136 /// <summary>
3237 /// Gets common set of icon resources that can be included in web page header.
3338 /// </summary>
34− 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"" />
36− <link rel=""apple-touch-icon-precomposed"" sizes=""114x114"" href=""/@GSF/Web/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"" />
38− <link rel=""apple-touch-icon-precomposed"" sizes=""144x144"" href=""/@GSF/Web/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"" />
40− <link rel=""apple-touch-icon-precomposed"" sizes=""120x120"" href=""/@GSF/Web/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"" />
42− <link rel=""apple-touch-icon-precomposed"" sizes=""152x152"" href=""/@GSF/Web/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"" />
44− <link rel=""icon"" type=""image/png"" href=""/@GSF/Web/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"" />
46− <link rel=""icon"" type=""image/png"" href=""/@GSF/Web/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"" />
48− <link rel=""shortcut icon"" href=""/@GSF/Web/Shared/Images/Icons/favicon.ico"" />
39+ public static readonly string HeaderIcons = $@"
40+ <link rel=""apple-touch-icon-precomposed"" sizes=""57x57"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-57x57.png"" />
41+ <link rel=""apple-touch-icon-precomposed"" sizes=""114x114"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-114x114.png"" />
42+ <link rel=""apple-touch-icon-precomposed"" sizes=""72x72"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-72x72.png"" />
43+ <link rel=""apple-touch-icon-precomposed"" sizes=""144x144"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-144x144.png"" />
44+ <link rel=""apple-touch-icon-precomposed"" sizes=""60x60"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-60x60.png"" />
45+ <link rel=""apple-touch-icon-precomposed"" sizes=""120x120"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-120x120.png"" />
46+ <link rel=""apple-touch-icon-precomposed"" sizes=""76x76"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-76x76.png"" />
47+ <link rel=""apple-touch-icon-precomposed"" sizes=""152x152"" href=""{Root}/Shared/Images/Icons/apple-touch-icon-152x152.png"" />
48+ <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-196x196.png"" sizes=""196x196"" />
49+ <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-96x96.png"" sizes=""96x96"" />
50+ <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-32x32.png"" sizes=""32x32"" />
51+ <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-16x16.png"" sizes=""16x16"" />
52+ <link rel=""icon"" type=""image/png"" href=""{Root}/Shared/Images/Icons/favicon-128.png"" sizes=""128x128"" />
53+ <link rel=""shortcut icon"" href=""{Root}/Shared/Images/Icons/favicon.ico"" />
4954 <meta name=""msapplication-TileColor"" content=""#AABBAA"" />
50− <meta name=""msapplication-TileImage"" content=""/@GSF/Web/Shared/Images/Icons/mstile-144x144.png"" />
51− <meta name=""msapplication-square70x70logo"" content=""/@GSF/Web/Shared/Images/Icons/mstile-70x70.png"" />
52− <meta name=""msapplication-square150x150logo"" content=""/@GSF/Web/Shared/Images/Icons/mstile-150x150.png"" />
53− <meta name=""msapplication-wide310x150logo"" content=""/@GSF/Web/Shared/Images/Icons/mstile-310x150.png"" />
54− <meta name=""msapplication-square310x310logo"" content=""/@GSF/Web/Shared/Images/Icons/mstile-310x310.png""/>
55− ".FixForwardSpacing();
55+ <meta name=""msapplication-TileImage"" content=""{Root}/Shared/Images/Icons/mstile-144x144.png"" />
56+ <meta name=""msapplication-square70x70logo"" content=""{Root}/Shared/Images/Icons/mstile-70x70.png"" />
57+ <meta name=""msapplication-square150x150logo"" content=""{Root}/Shared/Images/Icons/mstile-150x150.png"" />
58+ <meta name=""msapplication-wide310x150logo"" content=""{Root}/Shared/Images/Icons/mstile-310x150.png"" />
59+ <meta name=""msapplication-square310x310logo"" content=""{Root}/Shared/Images/Icons/mstile-310x310.png""/>
60+ ".FixForwardSpacing(0);
5661 }
5762 }
Source/Libraries/GSF.Web/Shared/Scripts/gsf.web.client.js+895−0
@@ -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+}
Source/Libraries/GSF.Web/Shared/Scripts/gsf.web.knockout.js+167−0
@@ -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+};
Source/Libraries/GSF.Web/Shared/Scripts/gsf.web.primeui.js+110−0
@@ -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+}
Source/Libraries/GSF.Web/Shared/Scripts/js.cookie.js+145−0
@@ -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+}));
Source/Libraries/GSF.Web/Shared/Scripts/knockout.mapping.js+21−0
@@ -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}});
Source/Libraries/GSF.Web/Shared/Scripts/knockout.observableDictionary.js+224−0
@@ -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+// ---------------------------------------------
Source/Libraries/GSF.Web/Shared/Scripts/knockout.reactor.js+247−0
@@ -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+};

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t