UNPKG

dojox

Version:

Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.

261 lines (249 loc) 10.6 kB
define(["dojo/_base/declare", "dojo/_base/lang", "dojo/data/ItemFileReadStore", "dojo/data/util/filter", "dojo/_base/array", "dojo/_base/json"], function(declare, lang, ItemFileReadStore, filterUtil, array, json) { // module: // dojox/data/AndOrReadStore // summary: // TODOC return declare("dojox.data.AndOrReadStore", [ItemFileReadStore], { // summary: // AndOrReadStore uses ItemFileReadStore as a base, modifying only the query (_fetchItems) section. // Supports queries of the form: query:"id:1* OR dept:'Sales Department' || (id:2* && NOT dept:S*)" // Includes legacy/widget support via: // | query:{complexQuery:"id:1* OR dept:'Sales Department' || (id:2* && NOT dept:S*)"} // The ItemFileReadStore implements the dojo/data/api/Read API and reads // data from JSON files that have contents in this format -- // | { items: [ // | { name:'Kermit', color:'green', age:12, friends:['Gonzo', {_reference:{name:'Fozzie Bear'}}]}, // | { name:'Fozzie Bear', wears:['hat', 'tie']}, // | { name:'Miss Piggy', pets:'Foo-Foo'} // | ]} // Note that it can also contain an 'identifier' property that specified which attribute on the items // in the array of items that acts as the unique identifier for that item. _containsValue: function(/*dojo/data/api/Item*/ item, /*attribute-name-string */ attribute, /*anything*/ value, /*String|RegExp?*/ regexp){ // summary: // Internal function for looking at the values contained by the item. // description: // Internal function for looking at the values contained by the item. This // function allows for denoting if the comparison should be case sensitive for // strings or not (for handling filtering cases where string case should not matter) // item: // The data item to examine for attribute values. // attribute: // The attribute to inspect. // value: // The value to match. // regexp: // Optional string or regular expression generated off value if value was of string type to handle wildcarding. // If present and attribute values are string, then it can be used for comparison instead of 'value' // If RegExp is a string, it is treated as an comparison statement and eval for number comparisons return array.some(this.getValues(item, attribute), function(possibleValue){ // if string... eval for math operator comparisons if(lang.isString(regexp)){ return eval(regexp); }else if(possibleValue !== null && !lang.isObject(possibleValue) && regexp){ if(possibleValue.toString().match(regexp)){ return true; // Boolean } } else if(value === possibleValue){ return true; // Boolean } else { return false; } }); }, filter: function(requestArgs, arrayOfItems, findCallback){ var items = []; if(requestArgs.query){ //Complete copy, we may have to mess with it. //Safer than clone, which does a shallow copy, I believe. var query = json.fromJson(json.toJson(requestArgs.query)); //Okay, object form query, we have to check to see if someone mixed query methods (such as using FilteringSelect //with a complexQuery). In that case, the params need to be anded to the complex query statement. //See defect #7980 if(typeof query == "object" ){ var count = 0; var p; for(p in query){ count++; } if(count > 1 && query.complexQuery){ var cq = query.complexQuery; var wrapped = false; for(p in query){ if(p !== "complexQuery"){ //We should wrap this in () as it should and with the entire complex query //Not just part of it. if(!wrapped){ cq = "( " + cq + " )"; wrapped = true; } //Make sure strings are quoted when going into complexQuery merge. var v = requestArgs.query[p]; if(lang.isString(v)){ v = "'" + v + "'"; } cq += " AND " + p + ":" + v; delete query[p]; } } query.complexQuery = cq; } } var ignoreCase = requestArgs.queryOptions ? requestArgs.queryOptions.ignoreCase : false; //for complex queries only: pattern = query[:|=]"NOT id:23* AND (type:'test*' OR dept:'bob') && !filed:true" //logical operators are case insensitive: , NOT AND OR ( ) ! && || // "," included for quoted/string legacy queries. if(typeof query != "string"){ query = json.toJson(query); query = query.replace(/\\\\/g,"\\"); //counter toJson expansion of backslashes, e.g., foo\\*bar test. } query = query.replace(/\\"/g,"\""); //ditto, for embedded \" in lieu of " availability. var complexQuery = lang.trim(query.replace(/{|}/g,"")); //we can handle these, too. var pos2, i; if(complexQuery.match(/"? *complexQuery *"?:/)){ //case where widget required a json object, so use complexQuery:'the real query' complexQuery = lang.trim(complexQuery.replace(/"?\s*complexQuery\s*"?:/,"")); var quotes = ["'",'"']; var pos1,colon; var flag = false; for(i = 0; i<quotes.length; i++){ pos1 = complexQuery.indexOf(quotes[i]); pos2 = complexQuery.indexOf(quotes[i],1); colon = complexQuery.indexOf(":",1); if(pos1 === 0 && pos2 != -1 && colon < pos2){ flag = true; break; } //first two sets of quotes don't occur before the first colon. } if(flag){ //dojo.toJson, and maybe user, adds surrounding quotes, which we need to remove. complexQuery = complexQuery.replace(/^\"|^\'|\"$|\'$/g,""); } } //end query="{complexQuery:'id:1* || dept:Sales'}" parsing (for when widget required json object query). var complexQuerySave = complexQuery; //valid logical operators. var begRegExp = /^>=|^<=|^<|^>|^,|^NOT |^AND |^OR |^\(|^\)|^!|^&&|^\|\|/i; //trailing space on some tokens on purpose. var sQuery = ""; //will be eval'ed for each i-th candidateItem, based on query components. var op = ""; var val = ""; var pos = -1; var err = false; var key = ""; var value = ""; var tok = ""; pos2 = -1; for(i = 0; i < arrayOfItems.length; ++i){ var match = true; var candidateItem = arrayOfItems[i]; if(candidateItem === null){ match = false; }else{ //process entire string for this i-th candidateItem. complexQuery = complexQuerySave; //restore query for next candidateItem. sQuery = ""; //work left to right, finding either key:value pair or logical operator at the beginning of the complexQuery string. //when found, concatenate to sQuery and remove from complexQuery and loop back. while(complexQuery.length > 0 && !err){ op = complexQuery.match(begRegExp); //get/process/append one or two leading logical operators. while(op && !err){ //look for leading logical operators. complexQuery = lang.trim(complexQuery.replace(op[0],"")); op = lang.trim(op[0]).toUpperCase(); //convert some logical operators to their javascript equivalents for later eval. op = op == "NOT" ? "!" : op == "AND" || op == "," ? "&&" : op == "OR" ? "||" : op; op = " " + op + " "; sQuery += op; op = complexQuery.match(begRegExp); }//end op && !err //now get/process/append one key:value pair. if(complexQuery.length > 0){ var opsRegex = /:|>=|<=|>|</g, matches = complexQuery.match(opsRegex), match = matches && matches.shift(), regex; pos = complexQuery.indexOf(match); if(pos == -1){ err = true; break; }else{ key = lang.trim(complexQuery.substring(0,pos).replace(/\"|\'/g,"")); complexQuery = lang.trim(complexQuery.substring(pos + match.length)); tok = complexQuery.match(/^\'|^\"/); //quoted? if(tok){ tok = tok[0]; pos = complexQuery.indexOf(tok); pos2 = complexQuery.indexOf(tok,pos + 1); if(pos2 == -1){ err = true; break; } value = complexQuery.substring(pos + match.length,pos2); if(pos2 == complexQuery.length - 1){ //quote is last character complexQuery = ""; }else{ complexQuery = lang.trim(complexQuery.substring(pos2 + 1)); } if (match != ':') { regex = this.getValue(candidateItem, key) + match + value; } else { regex = filterUtil.patternToRegExp(value, ignoreCase); } sQuery += this._containsValue(candidateItem, key, value, regex); } else{ //not quoted, so a space, comma, or closing parens (or the end) will be the break. tok = complexQuery.match(/\s|\)|,/); if(tok){ var pos3 = new Array(tok.length); for(var j = 0;j<tok.length;j++){ pos3[j] = complexQuery.indexOf(tok[j]); } pos = pos3[0]; if(pos3.length > 1){ for(var j=1;j<pos3.length;j++){ pos = Math.min(pos,pos3[j]); } } value = lang.trim(complexQuery.substring(0,pos)); complexQuery = lang.trim(complexQuery.substring(pos)); }else{ //not a space, so must be at the end of the complexQuery. value = lang.trim(complexQuery); complexQuery = ""; } //end inner if(tok) else if (match != ':') { regex = this.getValue(candidateItem, key) + match + value; } else { regex = filterUtil.patternToRegExp(value, ignoreCase); console.log("regex value: ", value, " regex pattern: ", regex); } sQuery += this._containsValue(candidateItem, key, value, regex); } //end outer if(tok) else } //end found ":" } //end if(complexQuery.length > 0) } //end while complexQuery.length > 0 && !err, so finished the i-th item. match = eval(sQuery); } //end else is non-null candidateItem. if(match){ items.push(candidateItem); } } //end for/next of all items. if(err){ //soft fail. items = []; console.log("The store's _fetchItems failed, probably due to a syntax error in query."); } }else{ // No query... // We want a copy to pass back in case the parent wishes to sort the array. // We shouldn't allow resort of the internal list, so that multiple callers // can get lists and sort without affecting each other. We also need to // filter out any null values that have been left as a result of deleteItem() // calls in ItemFileWriteStore. for(var i = 0; i < arrayOfItems.length; ++i){ var item = arrayOfItems[i]; if(item !== null){ items.push(item); } } } //end if there is a query. findCallback(items, requestArgs); } //end filter function }); });