JavaScript Model Framework

    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
        • v3
    • 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

     

    Tag page
    Viewing 8 of 8 comments: view all
    IModel should already be observable, removing the need for 'toObservable()'. I've updated the code accordingly. Also, Rx methods follow the C# convention of having the first letter capitalized.
    Posted 23:07, 10 Aug 2010
    Using ::Get() and ::Set(value) instead of ::Read()/::Update() would align IModel with the KVO (Key-Value Observing) model. edited 23:12, 10 Aug 2010
    Posted 23:10, 10 Aug 2010
    Consider enabling both ::Get() and ::Set() to support async subscribers. For example: data.Get(onNext, onError, onDone), data.Get(observer), data.Set(value, onValue, onNext, onDone), data.Set(value, observer). In all cases, the observer is subscribed for a single event sequence and then automatically unsubscribed (whereas .Subscribe(observer) has a persistent subscription for multiple events).
    Posted 23:20, 10 Aug 2010
    Also need a way to ::Poke() the object so that it automatically fires it update event, even if no change happened (needed for boot strapping).
    Posted 23:22, 10 Aug 2010
    BTW, I checked and ::Distinct() doesn't yet exist, but we should add it. One additional refinement would also be the ability to provide a function that returns the value that should be distinct. Example: data.Distinct(function(v) { return v.name }) will let all events pass through with distinct names, but the data passed through is still the entire value, not just the name.
    Posted 23:26, 10 Aug 2010
    Note: if models are observables and controls are collections of observers, then wiring things up would become as simple as:
    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
    Posted 23:29, 10 Aug 2010
    Ok, last comment for tonight, I promise. :)

    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
    Posted 23:40, 10 Aug 2010
    #1: We can use $.extend() to inherit the observable interface from Rx.Subject()

    #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]
    Posted 15:57, 12 Aug 2010
    Viewing 8 of 8 comments: view all
    You must login to post a comment.

    Copyright © 2011 MindTouch, Inc. Powered by