1. Matthew Schinckel
  2. knockout-simperium


# Knockout-Simperium Seamlessly persist [KnockoutJS][knockout] ViewModel data using [Simperium][simperium]. ## Requirements This package uses the following modules. Some of these may go away as requirements: * [jQuery][jquery] - for Ajax requests * [knockout-validators][knockout-validators] - for automatic validation of fields * [jquery.cookie.js][cookie] - for persistent authentication * [knockout-mapping][ko-mapping] - easily convert nested objects to observable and back. * [Knockout][knockout] - obviously * [Simperium][simperium] - obviously * [knockout/extenders/bool][ko-bool] - Ensure observables are boolean. * [knockout/extenders/cookie][ko-cookie] - Persist observables to cookies. * [knockout/extenders/track][ko-track] - Track changes to observables. ## 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][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][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][ko-cookie]. ## 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. [jquery]: http://jquery.com/ [knockout-validators]: https://bitbucket.org/schinckel/knockout-validators [simperium]: https://js.simperium.com/docs/js/ [knockout]: http://knockoutjs.com [ko-mapping]: http://knockoutjs.com/documentation/plugins-mapping.html [cookie]: https://github.com/carhartl/jquery-cookie/ [ko-cookie]: http://schinckel.net [ko-bool]: http://schinckel.net [ko-track]: http://schinckel.net