Source

knockout-simperium /

Filename Size Date modified Message
src
1.0 KB
8.5 KB

Knockout-Simperium

Seamlessly persist KnockoutJS ViewModel data using Simperium.

Requirements

This package uses the following modules. Some of these may go away as requirements:

Simperium.Auth()

All Simperium requests must be authenticated.

Simple example:

var auth = new Simperium.Auth({
  // These two fields are required!
  SIMPERIUM_APP_ID: 'your-app-id',
  SIMPERIUM_API_KEY: 'your-api-key-uuid'
});

auth.connect().then(function(simperium) {
  // Logic that relies on an active connection.
});

auth.* (observables and methods)

What's nice about this, over the plain Simperium stuff, is that you have a heap of observables on auth, that you can use in various ways. For instance, a simple sign-in form may look like:

<form data-bind="with: auth" id="sign-in">
  Username: <input data-bind="value: username">
  Password: <input data-bind="value: password" type="password">
  <input type="checkbox" data-bind="checked: remember"> Remember me
  <button data-bind="click: authorize">Login</button>
</form>

These observables have sane default validators, added using knockout-validators. For instance, username (which is an email address) and password must be non-empty.

You also get registration, and password change fields:

<form data-bind="with: auth" id="register">
  Username: <input data-bind="value: username">
  Password: <input data-bind="value: new_password1" type="password">
  Repeat: <input data-bind="value: new_password2" type="password">
  <button data-bind="click: register">Register</button>
</form>

<form data-bind="with: auth" id="change-password">
  Username: <input data-bind="value: username">
  Password: <input data-bind="value: password" type="password">      
  New Password: <input data-bind="value: new_password1" type="password">
  Repeat: <input data-bind="value: new_password2" type="password">
  <button data-bind="click: changePassword">Change Password</button>
</form>

In this case, as well as the validations listed above, there is a validator ensuring that the two new password fields are the same, and not empty.

In the future, you may also be able to delete an account: this is not part of the Simperium JS API as yet though.

In reality, the forms I have shown are simpler than they would actually be: you might want to wrap them in something that will only show the relevant forms under the appropriate circumstances. For this, we have a couple of other useful observables, and the complete list is:

auth.username();
auth.password();
auth.new_password1();
auth.new_password2();
auth.authorized();      // Boolean, read-only 
auth.unauthorized();    // Boolean, read-only
auth.in_progress();     // Is an auth-related request in progress now? Boolean, r/o
auth.remember();        // Remember this login session? Boolean, r/w

We also have a stack of handler methods, some of which we saw above. These are intended to be used as click/submit/event handlers in knockout bindings.

auth.authorize();
auth.deauthorize();
auth.register();
auth.changePassword();
auth.deleteAccount();    // Not working!!!

A reminder: these look for username/passwords on the auth ViewModel, not in the passed in data.

Also, note that jquery-cookie is used to store the access token if a person wants to remain logged in. This is seamlessly handled using a Knockout cookie extender.

Simperium.observableBucket()

The one 'missing' feature from Knockout is persistence of objects. This data type is an extension of ko.observableArray(), that persists data to Simperium automatically.

Because it is an extension to observableArray, then you have all of the properties and methods on it, plus a couple of others. Each object in the observableArray is monitored, and any changes are automatically persisted back to Simperium. Any changes on other devices to the loaded objects are seamlessly integrated into the currently loaded models too.

The observableArray itself is also monitored, and new objects that are added are automatically persisted, and objects that are removed are also removed from Simperium.

Note that the sorting order is not persisted to Simperium: if you care about that, you should handle that in your client. You could for instance store a key representing the index, and that would allow persistent sort order.

There are two ways to use it, depending upon if you have homogenous objects, or a heterogenous data set. The simplest, conceptually, is homogenous obejects.

Homogenous objects using mappingOptions

Internally, observableBucket() uses the ko.mapping plugin for serialising and deserialising objects to and from simple JS objects. If you have a bucket that should be storing objects all of a specific type, then you can pass in a mappingOptions object, that will be used to create the mapped objects coming from the server, and any new objects that you create locally.

var simperium = Simperium(...);
var people = simperium.observableBucket('people', PersonMapping);

This would create a new bucket for the authenticated user if one does not exist, or start fetching objects from that bucket on Simperium if it does. Each object is processed using ko.mapping.fromJS(), using the provided mappingOptions.

Heterogenous objects

You can also store objects that are potentially heterogenous, by omitting the mappingOptions argument. This does have some other implications.

var simperium = Simperium(...);
var prefs = simperium.observableBucket('prefs');

Any objects already in the bucket are fully mapped, and any objects that are created by pushing onto the array are also fully mapped. However, any new properties that are added (even if they are observable) are not correctly persisted. So, you are provided with a couple of extra methods on objects that are within an observableBucket.

  • object.addField(key, value)

Add a new attribute to the object, with name key, and value of value. This will be added in a way that ko.mapping will notice, and add these properties to the unwrapped version, allowing it to be correctly persisted.

  • object.addData({key1: value1, [key2: value2[,]]})

Add multiple attribute/value pairs in one action.

  • object.removeField(key)

Permanently remove the field provided. You can also use object[key](undefined), which has the same effect.

observableBucket additional methods

In addition to the regular Array and observableArray methods, we also have some extra methods, that make sense with this type of data structure.

  • observableBucket.get(id)

We can get an object using the id by which it is known to Simperium. It is likely this is not that useful, unless you are actually generating the ids yourself. Which leads into...

  • observableBucket.create(object, id)

If you are creating objects with ids yourself (and a good example might be the prefs bucket outlined above, with ids representing the different preference types you are storing), then you can use this method to create an object (using ko.mapping.fromJS if applicable), and assign it the given id.

  • observableBucket.sortBy(key)

Since sort order is not persisted, you are probably going to want to be able to sort easily. Currently, you may sort according to id, which sorts according to Simperium id, or by any top-level attribute in the object. Obviously, the latter only makes sense if the objects all have that key: no testing is done on that at this time.