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) 2008-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 /** 21 * @class LABKEY.Filter 22 * @namespace Filter static class to describe and create filters. 23 * <p>Additional Documentation: 24 * <ul> 25 * <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=filteringData">Filter via the LabKey UI</a></li> 26 * <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=tutorialActionURL">Tutorial: Basics: Building URLs and Filters</a></li> 27 * </ul> 28 * </p> 29 * @property {Object} Types Types static class to describe different types of filters. 30 * @property {LABKEY.Filter.FilterDefinition} Types.EQUAL Finds rows where the column value matches the given filter value. Case-sensitivity depends upon how your underlying relational database was configured. 31 * @property {LABKEY.Filter.FilterDefinition} Types.DATE_EQUAL Finds rows where the date portion of a datetime column matches the filter value (ignoring the time portion). 32 * @property {LABKEY.Filter.FilterDefinition} Types.DATE_NOT_EQUAL Finds rows where the date portion of a datetime column does not match the filter value (ignoring the time portion). 33 * @property {LABKEY.Filter.FilterDefinition} Types.NOT_EQUAL_OR_MISSING Finds rows where the column value does not equal the filter value, or is missing (null). 34 * @property {LABKEY.Filter.FilterDefinition} Types.NOT_EQUAL Finds rows where the column value does not equal the filter value. 35 * @property {LABKEY.Filter.FilterDefinition} Types.MISSING Finds rows where the column value is missing (null). Note that no filter value is required with this operator. 36 * @property {LABKEY.Filter.FilterDefinition} Types.NOT_MISSING Finds rows where the column value is not missing (is not null). Note that no filter value is required with this operator. 37 * @property {LABKEY.Filter.FilterDefinition} Types.GREATER_THAN Finds rows where the column value is greater than the filter value. 38 * @property {LABKEY.Filter.FilterDefinition} Types.LESS_THAN Finds rows where the column value is less than the filter value. 39 * @property {LABKEY.Filter.FilterDefinition} Types.GREATER_THAN_OR_EQUAL Finds rows where the column value is greater than or equal to the filter value. 40 * @property {LABKEY.Filter.FilterDefinition} Types.LESS_THAN_OR_EQUAL Finds rows where the column value is less than or equal to the filter value. 41 * @property {LABKEY.Filter.FilterDefinition} Types.CONTAINS Finds rows where the column value contains the filter value. Note that this may result in a slow query as this cannot use indexes. 42 * @property {LABKEY.Filter.FilterDefinition} Types.DOES_NOT_CONTAIN Finds rows where the column value does not contain the filter value. Note that this may result in a slow query as this cannot use indexes. 43 * @property {LABKEY.Filter.FilterDefinition} Types.DOES_NOT_START_WITH Finds rows where the column value does not start with the filter value. 44 * @property {LABKEY.Filter.FilterDefinition} Types.STARTS_WITH Finds rows where the column value starts with the filter value. 45 * @property {LABKEY.Filter.FilterDefinition} Types.IN Finds rows where the column value equals one of the supplied filter values. The values should be supplied as a semi-colon-delimited list (example usage: a;b;c). 46 * @property {LABKEY.Filter.FilterDefinition} Types.NOT_IN Finds rows where the column value is not in any of the supplied filter values. The values should be supplied as a semi-colon-delimited list (example usage: a;b;c). 47 * @property {LABKEY.Filter.FilterDefinition} Types.MEMBER_OF Finds rows where the column value contains a user id that is a member of the group id of the supplied filter value. 48 * @property {LABKEY.Filter.FilterDefinition} Types.CONTAINS_ONE_OF Finds rows where the column value contains any of the supplied filter values. The values should be supplied as a semi-colon-delimited list (example usage: a;b;c). 49 * @property {LABKEY.Filter.FilterDefinition} Types.CONTAINS_NONE_OF Finds rows where the column value does not contain any of the supplied filter values. The values should be supplied as a semi-colon-delimited list (example usage: a;b;c). 50 * @property {LABKEY.Filter.FilterDefinition} Types.BETWEEN Finds rows where the column value is between the two filter values, inclusive. The values should be supplied as a comma-delimited list (example usage: -4,4). 51 * @property {LABKEY.Filter.FilterDefinition} Types.NOT_BETWEEN Finds rows where the column value is not between the two filter values, exclusive. The values should be supplied as a comma-delimited list (example usage: -4,4). 52 * 53 */ 54 LABKEY.Filter = new function() 55 { 56 function validateMultiple(type, value, colName, sep, minOccurs, maxOccurs) 57 { 58 var values = value.split(sep); 59 var result = ''; 60 var separator = ''; 61 for (var i = 0; i < values.length; i++) 62 { 63 var value = validate(type, values[i].trim(), colName); 64 if (value == undefined) 65 return undefined; 66 67 result = result + separator + value; 68 separator = sep; 69 } 70 71 if (minOccurs !== undefined && minOccurs > 0) 72 { 73 if (values.length < minOccurs) 74 { 75 alert("At least " + minOccurs + " '" + sep + "' separated values are required"); 76 return undefined; 77 } 78 } 79 80 if (maxOccurs !== undefined && maxOccurs > 0) 81 { 82 if (values.length > maxOccurs) 83 { 84 alert("At most " + maxOccurs + " '" + sep + "' separated values are allowed"); 85 return undefined; 86 } 87 } 88 89 return result; 90 } 91 92 /** 93 * Note: this is an experimental API that may change unexpectedly in future releases. 94 * Validate a form value against the json type. Error alerts will be displayed. 95 * @param type The json type ("int", "float", "date", or "boolean") 96 * @param value The value to test. 97 * @param colName The column name to use in error messages. 98 * @return undefined if not valid otherwise a normalized string value for the type. 99 */ 100 function validate(type, value, colName) 101 { 102 if (type == "int") 103 { 104 var intVal = parseInt(value); 105 if (isNaN(intVal)) 106 { 107 alert(value + " is not a valid integer for field '" + colName + "'."); 108 return undefined; 109 } 110 else 111 return "" + intVal; 112 } 113 else if (type == "float") 114 { 115 var decVal = parseFloat(value); 116 if (isNaN(decVal)) 117 { 118 alert(value + " is not a valid decimal number for field '" + colName + "'."); 119 return undefined; 120 } 121 else 122 return "" + decVal; 123 } 124 else if (type == "date") 125 { 126 var year, month, day, hour, minute; 127 hour = 0; 128 minute = 0; 129 130 //Javascript does not parse ISO dates, but if date matches we're done 131 if (value.match(/^\s*(\d\d\d\d)-(\d\d)-(\d\d)\s*$/) || 132 value.match(/^\s*(\d\d\d\d)-(\d\d)-(\d\d)\s*(\d\d):(\d\d)\s*$/)) 133 { 134 return value; 135 } 136 else 137 { 138 var dateVal = new Date(value); 139 if (isNaN(dateVal)) 140 { 141 //filters can use relative dates, in the format +1d, -5H, etc. we try to identfy those here 142 //this is fairly permissive and does not attempt to parse this value into a date. See CompareType.asDate() 143 //for server-side parsing 144 if (value.match(/^(-|\+)/i)) 145 { 146 return value; 147 } 148 149 alert(value + " is not a valid date for field '" + colName + "'."); 150 return undefined; 151 } 152 //Try to do something decent with 2 digit years! 153 //if we have mm/dd/yy (but not mm/dd/yyyy) in the date 154 //fix the broken date parsing 155 if (value.match(/\d+\/\d+\/\d{2}(\D|$)/)) 156 { 157 if (dateVal.getFullYear() < new Date().getFullYear() - 80) 158 dateVal.setFullYear(dateVal.getFullYear() + 100); 159 } 160 year = dateVal.getFullYear(); 161 month = dateVal.getMonth() + 1; 162 day = dateVal.getDate(); 163 hour = dateVal.getHours(); 164 minute = dateVal.getMinutes(); 165 } 166 var str = "" + year + "-" + twoDigit(month) + "-" + twoDigit(day); 167 if (hour != 0 || minute != 0) 168 str += " " + twoDigit(hour) + ":" + twoDigit(minute); 169 170 return str; 171 } 172 else if (type == "boolean") 173 { 174 var upperVal = value.toUpperCase(); 175 if (upperVal == "TRUE" || value == "1" || upperVal == "YES" || upperVal == "Y" || upperVal == "ON" || upperVal == "T") 176 return "1"; 177 if (upperVal == "FALSE" || value == "0" || upperVal == "NO" || upperVal == "N" || upperVal == "OFF" || upperVal == "F") 178 return "0"; 179 else 180 { 181 alert(value + " is not a valid boolean for field '" + colName + "'. Try true,false; yes,no; y,n; on,off; or 1,0."); 182 return undefined; 183 } 184 } 185 else 186 return value; 187 } 188 189 function twoDigit(num) 190 { 191 if (num < 10) 192 return "0" + num; 193 else 194 return "" + num; 195 } 196 197 var urlMap = {}; 198 var oppositeMap = { 199 //HAS_ANY_VALUE: null, 200 eq: 'neqornull', 201 dateeq : 'dateneq', 202 dateneq : 'dateeq', 203 neqornull : 'eq', 204 neq : 'eq', 205 isblank : 'isnonblank', 206 isnonblank : 'isblank', 207 gt : 'lte', 208 dategt : 'datelte', 209 lt : 'gte', 210 datelt : 'dategte', 211 gte : 'lt', 212 dategte : 'datelt', 213 lte : 'gt', 214 datelte : 'dategt', 215 contains : 'doesnotcontain', 216 doesnotcontain : 'contains', 217 doesnotstartwith : 'startswith', 218 startswith : 'doesnotstartwith', 219 'in' : 'notin', 220 notin : 'in', 221 memberof : 'memberof', 222 containsoneof : 'containsnoneof', 223 containsnoneof : 'containsoneof', 224 hasmvvalue : 'nomvvalue', 225 nomvvalue : 'hasmvvalue', 226 between : 'notbetween', 227 notbetween : 'between' 228 }; 229 230 //NOTE: these maps contains the unambiguous pairings of single- and multi-valued filters 231 //due to NULLs, one cannot easily convert neq to notin 232 var multiValueToSingleMap = { 233 'in' : 'eq', 234 containsoneof : 'contains', 235 containsnoneof : 'doesnotcontain', 236 between: 'gte', 237 notbetween: 'lt' 238 }; 239 240 var singleValueToMultiMap = { 241 eq : 'in', 242 neq : 'notin', 243 neqornull: 'notin', 244 doesnotcontain : 'containsnoneof', 245 contains : 'containsoneof' 246 }; 247 248 function createNoValueFilterType(displayText, displaySymbol, urlSuffix, longDisplayText) 249 { 250 return createFilterType(displayText, displaySymbol, urlSuffix, false, false, null, longDisplayText); 251 } 252 253 function createSingleValueFilterType(displayText, displaySymbol, urlSuffix, longDisplayText) 254 { 255 return createFilterType(displayText, displaySymbol, urlSuffix, true, false, null, longDisplayText); 256 } 257 258 function createMultiValueFilterType(displayText, displaySymbol, urlSuffix, longDisplayText, multiValueSeparator, minOccurs, maxOccurs) 259 { 260 return createFilterType(displayText, displaySymbol, urlSuffix, true, false, multiValueSeparator, longDisplayText, minOccurs, maxOccurs); 261 } 262 263 function createTableFilterType(displayText, displaySymbol, urlSuffix, longDisplayText) 264 { 265 return createFilterType(displayText, displaySymbol, urlSuffix, true, true, null, longDisplayText); 266 } 267 268 function createFilterType(displayText, displaySymbol, urlSuffix, dataValueRequired, isTableWise, multiValueSeparator, longDisplayText, minOccurs, maxOccurs) 269 { 270 var result = { 271 getDisplaySymbol : function() { return displaySymbol }, 272 getDisplayText : function() { return displayText }, 273 getLongDisplayText : function() { return longDisplayText || displayText }, 274 getURLSuffix : function() { return urlSuffix }, 275 isDataValueRequired : function() { return dataValueRequired === true }, 276 isMultiValued : function() { return multiValueSeparator != null; }, 277 isTableWise : function() { return isTableWise === true }, 278 getMultiValueSeparator : function() { return multiValueSeparator }, 279 getMultiValueMinOccurs : function() { return minOccurs }, 280 getMultiValueMaxOccurs : function() { return maxOccurs; }, 281 getOpposite : function() {return oppositeMap[urlSuffix] ? urlMap[oppositeMap[urlSuffix]] : null}, 282 getSingleValueFilter : function() {return this.isMultiValued() ? urlMap[multiValueToSingleMap[urlSuffix]] : this}, 283 getMultiValueFilter : function() {return this.isMultiValued() ? null : urlMap[singleValueToMultiMap[urlSuffix]]}, 284 validate : function (value, type, colName) { 285 if (!dataValueRequired) 286 return true; 287 288 var f = filterTypes[type]; 289 var found = false; 290 for (var i = 0; !found && i < f.length; i++) 291 { 292 if (f[i].getURLSuffix() == urlSuffix) 293 found = true; 294 } 295 if (!found) { 296 alert("Filter type '" + displayText + "' can't be applied to " + type + " types."); 297 return undefined; 298 } 299 300 if (this.isMultiValued()) 301 return validateMultiple(type, value, colName, multiValueSeparator, minOccurs, maxOccurs); 302 else 303 return validate(type, value, colName); 304 } 305 }; 306 urlMap[urlSuffix] = result; 307 return result; 308 } 309 310 var ret = /** @scope LABKEY.Filter */{ 311 312 // WARNING: Keep in sync and in order with all other client apis and docs 313 // - server: CompareType.java 314 // - java: Filter.java 315 // - js: Filter.js 316 // - R: makeFilter.R, makeFilter.Rd 317 // - SAS: labkeymakefilter.sas, labkey.org SAS docs 318 // - Python & Perl don't have an filter operator enum 319 // - EXPERIMENTAL: Added an optional displaySymbol() for filters that want to support it 320 Types : { 321 322 HAS_ANY_VALUE : createNoValueFilterType("Has Any Value", null, "", null), 323 324 // 325 // These operators require a data value 326 // 327 328 EQUAL : createSingleValueFilterType("Equals", "=", "eq", null), 329 DATE_EQUAL : createSingleValueFilterType("Equals", "=", "dateeq", null), 330 331 NEQ : createSingleValueFilterType("Does Not Equal", "<>", "neq", null), 332 NOT_EQUAL : createSingleValueFilterType("Does Not Equal", "<>", "neq", null), 333 DATE_NOT_EQUAL : createSingleValueFilterType("Does Not Equal", "<>", "dateneq", null), 334 335 NEQ_OR_NULL : createSingleValueFilterType("Does Not Equal", "<>", "neqornull", null), 336 NOT_EQUAL_OR_MISSING : createSingleValueFilterType("Does Not Equal", "<>", "neqornull", null), 337 338 GT : createSingleValueFilterType("Is Greater Than", ">", "gt", null), 339 GREATER_THAN : createSingleValueFilterType("Is Greater Than", ">", "gt", null), 340 DATE_GREATER_THAN : createSingleValueFilterType("Is Greater Than", ">", "dategt", null), 341 342 LT : createSingleValueFilterType("Is Less Than", "<", "lt", null), 343 LESS_THAN : createSingleValueFilterType("Is Less Than", "<", "lt", null), 344 DATE_LESS_THAN : createSingleValueFilterType("Is Less Than", "<", "datelt", null), 345 346 GTE : createSingleValueFilterType("Is Greater Than or Equal To", ">=", "gte", null), 347 GREATER_THAN_OR_EQUAL : createSingleValueFilterType("Is Greater Than or Equal To", ">=", "gte", null), 348 DATE_GREATER_THAN_OR_EQUAL : createSingleValueFilterType("Is Greater Than or Equal To", ">=", "dategte", null), 349 350 LTE : createSingleValueFilterType("Is Less Than or Equal To", "=<", "lte", null), 351 LESS_THAN_OR_EQUAL : createSingleValueFilterType("Is Less Than or Equal To", "=<", "lte", null), 352 DATE_LESS_THAN_OR_EQUAL : createSingleValueFilterType("Is Less Than or Equal To", "=<", "datelte", null), 353 354 STARTS_WITH : createSingleValueFilterType("Starts With", null, "startswith", null), 355 DOES_NOT_START_WITH : createSingleValueFilterType("Does Not Start With", null, "doesnotstartwith", null), 356 357 CONTAINS : createSingleValueFilterType("Contains", null, "contains", null), 358 DOES_NOT_CONTAIN : createSingleValueFilterType("Does Not Contain", null, "doesnotcontain", null), 359 360 CONTAINS_ONE_OF : createMultiValueFilterType("Contains One Of", null, "containsoneof", 'Contains One Of (example usage: a;b;c)', ";"), 361 CONTAINS_NONE_OF : createMultiValueFilterType("Does Not Contain Any Of", null, "containsnoneof", 'Does Not Contain Any Of (example usage: a;b;c)', ";"), 362 363 IN : createMultiValueFilterType("Equals One Of", null, "in", 'Equals One Of (example usage: a;b;c)', ";"), 364 //NOTE: for some reason IN is aliased as EQUALS_ONE_OF. not sure if this is for legacy purposes or it was determined EQUALS_ONE_OF was a better phrase 365 //to follow this pattern I did the same for IN_OR_MISSING 366 EQUALS_ONE_OF : createMultiValueFilterType("Equals One Of", null, "in", 'Equals One Of (example usage: a;b;c)', ";"), 367 368 NOT_IN: createMultiValueFilterType("Does Not Equal Any Of", null, "notin", 'Does Not Equal Any Of (example usage: a;b;c)', ";"), 369 EQUALS_NONE_OF: createMultiValueFilterType("Does Not Equal Any Of", null, "notin", 'Does Not Equal Any Of (example usage: a;b;c)', ";"), 370 371 BETWEEN : createMultiValueFilterType("Between", null, "between", 'Between, Inclusive (example usage: -4,4)', ",", 2, 2), 372 NOT_BETWEEN : createMultiValueFilterType("Not Between", null, "notbetween", 'Not Between, Exclusive (example usage: -4,4)', ",", 2, 2), 373 374 MEMBER_OF : createSingleValueFilterType("Member Of", null, "memberof", 'Member Of'), 375 376 // 377 // These are the "no data value" operators 378 // 379 380 ISBLANK : createNoValueFilterType("Is Blank", null, "isblank", null), 381 MISSING : createNoValueFilterType("Is Blank", null, "isblank", null), 382 NONBLANK : createNoValueFilterType("Is Not Blank", null, "isnonblank", null), 383 NOT_MISSING : createNoValueFilterType("Is Not Blank", null, "isnonblank", null), 384 385 HAS_MISSING_VALUE : createNoValueFilterType("Has a missing value indicator", null, "hasmvvalue", null), 386 DOES_NOT_HAVE_MISSING_VALUE : createNoValueFilterType("Does not have a missing value indicator", null, "nomvvalue", null), 387 388 EXP_CHILD_OF : createSingleValueFilterType("Is Child Of", null, "exp:childof", " is child of" ), 389 390 // 391 // Table/Query-wise operators 392 // 393 Q : createTableFilterType("Search", null, "q", "Search across all columns") 394 }, 395 396 /** @private create a js object suitable for Query.selectRows, etc */ 397 appendFilterParams : function (params, filterArray, dataRegionName) 398 { 399 dataRegionName = dataRegionName || "query"; 400 params = params || {}; 401 if (filterArray) 402 { 403 for (var i = 0; i < filterArray.length; i++) 404 { 405 var filter = filterArray[i]; 406 // 10.1 compatibility: treat ~eq=null as a NOOP (ref 10482) 407 if (filter.getFilterType().isDataValueRequired() && null == filter.getURLParameterValue()) 408 continue; 409 410 // Create an array of filter values if there is more than one filter for the same column and filter type. 411 var paramName = filter.getURLParameterName(dataRegionName); 412 var paramValue = filter.getURLParameterValue(); 413 if (params[paramName] !== undefined) 414 { 415 var values = params[paramName]; 416 if (!LABKEY.Utils.isArray(values)) 417 values = [ values ]; 418 values.push(paramValue); 419 paramValue = values; 420 } 421 params[paramName] = paramValue; 422 } 423 } 424 return params; 425 }, 426 427 /** @private create a js object suitable for QueryWebPart, etc */ 428 appendAggregateParams : function (params, aggregateArray, dataRegionName) 429 { 430 dataRegionName = dataRegionName || "query"; 431 params = params || {}; 432 if (aggregateArray) 433 { 434 for (var idx = 0; idx < aggregateArray.length; ++idx) 435 { 436 var aggregate = aggregateArray[idx]; 437 var value = "type=" + aggregate.type; 438 if (aggregate.label) 439 value = value + "&label=" + aggregate.label; 440 if (aggregate.type && aggregate.column) 441 { 442 // Create an array of aggregate values if there is more than one aggregate for the same column. 443 var paramName = dataRegionName + '.analytics.' + aggregate.column; 444 var paramValue = encodeURIComponent(value); 445 if (params[paramName] !== undefined) 446 { 447 var values = params[paramName]; 448 if (!LABKEY.Utils.isArray(values)) 449 values = [ values ]; 450 values.push(paramValue); 451 paramValue = values; 452 } 453 params[paramName] = paramValue; 454 } 455 456 } 457 } 458 459 return params; 460 }, 461 462 463 /** 464 * Creates a filter 465 * @param {String} columnName String name of the column to filter 466 * @param value Value used as the filter criterion or an Array of values. 467 * @param {LABKEY.Filter#Types} [filterType] Type of filter to apply to the 'column' using the 'value' 468 * @example Example: <pre name="code" class="xml"> 469 <script type="text/javascript"> 470 function onFailure(errorInfo, options, responseObj) 471 { 472 if(errorInfo && errorInfo.exception) 473 alert("Failure: " + errorInfo.exception); 474 else 475 alert("Failure: " + responseObj.statusText); 476 } 477 478 function onSuccess(data) 479 { 480 alert("Success! " + data.rowCount + " rows returned."); 481 } 482 483 LABKEY.Query.selectRows({ 484 schemaName: 'lists', 485 queryName: 'People', 486 success: onSuccess, 487 failure: onFailure, 488 filterArray: [ 489 LABKEY.Filter.create('FirstName', 'Johnny'), 490 LABKEY.Filter.create('Age', 15, LABKEY.Filter.Types.LESS_THAN_OR_EQUAL) 491 LABKEY.Filter.create('LastName', ['A', 'B'], LABKEY.Filter.Types.DOES_NOT_START_WITH) 492 ] 493 }); 494 </script> </pre> 495 */ 496 497 create : function(columnName, value, filterType) 498 { 499 return new LABKEY.Query.Filter(columnName, value, filterType); 500 }, 501 502 /** 503 * Not for public use. Can be changed or dropped at any time. 504 * @param typeName 505 * @param displayText 506 * @param urlSuffix 507 * @param isMultiType 508 * @private 509 */ 510 _define : function(typeName, displayText, urlSuffix, isMultiType) { 511 if (!LABKEY.Filter.Types[typeName]) { 512 if (isMultiType) { 513 LABKEY.Filter.Types[typeName] = createMultiValueFilterType(displayText, null, urlSuffix, null); 514 } 515 else { 516 LABKEY.Filter.Types[typeName] = createSingleValueFilterType(displayText, null, urlSuffix, null); 517 } 518 } 519 }, 520 521 /** 522 * Given an array of filter objects, return a new filterArray with old filters from a column removed and new filters for the column added 523 * If new filters are null, simply remove all old filters from baseFilters that refer to this column 524 * @param {Array} baseFilters Array of existing filters created by {@link LABKEY.Filter.create} 525 * @param {String} columnName Column name of filters to replace 526 * @param {Array} columnFilters Array of new filters created by {@link LABKEY.Filter.create}. Will replace any filters referring to columnName 527 */ 528 merge : function(baseFilters, columnName, columnFilters) 529 { 530 var newFilters = []; 531 if (null != baseFilters) 532 for (var i = 0; i < baseFilters.length; i++) 533 { 534 var filt = baseFilters[i]; 535 if (filt.getColumnName() != columnName) 536 newFilters.push(filt); 537 } 538 539 return null == columnFilters ? newFilters : newFilters.concat(columnFilters); 540 }, 541 542 /** 543 * Convert from URL syntax filters to a human readable description, like "Is Greater Than 10 AND Is Less Than 100" 544 * @param {String} url URL containing the filter parameters 545 * @param {String} dataRegionName String name of the data region the column is a part of 546 * @param {String} columnName String name of the column to filter 547 * @return {String} human readable version of the filter 548 */ 549 getFilterDescription : function(url, dataRegionName, columnName) 550 { 551 var params = LABKEY.ActionURL.getParameters(url); 552 var result = ""; 553 var separator = ""; 554 for (var paramName in params) 555 { 556 // Look for parameters that have the right prefix 557 if (paramName.indexOf(dataRegionName + "." + columnName + "~") == 0) 558 { 559 var filterType = paramName.substring(paramName.indexOf("~") + 1); 560 var values = params[paramName]; 561 if (!LABKEY.Utils.isArray(values)) 562 { 563 values = [values]; 564 } 565 // Get the human readable version, like "Is Less Than" 566 var friendly = urlMap[filterType]; 567 var displayText; 568 if (!friendly) 569 { 570 displayText = filterType; 571 } 572 else 573 { 574 displayText = friendly.getDisplayText(); 575 } 576 577 for (var j = 0; j < values.length; j++) 578 { 579 // If the same type of filter is applied twice, it will have multiple values 580 result += separator; 581 separator = " AND "; 582 583 result += displayText; 584 result += " "; 585 result += values[j]; 586 } 587 } 588 } 589 return result; 590 }, 591 592 // Create an array of LABKEY.Filter objects from the filter parameters on the URL 593 getFiltersFromUrl : function(url, dataRegionName) 594 { 595 dataRegionName = dataRegionName || 'query'; 596 var params = LABKEY.ActionURL.getParameters(url); 597 var filterArray = []; 598 599 for (var paramName in params) 600 { 601 if (params.hasOwnProperty(paramName)) { 602 // Look for parameters that have the right prefix 603 if (paramName.indexOf(dataRegionName + ".") == 0) 604 { 605 var tilde = paramName.indexOf("~"); 606 607 if (tilde != -1) 608 { 609 var columnName = paramName.substring(dataRegionName.length + 1, tilde); 610 var filterName = paramName.substring(tilde + 1); 611 var filterType = LABKEY.Filter.getFilterTypeForURLSuffix(filterName); 612 var values = params[paramName]; 613 if (!LABKEY.Utils.isArray(values)) 614 { 615 values = [values]; 616 } 617 filterArray.push(LABKEY.Filter.create(columnName, values, filterType)); 618 } 619 } 620 } 621 } 622 return filterArray; 623 }, 624 625 getSortFromUrl : function(url, dataRegionName) 626 { 627 dataRegionName = dataRegionName || 'query'; 628 629 var params = LABKEY.ActionURL.getParameters(url); 630 return params[dataRegionName + "." + "sort"]; 631 }, 632 633 getQueryParamsFromUrl : function(url, dataRegionName) 634 { 635 dataRegionName = dataRegionName || 'query'; 636 637 var queryParams = {}; 638 var params = LABKEY.ActionURL.getParameters(url); 639 for (var paramName in params) 640 { 641 if (params.hasOwnProperty(paramName)) 642 { 643 if (paramName.indexOf(dataRegionName + "." + "param.") == 0) 644 { 645 var queryParamName = paramName.substring((dataRegionName + "." + "param.").length); 646 queryParams[queryParamName] = params[paramName]; 647 } 648 } 649 } 650 651 return queryParams; 652 }, 653 654 getFilterTypeForURLSuffix : function (urlSuffix) 655 { 656 return urlMap[urlSuffix]; 657 } 658 }; 659 660 var ft = ret.Types; 661 var filterTypes = { 662 "int":[ft.HAS_ANY_VALUE, ft.EQUAL, ft.NEQ_OR_NULL, ft.ISBLANK, ft.NONBLANK, ft.GT, ft.LT, ft.GTE, ft.LTE, ft.IN, ft.NOT_IN, ft.BETWEEN, ft.NOT_BETWEEN], 663 "string":[ft.HAS_ANY_VALUE, ft.EQUAL, ft.NEQ_OR_NULL, ft.ISBLANK, ft.NONBLANK, ft.GT, ft.LT, ft.GTE, ft.LTE, ft.CONTAINS, ft.DOES_NOT_CONTAIN, ft.DOES_NOT_START_WITH, ft.STARTS_WITH, ft.IN, ft.NOT_IN, ft.CONTAINS_ONE_OF, ft.CONTAINS_NONE_OF, ft.BETWEEN, ft.NOT_BETWEEN], 664 "boolean":[ft.HAS_ANY_VALUE, ft.EQUAL, ft.NEQ_OR_NULL, ft.ISBLANK, ft.NONBLANK], 665 "float":[ft.HAS_ANY_VALUE, ft.EQUAL, ft.NEQ_OR_NULL, ft.ISBLANK, ft.NONBLANK, ft.GT, ft.LT, ft.GTE, ft.LTE, ft.IN, ft.NOT_IN, ft.BETWEEN, ft.NOT_BETWEEN], 666 "date":[ft.HAS_ANY_VALUE, ft.DATE_EQUAL, ft.DATE_NOT_EQUAL, ft.ISBLANK, ft.NONBLANK, ft.DATE_GREATER_THAN, ft.DATE_LESS_THAN, ft.DATE_GREATER_THAN_OR_EQUAL, ft.DATE_LESS_THAN_OR_EQUAL] 667 }; 668 669 var defaultFilter = { 670 "int": ft.EQUAL, 671 "string": ft.CONTAINS, 672 "boolean": ft.EQUAL, 673 "float": ft.EQUAL, 674 "date": ft.DATE_EQUAL 675 }; 676 677 /** @private Returns an Array of filter types that can be used with the given json type ("int", "double", "string", "boolean", "date") */ 678 ret.getFilterTypesForType = function (type, mvEnabled) 679 { 680 var types = []; 681 if (filterTypes[type]) 682 types = types.concat(filterTypes[type]); 683 684 if (mvEnabled) 685 { 686 types.push(ft.HAS_MISSING_VALUE); 687 types.push(ft.DOES_NOT_HAVE_MISSING_VALUE); 688 } 689 690 return types; 691 }; 692 693 /** @private Return the default LABKEY.Filter.Type for a json type ("int", "double", "string", "boolean", "date"). */ 694 ret.getDefaultFilterForType = function (type) 695 { 696 if (defaultFilter[type]) 697 return defaultFilter[type]; 698 699 return ft.EQUAL; 700 }; 701 702 return ret; 703 }; 704 705 /** 706 * @name LABKEY.Filter.FilterDefinition 707 * @description Static class that defines the functions that describe how a particular 708 * type of filter is identified and operates. See {@link LABKEY.Filter}. 709 * <p>Additional Documentation: 710 * <ul> 711 * <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=filteringData">Filter via the LabKey UI</a></li> 712 * </ul> 713 * </p> 714 * @class Static class that defines the functions that describe how a particular 715 * type of filter is identified and operates. See {@link LABKEY.Filter}. 716 * <p>Additional Documentation: 717 * <ul> 718 * <li><a href="https://www.labkey.org/Documentation/wiki-page.view?name=filteringData">Filter via the LabKey UI</a></li> 719 * </ul> 720 * </p> 721 */ 722 723 /**#@+ 724 * @methodOf LABKEY.Filter.FilterDefinition# 725 */ 726 727 /** 728 * Get the string displayed for this filter. 729 * @name getDisplayText 730 * @type String 731 */ 732 733 /** 734 * Get the more descriptive string displayed for this filter. This is used in filter dialogs. 735 * @name getLongDisplayText 736 * @type String 737 */ 738 739 /** 740 * Get the URL suffix used to identify this filter. 741 * @name getURLSuffix 742 * @type String 743 */ 744 745 /** 746 * Get the Boolean that indicates whether a data value is required. 747 * @name isDataValueRequired 748 * @type Boolean 749 */ 750 751 /** 752 * Get the Boolean that indicates whether the filter supports a string with multiple filter values (ie. contains one of, not in, etc). 753 * @name isMultiValued 754 * @type Boolean 755 */ 756 757 /** 758 * Get the LABKEY.Filter.FilterDefinition the represents the opposite of this filter type. 759 * @name getOpposite 760 * @type LABKEY.Filter.FilterDefinition 761 */ 762 763 /**#@-*/ 764