1 /*
  2  * Copyright (c) 2012-2018 LabKey Corporation
  3  *
  4  * Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
  5  */
  6 Ext.namespace('LABKEY.ext');
  7 
  8 // TODO: Get these off the global 'Date' object
  9 Ext.ns("Date.patterns");
 10 Ext.applyIf(Date.patterns,{
 11     ISO8601Long:"Y-m-d H:i:s",
 12     ISO8601Short:"Y-m-d"
 13 });
 14 
 15 LABKEY.ext.Utils = new function() {
 16     var createHttpProxyImpl = function(containerPath, errorListener) {
 17         var proxy = new Ext.data.HttpProxy(new Ext.data.Connection({
 18             //where to retrieve data
 19             url: LABKEY.ActionURL.buildURL("query", "selectRows", containerPath), //url to data object (server side script)
 20             method: 'GET'
 21         }));
 22 
 23         if (errorListener)
 24             proxy.on("loadexception", errorListener);
 25 
 26         proxy.on("beforeload", mapQueryParameters);
 27 
 28         return proxy;
 29     };
 30 
 31     var mapQueryParameters = function(store, options) {
 32         // map all parameters from ext names to labkey names:
 33         for (var p in options)
 34         {
 35             if (options.hasOwnProperty(p)) {
 36                 if (_extParamMapping[p])
 37                     options[_extParamMapping[p]] = options[p];
 38             }
 39         }
 40 
 41         // fix up any necessary parameter values:
 42         if ("DESC" == options['query.sortdir'])
 43         {
 44             var sortCol = options['query.sort'];
 45             options['query.sort'] = "-" + sortCol;
 46         }
 47     };
 48 
 49     // migrated from util.js
 50     var handleTabsInTextArea = function(event) {
 51         // Check if the user hit TAB or SHIFT-TAB
 52         if (event.getKey() == Ext.EventObject.TAB && !event.ctrlKey && !event.altKey)
 53         {
 54             var t = event.target;
 55 
 56             if (Ext.isIE)
 57             {
 58                 var range = document.selection.createRange();
 59                 var stored_range = range.duplicate();
 60                 stored_range.moveToElementText(t);
 61                 stored_range.setEndPoint('EndToEnd', range);
 62                 t.selectionStart = stored_range.text.length - range.text.length;
 63                 t.selectionEnd = t.selectionStart + range.text.length;
 64                 t.setSelectionRange = function(start, end)
 65                 {
 66                     var range = this.createTextRange();
 67                     range.collapse(true);
 68                     range.moveStart("character", start);
 69                     range.moveEnd("character", end - start);
 70                     range.select();
 71                 };
 72             }
 73 
 74             var ss = t.selectionStart;
 75             var se = t.selectionEnd;
 76             var newSelectionStart = ss;
 77             var scrollTop = t.scrollTop;
 78 
 79             if (ss != se)
 80             {
 81                 // In case selection was not the entire line (e.g. selection begins in the middle of a line)
 82                 // we need to tab at the beginning as well as at the start of every following line.
 83                 var pre = t.value.slice(0,ss);
 84                 var sel = t.value.slice(ss,se);
 85                 var post = t.value.slice(se,t.value.length);
 86 
 87                 // If our selection starts in the middle of the line, include the full line
 88                 if (pre.length > 0 && pre.lastIndexOf('\n') != pre.length - 1)
 89                 {
 90                     // Add the beginning of the line to the indented area
 91                     sel = pre.slice(pre.lastIndexOf('\n') + 1, pre.length).concat(sel);
 92                     // Remove it from the prefix
 93                     pre = pre.slice(0, pre.lastIndexOf('\n') + 1);
 94                     if (!event.shiftKey)
 95                     {
 96                         // Add one to the starting index since we're going to add a tab before it
 97                         newSelectionStart++;
 98                     }
 99                 }
100                 // If our last selected character is a new line, don't add a tab after it since that's
101                 // part of the next line
102                 if (sel.lastIndexOf('\n') == sel.length - 1)
103                 {
104                     sel = sel.slice(0, sel.length - 1);
105                     post = '\n' + post;
106                 }
107 
108                 // Shift means remove indentation
109                 if (event.shiftKey)
110                 {
111                     // Remove one tab after each newline
112                     sel = sel.replace(/\n\t/g,"\n");
113                     if (sel.indexOf('\t') == 0)
114                     {
115                         // Remove one leading tab, if present
116                         sel = sel.slice(1, sel.length);
117                         // We're stripping out a tab before the selection, so march it back one character
118                         newSelectionStart--;
119                     }
120                 }
121                 else
122                 {
123                     pre = pre.concat('\t');
124                     sel = sel.replace(/\n/g,"\n\t");
125                 }
126 
127                 var originalLength = t.value.length;
128                 t.value = pre.concat(sel).concat(post);
129                 t.setSelectionRange(newSelectionStart, se + (t.value.length - originalLength));
130             }
131             // No text is selected
132             else
133             {
134                 // Shift means remove indentation
135                 if (event.shiftKey)
136                 {
137                     // Figure out where the current line starts
138                     var lineStart = t.value.slice(0, ss).lastIndexOf('\n');
139                     if (lineStart < 0)
140                     {
141                         lineStart = 0;
142                     }
143                     // Look for the first tab
144                     var tabIndex = t.value.slice(lineStart, ss).indexOf('\t');
145                     if (tabIndex != -1)
146                     {
147                         // The line has a tab - need to remove it
148                         tabIndex += lineStart;
149                         t.value = t.value.slice(0, tabIndex).concat(t.value.slice(tabIndex + 1, t.value.length));
150                         if (ss == se)
151                         {
152                             ss--;
153                             se = ss;
154                         }
155                         else
156                         {
157                             ss--;
158                             se--;
159                         }
160                     }
161                 }
162                 else
163                 {
164                     // Shove a tab in at the cursor
165                     t.value = t.value.slice(0,ss).concat('\t').concat(t.value.slice(ss,t.value.length));
166                     if (ss == se)
167                     {
168                         ss++;
169                         se = ss;
170                     }
171                     else
172                     {
173                         ss++;
174                         se++;
175                     }
176                 }
177                 t.setSelectionRange(ss, se);
178             }
179             t.scrollTop = scrollTop;
180 
181             // Don't let the browser treat it as a focus traversal
182             event.preventDefault();
183         }
184     };
185 
186     /**
187      * This method takes an object that is/extends an Ext3.Container (e.g. Panels, Toolbars, Viewports, Menus) and
188      * resizes it so the Container fits inside the its parent container.
189      * @param extContainer - (Required) outer container which is the target to be resized
190      * @param options - The set of options
191      * @param options.skipWidth - true to skip updating width, default false
192      * @param options.skipHeight - true to skip updating height, default false
193      * @param options.paddingWidth - total width padding
194      * @param options.paddingHeight - total height padding
195      * @param options.offsetY - distance between bottom of page to bottom of component
196      */
197     var resizeToContainer = function(extContainer, options) {
198         var config = {
199             offsetY: 35,
200             paddingHeight: 0,
201             paddingWidth: 0,
202             skipHeight: false,
203             skipWidth: false
204         };
205 
206         if (Ext.isObject(options)) {
207             config = Ext.apply(config, options);
208         }
209         // else ignore the parameters
210 
211         if (!extContainer || !extContainer.rendered || (config.skipWidth && config.skipHeight)) {
212             return;
213         }
214 
215         var height = 0;
216         var width = 0;
217 
218         if (!config.skipWidth) {
219             width = extContainer.el.parent().getBox().width;
220         }
221 
222 
223         if (!config.skipHeight) {
224             height = window.innerHeight - extContainer.el.getXY()[1];
225         }
226 
227         var padding = [
228             config.paddingWidth,
229             config.paddingHeight
230         ];
231 
232         var size = {
233             width: Math.max(100, width - padding[0]),
234             height: Math.max(100, height - padding[1] - config.offsetY)
235         };
236 
237         if (config.skipWidth) {
238             extContainer.setHeight(size.height);
239         }
240         else if (config.skipHeight) {
241             extContainer.setWidth(size.width);
242         }
243         else {
244             extContainer.setSize(size);
245         }
246 
247         extContainer.doLayout();
248     };
249 
250     return {
251         /**
252          * Creates an Ext.data.Store that queries the LabKey Server database and can be used as the data source
253          * for various components, including GridViews, ComboBoxes, and so forth.
254          * @deprecated
255          * @param {Object} config Describes the GridView's properties.
256          * @param {String} config.schemaName Name of a schema defined within the current
257          *                 container.  Example: 'study'.  See also: <a class="link"
258          href="https://www.labkey.org/Documentation/wiki-page.view?name=findNames">
259          How To Find schemaName, queryName & viewName</a>.
260          * @param {String} config.queryName Name of a query defined within the specified schema
261          *                 in the current container.  Example: 'SpecimenDetail'. See also: <a class="link"
262          href="https://www.labkey.org/Documentation/wiki-page.view?name=findNames">
263          How To Find schemaName, queryName & viewName</a>.
264          * @param {String} [config.containerPath] The container path in which the schemaName and queryName are defined.
265          * @param {String} [config.viewName] Name of a custom view defined over the specified query.
266          *                 in the current container. Example: 'SpecimenDetail'.  See also: <a class="link"
267          href="https://www.labkey.org/Documentation/wiki-page.view?name=findNames">
268          How To Find schemaName, queryName & viewName</a>.
269          * @param {Object} [config.allowNull] If specified, this configuration will be used to insert a blank
270          *                 entry as the first entry in the store.
271          * @param {String} [config.allowNull.keyColumn] If specified, the name of the column in the underlying database
272          *                 that holds the key.
273          * @param {String} [config.allowNull.displayColumn] If specified, the name of the column in the underlying database
274          *                 that holds the value to be shown by default in the display component.
275          * @param {String} [config.allowNull.emptyName] If specified, what to show in the list for the blank entry.
276          *                 Defaults to '[None]'.
277          * @param {String} [config.allowNull.emptyValue] If specified, the value to be used for the blank entry.
278          *                 Defaults to the empty string.
279          *
280          * @return {Ext.data.Store} The initialized Store object
281          */
282         createExtStore: function(storeConfig) {
283             if (!storeConfig)
284                 storeConfig = {};
285             if (!storeConfig.baseParams)
286                 storeConfig.baseParams = {};
287             storeConfig.baseParams['query.queryName'] = storeConfig.queryName;
288             storeConfig.baseParams['schemaName'] = storeConfig.schemaName;
289             if (storeConfig.viewName)
290                 storeConfig.baseParams['query.viewName'] = storeConfig.viewName;
291 
292             if (!storeConfig.proxy)
293                 storeConfig.proxy = createHttpProxyImpl(storeConfig.containerPath);
294 
295             if (!storeConfig.remoteSort)
296                 storeConfig.remoteSort = true;
297 
298             if (!storeConfig.listeners || !storeConfig.listeners.loadexception)
299                 storeConfig.listeners = { loadexception : { fn : handleLoadError } };
300 
301             storeConfig.reader = new Ext.data.JsonReader();
302 
303             var result = new Ext.data.Store(storeConfig);
304 
305             if (storeConfig.allowNull)
306             {
307                 var emptyValue = storeConfig.allowNull.emptyValue;
308                 if (!emptyValue)
309                 {
310                     emptyValue = "";
311                 }
312                 var emptyName = storeConfig.allowNull.emptyName;
313                 if (!emptyName)
314                 {
315                     emptyName = "[None]";
316                 }
317                 result.on("load", function(store)
318                 {
319                     var emptyRecordConstructor = Ext.data.Record.create([storeConfig.allowNull.keyColumn, storeConfig.allowNull.displayColumn]);
320                     var recordData = {};
321                     recordData[storeConfig.allowNull.keyColumn] = emptyValue;
322                     recordData[storeConfig.allowNull.displayColumn] = emptyName;
323                     var emptyRecord = new emptyRecordConstructor(recordData);
324                     store.insert(0, emptyRecord);
325                 });
326             }
327 
328             return result;
329         },
330 
331         /**
332          * Ensure BoxComponent is visible on the page.
333          * @param boxComponent
334          * @deprecated
335          */
336         ensureBoxVisible: function(boxComponent) {
337             var box = boxComponent.getBox(true);
338             var viewportWidth = Ext.lib.Dom.getViewWidth();
339             var scrollLeft = Ext.dd.DragDropMgr.getScrollLeft();
340 
341             var scrollBarWidth = 20;
342             if (viewportWidth - scrollBarWidth + scrollLeft < box.width + box.x) {
343                 boxComponent.setPosition(viewportWidth + scrollLeft - box.width - scrollBarWidth);
344             }
345         },
346 
347         /**
348          * Event handler that can be attached to text areas to let them handle indent/outdent with TAB/SHIFT-TAB.
349          * Handles region selection for multi-line indenting as well.
350          * Note that this overrides the browser's standard focus traversal keystrokes.
351          * Based off of postings from http://ajaxian.com/archives/handling-tabs-in-textareas
352          * Wire it up with a call like:
353          *     Ext.EventManager.on('queryText', 'keydown', LABKEY.ext.Utils.handleTabsInTextArea);
354          * @param event an Ext.EventObject for the keydown event
355          */
356         handleTabsInTextArea: handleTabsInTextArea,
357 
358         /**
359          * This method takes an object that is/extends an Ext.Container (e.g. Panels, Toolbars, Viewports, Menus) and
360          * resizes it so the Container fits inside the viewable region of the window. This is generally used in the case
361          * where the Container is not rendered to a webpart but rather displayed on the page itself (e.g. SchemaBrowser,
362          * manageFolders, etc).
363          * @param extContainer - (Required) outer container which is the target to be resized
364          * @param width - (Required) width of the viewport. In many cases, the window width. If a negative width is passed than
365          *                           the width will not be set.
366          * @param height - (Required) height of the viewport. In many cases, the window height. If a negative height is passed than
367          *                           the height will not be set.
368          * @param paddingX - distance from the right edge of the viewport. Defaults to 35.
369          * @param paddingY - distance from the bottom edge of the viewport. Defaults to 35.
370          */
371         resizeToViewport: function(extContainer, width, height, paddingX, paddingY, offsetX, offsetY)
372         {
373             resizeToContainer.apply(this, arguments);
374         }
375     };
376 };