0.7.10 • Published 10 years ago

qstore v0.7.10

Weekly downloads
3
License
MIT
Repository
github
Last release
10 years ago

Qstore

##Overview Work with collections in javascript

  • Create your collections.
  • Search and update data using queries.
  • Use computed fields.
  • Get collections changes.
  • Extend your query language.

###Simple examples Find all green apples from fruits collection:

fruits.find({type: 'apple', color: 'green'});

Find all apples and pears from fruits collection:

fruits.find({type: ['apple', 'pear']});

Which fruits can be red?

	fruits.getList({color: 'red'}, 'type');// ['apple', 'pear', 'strawberries']

See more examples

##Instalation

###Front-end

vanila-js style:

<script type="text/javascript" src="qstore/qstore.js"></script>

via bower package manager:

bower install qstore

###Back-end

nodejs:

npm install qstore

##API

###Initialisation

Using array of objects:

var fruits = new Qstore([
	{type: 'apple', color: 'red', weight: 0.25, price: 1.5},
	{type: 'pear', color: 'green', weight: 0.4, price: 2},
	{type: 'pear', color: 'red', weight: 0.3, price: 1.8},
	{type: 'apple', color: 'yellow', weight: 0.26, price: 1.2},
]);

Using reduced format:

var fruits = new Qstore({
	columns: ['type', 'color', 'weight', 'price'],
	rows: [
		['apple', 'red', 0.25, 1.5],
		['pear', 'green', 0.4, 2],
		['pear', 'red', 0.3, 1.8],
		['apple', 'yellow', 0.26, 1.2]
	]
});

###Data search ####.find (query, fields, options)

Returns all objects which are valid for query. See examples of usage

  • query {Object|Array|Function|Bolean}
    If query is true then all rows will be returned.
    If query is Object or Array:
    { } - contains conditions separeted with and
    - contains conditions separeted with or
    Operators describe as $<operator name>, see Operators.
    Example:

    // find all red or green fruits with price between 0.5 and 1.5  
    fruits.find({color: ['red', 'green'], price: {$gt: 0.5, $lt: 1.5}});
    // using regular expressions
    fruits.find({type: /apple/});//returns all apples and pineapples

    If query is Function:
    Example:

    // find all red fruits
    fruits.find(function (row) {
        return row.color == 'red';
    });

    If query is Object that contains functions:

    Function with field-context:

    // find all fruits with integer price or with 0.5$ price
    fruits.find({price: [0.5, function (price) { return price % 1 == 0}]);

    Function with row-context:

    // find all fruits with integer price or with 0.5$ price
    fruits.find([price: 0.5, function (row) { return row.price % 1 == 0});
  • fields=true {Array|Boolean}
    Array of field names which will be added to result.
    Example:

    	fruits.find({type: 'apple'}, ['type', 'color', 'price']);

Also you can use fields aliases

  • options {Object}

    Example:

    	// find first two apples
    	fruits.find({type: 'apple'}, true, {limit: 2});
    	
    	// find two yellow fruits beginning with third yellow fruit
    	fruits.find ({color: 'yellow'}, true, {limit: [3,2]});
    	

#####Deep search:

Deep search in object:

	var usersMessages = new Qstore ({
		columns: ['text', 'subject', 'user'],
		rows: [
			['Hi', 'new year', {id: 1, name: 'Bob', company: {name: 'IBM', phone: '+9999'} }],
			['Happy new year!', 'new year', {id: 2, name: 'Kate', company: {name: 'Microsoft', phone: '+8888'}}],
			['How to learn javascript?', 'programming', {id: 2, name: 'Stan'}],
			['Anyone want to dance?', 'new year', {id: 2, name: 'James'}]
		]
	});

	// find all messages with subject 'New year' from user with name 'Bob' who works in 'IBM' company
	
	messages.find({subject: 'new year', 'user.name': 'Bob', 'user.company.name': 'IBM'});

	// or
	messages.find({subject: 'new year', user: {name: 'Bob', company: {name: 'IBM'} }});
	

Deep search in collection:

	
	// create collection of costumes
	var costumes = new Qstore([
		{name: 'policeman', items: [ {name: 'tie', color: 'black'}, {name: 'cap', color: 'blue'}]},
		{name: 'fireman', items: [{name: 'hemlet', color: 'yellow'}]},
		{name: 'solder', items: [{name: 'hemlet', color: 'green'}]}
	]);
	
	// find costumes which have hemlet in items
	costumes.find({items: {name: 'hemlet'} });

#####Aliases:

You can create aliases for fields by using the syntax "fieldName:aliasName" or by alias map

	// create collection of messages
	var messages = new Qstore ({
		columns: ['text', 'subject', 'user'],
		rows: [
			['Hello world!', 'programming', {id: 1, name: 'Bob'}],
			['Happy new year!', 'new year', {id: 2, name: 'Kate'}],
			['How to learn javascript?', 'programming', {id: 2, name: 'Stan'}],
			['Anyone want to dance?', 'new year', {id: 2, name: 'James'}]
		]
	});

	// we need to select "text" and "user.name" from messages collection

	// first way
	messages.find({subject: 'new year'}, ['text', 'user.name:userName']);

	// second way
	messages.find({subject: 'new year'}, {text: true, userName: 'user.name'});

	// [ {text: 'Happy new year!', userName: 'Kate'}, {text: 'Anyone want to dance?', userName: 'James'}]

result of example:

	[
		{text: 'Happy new year!', userName: 'Kate'},
		{text: 'Anyone want to dance?', userName: 'James'}
	]

Use "fieldname:" syntax to extract field values on one level up.

	// create collection of changes
	var usersChanges = new Qstore ({
		columns: ['source', 'patch'],
		rows: [
			[{id: 2, name: 'Bob', age: 23}, {name: 'Mike'}],
			[{id: 4, name: 'Stan', age: 30}, {age: 31}]
		]
	});
	
	var patchForDataBase = usersChanges.find(true, ['source.id:id', 'patch:']);
	// now patchForDataBase contains:
	// [{id: 2, name: 'Mike'}, {id: 4, age: 31}];
	

#####Comparison of fields.

Use '$.fieldName' syntax to get the value of field

	// create collection of diet
	var diet = new Qstore ({
		columns: ['month', 'breakfast', 'dinner'],
		rows: [
			['april', {calories: 400, food: 'egg'}, {calories: 300, food: 'soup'}],
			['may', {calories: 300, food: 'bacon'}, {calories: 500, food: 'soup'}],
			['june', {calories: 350, food: 'porridge'}, {calories: 300, food: 'chicken'}]
		]
	});
	
	// find diet where dinner calories less when breackfast calories
	diet.find({'dinner.calories': {$lt: '$.breakfast.calories'} });

#####Queries concatenation:

	// create two filters
	var filter1 = {type: 'apple'};
	var filter2 = {color: 'red'};
	
	// search rows valid for filter1 or filter2
	var commonFilter1 = [filter1, filter2]
	
	// search rows valid for filter1 and filter2
	var commonFilter2 = {$and: [filter1, filter2]};
	

####.search (query ,fields=true) Same as .find but returns Qstore collection

	// get collection of red fruits sorted by type
	fruits.search({color: 'red'}).sort({fieldName: 'type', order: 'asc');

search with extending

	clients.search({status: 'online'}).sendMessage('hello!');

####.findOne (query, ,fields=true)

find first row valid for query. It same as:

	.find(query, fields, {limit: 1})[0]

####Qstore.findIn (rows, query ,fields=true) same as .find but work as static method with array.

	var users = [
		{name: 'user1', id: 1, email: 'user1@anymail.com'},
		{name: 'user2', id: 2, email: 'user2@anymail.com'},
		{name: 'user3', id: 3, email: 'user3@anymail.com'},
		{name: 'user4', id: 4, email: 'user4@anymail.com'}
	];
	
	// find user with id = 3
	Qstore.findIn(users, {id: 3});

####Qstore.test (object, query) Checks that the object match the query.

	var fruit = {type: 'pineapple', color: 'yellow', weight: 1, price: 4};
	
	// The fruit is yellow?
	Qstore.test(fruit, {color: 'yellow'}); //true
	
	// The fruit is pineapple or pear?
	Qstore.test(fruit, {type: ['pear', 'pineapple']}); //true
	
	// The fruit has "apple" in type?
	Qstore.test(fruit, {type: {$like: 'apple'}}); //true
	
	// Fruit price less when 1$ per kg
	Qstore.test(fruit, function (fruit) { return fruit.price/fruit.weight < 1});//false

####.getList (query); Returns list of values for fieldName.
Elements of the list are not repeated.

Examples:

	// list of all fruits colors
	fruits.getList('color'); // ['red', 'green', 'yellow']
	
	// list of all pears colors
	fruits.getList({type: 'pear'}, 'color');// ['green', 'red']
	
	// What fruits can be red?
	fruits.getList({color: 'red'}, 'type');// ['apple', 'pear', 'strawberries']
	
	// get fruits types with idx in [3, 5, 6]
	fruits.getList({idx: [3, 5, 6]}); //['pear', 'apple', 'banana']
	
	//get list of idx
	fruits.getList();
	
	// list of deep fields
	messages.getList('user.name'); // ['Bob', 'Kate', 'Stan', 'James']

	// you can also use function instead field name
	fruits.getList({type: 'apple'}, function (fruit) {
		return fruit.color + ' ' + fruit.type}
	} // ['red apple', 'yellow apple', 'green apple']

####.each (query=true, fn) apply function for each row

  • query=true {Object|Function|Boolean} filter query
  • fn {Function} function to apply
	// add message to log for each fruit
	fruits.each(function (row, i, query) {
		conslole.log('fruit №' + i + ' is ' + row.type);
	});
	
	// it will write:
	//
	// fruit №1 is apple
	// fruit №2 is pear
	// etc... 
	//
	

###Operators Оperators are used to extending the query language of search operations. Each operator is function which returs true if item valid for query or false if not.

####Build-in operators name | description ----- | ----------- $eq | equals $ne | not equals $gt | more then $lt | less then $gte | more or equals then $lte | less or equals then $and | change condition of operator from or to and $like | "like" search $has | check exsisting of value in array or in object or in string see $has operator

you can also add your operators - see addOperator method

$has operator:

	var clothes = new Qstore([
		{name: 'skirt', sizes: ['xs', 's', 'xl']},
		{name: 'jeans', sizes: ['m', 'xxl']},
		{name: 'skirt', sizes: ['xs', 's', 'xl']}
	]);
	
	clothes.find({name: 'skirt', sizes: {$has: 'xs'}});
	
	clothes.find({name: 'skirt', sizes: {$has: ['xs', 's'] }});
	
	

####Qstore.addOperator (operatorName, function isSimple=true)

Example:

	/* we need find fruits with integer price */
	
	// add "isInt" operator
	Qstore.addOperator('isInt', function (left, right) {
		var isInt = (left % 1 == 0);
		return right ? isInt : !isInt
	});
	
	// find them
	fruits.find({price: {$isInt: true}});
	
	// find other
	fruits.find({price: {$isInt: false}});
	

####Qstore.removeOperator (operatorName)

Remove operator by operatorName.


<a name="functions"">

Functions

Functions used for runtime calculations in query. You may use build-in functions or you own functions

Example of usage "length" function

	// create collection of users
	users = new Qstore ([
		{id: 1, name: 'Bob', friends: ['Mike', 'Sam']},
		{id: 2, name: 'Martin', friends: ['Bob']},
		{id: 3, name: 'Mike', friends: ['Bob', 'Martin', 'Sam']},
		{id: 4, name: 'Sam', friends: []}
	]);

	// find users who have not any friend
	users.find({'friends.$length': 0});
	
	// find users who have more than 2 friends
	users.find({'friends.$length': {$gt: 2} });
	 

Example of usage "max" and "min" function:

	// create collection of clothes
	var clothes =  new Qstore([
		{name: 'skirt', sizes: [42, 48, 50]},
		{name: 'jeans', sizes: [48, 54]},
		{name: 'skirt', sizes: [42, 45, 48]}
	]);
	
	// select name and maxSize of each item
	clothes.find(true, ['name', 'sizes.$max:maxSize']);
	
	// find clothes with min size = 42
	clothes.find({'size.$min': 42});
	

Functions also can be used in fields selection

	// select user name and count of friends
	users.find(true, ['name', 'friends.$length:friendsCount']);
	

The grouping methods and getList method also supports the functions syntax:

	// get list of all first friends
	users.getList('friends.$first')

You can use result of one function as arguments for another function:

	
	// get first letter in lower case of first friends
	
	users.getList({'friends.$length': {$gt: 0}}, 'friends.$first.$first.$lower')
	

You can pass additional arguments to functions:

// create collection of сostumes for Halloween
costumes = new Qstore([
		{name: 'policeman', items: [ {name: 'tie', color: 'black'}, {name: 'cap', color: 'blue'}]},
		{name: 'fireman', items: [{name: 'helmet', color: 'yellow'}]},
		{name: 'solder', items: [{name: 'helmet', color: 'green'}]},
		{name: 'zombie', items: [{name: 'skin', color: 'green'}, {name: 'brain', color: 'pink'}]}
	]);
	
// get list of colors for each costume
costumes.find(true, ['name', 'items.$getList("color"):colors']);

When you use functions, avoid redundant expressions. For example we need to find all costumes wich have yellow color. We may use find and lenght function:

	costumes.find({'items.$find({"color": "yellow"}).$length': {$gt: 0} });

But in this case the right way - using a deep search.

	costumes.find({'items': {color: 'yellow'}});

Build-in functions

namedescription
$lengthlength of array, string or count of keys in object
$firstfirst item of array or first letter of string or first property of object
$minretunrs max of array
$maxreturns min of array
$absabsolute value
$finduse Qstore.findIn method
$mapOfuse Qstore.mapOf method
$indexByuse Qstore.indexBy method
$testuse Qstore.test method
$getListuse Qstore.getList method
$uppertranslate string to upper case
$lowertranslate string to lower case
$toNumbercast to Number
$toStringcast to String

You can also add your functions - see addFunction method


Add function

in development


Remove function

in develomnent


###Fields selection


###Grouping

.indexBy (indexes)

returns map of keys for collection

indexes {String|Array} key or array of keys

	// create collection of users
	var users = new Qstore ([
		{id: 12, name: 'Bob', friends: ['Mike', 'Sam']},
		{id: 4, name: 'Martin', friends: ['Bob']},
		{id: 5, name: 'Mike', friends: ['Bob', 'Martin', 'Sam']},
		{id: 10, name: 'Sam', friends: []},
		{id: 15, name: 'Sam', friends: ['Mike']}
	]);

	// index by user's name
	users.indexBy('name');
	

Result of previous example:

{
	Bob: {id: 12, name: 'Bob', friends: ['Mike', 'Sam']},
	Martin: {id: 4, name: 'Martin', friends: ['Bob']}
	Mike: {id: 5, name: 'Mike', friends: ['Bob', 'Martin', 'Sam']},
	Sam: [{id: 10, name: 'Sam', friends: []}, {id: 15, name: 'Sam', friends: ['Mike']}]
}

If for one key exixting more then one item then values will be wrapped in array.

You can also use more then one key:

	// first indexed by name, and then by id
	users.indexBy(['name', 'id']);

Result of previous example:

{
	Bob: {
		12: {id: 12, name: 'Bob', friends: ['Mike', 'Sam']}
	Martin: {
		4: {id: 4, name: 'Martin', friends: ['Bob']}
	},
	Mike: {
		5: {id: 5, name: 'Mike', friends: ['Bob', 'Martin', 'Sam']}
	},
	Sam: {
		10: {id: 10, name: 'Sam', friends: []},
		15: {id: 15, name: 'Sam', friends: ['Mike']}
	}
}

You can also use static implementation of .indexBy : Qstore.indexBy (items, indexes)


.mapOf (indexes)

same as .indexBy, but it always wrap values in array

indexes {String|Array} key or array of keys

example:

	// create collection of shop's locations
	window.shops = new Qstore ({
		columns: ['country', 'city', 'address'],
		rows: [
			['UK', 'London', 'mace st. 5'],
			['UK', 'York', 'temple ave. 10'],
			['France', 'Paris', 'de rivoli st. 20'],
			['France', 'Paris', 'pelleport st. 3'],
			['Germany', 'Dresden', 'haydn st. 2'],
			['Germany', 'Berlin', 'bornitz st. 50'],
			['Germany', 'Munchen', 'eva st. 12'],
			['Russia', 'Vladivostok', 'stroiteley st. 9']
		]
	});
	
	// first group by country, and then by city
	shops.mapOf(['country', 'city']);
	

in previous example .mapOf returns object like this:

{
	France: {
		Paris: Array[2],
	Germany: {
		Berlin: Array[1],
		Dresden: Array[1],
		Munchen: Array[1]
	},
	Russia: {
		Vladivostok: Array[1]
	},
	UK: {
		London: Array[1]
		York: Array[1]
	}
}

You can use function which returns index:

	shops.mapOf(function (shop) {
		return shop.country + ' - ' + shop.city
	});

Reslut:

	'France - Paris': Array[2]
	'Germany - Berlin': Array[1]
	'Germany - Dresden': Array[1]
	'Germany - Munchen': Array[1]
	'Russia - Vladivostok': Array[1]
	'UK - London': Array[1]
	'UK - York': Array[1]

You can also use static implementation of .mapOf : Qstore.mapOf (items, indexes)


.groupBy (group1 , group2, ..., groupN)

Powerful method for creating grouped collections. Returns Qstore.

Group by country:

	var items =  shops.search({country: ['Germany', 'France']});
	var groups = items.groupBy('country');

Result of groups.rows:

	[
		{
			_g: Array[2],
			country: "France",
			idx: 1
		},
		{
			_g: Array[3],
			country: "Germany",
			idx: 2
		}
	]

All items of group stored in special field _g.

Group first 4 items by country and city:

	var items = shops.search(true, true, {limit: 4});
	var groups = items.groupBy(['country', 'city']);

Result of groups.rows:

	[
		{
			_g: Array[1],
			city: "London",
			country: "UK",
			idx: 1
		},
		{
			_g: Array[1],
			city: "York",
			country: "UK",
			idx: 2
		},
		{
			_g: Array[2]
			city: "Paris"
			country: "France"
			idx: 3
		}
	]

You can also use long syntax items.groupBy({country: true, city: true}) instead items.groupBy(['country', 'city']). You can use different ways of describing the same action:

	// group by field 'country' and set alias 'countryName' for this field
	shops.groupBy('country:countryName');
	shops.groupBy({'country:countryName': true});
	shops.groupBy({countryName: 'country'});

Group by country and when by city:

	var items = shops.search({country: ['Germany', 'France']});
	var groups = items.groupBy('country', 'city').rows

Result of groups.rows:

[
	{
		_g: [
			{
				_g: Array[2],
				city: "Paris"
			}
		],
		country: "France",
		idx: 1
	},
	{
		_g: [
			{
				_g: Array[1],
				city: "Dresden"
			},
			{
				_g: Array[1],
				city: "Berlin"
			},
			{
				_g: Array[1],
				city: "Munchen"
			}
		],
		country: "Germany",
		idx: 2
	}
]

You also can use function instead field name:

	// group contacts by first letter
	var groups = contacts.sort('name').groupBy({
		letter: function (contact) {
			var firstLetter = contact.name.charAt(0);
			if (firstLetter <= 'H') return 'A - H';
			if (firstLetter >= 'R') return 'R - Z';
			return 'I - Q';
		}
	});

Result of groups.rows:

	[
		{
			_g: Array[5],
			idx: 1,
			letter: "A - H"
		},
		{
			_g: Array[4],
			idx: 2,
			letter: "I - Q"
		},
		{
			_g: Array[2],
			idx: 3,
			letter: "R - Z"
		}
	]

Sometimes you may need to add additional field which must be linked with group. For this case you can use special directive $add:

	var groups = shops.groupBy({
		country: true,
		$add: {
			shopsCount: '_g.$length'
		}
	});

Result of groups.rows:

	{
		_g: Array[2],
		country: "UK",
		idx: 1,
		shopsCount: 2
	},
	{
		_g: Array[2],
		country: "France",
		idx: 2,
		shopsCount: 2
	},
	{
		_g: Array[3]
		country: "Germany"
		idx: 3
		shopsCount: 3
	},
	{
		_g: Array[1]
		country: "Russia"
		idx: 4
		shopsCount: 1
	}

Additional fields processing when operation of grouping was done, therefore in previous example we can got count of items in group with help of $length function.

You can use function as argument for $add directive:

	// group by country and add cities list for each group
	var groups = shops.groupBy({
		country: true,
		$add: function (item) {
			var cities = Qstore.getList(item._g, 'city');
			return {cities: cities.join(', ')};
		}
	});

Result of groups.rows:

[
	{
		_g: Array[2],
		cities: "London, York",
		country: "UK",
		idx: 1
	},
	{
		_g: Array[2],
		cities: "Paris",
		country: "France",
		idx: 2
	},
	{
		_g: Array[3],
		cities: "Dresden, Berlin, Munchen",
		country: "Germany",
		idx: 3
	},
	{
		_g: Array[1],
		cities: "Vladivostok",
		country: "Russia",
		idx: 4
	}
]

Also you can use function as value for additional field, and write previous example at another manner:

	var groups = shops.groupBy({
		country: true,
		$add: {
			cities: function (item) {
				var cities = Qstore.getList(item._g, 'city');
				return cities.join(', ');
			}
		}
	});

###Data manipulation

####.add (rows ,soft=false)

add new items to collection

  • row {Object|Array}
  • soft soft add. See soft mode.

Examples:

	//add one new fruit
	fruits.add({type: 'carrot', color: 'red', weight: 0.3, price: 0.3});
	
	//add few new fruits
	fruits.add([
		{type: 'carrot', color: 'red', weight: 0.3, price: 0.3},
		{type: 'orange', color: 'orange', weight: 0.4, price: 0.5}
	]);

####.update (searchQuery, updateQuery ,soft=false) Update items in collection.

  • searchQuery {Object|Function} if option is set then will be updated only finded items
  • updateQuery {Object|Function} patch or function returned patch
  • soft=false soft update. See soft mode

Examples:

	//all fruits will be apples
	fruits.update({type: 'apple'});
	
	//make all green fruits red
	fruits.update({color: 'green'}, {color: 'red'});
	
	//The price of all pears will increase by 1 $
	fruits.update(function (item) {
		if (item.type == 'pear') {
			return {price: item.price + 1}
		}
	});

####.patch (values ,key='idx') Update current collection by using update-collection.

	var patch = [
		{id: 21, connected: true},
		{id: 22, connected: false},
		{id: 33, name: 'unknown'}
	];
	
	users.patch(patch, 'id');

####.remove (expr ,soft=false)

Delete items from collection and returns count of deleted items.

	// delete messages that do not have author
	messages.remove({author: undefined});

####.addFields (fields) Add new fields in collection.

  • fields {Array|Object} array of new fields settings

Fields with default values:

	messages.addFields([
		{name: 'author', default: 'unknown'},
		{name: 'rating', default: 0}
	]);
	
	messages.add({text: 'hello world'});
	messages.findOne({text: 'hello world'}); // {text: 'hello world', author: 'unknown', rating: 0}

Computed fields:

	fruits.addFields({name: 'pricePerKg', compute: function (fruit) {
		return fruit.price / fruit.weight;
	});

.compute ()

Forced recalculate computed fields.
Computed fields automatically recalculeted whan collection was changed. Use this method if you need recalculate computed fields manualy.


####.removeFields (fields) remove fields from collection

  • fields {String|Array} field name or array of field names to delete
	// delete one field
	fruits.removeFields('weight');
	
	// delete few fields
	fruits.removeFields(['price', 'color']);

####.sort (fields ,zeroIsLast=false) another variant: sort (fn) where fn is sort function

Sort collection.

Examples:

	// sort by idx
	fruits.sort();
	
	// sort by type (asc)
	fruits.sort({fieldName: 'type', order: 'asc'});
	
	// sort by type (asc) when by price (desc)
	fruits.sort([
		{fieldName: 'type', order: 'asc'},
		{fieldName: 'price', order: 'desc'},
	]);
	
	// sort by price, zero values will be in the end of collection
	fruits.sort({fieldName: 'price', zeroIsLast: true});
	
	// use sort function
	fruits.sort(function (fruit1, fruit2) {
		return fruit1.price - fruit2.price;
	});
	

###Work with changes By default, your collections keep changes until you call the method commit or rollback. If you do not need this functionality, see soft mode.

####.getChanges () returns collection of changes

Examples:

	// we need get the list of idx of removed items
	fruits.remove({type: 'apple'});
	fruits.getChanges().search({action: 'remove'}).getList('source.idx');// [1, 4, 9]
	
	// do some changes
	fruits.update({type: 'pear'}, {color: 'blue', price: 0.5});
	fruits.add({type: 'apple', color: 'green'});
	fruits.remove({type: 'pineapple'});
	
	// we need to create patch for database
	var changes = fruits.getChanges();
	var patch = {};
	patch.add = changes.find({action: 'add'}, ['values:']);
	patch.remove = changes.search({action: 'remove'}).getList('source.idx');
	patch.update = changes.find(true, ['source.idx:id', 'values:']);
	

An easier way to get the map of changes - use getChangesMap method.


####.getChangesMap (keyField='idx')

Use this method to get map of changes group by action like:

{
	add: [array of added items]
	remove: [array of removed keys]
	update: [array of updated values]
}	

####.commit () Commit changes.


####.rollback () Revert all changes.


####softMode Some actions may be called with soft mode. This means that they do not add information about the change in the list of changes.

If you want that your collection always work in soft mode use .setSoftMode method.

	fruits.setSoftMode(true)

If you want that anyone new collection will be work in soft mode use Qstore.setSoftMode method


###Utilites

####.size () Returns rows count.


####.pack (query) Returs reduced collection.

	// create fruits collection
	var fruits = new Qstore([
		{type: 'apple', color: 'red', weight: 0.25, price: 1.5},
		{type: 'pear', color: 'green', weight: 0.4, price: 2},
		{type: 'pear', color: 'red', weight: 0.3, price: 1.8},
		{type: 'apple', color: 'yellow', weight: 0.26, price: 1.2}
	]);
	
	var apples =  fruits.pack({type: 'apple'}, ['idx', 'weight', 'price']);
	
	//now apples contains:
	{
		columns: ['idx', 'weight', 'price'],
		rows: [
			[1, 0.25, 1.5],
			[4, 0.26, 1.2]
		]
	}
	

You can use this method if you whant to send collection or part of collection by network, because it will reduce the outgoing traffic.


####.getCopy () Returns a new independent collection, which will be copy of current collection.


###Events

Events list

  • change
  • commit
  • sort

Use setListener method to react on changes.


.setListener (fn)

  • fn {Function} listener function

Example:

// We want to add messages to the log, if any apple will change its color.
var listener = function (name, data, collection) {

	// We are interested only in the event "change" with the action "update"
	if (name != 'change' || data.action != 'update') return;
	
	// get operations changes
	var changes = data.changes;
	
	// find apples with changed color
	var applePainting = changes.find({'source.type': 'apple', 'patch.color': {$ne: undefined} });
	
	// write to log
	for (var i = 0; i < applePainting.length; i++) {
		var change = applePainting[i];
		console.log('Some apple change color from ' + change.source.color + ' to ' + change.patch.color);
	}
};

fruits.setListener(listener);

fruits.update({color: 'blue'}); // it will write to log:
// Some apple change color from red to blue
// Some apple change color from yellow to blue
// Some apple change color from green to blue

###Examples of collections

In many examples of the API docs using various collections. You can explore their in this section.

####fruits

	var fruits = new Qstore({
		columns: ['type', 'color', 'weight', 'price'],
		rows: [
			['apple', 'red', 0.25, 1.5],
			['pear', 'green', 0.4, 2],
			['pear', 'red', 0.3, 1.8],
			['apple', 'yellow', 0.26, 1.2],
			['pineapple', 'yellow', 1, 4],
			['banana', 'yellow', 0.3, 1.5],
			['melon', 'yellow', 3, 3],
			['watermelon', 'green', 10, 5],
			['apple', 'green', 0.24, 1],
			['strawberries', 'red', 0.1, 0.2]
		]
	});

####usersMessages

	var usersMessages = new Qstore ({
		columns: ['text', 'subject', 'user'],
		rows: [
			['Hi', 'new year', {id: 1, name: 'Bob', company: {name: 'IBM', phone: '+9999'} }],
			['Happy new year!', 'new year', {id: 2, name: 'Kate', company: {name: 'Microsoft', phone: '+8888'}}],
			['How to learn javascript?', 'programming', {id: 2, name: 'Stan'}],
			['Anyone want to dance?', 'new year', {id: 2, name: 'James'}]
		]
	});

####messages

	var messages = new Qstore ({
		columns: ['text', 'subject', 'user'],
		rows: [
			['Hello world!', 'programming', {id: 1, name: 'Bob'}],
			['Happy new year!', 'new year', {id: 2, name: 'Kate'}],
			['How to learn javascript?', 'programming', {id: 2, name: 'Stan'}],
			['Anyone want to dance?', 'new year', {id: 2, name: 'James'}]
		]
	});

####diet

	var diet = new Qstore ({
		columns: ['month', 'breakfast', 'dinner'],
		rows: [
			['april', {calories: 400, food: 'egg'}, {calories: 300, food: 'soup'}],
			['may', {calories: 300, food: 'bacon'}, {calories: 500, food: 'soup'}],
			['june', {calories: 350, food: 'porridge'}, {calories: 300, food: 'chicken'}]
		]
	});

####users

	var users = new Qstore ([
		{id: 12, name: 'Bob', friends: ['Mike', 'Sam']},
		{id: 4, name: 'Martin', friends: ['Bob']},
		{id: 5, name: 'Mike', friends: ['Bob', 'Martin', 'Sam']},
		{id: 10, name: 'Sam', friends: []},
		{id: 15, name: 'Sam', friends: ['Mike']}
	]);

####costumes

	var costumes = new Qstore([
		{name: 'policeman', items: [ {name: 'tie', color: 'black'}, {name: 'cap', color: 'blue'}]},
		{name: 'fireman', items: [{name: 'helmet', color: 'yellow'}]},
		{name: 'solder', items: [{name: 'helmet', color: 'green'}]},
		{name: 'zombie', items: [{name: 'skin', color: 'green'}, {name: 'brain', color: 'pink'}]}
	]);

####clothes

	var clothes =  new Qstore([
		{name: 'skirt', sizes: [42, 48, 50]},
		{name: 'jeans', sizes: [48, 54]},
		{name: 'skirt', sizes: [42, 45, 48]}
	]);

####usersChanges

	var usersChanges = new Qstore ({
		columns: ['source', 'patch'],
		rows: [
			[{id: 2, name: 'Bob', age: 23}, {name: 'Mike'}],
			[{id: 4, name: 'Stan', age: 30}, {age: 31}]
		]
	});

####shops

	var shops = new Qstore ({
		columns: ['country', 'city', 'address'],
		rows: [
			['UK', 'London', 'mace st. 5'],
			['UK', 'York', 'temple ave. 10'],
			['France', 'Paris', 'de rivoli st. 20'],
			['France', 'Paris', 'pelleport st. 3'],
			['Germany', 'Dresden', 'haydn st. 2'],
			['Germany', 'Berlin', 'bornitz st. 50'],
			['Germany', 'Munchen', 'eva st. 12'],
			['Russia', 'Vladivostok', 'stroiteley st. 9']
		]
	});

####contacts

	var contacts = new Qstore({
		columns: ['name', 'phone'],
		rows: [
			['Leonardo Da Vinci', '23090533'],
			['Elvis Presley', '247543'],
			['Christopher Columbus', '85321443'],
			['Pablo Piccaso', '2512567'],
			['Walt Disney', '123456464'],
			['Albert Einstein', '0865443'],
			['Aristotle', '23090533'],
			['William Shakespeare', '235667'],
			['Ludwig van Beethoven', '245433'],
			['Cleopatra', '346422'],
			['Paul McCartney', '5532173'],
		]
	});

####meetings

	var meetings = new Qstore({
		columns: ['day','month', 'year', 'details'],
		rows: [
			[2, 'feb', 2012, 'Meeting with Albert Einstein'],
			[14, 'feb', 2012,'Meeting with Elvis Presley'],
			[20, 'feb', 2013, 'Meeting with Christopher Columbus'],
			[3, 'mar', 2013, 'Meeting with Pablo Piccaso'],
			[2, 'apr', 2013, 'Meeting with Walt Disney'],
			[10, 'apr', 2013,'Meeting with Aristotle'],
			[11, 'may', 2013, 'Meeting with William Shakespeare'],
			[13, 'may', 2013, 'Meeting with Cleopatra']
		]
	});