1 /*
  2  * Copyright (c) 2012-2014 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     return {
187         /**
188          * Creates an Ext.data.Store that queries the LabKey Server database and can be used as the data source
189          * for various components, including GridViews, ComboBoxes, and so forth.
190          * @deprecated
191          * @param {Object} config Describes the GridView's properties.
192          * @param {String} config.schemaName Name of a schema defined within the current
193          *                 container.  Example: 'study'.  See also: <a class="link"
194          href="https://www.labkey.org/wiki/home/Documentation/page.view?name=findNames">
195          How To Find schemaName, queryName & viewName</a>.
196          * @param {String} config.queryName Name of a query defined within the specified schema
197          *                 in the current container.  Example: 'SpecimenDetail'. See also: <a class="link"
198          href="https://www.labkey.org/wiki/home/Documentation/page.view?name=findNames">
199          How To Find schemaName, queryName & viewName</a>.
200          * @param {String} [config.containerPath] The container path in which the schemaName and queryName are defined.
201          * @param {String} [config.viewName] Name of a custom view defined over the specified query.
202          *                 in the current container. Example: 'SpecimenDetail'.  See also: <a class="link"
203          href="https://www.labkey.org/wiki/home/Documentation/page.view?name=findNames">
204          How To Find schemaName, queryName & viewName</a>.
205          * @param {Object} [config.allowNull] If specified, this configuration will be used to insert a blank
206          *                 entry as the first entry in the store.
207          * @param {String} [config.allowNull.keyColumn] If specified, the name of the column in the underlying database
208          *                 that holds the key.
209          * @param {String} [config.allowNull.displayColumn] If specified, the name of the column in the underlying database
210          *                 that holds the value to be shown by default in the display component.
211          * @param {String} [config.allowNull.emptyName] If specified, what to show in the list for the blank entry.
212          *                 Defaults to '[None]'.
213          * @param {String} [config.allowNull.emptyValue] If specified, the value to be used for the blank entry.
214          *                 Defaults to the empty string.
215          *
216          * @return {Ext.data.Store} The initialized Store object
217          */
218         createExtStore: function(storeConfig) {
219             if (!storeConfig)
220                 storeConfig = {};
221             if (!storeConfig.baseParams)
222                 storeConfig.baseParams = {};
223             storeConfig.baseParams['query.queryName'] = storeConfig.queryName;
224             storeConfig.baseParams['schemaName'] = storeConfig.schemaName;
225             if (storeConfig.viewName)
226                 storeConfig.baseParams['query.viewName'] = storeConfig.viewName;
227 
228             if (!storeConfig.proxy)
229                 storeConfig.proxy = createHttpProxyImpl(storeConfig.containerPath);
230 
231             if (!storeConfig.remoteSort)
232                 storeConfig.remoteSort = true;
233 
234             if (!storeConfig.listeners || !storeConfig.listeners.loadexception)
235                 storeConfig.listeners = { loadexception : { fn : handleLoadError } };
236 
237             storeConfig.reader = new Ext.data.JsonReader();
238 
239             var result = new Ext.data.Store(storeConfig);
240 
241             if (storeConfig.allowNull)
242             {
243                 var emptyValue = storeConfig.allowNull.emptyValue;
244                 if (!emptyValue)
245                 {
246                     emptyValue = "";
247                 }
248                 var emptyName = storeConfig.allowNull.emptyName;
249                 if (!emptyName)
250                 {
251                     emptyName = "[None]";
252                 }
253                 result.on("load", function(store)
254                 {
255                     var emptyRecordConstructor = Ext.data.Record.create([storeConfig.allowNull.keyColumn, storeConfig.allowNull.displayColumn]);
256                     var recordData = {};
257                     recordData[storeConfig.allowNull.keyColumn] = emptyValue;
258                     recordData[storeConfig.allowNull.displayColumn] = emptyName;
259                     var emptyRecord = new emptyRecordConstructor(recordData);
260                     store.insert(0, emptyRecord);
261                 });
262             }
263 
264             return result;
265         },
266 
267         /**
268          * Ensure BoxComponent is visible on the page.
269          * @param boxComponent
270          * @deprecated
271          */
272         ensureBoxVisible: function(boxComponent) {
273             var box = boxComponent.getBox(true);
274             var viewportWidth = Ext.lib.Dom.getViewWidth();
275             var scrollLeft = Ext.dd.DragDropMgr.getScrollLeft();
276 
277             var scrollBarWidth = 20;
278             if (viewportWidth - scrollBarWidth + scrollLeft < box.width + box.x) {
279                 boxComponent.setPosition(viewportWidth + scrollLeft - box.width - scrollBarWidth);
280             }
281         },
282 
283         /**
284          * Event handler that can be attached to text areas to let them handle indent/outdent with TAB/SHIFT-TAB.
285          * Handles region selection for multi-line indenting as well.
286          * Note that this overrides the browser's standard focus traversal keystrokes.
287          * Based off of postings from http://ajaxian.com/archives/handling-tabs-in-textareas
288          * Wire it up with a call like:
289          *     Ext.EventManager.on('queryText', 'keydown', LABKEY.ext.Utils.handleTabsInTextArea);
290          * @param event an Ext.EventObject for the keydown event
291          */
292         handleTabsInTextArea: handleTabsInTextArea,
293 
294         /**
295          * This method takes an object that is/extends an Ext.Container (e.g. Panels, Toolbars, Viewports, Menus) and
296          * resizes it so the Container fits inside the viewable region of the window. This is generally used in the case
297          * where the Container is not rendered to a webpart but rather displayed on the page itself (e.g. SchemaBrowser,
298          * manageFolders, etc).
299          * @param extContainer - (Required) outer container which is the target to be resized
300          * @param width - (Required) width of the viewport. In many cases, the window width. If a negative width is passed than
301          *                           the width will not be set.
302          * @param height - (Required) height of the viewport. In many cases, the window height. If a negative height is passed than
303          *                           the height will not be set.
304          * @param paddingX - distance from the right edge of the viewport. Defaults to 35.
305          * @param paddingY - distance from the bottom edge of the viewport. Defaults to 35.
306          */
307         resizeToViewport: function(extContainer, width, height, paddingX, paddingY, offsetX, offsetY)
308         {
309             if (!extContainer || !extContainer.rendered)
310                 return;
311 
312             if (width < 0 && height < 0)
313                 return;
314 
315             var padding = [];
316             if (offsetX == undefined || offsetX == null)
317                 offsetX = 35;
318             if (offsetY == undefined || offsetY == null)
319                 offsetY = 35;
320 
321             if (paddingX !== undefined && paddingX != null)
322                 padding.push(paddingX);
323             else
324             {
325 
326                 var bp = Ext.get('bodypanel');
327                 if (bp) {
328                     var t  = Ext.query('table.labkey-proj');
329                     if (t && t.length > 0) {
330                         t = Ext.get(t[0]);
331                         padding.push((t.getWidth()-(bp.getWidth())) + offsetX);
332                     }
333                     else
334                         padding.push(offsetX);
335                 }
336                 else
337                     padding.push(offsetX);
338             }
339             if (paddingY !== undefined && paddingY != null)
340                 padding.push(paddingY);
341             else
342                 padding.push(offsetY);
343 
344             var xy = extContainer.el.getXY();
345             var size = {
346                 width  : Math.max(100,width-xy[0]-padding[0]),
347                 height : Math.max(100,height-xy[1]-padding[1])
348             };
349 
350             if (width < 0)
351                 extContainer.setHeight(size.height);
352             else if (height < 0)
353                 extContainer.setWidth(size.width);
354             else
355                 extContainer.setSize(size);
356             extContainer.doLayout();
357         }
358     };
359 };