1 /* 2 * Copyright (c) 2012-2017 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 };