Skip to content

Instantly share code, notes, and snippets.

@clarle
Created September 20, 2013 21:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save clarle/6644126 to your computer and use it in GitHub Desktop.
Save clarle/6644126 to your computer and use it in GitHub Desktop.
AttributeComputable and BaseComputable

AttributeComputable and BaseComputable

Currently, Attribute and Base have the idea of observable properties, through AttributeObservable and BaseObservable. The next step from this is the idea of computable properties, where one Attribute may be dependent on the value of one or more other Attributes.

There's an example of how this is currently done in YUI with our current system in Attribute Getters, Setters, and Validators, which gets unwieldy when a large number of Attributes become dependent on each other.

Use Cases
  • The simple case of re-evaluating a synchronous computed attribute when one of the attributes it depends on changes. (Example: fullName should change when either firstName or lastName changes)

  • The more advanced case of asynchronous computed properties.

    • What happens when you have a computed attribute that relies on other attributes, but this computed attribute makes a call to a remote data source, like a REST API?
    • How will we determine when that computed Attribute is ready to be used, and if that computed Attribute ends up being a dependency for yet another Attribute, how will we determine when the entire Base object has been stabilized?
  • (Optional, but would be nice to have) Not require the need to explicitly declare computable properties (like in Ember), but automatically set up dependency tracking upon first access of the dependent Attributes. There's a good dependency tracking algorithm that Knockout uses here.

Proposed ideas
  • Set dependency tracking in a similar way to how Knockout does it (set subscribers to dependencies upon first access). We can do this at a higher level (such as having the computed Attribute subscribe to the [name]Change event of all of its dependencies), or we can do it at a lower level for performance reasons. This probably will need to be benchmarked.

  • Have the idea of asynchronous evaluation of computed properties (technique used in Knockout and Angular). Asynchronous computed Attributes should return a Promise object if necessary. There's good discussion about this subject in Knockout's Asynchronous Dependent Observable wiki page.

  • New idea (not found in any other frameworks). Currently, the Angular runtime queues up the changes that need to be evaluated in a dirty fashion, and continues the evaluation loop until stability has been reached. We want this same idea inside of our Base objects, but not in the way that Angular does it, and we can do this by having BaseComputable gain two new events: destabilized and stabilized.

    • The destabilized event is fired when something causes the Base object to change its state for the first time, normally due to set().
    • The stabilized event is fired when all enqueued computed Attribute evaluations have finished, and there are no computed changes left to be made.
    • This idea serves two purposes: it can be used as an event that coalesces all of the individual change events, and it also prevents the problem where we re-render the page upon every single computed change. We want to be able to wait until all of the computed changes have settled before beginning to render.
Example usage
Simple computed Attribute
YUI().use("attribute", "attribute-computable", function (Y) {
  
  function Person(options) {
    var defaultAttrs = {
      firstName : {
        value: "John"
      },

      lastName  : {
        value: "Doe"
      },
      
      fullName  : {
        // Possibly default to true if `attribute-computable` is used?
        computed: true,

        // `getter` is evaluated upon instantiation. If getter uses `this.get(attr)`,
        // then subscriptions are set up from this attribute to those attributes.
        getter: function () {
          return this.get('firstName') + ' ' + this.get('lastName');
        },

        setter: function (value) {
          var lastSpacePos = value.lastIndexOf(" ");
          if (lastSpacePos > 0) { // Ignore values with no space character
            this.set('firstName', value.substring(0, lastSpacePos)); 
            this.set('lastName', value.substring(lastSpacePos + 1));
          }
        }
      }
    };
    
    this.addAttrs(defaultAttrs, options);
  }
  
  Y.augment(Person, Y.Attribute);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment