1 /*
  2  * Copyright (c) 2008-2019 LabKey Corporation
  3  *
  4  * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
  5  */
  6 
  7 // NOTE labkey.js should NOT depend on any external libraries like ExtJS
  8 
  9 if (typeof LABKEY == "undefined")
 10 {
 11     /**
 12      * @namespace Namespace used to encapsulate LabKey core API and utilities.
 13      */
 14     LABKEY = new function()
 15     {
 16         var configs = {
 17             container: undefined,
 18             contextPath: "",
 19             DataRegions: {},
 20             devMode: false,
 21             demoMode: false,
 22             dirty: false,
 23             isDocumentClosed: false,
 24             extJsRoot: "ext-3.4.1",
 25             extJsRoot_42: "ext-4.2.1",
 26             extThemeRoot: "labkey-ext-theme",
 27             extThemeName_42: "seattle",
 28             extThemeRoot_42: "ext-theme",
 29             fieldMarker: '@',
 30             hash: 0,
 31             imagePath: "",
 32             requestedCssFiles: {},
 33             submit: false,
 34             unloadMessage: "You will lose any changes made to this page.",
 35             verbose: false,
 36             widget: {}
 37         };
 38 
 39         // private variables not configurable
 40         var _requestedCssFiles = {};
 41 
 42         // prepare null console to avoid errors in IE 11 and earlier, which only makes console available when the dev tools are open
 43         (function(){
 44             var method;
 45             var noop = function () {};
 46             var methods = [
 47                 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
 48                 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
 49                 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
 50                 'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
 51             ];
 52             var length = methods.length;
 53             var console = (window.console = window.console || {});
 54 
 55             while (length--) {
 56                 method = methods[length];
 57 
 58                 // Only stub undefined methods.
 59                 if (!console[method]) {
 60                     console[method] = noop;
 61                 }
 62             }
 63         })();
 64 
 65         // private caching mechanism for script loading
 66         var ScriptCache = function()
 67         {
 68             var cache = {};
 69 
 70             var callbacksOnCache = function(key)
 71             {
 72                 // console.log('calling --', key);
 73                 var cbs = cache[key];
 74 
 75                 // set the cache to hit
 76                 cache[key] = true;
 77 
 78                 // Tell mothership.js to hook event callbacks
 79                 if (LABKEY.Mothership)
 80                 {
 81                     if (key.indexOf(configs.extJsRoot + '/ext-all') === 0)
 82                         LABKEY.Mothership.hookExt3();
 83 
 84                     if (key.indexOf(configs.extJsRoot_42 + '/ext-all') === 0)
 85                         LABKEY.Mothership.hookExt4();
 86                 }
 87 
 88                 // call on the callbacks who have been waiting for this resource
 89                 if (isArray(cbs))
 90                 {
 91                     var cb;
 92                     for (var c=0; c < cbs.length; c++)
 93                     {
 94                         cb = cbs[c];
 95                         handle(cb.fn, cb.scope);
 96                     }
 97                 }
 98             };
 99 
100             var inCache = function(key)
101             {
102                 // console.log('hit --', key);
103                 return cache[key] === true;
104             };
105 
106             var inFlightCache = function(key)
107             {
108                 return isArray(cache[key]);
109             };
110 
111             var loadCache = function(key, cb, s)
112             {
113                 // console.log('miss --', key);
114                 // The value as an array denotes the cache resource is in flight
115                 if (!cache[key])
116                     cache[key] = [];
117 
118                 if (isFunction(cb))
119                     cache[key].push({fn: cb, scope: s});
120             };
121 
122             return {
123                 callbacksOnCache: callbacksOnCache,
124                 inCache: inCache,
125                 inFlightCache: inFlightCache,
126                 loadCache: loadCache
127             };
128         };
129 
130         // instance of scripting cache used by public methods
131         var scriptCache = new ScriptCache();
132 
133         // Public Method Definitions
134 
135         var addElemToHead = function(elemName, attributes)
136         {
137             var elem = document.createElement(elemName);
138             for (var a in attributes) {
139                 if (attributes.hasOwnProperty(a)) {
140                     elem[a] = attributes[a];
141                 }
142             }
143             return document.getElementsByTagName("head")[0].appendChild(elem);
144         };
145 
146         var addMarkup = function(html)
147         {
148             if (configs.isDocumentClosed)
149             {
150                 var elem = document.createElement("div");
151                 elem.innerHTML = html;
152                 document.body.appendChild(elem.firstChild);
153             }
154             else
155                 document.write(html);
156         };
157 
158         //private. used to append additional module context objects for AJAXd views
159         var applyModuleContext = function(ctx) {
160             for (var mn in ctx) {
161                 if (ctx.hasOwnProperty(mn)) {
162                     LABKEY.moduleContext[mn.toLowerCase()] = ctx[mn];
163                 }
164             }
165         };
166 
167         var beforeunload = function (dirtyCallback, scope, msg)
168         {
169             return function () {
170                 if (!getSubmit() && (isDirty() || (dirtyCallback && dirtyCallback.call(scope)))) {
171                     return msg || configs.unloadMessage;
172                 }
173             };
174         };
175 
176         var checkMute = false;
177 
178         var checkCallback = function(methodName, callback, files)
179         {
180             if (checkMute)
181                 return;
182 
183             if (!isFunction(callback))
184             {
185                 console.warn([
186                     'A usage of LABKEY.' + methodName + '() is missing the "callback" parameter.',
187                     'It is recommended that a callback be provided as it will be called once',
188                     methodName + '() can guarantee your resource is loaded.',
189                     (files ? '\nRequested: "' + files.toString() + '"' : ''),
190                     (LABKEY.Utils && isFunction(LABKEY.Utils.getHelpTopicHref)) ? '\nSee ' + LABKEY.Utils.getHelpTopicHref('scriptdepend#requiresScript') : ''
191                 ].join(' '));
192 
193                 // do not spam usage warning
194                 checkMute = true;
195             }
196         };
197 
198         var createElement = function(tag, innerHTML, attributes)
199         {
200             var e = document.createElement(tag);
201             if (innerHTML)
202                 e.innerHTML = innerHTML;
203             if (attributes)
204             {
205                 for (var att in attributes)
206                 {
207                     if (attributes.hasOwnProperty(att))
208                     {
209                         try
210                         {
211                             e[att] = attributes[att];
212                         }
213                         catch (x)
214                         {
215                             console.log(x); // e['style'] is read-only in old firefox
216                         }
217                     }
218                 }
219             }
220             return e;
221         };
222 
223         var getModuleContext = function(moduleName) {
224             return LABKEY.moduleContext[moduleName.toLowerCase()];
225         };
226 
227         var getModuleProperty = function(moduleName, property) {
228             var ctx = getModuleContext(moduleName);
229             if (!ctx) {
230                 return null;
231             }
232             return ctx[property];
233         };
234 
235         var getSubmit = function()
236         {
237             return configs.submit;
238         };
239 
240         // simple callback handler that will type check then call with scope
241         var handle = function(callback, scope)
242         {
243             if (isFunction(callback))
244             {
245                 callback.call(scope || this);
246             }
247         };
248 
249         // If we're in demo mode, replace each ID with an equal length string of "*".  This code should match DemoMode.id().
250         var id = function(id)
251         {
252             if (configs.demoMode)
253             {
254                 return new Array(id.length + 1).join("*");
255             }
256             return id;
257         };
258 
259         var init = function(config)
260         {
261             for (var p in config)
262             {
263                 //TODO: we should be trying to seal some of these objects, or at least wrap them to make them harder to manipulate
264                 if (config.hasOwnProperty(p)) {
265                     configs[p] = config[p];
266                     LABKEY[p] = config[p];
267                 }
268             }
269             if ("Security" in LABKEY)
270                 LABKEY.Security.currentUser = LABKEY.user;
271         };
272 
273         var isArray = function(value)
274         {
275             return Object.prototype.toString.call(value) === "[object Array]";
276         };
277 
278         var isBoolean = function(value)
279         {
280             return typeof value === "boolean";
281         };
282 
283         var isDirty = function()
284         {
285             return configs.dirty;
286         };
287 
288         var isFunction = function(value)
289         {
290             return typeof value === "function";
291         };
292 
293         var isLibrary = function(file)
294         {
295             return file && (file.indexOf('.') === -1 || file.indexOf('.lib.xml') > -1);
296         };
297 
298         var loadScripts = function()
299         {
300             configs.isDocumentClosed = true;
301         };
302 
303         var loadedScripts = function()
304         {
305             for (var i=0; i < arguments.length; i++)
306             {
307                 if (isArray(arguments[i]))
308                 {
309                     for (var j=0; j < arguments[i].length; j++)
310                     {
311                         scriptCache.callbacksOnCache(arguments[i][j]);
312                     }
313                 }
314                 else
315                 {
316                     scriptCache.callbacksOnCache(arguments[i]);
317                 }
318             }
319             return true;
320         };
321 
322         var qs = function(params)
323         {
324             if (!params)
325                 return '';
326 
327             var qs = '', and = '', pv, p;
328 
329             for (p in params)
330             {
331                 if (params.hasOwnProperty(p))
332                 {
333                     pv = params[p];
334 
335                     if (pv === null || pv === undefined)
336                         pv = '';
337 
338                     if (isArray(pv))
339                     {
340                         for (var i=0; i < pv.length; i++)
341                         {
342                             qs += and + encodeURIComponent(p) + '=' + encodeURIComponent(pv[i]);
343                             and = '&';
344                         }
345                     }
346                     else
347                     {
348                         qs += and + encodeURIComponent(p) + '=' + encodeURIComponent(pv);
349                         and = '&';
350                     }
351                 }
352             }
353 
354             return qs;
355         };
356 
357         // So as not to confuse with native support for fetch()
358         var _fetch = function(url, params, success, failure)
359         {
360             var xhr = new XMLHttpRequest();
361             var _url = url + (url.indexOf('?') === -1 ? '?' : '&') + qs(params);
362 
363             xhr.onreadystatechange = function()
364             {
365                 if (xhr.readyState === 4)
366                 {
367                     var _success = (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304;
368                     _success ? success(xhr) : failure(xhr);
369                 }
370             };
371 
372             xhr.open('GET', _url, true);
373 
374             xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
375             if (LABKEY.CSRF)
376                 xhr.setRequestHeader('X-LABKEY-CSRF', LABKEY.CSRF);
377 
378             xhr.send(null);
379 
380             return xhr;
381         };
382 
383         var requiresCss = function(file)
384         {
385             if (isArray(file))
386             {
387                 for (var i=0;i<file.length;i++)
388                     requiresCss(file[i]);
389                 return;
390             }
391 
392             if (file.indexOf('/') === 0)
393             {
394                 file = file.substring(1);
395             }
396 
397             var key = file,
398                 fullPath;
399 
400             if (!_requestedCssFiles[key])
401             {
402                 _requestedCssFiles[key] = true;
403 
404                 // Support both LabKey and external CSS files
405                 if (file.substr(0, 4) !== 'http')
406                 {
407                     // local files
408                     fullPath = configs.contextPath + '/' + file + '?' + configs.hash;
409                 }
410                 else
411                 {
412                     // external files
413                     fullPath = file;
414                 }
415 
416                 addElemToHead("link", {
417                     type: "text/css",
418                     rel: "stylesheet",
419                     href: fullPath
420                 });
421             }
422         };
423 
424         var requestedCssFiles = function()
425         {
426             var ret = arguments.length > 0 && _requestedCssFiles[arguments[0]];
427             for (var i=0; i < arguments.length ; i++)
428                 _requestedCssFiles[arguments[i]] = true;
429             return ret;
430         };
431 
432         var requiresClientAPI = function(callback, scope)
433         {
434             // backwards compat for 'immediate'
435             if (arguments.length > 0 && isBoolean(arguments[0]))
436             {
437                 callback = arguments[1];
438                 scope = arguments[2];
439             }
440 
441             checkCallback('requiresClientAPI', callback);
442 
443             requiresLib('clientapi', function()
444             {
445                 requiresExt3ClientAPI(callback, scope);
446             });
447         };
448 
449         var requiresExt3 = function(callback, scope)
450         {
451             // backwards compat for 'immediate'
452             if (arguments.length > 0 && isBoolean(arguments[0]))
453             {
454                 callback = arguments[1];
455                 scope = arguments[2];
456             }
457 
458             checkCallback('requiresExt3', callback);
459 
460             if (window.Ext)
461             {
462                 handle(callback, scope);
463             }
464             else
465             {
466                 requiresLib('Ext3', callback, scope);
467             }
468         };
469 
470         var requiresExt3ClientAPI = function(callback, scope)
471         {
472             // backwards compat for 'immediate'
473             if (arguments.length > 0 && isBoolean(arguments[0]))
474             {
475                 callback = arguments[1];
476                 scope = arguments[2];
477             }
478 
479             checkCallback('requiresExt3ClientAPI', callback);
480 
481             requiresExt3(function()
482             {
483                 requiresLib('clientapi/ext3', callback, scope);
484             });
485         };
486 
487         var requiresExt4ClientAPI = function(callback, scope)
488         {
489             // backwards compat for 'immediate'
490             if (arguments.length > 0 && isBoolean(arguments[0]))
491             {
492                 callback = arguments[1];
493                 scope = arguments[2];
494             }
495 
496             checkCallback('requiresExt4ClientAPI', callback);
497 
498             requiresExt4Sandbox(function()
499             {
500                 requiresLib('Ext4ClientApi', callback, scope);
501             });
502         };
503 
504         var requiresExt4Sandbox = function(callback, scope)
505         {
506             // backwards compat for 'immediate'
507             if (arguments.length > 0 && isBoolean(arguments[0]))
508             {
509                 callback = arguments[1];
510                 scope = arguments[2];
511             }
512 
513             checkCallback('requiresExt4Sandbox', callback);
514 
515             if (window.Ext4)
516             {
517                 handle(callback, scope);
518             }
519             else
520             {
521                 requiresLib('Ext4', callback, scope);
522             }
523         };
524 
525         var requiresLib = function(lib, callback, scope)
526         {
527             if (!lib)
528             {
529                 handle(callback, scope);
530                 return;
531             }
532 
533             var _lib = lib.split('.lib.xml')[0];
534 
535             // in case _lib is now empty
536             if (!_lib)
537             {
538                 handle(callback, scope);
539                 return;
540             }
541 
542             if (scriptCache.inCache(_lib))
543             {
544                 handle(callback, scope);
545                 return;
546             }
547             else if (scriptCache.inFlightCache(_lib))
548             {
549                 scriptCache.loadCache(_lib, callback, scope);
550                 return;
551             }
552             else
553             {
554                 scriptCache.loadCache(_lib, callback, scope);
555             }
556 
557             var cacheLoader = function()
558             {
559                 scriptCache.callbacksOnCache(_lib);
560             };
561 
562             _fetch('core-loadLibrary.api', {
563                 library: _lib
564             }, function(data) {
565                 // success
566                 var json = JSON.parse(data.responseText);
567                 var definition = json['libraries'][_lib];
568 
569                 if (definition)
570                 {
571                     var styles = [];
572                     var scripts = [];
573                     for (var d=0; d < definition.length; d++)
574                     {
575                         if (definition[d].indexOf('.css') > -1)
576                         {
577                             styles.push(definition[d]);
578                         }
579                         else
580                         {
581                             scripts.push(definition[d]);
582                         }
583                     }
584 
585                     LABKEY.requiresCss(styles);
586                     LABKEY.requiresScript(scripts, cacheLoader, undefined, true /* inOrder, sadly */);
587                 }
588                 else
589                 {
590                     throw new Error('Failed to retrieve library definition \"' + _lib + '\"');
591                 }
592             }, function() {
593                 // failure
594                 throw new Error('Failed to load library: \"' + _lib + '\"');
595             });
596         };
597 
598         var requiresScript = function(file, callback, scope, inOrder)
599         {
600             if (arguments.length === 0)
601             {
602                 throw "LABKEY.requiresScript() requires the 'file' parameter.";
603             }
604             else if (!file)
605             {
606                 throw "LABKEY.requiresScript() invalid 'file' argument.";
607             }
608 
609             // backwards compat for 'immediate'
610             if (arguments.length > 1 && isBoolean(arguments[1]))
611             {
612                 callback = arguments[2];
613                 scope = arguments[3];
614                 inOrder = arguments[4];
615             }
616 
617             checkCallback('requiresScript', callback, file);
618 
619             if (isArray(file))
620             {
621                 var requestedLength = file.length;
622                 var loaded = 0;
623 
624                 if (requestedLength === 0)
625                 {
626                     handle(callback, scope);
627                 }
628                 else if (inOrder)
629                 {
630                     var chain = function()
631                     {
632                         loaded++;
633                         if (loaded === requestedLength)
634                         {
635                             handle(callback, scope);
636                         }
637                         else if (loaded < requestedLength)
638                             requiresScript(file[loaded], chain, undefined, true);
639                     };
640 
641                     if (scriptCache.inCache(file[loaded]))
642                     {
643                         chain();
644                     }
645                     else
646                         requiresScript(file[loaded], chain, undefined, true);
647                 }
648                 else
649                 {
650                     // request all the scripts (order does not matter)
651                     var allDone = function()
652                     {
653                         loaded++;
654                         if (loaded === requestedLength)
655                         {
656                             handle(callback, scope);
657                         }
658                     };
659 
660                     for (var i = 0; i < file.length; i++)
661                     {
662                         if (scriptCache.inCache(file[i]))
663                         {
664                             allDone();
665                         }
666                         else
667                             requiresScript(file[i], allDone);
668                     }
669                 }
670                 return;
671             }
672 
673             if (isLibrary(file))
674             {
675                 if (file === 'Ext3')
676                 {
677                     requiresExt3(callback, scope);
678                 }
679                 else if (file === 'Ext4')
680                 {
681                     requiresExt4Sandbox(callback, scope);
682                 }
683                 else
684                 {
685                     requiresLib(file, callback, scope);
686                 }
687                 return;
688             }
689 
690             if (file.indexOf('/') === 0)
691             {
692                 file = file.substring(1);
693             }
694 
695             if (scriptCache.inCache(file))
696             {
697                 // cache hit -- script is loaded and ready to go
698                 handle(callback, scope);
699                 return;
700             }
701             else if (scriptCache.inFlightCache(file))
702             {
703                 // cache miss -- in flight
704                 scriptCache.loadCache(file, callback, scope);
705                 return;
706             }
707             else
708             {
709                 // cache miss
710                 scriptCache.loadCache(file, callback, scope);
711             }
712 
713             // although FireFox and Safari allow scripts to use the DOM
714             // during parse time, IE does not. So if the document is
715             // closed, use the DOM to create a script element and append it
716             // to the head element. Otherwise (still parsing), use document.write()
717 
718             // Support both LabKey and external JavaScript files
719             var src = file.substr(0, 4) != "http" ? configs.contextPath + "/" + file + '?' + configs.hash : file;
720 
721             var cacheLoader = function()
722             {
723                 scriptCache.callbacksOnCache(file);
724             };
725 
726             if (configs.isDocumentClosed || callback)
727             {
728                 //create a new script element and append it to the head element
729                 var script = addElemToHead("script", {
730                     src: src,
731                     type: "text/javascript"
732                 });
733 
734                 // IE has a different way of handling <script> loads
735                 if (script.readyState)
736                 {
737                     script.onreadystatechange = function() {
738                         if (script.readyState == "loaded" || script.readyState == "complete") {
739                             script.onreadystatechange = null;
740                             cacheLoader();
741                         }
742                     };
743                 }
744                 else
745                 {
746                     script.onload = cacheLoader;
747                 }
748             }
749             else
750             {
751                 document.write('\n<script type="text/javascript" src="' + src + '"></script>\n');
752                 cacheLoader();
753             }
754         };
755 
756         var requiresVisualization = function(callback, scope)
757         {
758             requiresExt4Sandbox(function() {
759                 requiresLib('vis/vis', callback, scope);
760             }, scope);
761         };
762 
763         var setDirty = function (dirty)
764         {
765             configs.dirty = (dirty ? true : false); // only set to boolean
766         };
767 
768         var setSubmit = function (submit)
769         {
770             configs.submit = (submit ? true : false); // only set to boolean
771         };
772 
773         var showNavTrail = function()
774         {
775             var elem = document.getElementById("navTrailAncestors");
776             if(elem)
777                 elem.style.visibility = "visible";
778             elem = document.getElementById("labkey-nav-trail-current-page");
779             if(elem)
780                 elem.style.visibility = "visible";
781         };
782 
783         return {
784             /**
785              * A collection of properties related to the "current" LabKey Server container scope.
786              * The properties are as follows:
787              * <ul>
788              *     <li>formats: Java formatting strings as set in /admin-projectSettings.view
789              *         <ul>
790              *             <li>dateFormat: The display format for dates</li>
791              *             <li>dateTimeFormat: The display format for date-times</li>
792              *             <li>numberFormat: The display format for numbers</li>
793              *         </ul>
794              *     </li>
795              * </ul>
796              */
797             container: configs.container,
798 
799             /**
800              * This callback type is called 'requireCallback' and is displayed as a global symbol
801              *
802              * @callback requireCallback
803              */
804 
805             /**
806              * The DataRegion class allows you to interact with LabKey grids,
807              * including querying and modifying selection state, filters, and more.
808              * @field
809              */
810             DataRegions: configs.DataRegions,
811 
812             demoMode: configs.demoMode,
813             devMode: configs.devMode,
814             dirty: configs.dirty,
815             extJsRoot: configs.extJsRoot,
816             extJsRoot_42: configs.extJsRoot_42,
817             extThemeRoot: configs.extThemeRoot,
818             fieldMarker: configs.fieldMarker,
819             hash: configs.hash,
820             imagePath: configs.imagePath,
821             submit: configs.submit,
822             unloadMessage: configs.unloadMessage,
823             verbose: configs.verbose,
824             widget: configs.widget,
825 
826             /** @field */
827             contextPath: configs.contextPath,
828 
829             /**
830              * Appends an element to the head of the document
831              * @private
832              * @param {String} elemName First argument for docoument.createElement
833              * @param {Object} [attributes]
834              * @returns {*}
835              */
836             addElemToHead: addElemToHead,
837 
838             // TODO: Eligible for removal after util.js is migrated
839             addMarkup: addMarkup,
840             applyModuleContext: applyModuleContext,
841             beforeunload: beforeunload,
842             createElement: createElement,
843 
844             /**
845              * @function
846              * @param {String} moduleName The name of the module
847              * @returns {Object} The context object for this module.  The current view must have specifically requested
848              * the context for this module in its view XML
849              */
850             getModuleContext: getModuleContext,
851 
852             /**
853              * @function
854              * @param {String} moduleName The name of the module
855              * @param {String} property The property name to return
856              * @returns {String} The value of the module property.  Will return null if the property has not been set.
857              */
858             getModuleProperty: getModuleProperty,
859             getSubmit: getSubmit,
860             id: id,
861             init: init,
862             isDirty: isDirty,
863             loadScripts: loadScripts,
864             loadedScripts: loadedScripts,
865 
866             /**
867              * Loads a CSS file from the server.
868              * @function
869              * @param {(string|string[])} file - The path of the CSS file to load
870              * @example
871              <script type="text/javascript">
872                 LABKEY.requiresCss("myModule/myFile.css");
873              </script>
874              */
875             requiresCss: requiresCss,
876             requestedCssFiles: requestedCssFiles,
877             requiresClientAPI: requiresClientAPI,
878 
879             /**
880              * This can be added to any LABKEY page in order to load ExtJS 3.  This is the preferred method to declare Ext3 usage
881              * from wiki pages.  For HTML or JSP pages defined in a module, see our <a href="https://www.labkey.org/Documentation/wiki-page.view?name=scriptdepend">documentation</a> on declaration of client dependencies.
882              * @function
883              * @param {boolean} [immediate=true] - True to load the script immediately; false will defer script loading until the page has been downloaded.
884              * @param {requireCallback} [callback] - Callback for when all dependencies are loaded.
885              * @param {Object} [scope] - Scope of callback.
886              * @example
887              <script type="text/javascript">
888                 LABKEY.requiresExt3(true, function() {
889                     Ext.onReady(function() {
890                         // Ext 3 is loaded and ready
891                     });
892                 });
893              </script>
894              */
895             requiresExt3: requiresExt3,
896 
897             /**
898              * This can be added to any LABKEY page in order to load the LabKey ExtJS 3 Client API.
899              * @function
900              * @param {boolean} [immediate=true] - True to load the script immediately; false will defer script loading until the page has been downloaded.
901              * @param {requireCallback} [callback] - Callback for when all dependencies are loaded.
902              * @param {Object} [scope] - Scope of callback.
903              * @example
904              <script type="text/javascript">
905                  LABKEY.requiresExt3ClientAPI(true, function() {
906                     // your code here
907                  });
908              </script>
909              */
910             requiresExt3ClientAPI: requiresExt3ClientAPI,
911 
912             /**
913              * This can be added to any LABKEY page in order to load the LabKey ExtJS 4 Client API. This primarily
914              * consists of a set of utility methods {@link LABKEY.ext4.Util} and an extended Ext.data.Store {@link LABKEY.ext4.data.Store}.
915              * It will load ExtJS 4 as a dependency.
916              * @function
917              * @param {boolean} [immediate=true] - True to load the script immediately; false will defer script loading until the page has been downloaded.
918              * @param {requireCallback} [callback] - Callback for when all dependencies are loaded.
919              * @param {Object} [scope] - Scope of callback.
920              * @example
921              <script type="text/javascript">
922                  LABKEY.requiresExt4ClientAPI(true, function() {
923                     // your code here
924                  });
925              </script>
926              */
927             requiresExt4ClientAPI: requiresExt4ClientAPI,
928 
929             /**
930              * This can be added to any LABKEY page in order to load ExtJS 4.  This is the preferred method to declare Ext4 usage
931              * from wiki pages.  For HTML or JSP pages defined in a module, see our <a href="https://www.labkey.org/Documentation/wiki-page.view?name=scriptdepend">documentation</a> on declaration of client dependencies.
932              * @function
933              * @param {boolean} [immediate=true] - True to load the script immediately; false will defer script loading until the page has been downloaded.
934              * @param {requireCallback} [callback] - Callback for when all dependencies are loaded.
935              * @param {Object} [scope] - Scope of callback.
936              * @example
937              <script type="text/javascript">
938                  LABKEY.requiresExt4Sandbox(true, function() {
939                     Ext4.onReady(function(){
940                         // Ext4 is loaded and ready
941                     });
942                  });
943              </script>
944              */
945             requiresExt4Sandbox: requiresExt4Sandbox,
946 
947             /**
948              * Deprecated.  Use LABKEY.requiresExt3 instead.
949              * @function
950              * @private
951              */
952             requiresExtJs: requiresExt3,
953 
954             /**
955              * Loads JavaScript file(s) from the server.
956              * @function
957              * @param {(string|string[])} file - A file or Array of files to load.
958              * @param {Function} [callback] - Callback for when all dependencies are loaded.
959              * @param {Object} [scope] - Scope of callback.
960              * @param {boolean} [inOrder=false] - True to load the scripts in the order they are passed in. Default is false.
961              * @example
962              <script type="text/javascript">
963                 LABKEY.requiresScript("myModule/myScript.js", true, function() {
964                     // your script is loaded
965                 });
966              </script>
967              */
968             requiresScript: requiresScript,
969             requiresVisualization: requiresVisualization,
970             setDirty: setDirty,
971             setSubmit: setSubmit,
972             showNavTrail: showNavTrail
973         }
974     };
975 
976 }
977