Overview
For Pipestone, we'd like to create a simple model framework to handle in-memory single value stores, with the ability to update the item and provide event notifications to subscribers.
Issues to consider
- Data update interface (setter/getter vs. raw objects + manual update)
- With setter/getter, updates can be fired automatically, but may be spurious (changing 3 fields individually fires 3 update events)
- Event Notification
- Use toObservable (Rx extensions) with similar LINQ filtering, selection, etc.
- Optional PageBus channels
- Data Persistence
- Single Value Stores
- v1: Objects live in memory, and are not persisted between page loads
- v2: Store objects in page properties
- start-up option: {checkEtag: true/false}
- Multi-value stores
- Application level events
- App.Ready()
- Do we need to store the application ready state?
- How do models that are in the process of loading know whether to trigger subscription events?
- App.Stop()?
- Could be triggered on page unloads
- How to allow app unload cancelling? Simply e.preventDefault()?
Data Model Interface
IModel implements the IObservable interface.
IModel::Constructor(opts)
- @param opts - options including error callback
- Register with App.Ready() to initialize content for subscribers?
- Alternatively, could republish on app ready?
IModel::Get(opts)
- @param opts - options including error callback
- Single value to be read. Can be any JavaScript literal
- Value should be cloned
IModel::Get::OnError
- How to handle reading errors?
- Have callback with an error message / exception type
IModel::Set(value, opts)
- @param opts - options including error callback
- Set object fields
- Call update to save fields
IModel::Set::OnSuccess
- Subscribers notified that the model's value changed.
IModel::Set::OnError
- Should field values rollback to originals?
- I think so. Atomic update -- couldn't update to new value, so object still at old. Subscription event not fired.
- Ability to revert to old values?
- OnError should be passed opts including
- original - original value from read() at time of update
- attempt - the value that was attempt to be saved
- error - error message string
Examples
Key goal: Have data stream, apply changes to a label
// Core object model lifecycle and interactions
// create single value store
obj = DataObject.create();
obj.update({name: 'foo', type: 'bar'}); // save single value (js object, in this case) into the store
// get observable events from Rx
nameChangeEvents = obj.Select(function(data){ return data.name; }).Distinct();
// subscribe to updates
nameChangeEvents.Subscribe(function(name){
// update label on change
});
// modify object and notify subscribers
var data = obj.read(); // clones data for private copy
data.name = 'foo2';
obj.update(data); // in later versions, this can persist to page properties
Open Questions
- Subscribe -- way to "subscribe & fire event" (otherwise you have to create object, get events, subscribe, then update object).
- More natural (?): create object & update it. Hook up subscription events, which can immediately take action based on the object's value
- .subscribeWithEvent(function(){ ... }); // fires immediately
data.Select(function(v) { return v.name }).Subscribe(label.Text);
data.Select(function(v) { return v.zipcode == 92101 ? 'red' : 'black' }).Subscribe(label.Color); edited 23:30, 10 Aug 2010
var label = jObserver.create("#id");
var data = SingleMemoryValue.create({ name: 'John', zipcode: 98052 });
var distinct = data.Distinct(function(v) { return v.name });
distinct.Select(function(v) { return v.name }).Subscribe(label.Text);
distinct.Select(function(v) { return v.zipcode == 92101 ? 'red' : 'black' }).Subscribe(label.Color);
data.Poke();
The jObserver class abstracts an HTML element into a collection of observers that are mapped to properties of the element. This way, every HTML property can subscribe to any observable event and react to it. In short, jObserver is the observer equivalent of jQuery selectors. As such, jObserver could even represent multiple selected elements. edited 23:44, 10 Aug 2010
#2: Sounds good
#3: Sure, an async overload works
#4: Poke() added to prototype page
#5: We can extend Rx to include Distinct() via Rx.Subject.prototype
#6/#7: Binding to properties is interesting, will have to see if it's easier than using jQuery.data() [discussed Wed 8/11]