/* * jLinq - 3.0.1 * Hugo Bonacci - hugoware.com * http://creativecommons.org/licenses/by/3.0/ */ var jLinq; var jlinq; var jl; (function() { //jLinq functionality var framework = { //command types for extensions command:{ //queues a comparison to filter records query:0, //executes all queued commands and filters the records select:1, //performs an immediate action to the query action:2 }, //common expressions exp:{ //gets each part of a dot notation path get_path:/\./g, //escapes string so it can be used in a regular expression escape_regex:/[\-\[\]\{\}\(\)\*\+\?\.\,\\\^\$\|\#\s]/g }, //common javascript types type:{ nothing:-1, undefined:0, string:1, number:2, array:3, regex:4, bool:5, method:6, datetime:7, object:99 }, //contains jLinq commands and functions library:{ //the current commands in jLinq commands:{}, //the type comparisons for jLinq types:{}, //includes a comparison to identify types addType:function(type, compare) { framework.library.types[type] = compare; }, //adds a command to the jLinq library extend:function(commands) { //convert to an array if not already if (!framework.util.isType(framework.type.array, commands)) { commands = [commands]; } //append each method framework.util.each(commands, function(command) { framework.library.commands[command.name] = command; }); }, //starts a new jLinq query query:function(collection, params) { //make sure something is there if (!framework.util.isType(framework.type.array, collection)) { throw "jLinq can only query arrays of objects."; } //clone the array to prevent changing objects - by default //this is off collection = params.clone || (params.clone == null && jLinq.alwaysClone) ? framework.util.clone(collection) : collection; //holds the state of the current query var self = { //the public instance of the query instance:{ //should this query ignore case ignoreCase:jLinq.ignoreCase, //should the next command be evaluated as not not:false, //the action that was last invoked lastCommand:null, //the name of the last field queried lastField:null, //the current records available records:collection, //records that have been filtered out removed:[], //tells a query to start a new function or:function() { self.startNewCommandSet(); }, //the query creator object query:{} }, //determines if the arguments provided meet the //requirements to be a repeated command canRepeatCommand:function(args) { return self.instance.lastCommand != null && args.length == (self.instance.lastCommand.method.length + 1) && framework.util.isType(framework.type.string, args[0]) }, //commands waiting to execute commands:[[]], //executes the current query and updated the records execute:function() { var results = []; //get the current state of the query var state = self.instance; //start checking each record framework.util.each(self.instance.records, function(record) { //update the state state.record = record; //perform the evaluation if (self.evaluate(state)) { results.push(record); } else { self.instance.removed.push(record); } }); //update the matching records self.instance.records = results; }, //tries to find a value from the path name findValue:framework.util.findValue, //evaluates each queued command for matched evaluate:function(state) { //check each of the command sets for (var command = 0, l = self.commands.length; command < l; command++) { //each set represents an 'or' set - if any //match then return this worked var set = self.commands[command]; if (self.evaluateSet(set, state)) { return true; } }; //since nothing evaluated, return it failed return false; }, //evaluates a single set of commands evaluateSet:function(set, state) { //check each command in this set for (var item in set) { if (!set.hasOwnProperty(item)) continue; //get the details to use var command = set[item]; state.value = self.findValue(state.record, command.path); state.compare = function(types) { return framework.util.compare(state.value, types, state); }; state.when = function(types) { return framework.util.when(state.value, types, state); }; //evaluate the command try { var result = command.method.apply(state, command.args); if (command.not) { result = !result; } if (!result) { return false; } } //errors and exceptions just result in a failed //to evaluate as true catch (e) { return false; } } //if nothing failed then return it worked return true; }, //repeats the previous command with new //arguments repeat:function(arguments) { //check if there is anything to repeat if (!self.instance.lastCommand || arguments == null) { return; } //get the array of arguments to work with arguments = framework.util.toArray(arguments); //check if there is a field name has changed, and //if so, update the arguments to match if (self.canRepeatCommand(arguments)) { self.instance.lastField = arguments[0]; arguments = framework.util.select(arguments, null, 1, null); } //invoke the command now self.queue(self.instance.lastCommand, arguments); }, //saves a command to evaluate later queue:function(command, args) { self.instance.lastCommand = command; //the base detail for the command var detail = { name:command.name, method:command.method, field:self.instance.lastField, count:command.method.length, args:args, not:self.not }; //check to see if there is an extra argument which should //be the field name argument if (detail.args.length > command.method.length) { //if so, grab the name and update the arguments detail.field = detail.args[0]; detail.args = framework.util.remaining(detail.args, 1); self.instance.lastField = detail.field; } //get the full path for the field name detail.path = detail.field; //queue the command to the current set self.commands[self.commands.length-1].push(detail); //then reset the not state self.not = false; }, //creates a new set of methods that should be evaluated startNewCommandSet:function() { self.commands.push([]); }, //marks a command to evaluate as NOT setNot:function() { self.not = !self.not; } }; //append each of the functions framework.util.each(framework.library.commands, function(command) { //Query methods queue up and are not evaluated until //a selection or action command is called if (command.type == framework.command.query) { //the default action to perform var action = function() { self.queue(command, arguments); return self.instance.query; }; //create the default action self.instance.query[command.name] = action; //orCommand var name = framework.util.operatorName(command.name); self.instance.query["or"+name] = function() { self.startNewCommandSet(); return action.apply(null, arguments); }; //orNotCommand self.instance.query["orNot"+name] = function() { self.startNewCommandSet(); self.setNot(); return action.apply(null, arguments); }; //andCommand self.instance.query["and"+name] = function() { return action.apply(null, arguments); }; //andNotCommand self.instance.query["andNot"+name] = function() { self.setNot(); return action.apply(null, arguments); }; //notCommand self.instance.query["not"+name] = function() { self.setNot(); return action.apply(null, arguments); }; } //Selections commands flush the queue of commands //before they are executed. A selection command //must return something (even if it is the current query) else if (command.type == framework.command.select) { self.instance.query[command.name] = function() { //apply the current changes self.execute(); //get the current state of the query var state = self.instance; state.compare = function(value, types) { return framework.util.compare(value, types, state); }; state.when = function(value, types) { return framework.util.when(value, types, state); }; //perform the work return command.method.apply(state, arguments); }; } //actions evaluate immediately then return control to //the query else if (command.type == framework.command.action) { self.instance.query[command.name] = function() { //get the current state of the query var state = self.instance; state.compare = function(value, types) { return framework.util.compare(value, types, state); }; state.when = function(value, types) { return framework.util.when(value, types, state); }; //perform the work command.method.apply(state, arguments); return self.instance.query; }; } }); //causes the next command to be an 'or' self.instance.query.or = function() { self.startNewCommandSet(); self.repeat(arguments); return self.instance.query; }; //causes the next command to be an 'and' (which is default) self.instance.query.and = function() { self.repeat(arguments); return self.instance.query; }; //causes the next command to be a 'not' self.instance.query.not = function() { self.setNot(); self.repeat(arguments); return self.instance.query; }; //causes the next command to be a 'not' self.instance.query.andNot = function() { self.setNot(); self.repeat(arguments); return self.instance.query; }; //causes the next command to be a 'not' and 'or' self.instance.query.orNot = function() { self.startNewCommandSet(); self.setNot(); self.repeat(arguments); return self.instance.query; }; //return the query information return self.instance.query; } }, //variety of helper methods util:{ //removes trailing and leading spaces from a value trim:function(value) { //get the string value value = value == null ? "" : value; value = value.toString(); //trim the spaces return value.replace(/^\s*|\s*$/g, ""); }, //clones each item in an array cloneArray:function(array) { var result = []; framework.util.each(array, function(item) { result.push(framework.util.clone(item)); }); return result; }, //creates a copy of an object clone:function(obj) { //for arrays, copy each item if (framework.util.isType(framework.type.array, obj)) { return framework.util.cloneArray(obj); } //for object check each value else if (framework.util.isType(framework.type.object, obj)) { var clone = {}; for(var item in obj) { if (obj.hasOwnProperty(item)) clone[item] = framework.util.clone(obj[item]); } return clone; } //all other types just return the value else { return obj; } }, //creates an invocation handler for a field //name instead of grabbing values invoke:function(obj, args) { //copy the array to avoid breaking any other calls args = args.concat(); //start by getting the path var path = args[0]; //find the method and extract the arguments var method = framework.util.findValue(obj, path); args = framework.util.select(args, null, 1, null); //if we are invoking a method that hangs off //another object then we need to find the value path = path.replace(/\..*$/, ""); var parent = framework.util.findValue(obj, path); obj = parent === method ? obj : parent; //return the result of the call try { var result = method.apply(obj, args); return result; } catch (e) { return null; } }, //gets a path from a field name getPath:function(path) { return framework.util.toString(path).split(framework.exp.get_path); }, //searches an object to find a value findValue:function(obj, path) { //start by checking if this is actualy an attempt to //invoke a value on this property if (framework.util.isType(framework.type.array, path)) { return framework.util.invoke(obj, path); } //if this referring to a field else if (framework.util.isType(framework.type.string, path)) { //get each part of the path path = framework.util.getPath(path); //search for the record var index = 0; while(obj != null && index < path.length) { obj = obj[path[index++]]; } //return the final found object return obj; } //nothing that can be read, just return the value else { return obj; } }, //returns the value at the provided index elementAt:function(collection, index) { return collection && collection.length > 0 && index < collection.length && index >= 0 ? collection[index] : null; }, //makes a string save for regular expression searching regexEscape:function(val) { return (val ? val : "").toString().replace(framework.exp.escape_regex, "\\$&"); }, //matches expressions to a value regexMatch:function(expression, source, ignoreCase) { //get the string value if needed if (framework.util.isType(framework.type.regex, expression)) { expression = expression.source; } //create the actual expression and match expression = new RegExp(framework.util.toString(expression), ignoreCase ? "gi" : "g"); return framework.util.toString(source).match(expression) != null; }, //converts a command to an operator name operatorName:function(name) { return name.replace(/^\w/, function(match) { return match.toUpperCase(); }); }, //changes a value based on the type compare:function(value, types, state) { var result = framework.util.when(value, types, state); return result == true ? result : false; }, //performs the correct action depending on the type when:function(value, types, state) { //get the kind of object this is var kind = framework.util.getType(value); //check each of the types for (var item in types) { if (!types.hasOwnProperty(item)) continue; var type = framework.type[item]; if (type == kind) { return types[item].apply(state, [value]); } } //if there is a fallback comparison if (types.other) { return types.other.apply(state, [value]); } //no matches were found return null; }, //performs an action on each item in a collection each:function(collection, action) { var index = 0; for(var item in collection){ if (collection.hasOwnProperty(item)) action(collection[item], index++); } }, //performs an action to each item in a collection and then returns the items grab:function(collection, action) { var list = []; framework.util.each(collection, function(item) { list.push(action(item)); }); return list; }, //performs an action on each item in a collection until:function(collection, action) { for(var item = 0, l = collection.length; item < l; item++) { var result = action(collection[item], item + 1); if (result === true) { return true; } } return false; }, //checks if the types match isType:function(type, value) { return framework.util.getType(value) == type; }, //finds the type for an object getType:function(obj) { //check if this even has a value if (obj == null) { return framework.type.nothing; } //check each type except object for (var item in framework.library.types) { if (framework.library.types[item](obj)) { return item; } } //no matching type was found return framework.type.object; }, //grabs remaining elements from and array remaining:function(array, at) { var results = []; for(; at < array.length; at++) results.push(array[at]); return results; }, //append items onto a target object apply:function(target, source) { for(var item in source) { if (source.hasOwnProperty(item)) target[item] = source[item]; } return target; }, //performs sorting on a collection of records reorder:function(collection, fields, ignoreCase) { //reverses the fields so that they are organized //in the correct order return framework.util._performSort(collection, fields, ignoreCase); }, //handles actual work of reordering (call reorder) _performSort:function(collection, fields, ignoreCase) { //get the next field to use var field = fields.splice(0, 1); if (field.length == 0) { return collection; } field = field[0]; //get the name of the field and descending or not var invoked = framework.util.isType(framework.type.array, field); var name = (invoked ? field[0] : field); var desc = name.match(/^\-/); name = desc ? name.substr(1) : name; //updat the name if needed if (desc) { if (invoked) { field[0] = name; } else { field = name; } } //IE sorting bug resolved (Thanks @rizil) //http://webcache.googleusercontent.com/search?q=cache:www.zachleat.com/web/2010/02/24/array-sort/+zach+array+sort //create the sorting method for this field var sort = function(val1, val2) { //find the values to compare var a = framework.util.findValue(val1, field); var b = framework.util.findValue(val2, field); //default to something when null if (a == null && b == null) { a = 0; b = 0; } else if (a == null && b != null) { a = 0; b = 1; } else if (a != null && b == null) { a = 1; b = 0; } //check for string values else if (ignoreCase && framework.util.isType(framework.type.string, a) && framework.util.isType(framework.type.string, b)) { a = a.toLowerCase(); b = b.toLowerCase(); } //if there is a length attribute use it instead else if (a.length && b.length) { a = a.length; b = b.length; } //perform the sorting var result = (a < b) ? -1 : (a > b) ? 1 : 0; return desc ? -result : result; }; //then perform the sorting collection.sort(sort); //check for sub groups if required if (fields.length > 0) { //create the container for the results var sorted = []; var groups = framework.util.group(collection, field, ignoreCase); framework.util.each(groups, function(group) { var listing = fields.slice(); var records = framework.util._performSort(group, listing, ignoreCase); sorted = sorted.concat(records); }); //update the main collection collection = sorted; } //the final results return collection; }, //returns groups of unique field values group:function(records, field, ignoreCase) { //create a container to track group names var groups = {}; for(var item = 0, l = records.length; item < l; item++) { //get the values var record = records[item]; var alias = framework.util.toString(framework.util.findValue(record, field)); alias = ignoreCase ? alias.toUpperCase() : alias; //check for existing values if (!groups[alias]) { groups[alias] = [record]; } else { groups[alias].push(record); } } //return the matches return groups; }, //compares two values for equality equals:function(val1, val2, ignoreCase) { return framework.util.when(val1, { string:function() { return framework.util.regexMatch( "^"+framework.util.regexEscape(val2)+"$", val1, ignoreCase); }, other:function() { return (val1 == null && val2 == null) || (val1 === val2); } }); }, //converts an object to an array of elements toArray:function(obj) { var items = []; if (obj.length) { for (var i = 0; i < obj.length; i++) { items.push(obj[i]); } } else { for (var item in obj) { if (obj.hasOwnProperty(item)) items.push(obj[item]); } } return items; }, //converts a value into a string toString:function(val) { return val == null ? "" : val.toString(); }, //grabs a range of records from a collection skipTake:function(collection, action, skip, take) { //set the defaults skip = skip == null ? 0 : skip; take = take == null ? collection.length : take; //check if this will return any records if (skip >= collection.length || take == 0) { return []; } //return the results return framework.util.select(collection, action, skip, skip + take); }, //grabs a range and format for records select:function(collection, action, start, end) { //grab the records if there is a range start = start == null ? 0 : start; end = end == null ? collection.length : end; //slice the records var results = collection.slice(start, end); //check if this is a mapping method if (jLinq.util.isType(jLinq.type.object, action)) { var map = action; action = function(rec) { //map existing values or defaults // TODO: tests do not cover this method! var create = {}; for (var item in map) { if (!map.hasOwnProperty(item)) continue; create[item] = rec[item] ? rec[item] : map[item]; } //return the created record return create; }; }; //if there is a selection method, use it if (jLinq.util.isType(jLinq.type.method, action)) { for (var i = 0; i < results.length; i++) { var record = results[i]; results[i] = action.apply(record, [record]); } } //return the final set of records return results; } } }; //default types framework.library.addType(framework.type.nothing, function(value) { return value == null; }); framework.library.addType(framework.type.array, function(value) { return value instanceof Array; }); framework.library.addType(framework.type.string, function(value) { return value.substr && value.toLowerCase; }); framework.library.addType(framework.type.number, function(value) { return value.toFixed && value.toExponential; }); framework.library.addType(framework.type.regex, function(value) { return value instanceof RegExp; }); framework.library.addType(framework.type.bool, function(value) { return value == true || value == false; }); framework.library.addType(framework.type.method, function(value) { return value instanceof Function; }); framework.library.addType(framework.type.datetime, function(value) { return value instanceof Date; }); //add the default methods framework.library.extend([ //sets a query to ignore case { name:"ignoreCase", type:framework.command.action, method:function() { this.ignoreCase = true; }}, //reverses the current set of records { name:"reverse", type:framework.command.action, method:function() { this.records.reverse(); }}, //sets a query to evaluate case { name:"useCase", type:framework.command.action, method:function() { this.ignoreCase = false; }}, //performs an action for each record { name:"each", type:framework.command.action, method:function(action) { jLinq.util.each(this.records, function(record) { action(record); }); }}, //attaches a value or result of a method to each record { name:"attach", type:framework.command.action, method:function(field, action) { this.when(action, { method:function() { jLinq.util.each(this.records, function(record) { record[field] = action(record); }); }, other:function() { jLinq.util.each(this.records, function(record) { record[field] = action; }); } }); }}, //joins two sets of records by the key information provided { name:"join", type:framework.command.action, method:function(source, alias, pk, fk) { jLinq.util.each(this.records, function(record) { record[alias] = jLinq.from(source).equals(fk, record[pk]).select(); }); }}, //joins a second array but uses only the first matched record. Allows for a default for a fallback value { name:"assign", type:framework.command.action, method:function(source, alias, pk, fk, fallback) { jLinq.util.each(this.records, function(record) { record[alias] = jLinq.from(source).equals(fk, record[pk]).first(fallback); }); }}, //joins two sets of records by the key information provided { name:"sort", type:framework.command.action, method:function() { var args = jLinq.util.toArray(arguments); this.records = jLinq.util.reorder(this.records, args, this.ignoreCase); }}, //are the two values the same { name:"equals", type:framework.command.query, method:function(value) { return jLinq.util.equals(this.value, value, this.ignoreCase); }}, //does this start with a value { name:"starts", type:framework.command.query, method:function(value) { return this.compare({ array:function() { return jLinq.util.equals(this.value[0], value, this.ignoreCase); }, other:function() { return jLinq.util.regexMatch(("^"+jLinq.util.regexEscape(value)), this.value, this.ignoreCase); } }); }}, //does this start with a value { name:"ends", type:framework.command.query, method:function(value) { return this.compare({ array:function() { return jLinq.util.equals(this.value[this.value.length - 1], value, this.ignoreCase); }, other:function() { return jLinq.util.regexMatch((jLinq.util.regexEscape(value)+"$"), this.value, this.ignoreCase); } }); }}, //does this start with a value { name:"contains", type:framework.command.query, method:function(value) { return this.compare({ array:function() { var ignoreCase = this.ignoreCase; return jLinq.util.until(this.value, function(item) { return jLinq.util.equals(item, value, ignoreCase); }); }, other:function() { return jLinq.util.regexMatch(jLinq.util.regexEscape(value), this.value, this.ignoreCase); } }); }}, //does this start with a value { name:"match", type:framework.command.query, method:function(value) { return this.compare({ array:function() { var ignoreCase = this.ignoreCase; return jLinq.util.until(this.value, function(item) { return jLinq.util.regexMatch(value, item, ignoreCase); }); }, other:function() { return jLinq.util.regexMatch(value, this.value, this.ignoreCase); } }); }}, //checks if the value matches the type provided { name:"type", type:framework.command.query, method:function(type) { return jLinq.util.isType(type, this.value); }}, //is the value greater than the argument { name:"greater", type:framework.command.query, method:function(value) { return this.compare({ array:function() { return this.value.length > value; }, string:function() { return this.value.length > value; }, other:function() { return this.value > value; } }); }}, //is the value greater than or equal to the argument { name:"greaterEquals", type:framework.command.query, method:function(value) { return this.compare({ array:function() { return this.value.length >= value; }, string:function() { return this.value.length >= value; }, other:function() { return this.value >= value; } }); }}, //is the value less than the argument { name:"less", type:framework.command.query, method:function(value) { return this.compare({ array:function() { return this.value.length < value; }, string:function() { return this.value.length < value; }, other:function() { return this.value < value; } }); }}, //is the value less than or equal to the argument { name:"lessEquals", type:framework.command.query, method:function(value) { return this.compare({ array:function() { return this.value.length <= value; }, string:function() { return this.value.length <= value; }, other:function() { return this.value <= value; } }); }}, //is the value between the values provided { name:"between", type:framework.command.query, method:function(low, high) { return this.compare({ array:function() { return this.value.length > low && this.value.length < high; }, string:function() { return this.value.length > low && this.value.length < high; }, other:function() { return this.value > low && this.value < high; } }); }}, //is the value between or equal to the values provided { name:"betweenEquals", type:framework.command.query, method:function(low, high) { return this.compare({ array:function() { return this.value.length >= low && this.value.length <= high; }, string:function() { return this.value.length >= low && this.value.length <= high; }, other:function() { return this.value >= low && this.value <= high; } }); }}, //returns if a value is null or contains nothing { name:"empty", type:framework.command.query, method:function() { return this.compare({ array:function() { return this.value.length == 0; }, string:function() { return jLinq.util.trim(this.value).length == 0; }, other:function() { return this.value == null; } }); }}, //returns if a value is true or exists { name:"is", type:framework.command.query, method:function() { return this.compare({ bool:function() { return this.value === true; }, other:function() { return this.value != null; } }); }}, //gets the smallest value from the collection { name:"min", type:framework.command.select, method:function(field) { var matches = jLinq.util.reorder(this.records, [field], this.ignoreCase); return jLinq.util.elementAt(matches, 0); }}, //gets the largest value from the collection { name:"max", type:framework.command.select, method:function(field) { var matches = jLinq.util.reorder(this.records, [field], this.ignoreCase); return jLinq.util.elementAt(matches, matches.length - 1); }}, //returns the sum of the values of the field { name:"sum", type:framework.command.select, method:function(field) { var sum; jLinq.util.each(this.records, function(record) { var value = jLinq.util.findValue(record, field); sum = sum == null ? value : (sum + value); }); return sum; }}, //returns the sum of the values of the field { name:"average", type:framework.command.select, method:function(field) { var sum; jLinq.util.each(this.records, function(record) { var value = jLinq.util.findValue(record, field); sum = sum == null ? value : (sum + value); }); return sum / this.records.length; }}, //skips the requested number of records { name:"skip", type:framework.command.select, method:function(skip, selection) { this.records = this.when(selection, { method:function() { return jLinq.util.skipTake(this.records, selection, skip, null); }, object:function() { return jLinq.util.skipTake(this.records, selection, skip, null); }, other:function() { return jLinq.util.skipTake(this.records, null, skip, null); } }); return this.query; }}, //takes the requested number of records { name:"take", type:framework.command.select, method:function(take, selection) { return this.when(selection, { method:function() { return jLinq.util.skipTake(this.records, selection, null, take); }, object:function() { return jLinq.util.skipTake(this.records, selection, null, take); }, other:function() { return jLinq.util.skipTake(this.records, null, null, take); } }); }}, //skips and takes records { name:"skipTake", type:framework.command.select, method:function(skip, take, selection) { return this.when(selection, { method:function() { return jLinq.util.skipTake(this.records, selection, skip, take); }, object:function() { return jLinq.util.skipTake(this.records, selection, skip, take); }, other:function() { return jLinq.util.skipTake(this.records, null, skip, take); } }); }}, //selects the remaining records { name:"select", type:framework.command.select, method:function(selection) { return this.when(selection, { method:function() { return jLinq.util.select(this.records, selection); }, object:function() { return jLinq.util.select(this.records, selection); }, other:function() { return this.records; } }); }}, //selects all of the distinct values for a field { name:"distinct", type:framework.command.select, method:function(field) { var groups = jLinq.util.group(this.records, field, this.ignoreCase); return jLinq.util.grab(groups, function(record) { return jLinq.util.findValue(record[0], field); }); }}, //groups the values of a field by unique values { name:"group", type:framework.command.select, method:function(field) { return jLinq.util.group(this.records, field, this.ignoreCase); }}, //selects records into a new format { name:"define", type:framework.command.select, method:function(selection) { var results = this.when(selection, { method:function() { return jLinq.util.select(this.records, selection); }, object:function() { return jLinq.util.select(this.records, selection); }, other:function() { return this.records; } }); return jLinq.from(results); }}, //returns if a collection contains any records { name:"any", type:framework.command.select, method:function() { return this.records.length > 0; }}, //returns if no records matched this query { name:"none", type:framework.command.select, method:function() { return this.records.length == 0; }}, //returns if all records matched the query { name:"all", type:framework.command.select, method:function() { return this.removed.length == 0; }}, //returns the first record found or the fallback value if nothing was found { name:"first", type:framework.command.select, method:function(fallback) { var record = jLinq.util.elementAt(this.records, 0); return record == null ? fallback : record; }}, //returns the last record found or the fallback value if nothing was found { name:"last", type:framework.command.select, method:function(fallback) { var record = jLinq.util.elementAt(this.records, this.records.length - 1); return record == null ? fallback : record; }}, //returns the record at the provided index or the fallback value if nothing was found { name:"at", type:framework.command.select, method:function(index, fallback) { var record = jLinq.util.elementAt(this.records, index); return record == null ? fallback : record; }}, //returns the remaining count of records { name:"count", type:framework.command.select, method:function() { return this.records.length; }}, //selects the remaining records { name:"removed", type:framework.command.select, method:function(selection) { return this.when(selection, { method:function() { return jLinq.util.select(this.removed, selection); }, object:function() { return jLinq.util.select(this.removed, selection); }, other:function() { return this.removed; } }); }}, //performs a manual comparison of records { name:"where", type:framework.command.select, method:function(compare) { //filter the selection var state = this; var matches = []; jLinq.util.each(this.records, function(record) { if (compare.apply(state, [record]) === true) { matches.push(record); } }); //create a new query with matching arguments var query = jLinq.from(matches); if (!this.ignoreCase) { query.useCase(); } return query; }} ]); //set the public object jLinq = { //determines if new queries should always be //cloned to prevent accidental changes to objects alwaysClone:false, //sets the default for jLinq query case checking ignoreCase:true, //command types (select, query, action) command:framework.command, //types of object and values type:framework.type, //allows command to be added to the library extend:function() { framework.library.extend.apply(null, arguments); }, //core function to start and entirely new query query:function(collection, params) { return library.framework.query(collection, params); }, //starts a new query with the array provided from:function(collection) { return framework.library.query(collection, { clone:false }); }, //returns a list of commands in the library getCommands:function() { return framework.util.grab(framework.library.commands, function(command) { return { name:command.name, typeId:command.type, type:command.type == framework.command.select ? "select" : command.type == framework.command.query ? "query" : command.type == framework.command.action ? "action" : "unknown" }; }); }, //helper functions for jLinq util:{ //removes leading and trailing spaces trim:framework.util.trim, //loops and finds a value in an object from a path findValue:framework.util.findValue, //gets an element at the specified index (if any) elementAt:framework.util.elementAt, //returns a regex safe version of a string regexEscape:framework.util.regexEscape, //compares an expression to another string regexMatch:framework.util.regexMatch, //compares equality of two objects equals:framework.util.equals, //gets groups for a collection group:framework.util.group, //updates the order of a collection reorder:framework.util.reorder, //performs a function when a value matches a type when:framework.util.when, //converts an object to an array of values toArray:framework.util.toArray, //loops for each record in a set each:framework.util.each, //grabs a collection of items grab:framework.util.grab, //loops records until one returns true or the end is reached until:framework.util.until, //returns if an object is the provided type isType:framework.util.isType, //determines the matching type for a value getType:framework.util.getType, //applies each source property to the target apply:framework.util.apply, //uses the action to select items from a collection select:framework.util.select, //grabs records for a specific range skipTake:framework.util.skipTake } }; //set the other aliases jlinq = jLinq; jl = jLinq; })();