1 /**
  2  * @fileOverview
  3  * @author <a href="https://www.labkey.org">LabKey</a> (<a href="mailto:info@labkey.com">info@labkey.com</a>)
  4  * @license Copyright (c) 2014-2018 LabKey Corporation
  5  * <p/>
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  * <p/>
 10  * http://www.apache.org/licenses/LICENSE-2.0
 11  * <p/>
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing permissions and
 16  * limitations under the License.
 17  * <p/>
 18  */
 19 
 20 LABKEY.Utils = new function(impl, $) {
 21 
 22     // Insert a hidden html FORM into to page, put the JSON into it, and submit it - the server's response will
 23     // make the browser pop up a dialog
 24     var formSubmit = function(url, value)
 25     {
 26         var formId = LABKEY.Utils.generateUUID();
 27         var formHTML = '<f' +       // avoid form tag, it causes skipfish false positive
 28                 'orm method="POST" id="' + formId + '" action="' + url + '">' +
 29                 '<input type="hidden" name="json" value="' + LABKEY.Utils.encodeHtml(LABKEY.Utils.encode(value)) + '" />' +
 30                 '<input type="hidden" name="X-LABKEY-CSRF" value="' + LABKEY.CSRF + '" />' +
 31                 '</form>';
 32         $('body').append(formHTML);
 33         $('#'+formId).submit();
 34     };
 35 
 36     var displayModalAlert = function(title, msg) {
 37        displayModal(title, msg, undefined);
 38     };
 39 
 40     var displayModal = function(title, msg, fn, args) {
 41         var modal = $('#lk-utils-modal');
 42 
 43         if (modal.length === 0) {
 44             $('body').append([
 45                 '<div id="lk-utils-modal" class="modal fade" role="dialog">',
 46                 '<div class="modal-dialog"><div class="modal-content"></div></div>',
 47                 '</div>'
 48             ].join(''));
 49 
 50             modal = $('#lk-utils-modal');
 51         }
 52         var html = [
 53             '<div class="modal-header">',
 54                 '<button type="button" class="close" data-dismiss="modal">×</button>',
 55                 '<h4 class="modal-title">' + LABKEY.Utils.encodeHtml(title) + '</h4>',
 56             '</div>',
 57             '<div class="modal-body">'
 58         ];
 59         if (msg) {
 60             html.push('<br><p>' + LABKEY.Utils.encodeHtml(msg) + '<br></p>');
 61         }
 62          html.push(
 63                  '<div id="modal-fn-body"></div>',
 64                  '</div>'
 65          );
 66 
 67         modal.find('.modal-content').html(html.join(''));
 68         if (fn && typeof fn === 'function')
 69             fn.apply(this, args);
 70 
 71         modal.modal('show');
 72     };
 73 
 74     /**
 75      * Documentation available in core/Utils.js -- search for "@name displayAjaxErrorResponse"
 76      */
 77     impl.displayAjaxErrorResponse = function(responseObj, exceptionObj, showExceptionClass, msgPrefix)
 78     {
 79         if (responseObj.status == 0)
 80         {
 81             // Don't show an error dialog if the user cancelled the request in the browser, like navigating
 82             // to another page
 83             return;
 84         }
 85 
 86         var error = LABKEY.Utils.getMsgFromError(responseObj, exceptionObj, {
 87             msgPrefix: msgPrefix,
 88             showExceptionClass: showExceptionClass
 89         });
 90         LABKEY.Utils.alert("Error", error);
 91     };
 92 
 93     /**
 94      * Documentation available in core/Utils.js -- search for "@name convertToExcel"
 95      */
 96     impl.convertToExcel = function(spreadsheet) {
 97         formSubmit(LABKEY.ActionURL.buildURL("experiment", "convertArraysToExcel"), spreadsheet);
 98     };
 99 
100     /**
101      * Documentation available in core/Utils.js -- search for "@name convertToTable"
102      */
103     impl.convertToTable = function(config) {
104         formSubmit(LABKEY.ActionURL.buildURL("experiment", "convertArraysToTable"), config);
105     };
106 
107     /**
108      * Documentation specified in core/Utils.js -- search for "@name alert"
109      */
110     impl.alert = function(title, msg) {
111         if (window.Ext4) {
112             Ext4.Msg.alert(title?Ext4.htmlEncode(title):"", msg?Ext4.htmlEncode(msg):"")
113         }
114         else if (window.Ext) {
115             Ext.Msg.alert(title?Ext.util.Format.htmlEncode(title):"", msg?Ext.util.Format.htmlEncode(msg):"");
116         }
117         else {
118             displayModalAlert(title, msg);
119         }
120     };
121 
122     /**
123      * Documentation specified in core/Utils.js -- search for "@name modal"
124      */
125     impl.modal = function(title, msg, fn, args) {
126       displayModal(title, msg, fn, args);
127     };
128 
129     /**
130      * Documentation specified in core/Utils.js -- search for "@name onError"
131      */
132     impl.onError = function(error) {
133 
134         if (!error)
135             return;
136 
137         console.log('ERROR: ' + error.exception);
138         console.log(error);
139 
140         if (!error.noAuditLog)
141         {
142             LABKEY.Query.insertRows({
143                 //it would be nice to store them in the current folder, but we cant guarantee the user has write access..
144                 containerPath: error.containerPath || '/shared',
145                 schemaName: 'auditlog',
146                 queryName: 'Client API Actions',
147                 rows: [{
148                     EventType: "Client API Actions",
149                     Key1: 'Client Error',
150                     //NOTE: labkey should automatically crop these strings to the allowable length for that field
151                     Key2: window.location.href,
152                     Key3: (error.stackTrace && LABKEY.Utils.isArray(error.stackTrace) ? error.stackTrace.join('\n') : null),
153                     Comment: (error.exception || error.statusText || error.message),
154                     Date: new Date()
155                 }],
156                 success: function() {},
157                 failure: function(error){
158                     console.log('Problem logging error');
159                     console.log(error);
160                 }
161             });
162         }
163     };
164 
165     /**
166      * Documentation specified in core/Utils.js -- search for "@name setWebpartTitle"
167      */
168     impl.setWebpartTitle = function(title, webPartId)
169     {
170         $('#webpart_' + webPartId + ' span.labkey-wp-title-text').html(LABKEY.Utils.encodeHtml(title));
171     };
172 
173     /**
174      * Documentation specified in core/Utils.js -- search for "@name onReady"
175      */
176     impl.onReady = function(config)
177     {
178         var scope;
179         var callback;
180         var scripts;
181 
182         if (LABKEY.Utils.isFunction(config))
183         {
184             scope = this;
185             callback = config;
186             scripts = null;
187         }
188         else if (LABKEY.Utils.isObject(config) && LABKEY.Utils.isFunction(config.callback))
189         {
190             scope = config.scope || this;
191             callback = config.callback;
192             scripts = config.scripts;
193         }
194         else
195         {
196             LABKEY.Utils.alert("Configuration Error", "Improper configuration for LABKEY.onReady()");
197             return;
198         }
199 
200         if (scripts)
201         {
202             LABKEY.requiresScript(scripts, callback, scope, true);
203         }
204         else
205         {
206             $(function() { callback.call(scope); });
207         }
208     };
209 
210     impl.addClass = function(element, cls)
211     {
212         if (LABKEY.Utils.isDefined(element))
213         {
214             if (LABKEY.Utils.isDefined(element.classList))
215             {
216                 element.classList.add(cls);
217             }
218             else
219             {
220                 element.className += " " + cls;
221             }
222         }
223     };
224 
225     impl.removeClass = function(element, cls)
226     {
227         if (LABKEY.Utils.isDefined(element))
228         {
229             if (LABKEY.Utils.isDefined(element.classList))
230             {
231                 element.classList.remove(cls);
232             }
233             else
234             {
235                 // http://stackoverflow.com/questions/195951/change-an-elements-css-class-with-javascript
236                 var reg = new RegExp("(?:^|\\s)" + cls + "(?!\\S)/g");
237                 element.className.replace(reg, '');
238             }
239         }
240     };
241 
242     impl.replaceClass = function(element, removeCls, addCls)
243     {
244         LABKEY.Utils.removeClass(element, removeCls);
245         LABKEY.Utils.addClass(element, addCls);
246     };
247 
248     //private
249     impl.loadAjaxContent = function(response, targetEl, success, scope, useReplace) {
250         var json = LABKEY.Utils.decode(response.responseText);
251         if (!json)
252             return;
253 
254         if (json.moduleContext)
255             LABKEY.applyModuleContext(json.moduleContext);
256 
257         if (json.requiredCssScripts)
258             LABKEY.requiresCss(json.requiredCssScripts);
259 
260         if (json.implicitCssIncludes)
261         {
262             for (var i=0;i<json.implicitCssIncludes.length;i++)
263             {
264                 LABKEY.requestedCssFiles(json.implicitCssIncludes[i]);
265             }
266         }
267 
268         if (json.requiredJsScripts && json.requiredJsScripts.length)
269         {
270             LABKEY.requiresScript(json.requiredJsScripts, onLoaded, this, true);
271         }
272         else
273         {
274             onLoaded();
275         }
276 
277         function onLoaded()
278         {
279             if (json.html)
280             {
281                 if (LABKEY.Utils.isString(targetEl)) {
282                     targetEl = $('#'+targetEl);
283                 }
284 
285                 if (useReplace === true) {
286                     targetEl.replaceWith(json.html);
287                 }
288                 else {
289                     targetEl.html(json.html); // execute scripts...so bad
290                 }
291 
292                 if (LABKEY.Utils.isFunction(success)) {
293                     success.call(scope || window);
294                 }
295 
296                 if (json.implicitJsIncludes)
297                     LABKEY.loadedScripts(json.implicitJsIncludes);
298             }
299         }
300     };
301 
302     impl.tabInputHandler = function(elementSelector) {
303         // http://stackoverflow.com/questions/1738808/keypress-in-jquery-press-tab-inside-textarea-when-editing-an-existing-text
304         $(elementSelector).keydown(function (e) {
305             if (e.keyCode == 9) {
306                 var myValue = "\t";
307                 var startPos = this.selectionStart;
308                 var endPos = this.selectionEnd;
309                 var scrollTop = this.scrollTop;
310                 this.value = this.value.substring(0, startPos) + myValue + this.value.substring(endPos,this.value.length);
311                 this.focus();
312                 this.selectionStart = startPos + myValue.length;
313                 this.selectionEnd = startPos + myValue.length;
314                 this.scrollTop = scrollTop;
315 
316                 e.preventDefault();
317             }
318         });
319     };
320 
321     impl.signalWebDriverTest = function(signalName, signalResult)
322     {
323         var signalContainerId = 'testSignals';
324         var signalContainerSelector = '#' + signalContainerId;
325         var signalContainer = $(signalContainerSelector);
326         var formHTML = '<div id="' + signalContainerId + '"/>';
327 
328         if (!signalContainer.length)
329         {
330             $('body').append(formHTML);
331             signalContainer = $(signalContainerSelector);
332             signalContainer.hide();
333         }
334 
335         signalContainer.find('div[name=' + LABKEY.Utils.encode(signalName) + ']').remove();
336         signalContainer.append('<div name="' + LABKEY.Utils.encodeHtml(signalName) + '" id="' + LABKEY.Utils.id() + '"/>');
337         if (signalResult !== undefined)
338         {
339             signalContainer.find('div[name="' + LABKEY.Utils.encodeHtml(signalName) + '"]').attr("value", LABKEY.Utils.encodeHtml(signalResult));
340         }
341     };
342 
343     /**
344      * Returns a string containing an absolute URL to a specific labkey.org documentation page. Modeled after HelpTopic.java getHelpTopicHref().
345      * <li>topic (required) The documentation page name</li>
346      */
347     impl.getHelpTopicHref = function(topic)
348     {
349         return LABKEY.helpLinkPrefix + topic;
350     };
351 
352     /**
353      * Returns a string containing a well-formed html anchor that opens a link to a specific labkey.org documentation
354      * page in a separate tab, using the standard target name. Modeled after HelpTopic.java getSimpleLinkHtml().
355      * <li>topic (required) The documentation page name</li>
356      * <li>displayText (required) The text to display inside the anchor</li>
357      */
358     impl.getSimpleLinkHtml = function(topic, displayText)
359     {
360         return '<a href="' + LABKEY.Utils.encodeHtml(LABKEY.Utils.getHelpTopicHref(topic)) + '" target="labkeyHelp">' + LABKEY.Utils.encodeHtml(displayText) + "</a>";
361     };
362 
363     return impl;
364 
365 }(LABKEY.Utils || new function() { return {}; }, jQuery);
366