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-2017 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 LABKEY.Query = new function(impl, $) { 20 21 // Insert a hidden <form> into to page, put the JSON into it, and submit it - the server's response 22 // will make the browser pop up a dialog 23 function submitForm(url, formData) { 24 if (!formData['X-LABKEY-CSRF']) 25 formData['X-LABKEY-CSRF'] = LABKEY.CSRF; 26 27 var formId = LABKEY.Utils.generateUUID(); 28 29 var html = '<form method="POST" id="' + formId + '"action="' + url + '">'; 30 for (var name in formData) 31 { 32 if (!formData.hasOwnProperty(name)) 33 continue; 34 35 var value = formData[name]; 36 if (value == undefined) 37 continue; 38 39 html += '<input type="hidden"' + 40 ' name="' + LABKEY.Utils.encodeHtml(name) + '"' + 41 ' value="' + LABKEY.Utils.encodeHtml(value) + '" />'; 42 } 43 html += "</form>"; 44 45 $('body').append(html); 46 $('form#' + formId).submit(); 47 } 48 49 /** 50 * Documentation specified in core/Query.js -- search for "@name exportSql" 51 */ 52 impl.exportSql = function(config) { 53 54 var url = LABKEY.ActionURL.buildURL("query", "exportSql", config.containerPath); 55 var formData = { 56 sql: config.sql, 57 schemaName: config.schemaName, 58 format: config.format, 59 containerFilter: config.containerFilter 60 }; 61 62 submitForm(url, formData); 63 }; 64 65 /** 66 * @private Not yet official API 67 * Export a set of tables 68 * @param config An object which contains the following: 69 * @param {String} config.schemas An object with the following structure: 70 * <pre> 71 * { 72 * schemas: { 73 * 74 * // export the named queries from schema "A" using the default view or the named view 75 * "A": [{ 76 * queryName: "a" 77 * filters: [ LABKEY.Filters.create("Name", "bob", LABKEY.Filter.Types.NEQ) ], 78 * sort: "Name" 79 * },{ 80 * queryName: "b", 81 * viewName: "b-view" 82 * }] 83 * 84 * } 85 * } 86 * </pre> 87 * @param {String} [config.headerType] Column header type 88 * 89 */ 90 impl.exportTables = function(config) { 91 92 var formData = {}; 93 94 if (config.headerType) 95 formData.headerType = config.headerType; 96 97 // Create a copy of the schema config that we can mutate 98 var schemas = LABKEY.Utils.merge({}, config.schemas); 99 for (var schemaName in schemas) 100 { 101 if (!schemas.hasOwnProperty(schemaName)) 102 continue; 103 104 var queryList = schemas[schemaName]; 105 for (var i = 0; i < queryList.length; i++) 106 { 107 var querySettings = queryList[i]; 108 var o = LABKEY.Utils.merge({}, querySettings); 109 110 delete o.filter; 111 delete o.filterArray; 112 delete o.sort; 113 114 // Turn the filters array into a filters map similar to LABKEY.QueryWebPart 115 o.filters = LABKEY.Filter.appendFilterParams(null, querySettings.filters || querySettings.filterArray); 116 117 if (querySettings.sort) 118 o.filters["query.sort"] = querySettings.sort; 119 120 queryList[i] = o; 121 } 122 } 123 124 formData.schemas = JSON.stringify(schemas); 125 126 var url = LABKEY.ActionURL.buildURL("query", "exportTables.view"); 127 submitForm(url, formData); 128 }; 129 130 function loadingSelect(select) { 131 select.prop('disabled', true); 132 select.empty().append($('<option>', {text: 'Loading...'})); 133 } 134 135 function populateSelect(select, options, valueProperty, textProperty, initialValue) { 136 select.empty().append($('<option>')); 137 $.each(options, function (i, option) { 138 var value = valueProperty ? option[valueProperty] : option; 139 var text = textProperty ? option[textProperty] : option; 140 var selected = initialValue && value === initialValue; 141 select.append($('<option>', { value: value, text: text, selected: selected})); 142 }); 143 144 select.prop('disabled', false); 145 select.on('change', function(){ 146 if (initialValue != select.val()) 147 LABKEY.setDirty(true); 148 }); 149 } 150 151 function sortObjectArrayByTitle(a, b){ 152 var aTitle = a.title ? a.title : a.caption; 153 var bTitle = b.title ? b.title : b.caption; 154 return aTitle.localeCompare(bTitle); 155 } 156 157 var SCHEMA_QUERIES_CACHE = {}; // cache of queries by schema 158 function loadQueries(schemaSelect, querySelect, selectedSchema, initialValue) { 159 schemaSelect.prop('disabled', true); 160 loadingSelect(querySelect); 161 162 if (SCHEMA_QUERIES_CACHE[selectedSchema]) { 163 populateSelect(querySelect, SCHEMA_QUERIES_CACHE[selectedSchema], 'name', 'title', initialValue); 164 schemaSelect.prop('disabled', false); 165 } 166 else { 167 LABKEY.Query.getQueries({ 168 schemaName: selectedSchema, 169 includeColumns: false, 170 success: function(data) { 171 // add the sorted set of queries for this schema to the cache 172 SCHEMA_QUERIES_CACHE[selectedSchema] = data.queries.sort(sortObjectArrayByTitle); 173 174 populateSelect(querySelect, SCHEMA_QUERIES_CACHE[selectedSchema], 'name', 'title', initialValue); 175 schemaSelect.prop('disabled', false); 176 177 // if there is a selected query, fire the change event 178 if (querySelect.val()) { 179 querySelect.trigger('change'); 180 } 181 } 182 }); 183 } 184 } 185 186 var QUERY_COLUMNS_CACHE = {}; // cache of columns by schema|query 187 function loadQueryColumns(select, schemaName, queryName, filterFn, initValue) { 188 loadingSelect(select); 189 190 var queryKey = schemaName + '|' + queryName; 191 if (QUERY_COLUMNS_CACHE[queryKey]) { 192 populateColumnsWithFilterFn(select, QUERY_COLUMNS_CACHE[queryKey], filterFn, initValue); 193 } 194 else { 195 LABKEY.Query.getQueryDetails({ 196 schemaName: schemaName, 197 queryName: queryName, 198 success: function(data) { 199 // find the default view from the views array returned 200 // NOTE: in the future if we allow this to work for view other than the default, this logic will need to change 201 var queryView = null; 202 $.each(data.views, function(i, view) { 203 if (view['default']) { 204 queryView = view; 205 return false; 206 } 207 }); 208 209 QUERY_COLUMNS_CACHE[queryKey] = []; 210 if (queryView) { 211 QUERY_COLUMNS_CACHE[queryKey] = queryView.fields.sort(sortObjectArrayByTitle); 212 } 213 214 populateColumnsWithFilterFn(select, QUERY_COLUMNS_CACHE[queryKey], filterFn, initValue); 215 } 216 }); 217 } 218 } 219 220 function populateColumnsWithFilterFn(select, origFields, filterFn, initValue) { 221 var fields = []; 222 $.each(origFields, function(i, field) { 223 var includeField = true; 224 225 // allow for a filter function to be called for each field 226 if (filterFn && LABKEY.Utils.isFunction(filterFn)) { 227 includeField = filterFn.call(this, field); 228 } 229 230 if (includeField) { 231 fields.push($.extend({}, field)); 232 } 233 }); 234 235 if (fields.length > 0) { 236 populateSelect(select, fields, 'name', 'caption', initValue); 237 } 238 else { 239 select.empty().append($('<option>', {text: 'No columns available'})); 240 } 241 } 242 243 /** 244 * Documentation specified in core/Query.js -- search for "@name schemaSelectInput" 245 */ 246 impl.schemaSelectInput = function(config) { 247 var SCHEMA_SELECT; 248 249 if (!config || !config.renderTo) { 250 console.error('Invalid config object. Missing renderTo property for the <select> element.'); 251 return; 252 } 253 254 SCHEMA_SELECT = $("select[id='" + config.renderTo + "']"); 255 if (SCHEMA_SELECT.length !== 1) { 256 console.error('Invalid config object. Expect to find exactly one <select> element for the renderTo provided (found: ' + SCHEMA_SELECT.length + ').'); 257 return; 258 } 259 260 loadingSelect(SCHEMA_SELECT); 261 LABKEY.Query.getSchemas({ 262 includeHidden: false, 263 success: function(data) { 264 populateSelect(SCHEMA_SELECT, data.schemas.sort(), null, null, config.initValue); 265 266 // if there is a selected schema, fire the change event 267 if (SCHEMA_SELECT.val()) { 268 SCHEMA_SELECT.trigger('change', [SCHEMA_SELECT.val()]); 269 } 270 } 271 }); 272 }; 273 274 /** 275 * Documentation specified in core/Query.js -- search for "@name querySelectInput" 276 */ 277 impl.querySelectInput = function(config) { 278 var SCHEMA_SELECT, QUERY_SELECT; 279 280 if (!config || !config.renderTo || !config.schemaInputId) { 281 var msg = 'Invalid config object. '; 282 if (!config.renderTo) { 283 msg += 'Missing renderTo property for the <select> element. '; 284 } 285 if (!config.schemaInputId) { 286 msg += 'Missing schemaInputId property for the parent <select> element. '; 287 } 288 console.error(msg); 289 return; 290 } 291 292 QUERY_SELECT = $("select[id='" + config.renderTo + "']"); 293 if (QUERY_SELECT.length !== 1) { 294 console.error('Invalid config object. Expect to find exactly one <select> element with the name provided (found: ' + QUERY_SELECT.length + ').'); 295 return; 296 } 297 298 SCHEMA_SELECT = $("select[id='" + config.schemaInputId + "']"); 299 if (SCHEMA_SELECT.length !== 1) { 300 console.error('Invalid config object. Expect to find exactly one <select> element with the name provided (found: ' + SCHEMA_SELECT.length + ').'); 301 return; 302 } 303 304 SCHEMA_SELECT.on('change', function (event, schemaName) { 305 loadQueries(SCHEMA_SELECT, QUERY_SELECT, schemaName || event.target.value, config.initValue); 306 }); 307 }; 308 309 /** 310 * Documentation specified in core/Query.js -- search for "@name columnSelectInput" 311 */ 312 impl.columnSelectInput = function(config) { 313 var COLUMN_SELECT; 314 315 if (!config || !config.renderTo || !config.schemaName || !config.queryName) { 316 var msg = 'Invalid config object. '; 317 if (!config.renderTo) { 318 msg += 'Missing renderTo property for the <select> element. '; 319 } 320 if (!config.schemaName) { 321 msg += 'Missing schemaName property. '; 322 } 323 if (!config.queryName) { 324 msg += 'Missing queryName property. '; 325 } 326 console.error(msg); 327 return; 328 } 329 330 COLUMN_SELECT = $("select[id='" + config.renderTo + "']"); 331 if (COLUMN_SELECT.length !== 1) { 332 console.error('Invalid config object. Expect to find exactly one <select> element with the name provided (found: ' + COLUMN_SELECT.length + ').'); 333 return; 334 } 335 336 loadQueryColumns(COLUMN_SELECT, config.schemaName, config.queryName, config.filterFn, config.initValue); 337 }; 338 339 /** 340 * Documentation specified in core/Query.js -- search for "@name importData" 341 */ 342 impl.importData = function(config) { 343 if (!window.FormData) { 344 throw new Error('modern browser required'); 345 } 346 347 var form = new FormData(); 348 349 form.append('schemaName', config.schemaName); 350 form.append('queryName', config.queryName); 351 if (config.text) 352 form.append('text', config.text); 353 if (config.path) 354 form.append('path', config.path); 355 if (config.format) 356 form.append('format', config.format); 357 if (config.module) 358 form.append('module', config.module); 359 if (config.moduleResource) 360 form.append('moduleResource', config.moduleResource); 361 if (config.importIdentity) 362 form.append('importIdentity', config.importIdentity); 363 if (config.importLookupByAlternateKey !== undefined) 364 form.append('importLookupByAlternateKey', config.importLookupByAlternateKey); 365 if (config.saveToPipeline !== undefined) 366 form.append('saveToPipeline', config.saveToPipeline); 367 368 if (config.file) { 369 if (config.file instanceof File) 370 form.append('file', config.file); 371 else if (config.file.tagName === 'INPUT' && config.file.files.length > 0) 372 form.append('file', config.file.files[0]); 373 } 374 375 return LABKEY.Ajax.request({ 376 url: config.importUrl || LABKEY.ActionURL.buildURL('query', 'import.api', config.containerPath), 377 method: 'POST', 378 success: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnSuccess(config), config.scope, false), 379 failure: LABKEY.Utils.getCallbackWrapper(LABKEY.Utils.getOnFailure(config), config.scope, true), 380 form: form, 381 timeout: config.timeout 382 }); 383 }; 384 385 return impl; 386 387 }(LABKEY.Query || new function() { return {}; }, jQuery); 388