LocalModel allows you to use a simple model structure to utilise localstorage. Its based loosely on the basic functionality of Mongoose for node.
Note: Be aware that Web storage is limited in size, for more details see: https://en.wikipedia.org/wiki/Web_storage
This library is currently ready for use. Feedback is welcomed, please leave an issue if you find any bugs, or have any suggestions that will improve the library.
There are multiple ways of installing LocalModel.
Download the repo and copy dist/js/localmodel.min.js into the relevant folder in your application. Then include it on the page
<script src='/path/to/localmodel.min.js'></script>
bower install localmodel
And include it on the page
<script src="/bower_components/localmodel/dist/localmodel.min.js"></script>
This will cover the basic usage of LocalModel:
- Basic setup
- Adding models
- Getting a model
- All
- Find
- Count
- Find By ID
- Using returned data
- Saving an updated entry
- Batch Update
- Removing/Deleting
- Population
- Find and populate
- Aggregate
LocalModel needs to be instantiated. At the moment there are no options to pass:
var localmodel = new LocalModel();
Once instanciated you can use it to add models.
LocalModel will allow you to add an option to change the storage method, by default it is set to localStorage.
// Default - use localStorage
var localmodel = new LocalModel();
// Use sessionStorage
var localmodel = new LocalModel({
storage: sessionStorage
});
You could even add your own method of storage. Note that it must contain storage.setItem(key, value)
, storage.getItem(key)
and storage.removeItem(key)
to allow the full functionality of LocalModel (and stop and huge errors).
var myStorage = {
getItem: function(key) {
console.log('Getting item with key: ' + key);
},
setItem: function(key, value) {
console.log('Setting the value ' + value + ' for key ' + key);
},
removeItem: function(key) {
console.log('Removing item with key: ' + key);
}
};
var localmodel = new LocalModel({
storage: myStorage
});
To enable debugging set the options 'debug' to true, the default is false.
var localmodel = new LocalModel({
debug: true
});
To add a basic model do the following:
var human = localmodel.addModel('Human', {
name: LocalSchema.SchemaTypes.String,
age: LocalSchema.SchemaTypes.Number
});
LocalSchema currently has 4 types:
- String: 'string'
- Number: 'number'
- Boolean: 'boolean'
- Mixed: 'mixed'
- Date: 'date'
You can use the string (e.g. 'number', 'boolean') or the static variable (LocalSchema.LocalSchema.SchemaTypes.Number) but make sure when using the string that it's all lowercase and matches the static exactly.
When adding a model the 'addModel' function returns the created model instance.
You can add a default value to a property when adding the model like so:
var human = localmodel.addModel({
name: LocalSchema.SchemaTypes.String,
isAlive: { type: LocalSchema.SchemaTypes.Boolean, default: true }
});
This will set the isAlive property to true by default, this will simply be overwritten when the isAlive property is given a value:
var sammy = human.create({ name: 'Sammy' });
console.log(sammy.isAlive); // true
var billy = human.create({ name: 'Billy', isAlive: false });
console.log(billy.isAlive); // false
If you ever need to retrieve a model instance you can call the following:
var human = localmodel.model('MyModel');
To add an entry to a model you just need to use the create function on the model instance.
human.create({
name: 'Sammy',
age: 35
});
All will return all of the entries relevant to the model used.
var allTheHumans = human.all();
Find will allow you to use a query to find matching entries.
var billys = human.find({
name: 'Billy'
});
var certainAge = human.find({
age: 35
});
You can also use regular expression to find partial matches.
var partialMatches = human.find({
name: /Sam/g
});
You can also return a count instead of an array by setting the second argument to true
var totalHumansAgeTen = human.find({ age: 10 }, true);
You can use a more advanced query to get numbers and dates that are greater than or equal, greater than, less than or equal and less than:
// Find with age greater than or equal to 25
var humans = human.find({ age: { $gte: 25 } });
// Find with age greater than 30
var humans = human.find({ age: { $gt: 30 } });
// Find with age less than or equal to 45
var humans = human.find({ age: { $lte: 45 } });
// Find with age less than to 50
var humans = human.find({ age: { $lt: 50 } });
// Find with age less than to 50 but greater than 20
var humans = human.find({ age: { $lt: 50, $gt: 20 } });
// Find all with a created date between 2010 and now
var human = human.find({ created: { $lte: new Date(), $gte: new Date(2010, 1, 1) } });
Count is a helper that returns a count of entries based on a query. It does the same thing as MyModel.find(query, true)
, but has better semantics.
// Count all the humans
var totalHumans = human.count();
// Count humans with name 'Sammy'
var totalSammys = human.count({ name: 'Sammy' });
If you have the ID of the entry you can quickly find it with findById(ID)
.
var specificHuman = human.findById('af6fa5c5-e197-4e59-a04a-58d8af366554');
Data returned from the .all()
and .find()
is returned in an array. Data returned from a .findById
is returned as a single object. Individual objects are instances of LocalDocument
which house the data inside a property named 'data'...
var rick = human.findById('af6fa5c5-e197-4e59-a04a-58d8af366554');
console.log('Rick\'s age is: ' + rick.age);
You can alter a LocalDocument data object and save it using the .save()
method.
var rick = human.findById('af6fa5c5-e197-4e59-a04a-58d8af366554');
// Change the age and save it
rick.age = 32;
rick.save();
You can update multiple entries in a single call, utilising the find query mechanism.
// Update all entries named 'Sammy' to be active
var numUpdated = human.update({ name: 'Sammy' }, { active: true });
You can remove an entry individually:
// Remove rick, no one likes him anyway...
var rick = human.findById('af6fa5c5-e197-4e59-a04a-58d8af366554');
rick.remove();
or you can remove multiple entries using the same query mechanism as find from the model:
// Remove all entries with age = 16
human.remove({ age: 16 });
// Remove all entries
human.remove();
The LocalModal.remove()
function returns the number of entries removed
Population allows you to add a relationship between one model and another. To add a relation property just add a ref property to the configuration of a schema property.
var Brand = localmodel,addModel('Brand', {
name: LocalSchema.SchemaTypes.String,
logo: LocalSchema.SchemaTypes.String
});
var Car = localmodel.addModel('Car', {
model: LocalSchema.SchemaTypes.String
brand: { type: LocalSchema.SchemaTypes.String, ref: 'Brand' }
});
The ref property takes a string, the name of the related model. By default the ref will use the _id of the related model as the foreign key. This means that when you populate a Car entry it will replace the property with an object.
var aston = Brand.create({
name: 'Aston Martin',
logo: 'http://cdn.astonmartin.com/icons/logos/aml-logo-medium.png'
});
var dbNine = Car.create({ model: 'DB9 GT', brand: aston._id });
console.log(dbNine.populate('brand'));
Logs:
// Basic object
{
model: 'DB9 GT',
brand: {
_id: 'af6fa5c5-e197-4e59-a04a-58d8af366554',
name: 'Aston Martin',
logo: 'http://cdn.astonmartin.com/icons/logos/aml-logo-medium.png'
}
}
Adding a foreign key to the property configuration will use a different property on the related to model to be used to populate:
var Extra = localmodel.create('Extra', {
pack: LocalSchema.SchemaTypes.String,
name: LocalSchema.SchemaTypes.String,
cost: LocalSchema.SchemaTypes.Number
});
var Car = localmodel.addModel('Car', {
model: LocalSchema.SchemaTypes.String
pack: { type: LocalSchema.SchemaTypes.String, ref: 'Extra', foreignKey: 'pack' }
});
When using a foreign key you open up the possibility of replacing the property with an array, because more than one entry in the related model could have a matching property:
Extra.create({ name: 'Mud Flaps', pack: 'protection', cost: 20 });
Extra.create({ name: 'Rubber Mats', pack: 'protection', cost: 35 });
Extra.create({ name: 'Scotch Guard', pack: 'protection', cost: 15 });
var dbNine = Car.create({ model: 'DB9 GT', pack: 'protection' });
console.log(dbNine.populate('pack'));
Logs:
// Basic object
{
model: 'DB9 GT',
pack: [
{
_id: 'af6fa5c5-e197-4e59-a04a-58d8af366554',
name: 'Mud Flaps',
pack: 'protection',
cost: 20
},
{
_id: 'af6fa5c5-e197-4e59-a04a-58d8af444355',
name: 'Rubber Mats',
pack: 'protection',
cost: 35
},
{
_id: 'af6fa5c5-e197-4e59-a04a-58d8af948829',
name: 'Scotch Guard',
pack: 'protection',
cost: 15
}
]
}
You can also name multiple properties to populate using a space separated string.
// This will populate relatedProp and otherRelated properties
exampleThing.populate('relatedProp otherRelated');
There a few options that will make using the populate feature more functional:
match will allow you to add more conditions to returning related properties, this uses the same query logic as the find function.
// This will only populate with extras that cost over 18
dbNine.populate('pack', {
match: { cost: { $gt: 18 } }
});
To sort the populated data just pass in a compare function:
dbNine.populate('pack', {
sort: function(a, b) {
return a.cost > b.cost ? 1 : -1;
}
});
If you would like to limit the populated data just pass limit with a number.
dbNine.populate('pack', {
limit: 2
});
If you wish to only populate with selected properties from the related model, use the select option. To add multiple selections just use a space separated string:
dbNine.populate('pack', {
select: 'cost name'
});
Find an populate works on exactly the same principle as population but allows you to find an array of entries and batch populate them. It accepts the same options as LocalDocument.populate();
Extra.create({ name: 'Mud Flaps', pack: 'protection', cost: 20 });
Extra.create({ name: 'Rubber Mats', pack: 'protection', cost: 35 });
Extra.create({ name: 'Scotch Guard', pack: 'protection', cost: 15 });
Car.create({ model: 'DB9 GT', pack: 'protection' });
Car.create({ model: 'DB9', pack: 'protection' });
var dbNines = Car.findAndPopulate({ name: /DB9/ }, 'pack', { limit: 1 });
console.log(dbNines);
Logs:
// Basic objects
[
{
model: 'DB9 GT',
pack: [
{
_id: 'af6fa5c5-e197-4e59-a04a-58d8af366554',
name: 'Mud Flaps',
pack: 'protection',
cost: 20
}
]
},
{
model: 'DB9',
pack: [
{
_id: 'af6fa5c5-e197-4e59-a04a-58d8af366554',
name: 'Mud Flaps',
pack: 'protection',
cost: 20
}
]
},
]
The aggregate feature allows you to match, group, sort and limit results as part of a pipeline.
Match is exactly the same as a find query, it will filter the results.
var results = Car.aggregate([
{
$match: {
cost: { $gt: 50000 }
}
}
]);
Group allows you to group results by a field using _id (required).
var results = Car.aggregate([
{
$group: {
_id: 'engine'
}
}
]);
To support $group you can add properties via the following helpers:
$sum has 2 uses, you can count with it or get a total of all fields in a group.
// Count how many items are in the group
var results = Car.aggregate([
{
$group: {
_id: 'engine',
count: { $sum: 1 } // Add 1 for every item in the group
}
}
]);
// Add all of the 'cost' fields to get a total cost
var results = Car.aggregate([
{
$group: {
_id: 'engine',
count: { $sum: 'cost' } // Add all of the costs together
}
}
]);
Adds the property from the first or last item in the group.
// returns the first model in the group
var results = Car.aggregate([
$group: {
_id: 'engine',
model: { $first: 'model' },
}
]);
// returns the last cost in the group
var results = Car.aggregate([
$group: {
_id: 'engine',
cost: { $last: 'cost' },
}
]);
Gets the average of all relevant fields in a group (must be a number).
var results = Car.aggregate([
$group: {
_id: 'engine',
averageCost: { $avg: 'cost' },
}
]);
Gets the minimum or maximum number in a group.
var results = Car.aggregate([
$group: {
_id: 'engine',
minCost: { $min: 'cost' },
maxCost: { $max: 'cost' }
}
]);
Sorts the array. Pass a compare function to sort.
// Order results by cost
var results = Car.aggregate([
{ $match: { car: 'DB9' } },
{
$sort: function(a, b) {
return a.cost < b.cost ? -1 : 1;
}
}
]);
Limits the results to the supplied number.
// Limit the results to 5
var results = Car.aggregate([
{ $match: { car: 'DB9' } },
{ $limit: 5 }
]);
Every part of the pipeline will alter the results of the previous part. For example, if a $match is called at the start that returns an array of objects that have a name, age. The next part will alter those results. If a $group is called at the start that returns an array of groups (_id, _group etc), the next part will alter the groups result.
Get all cars with model 'DB9' and group them by engine, then sort by average cost.
var cars = Car.aggregate([
{ $match: { model: 'DB9' } },
{ $group: { _id: 'engine', averageCost: { $avg: 'cost' } } },
{
$sort: function(a, b) {
return a.averageCost < b.averageCost ? -1 : 1;
}
}
]);
Get the top 5 cars based on engine by cost. var topFiveMostExpensive = Car.aggregate([ { $group: { _id: engine, maxCost: { $max: 'cost' } } }, { $sort: function(a, b) { return a.maxCost < b.maxCost ? 1 : -1; } }, { $limit: 5 } ]);
Each ID is generated with a mixture of the date and random number generation. Each ID will be unique and can be accessed by the _id
property.
I have developed this myself as a fun project, which I will continue to add to as long as I can think of things to add... If you have any issues or feature requests feel free to add an issue. If you have anything to improve this, feel free to add a pull request and I will review it for addition.
Tests are run using jasmine. This project uses gulp as it's build tool.
npm test
or
gulp test
v0.6.0
- Fixed issue with find when nothing has been saved
- Fixed falsy check for create default addition
- Fixed falsy check on property in LocalDocument constructor
- Added boolean to the list of acceptable query types
- Fixed date saving
- Fixed default when it is false (boolean)
- Cancel $avg and $min/$max when not being used
v0.5.0
- Add a basic aggregate function
v0.4.0
- Added populate feature
- Removed .data from local document
- Add references/relationships to other models
v0.3.1
- Minor optimisations
v0.3.0
- Add the option of using localsession
- Add Count helper
- Add batch update
v0.2.0:
- Add Delete/Remove
- Add a check for localstorage
v0.1.2:
- Add query date modifiers ($gt, $gte, $lt, $lte)
- Split off matching to it's own function
v0.1.1:
- Add property defaults
v0.1.0:
- Added instanced data
- Added save to data instance for updated
- Added the Date schema type
v0.0.2:
- Added query number modifiers ($gt, $gte, $lt, $lte)
- Optimise/Refactor
- Check for LocalStorage capacity