multiplexjs
v1.0.0
Published
Comprehensive data-structure and LINQ library for JavaScript.
Downloads
1,084
Maintainers
Readme
Multiplex
Comprehensive data-structure and LINQ library for JavaScript.
mx(source).operator()
- A
mx
function to define an Enumerable object. - A
(source)
to "query" data. - A LINQ
operator()
to be performed on the data.
Example:
var query = mx([1, 2, 3, 4, 5]).select(function (t) { return t * t });
Creates an Enumerable object which upon execution returns a square root of sequence of numbers.
Previous example using Lambda notation:
var query = mx([1, 2, 3, 4, 5]).select("t => t * t");
In a query that returns a sequence of values, the query variable itself never holds the query results and only stores the query commands. This is known as deferred execution; that is, query execution occurs some time after the query is constructed. Execution of the query is deferred until the query variable is iterated over in a forEach
loop or when you use singleton queries like count
, min
, max
, sum
, aggregate
. These methods execute immediately because the query must produce a sequence to calculate the singleton result. To force immediate execution of a query that does not produce a singleton value, you can call the toList
method, the toDictionary
method, or the toArray
method on a query or query variable.
The following example uses the toArray
method to immediately evaluate a sequence into an array:
mx([1, 2, 3, 4, 5]).select("t => t * t").toArray(); // [1, 4, 9, 16, 25]
The following example uses the sum
method to evaluate sum of the first 10 numbers:
mx([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).sum(); // 55
And the following example uses the forEach
method to iterate over an array of numbers and print them in the developer console:
mx([1, 2, 3, 4, 5]).forEach("t => console.log(t)");
// 1
// 2
// 3
// 4
// 5
mx.range(0, 1000)
.where("t => t % 2 == 0")
.orderByDescending("t => t")
.take(10)
.toArray();
In the example above, mx.range
method is used to create 1000 integer numbers starting from 0, then filter even numbers, sort the result in descending order and only take 10 of them. The result is the last 10 even numbers less than 1000:
[998, 996, 994, 992, 990, 988, 986, 984, 982, 980]
Note that the query is executed only 10 times, as soon as the query reaches the 10th element, the execution breaks and the result is evaluated into an array. read more about LINQ iteration over a query.
mx([1, 2, 3]).select("t => { val: t }").toArray(); // [{ val: 1 }, { val: 2 }, { val: 3 }]
In Multiplex, equality comparison on anonymous types are defined in terms of the equality of the properties, two instances of the same anonymous type are equal only if all their properties are equal. That becomes very handy working with LINQ operations which make use of equality to produce results, eg. contains
, join
, groupBy
, groupJoin
, distinct
, except
and intersect
.
Note that in JavaScript two distinct objects are never equal for either strict or abstract comparisons:
{ val: 1 } == { val: 1 } // false
{ val: 1 } === { val: 1 } // false
However using mx.equals
method you can compare two object literals which results in true if all their properties are equal:
mx.equals({ val: 1 }, { val: 1 }); // true
Since Multiplex LINQ operations internally make use of the mx.equals
method, you can write expressions like these using object literals:
mx([{ val: 1 }, { val: 1 }]).contains({ val: 1 }); // true
mx([{ val: 1 }, { val: 1 }]).distinct().count(); // 1
mx([{ val: 1 }, { val: 1 }]).except([{ val: 1 }]).count(); // 0
The following example uses object literals as the key for the groupBy
operator:
var arr =
[
{ id: 1, val: 10, name: 'A' },
{ id: 1, val: 10, name: 'B' },
{ id: 2, val: 20, name: 'C' },
{ id: 2, val: 20, name: 'D' }
];
var grp = mx(arr)
.groupBy("t => { id: t.id, val: t.val }") // group `key`
.select("t => t.key")
.toArray();
// [{ id: 1, val: 10 }, { id: 2, val: 20 }]
The methods in Enumerable class provide an implementation of the standard query operators for querying data sources which are Enumerable, that is, either are sub-class of the Enumerable class or implement getEnumerator()
method. The standard query operators are general purpose methods that follow the LINQ pattern and enable you to express traversal, filter, and projection operations over data in JavaScript.
The followings are types which can be used to create an Enumerable in Multiplex:
list.select("t => t").toArray(); // [1, 2, 3, 4] set.select("t => t").toArray(); // [1, 2, 3, 4] dic.select("t => t.key").toArray(); // [1, 2, 3, 4]
<br/>
#### - Array and String
*Array*s and *Strings* are *Enumerable* per ser, because they have a default iteration behavior. This means you can pass *String* or *Array* objects to any method accepting *Iterable* argument without wrapping it in an *Enumerable* object.
This comes handy in LINQ operations, so instead of this:
````javascript
mx([1, 2]).union(mx([3, 4])).toArray(); // [1, 4, 9, 16]
mx("str").union(mx("ing")).toArray(); // ["s", "t", "r", "i", "n", "g"]
You can write:
mx([1, 2]).union([3, 4]).toArray(); // [1, 2, 3, 4]
mx("str").union("ing").toArray(); // ["s", "t", "r", "i", "n", "g"]
Note that, in the example above the string object is queried as a sequence of characters. In practice, LINQ operations accept any argument implementing ES6 iteration protocols.
The following example uses jQuery
and Multiplex to get the count of each element in a page:
mx($("*"))
.groupBy("t => t.nodeName")
.select("t => { name: t.key, count: t.count() }")
.toArray();
The same result using document.querySelectorAll
:
mx(document.querySelectorAll("*"))
.groupBy("t => t.nodeName")
.select("t => { name: t.key, count: t.count() }")
.toArray();
The following example uses Multiplex to enumerate arguments
variable available within the Test
function:
function Test()
{
mx(arguments).forEach("t => console.log(t)");
}
Test(1, 2, 3); // 1, 2, 3
Test("a", "b"); // "a", "b"
Initially, the enumerator is positioned before the first element in the collection. At this position, the current
property is undefined. Therefore, you must call the next()
method to advance the enumerator to the first element of the collection before reading the value of current
.
current
returns the same object until next()
is called. next()
sets current
to the next element. If next()
passes the end of the collection, the enumerator is positioned after the last element in the collection and next()
returns false. When the enumerator is at this position, subsequent calls to next()
also return false. If the last call to next()
returns false, current
is undefined.
The following code example demonstrates the implementation of the getEnumerator()
method for a custom object. In this example, getEnumerator()
is not explicitly called, but it is implemented to yield 3 integer numbers in a LINQ operation.
var obj = {
getEnumerator: function () {
var count = 3, index = 0;
return {
current: undefined,
next: function () {
if (index++ < count) {
this.current = index;
return true;
}
else {
this.current = undefined;
return false;
}
}
}
}
};
mx(obj).toArray(); // [1, 2, 3]
Read more about getEnumerator() method.
var obj = { name: "myObj", val: 1 };
mx(obj).toArray(); // [{ key: "name", value: "myObj" }, { key: "val", value: 1 }]
ES6 Iteration protocols
ECMAScript 6 comes with two iteration protocols: The iterable protocol and the iterator protocol:
- The iterable protocol allows JavaScript objects to define or customize their iteration behavior. In order to be iterable, an object must implement the
@@iterator
method, meaning that the object (or one of the objects up its prototype chain) must have a property with aSymbol.iterator
key. It is pretty much like thegetEnumerator()
method in the Enumerable class. - The iterator protocol defines a standard way to produce a sequence of values. An object is an iterator when it implements a
next()
method. It is pretty much like the Enumerator class.
Whenever an object needs to be iterated (such as at the beginning of a for..of
loop), its @@iterator
method is called with no arguments, and the returned iterator is used to obtain the values to be iterated.
Both iterable and iterator protocols are supported in Multiplex:
- Every Enumerable object implements iterable protocol
- Every JavaScript object implementing iterator protocol can be used as a source to Enumerable
The following example demonstrates the use of iterable protocol and for-of
loop in an Enumerable object:
var source = mx.range(0, 4); // An Enumerable of numbers
var iterable = source[Symbol.iterator]; // Retrieve @@iterator method
for(var value of source){
console.log(value);
}
// 0
// 1
// 2
// 3
The following example demonstrates the use of iterator protocol
in a Set
to create an Enumerable
:
(String
, Array
, TypedArray
, Map
and Set
are all built-in JavaScript iterables, because the prototype objects of them all have an @@iterator
method.)
var set = new Set([1, 2, 3]); // Create a Set of numbers
mx(set).toArray(); // [1, 2, 3]
Generator functions
Generators are functions which can be exited and later re-entered. Their context (variable bindings) will be saved across re-entrances. Generators are part of ES6 iteration protocols.
The function*
declaration defines a generator function, which returns a Generator object. A generator object is both, iterator and iterable and is a simple, efficient way to create an Enumerable:
The following example demonstrates the use of generator function in to create an Enumerable:
var gen = function* () {
yield 1;
yield 2;
yield 3;
}
mx(gen).toArray(); // [1, 2, 3]
In practice, using generator function, is the best way to create a custom Enumerable.
Legacy generator functions
Generator functions are great to create an Enumerable, however, browser support is at this point very limited (Chrome 39+, FireFox 36+)
To simulate generators functions, Multiplex supports an alternative legacy syntax which makes use of closure
to create a stateful generator function. You have to use Multiplex's Enumerator class to initiate a generator function.
When the Enumerator's next()
method is called, the generator function's body is executed and a yielder
parameter is passed to the generator function. The yielder
parameter is itself a function which upon execution yields the value to be returned from the Enumerator:
The following example creates an infinite Enumerator, each time the next()
method is called, it increments and yields a number:
var index = 0;
var gen = new mx.Enumerator(function(yielder){
yielder(index++);
});
gen.next(); // true
gen.current; // 0
gen.next(); // true
gen.current; // 1
Using legacy generator functions as Enumerable
To use a legacy generator function with Multiplex, you need to wrap the process of creating an Enumerator in a factory function. The following example demonstrates creating an Enumerable of numbers using legacy generator function:
var source = mx(function(){
var count = 3, index = 0;
return new mx.Enumerator(function(yielder){
if(index++ < count)
yielder(index);
});
});
source.toArray(); // [1, 2, 3]
Clone a copy of the main Multiplex git repo by running:
git clone git://github.com/multiplex/multiplex.js.git
To create custom build install grunt command line interface as a global package (If you haven't used Grunt before, be sure to check out the Getting Started guide):
npm install -g grunt-cli
Then, make sure all Node dependencies are installed by runnung the following command in the Multiplex
directory:
npm install
Now by running the grunt
command, in the Multiplex
directory, you can build Multiplex:
grunt
To execute all unit tests using grunt, run grunt tests
command; This uses PhantomJS to run over 500 unit tests defined in test
directory, you can also run tests from within your browser by running test\mx.html
file.
To build a full version of Multiplex use:
grunt release
When the tests pass, the built version of Multiplex will be put in the build/
subdirectory, along with the minified copy and associated map file.