Showing posts with label mdv. Show all posts
Showing posts with label mdv. Show all posts

Friday, May 30, 2014

Model Driven Polymer Attributes


Up tonight, a (hopefully) simple update of a Polymer feature.

Last night I was able to use attributes to set the initial toppings for a pizza in the <x-pizza> Polymer-based custom element:



Today, I would like to do the opposite: ensure that topping changes are reflected in the same attributes that are used to set the initial toppings. That is, if an <x-pizza> starts with:
<x-pizza toppings="pepperoni,sausage"></x-pizza>
And the user adds green peppers, then the attribute should report:
<x-pizza toppings="pepperoni,sausage,green peppers"></x-pizza>
Mercifully, this is fairly easy in Polymer. It also helps that the infrastructure is already in place in the backing x_pizza.dart backing class. The whole toppings attribute (toppings) and the first and second half attributes (toppings1 and toppings2) are already published in the backing class. I also have an updatePizzaState method that gets invoked whenever the model in my model driven view is updated:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  @published String toppings = '';
  @published String toppings1 = '';
  @published String toppings2 = '';

  XPizza.created(): super.created() {
    model = new Pizza()
      ..changes.listen(updatePizzaState);
    // ...
  }
  // ...
}
The above changes stream just works because the model class has all the observables in place:
class Pizza extends Observable {
  ObservableList<String> firstHalfToppings = toObservable([]);
  ObservableList<String> secondHalfToppings = toObservable([]);
  ObservableList<String> wholeToppings = toObservable([]);

  Pizza() {
    firstHalfToppings.changes.listen((_)=> notifyChange(_));
    secondHalfToppings.changes.listen((_)=> notifyChange(_));
    wholeToppings.changes.listen((_)=> notifyChange(_));
  }
}
Back in the MDV class, updatePizzaState needs only a new secondary method call, to the new _updateAttributes() method which simply assigns the three attributes::
  
  // ...
  updatePizzaState([_]) {
    _updateText();
    _updateGraphic();
    _updateAttributes();
  }
  // ...
  _updateAttributes() {
    toppings = model.wholeToppings.join(',');
    toppings1 = model.firstHalfToppings.join(',');
    toppings2 = model.secondHalfToppings.join(',');
  }
  // ...
And that does the trick nicely:



I am not sure that is the simplest solution possible, so I had hoped to drive some of this with tests. Unfortunately, testing turns out to be a little harder than expected. But that's a tale for tomorrow. For today, I am happy that updating attributes had no unexpected surprises.


Day #79

Thursday, January 16, 2014

Day 998: Binding to Model Changes in Polymer (Dart)


Tonight, I hope to better express what needs to update when the Pizza model changes in my <x-pizza> Polymer. The JavaScript version of Polymer has an observe block to do this, but I cannot find a Dart equivalent. I came up with a solution (and a pretty good one), but I do not know if it is the solution. And I must strive for the solution in Patterns in Polymer.

I currently have a simple model driven Polymer that updates the pizza state for display:



Sure, it could use some fancy graphics, but this is fine for illustration and exploration. When a human clicks the add topping buttons, the Polymer adds the selected ingredient to the model. By virtue of property change listeners, the change in the Pizza model results in an update to the current pizza state representation:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  XPizza.created(): super.created() {
    model = new Pizza()
      ..firstHalfToppings.changes.listen(updatePizzaState)
      ..secondHalfToppings.changes.listen(updatePizzaState)
      ..wholeToppings.changes.listen(updatePizzaState);
    // ...
  }
  // ...
}
Thanks to the magic of Dart method cascades, I can establish change listeners for multiple properties on my model.

That is nice and all, but the three listeners seem a tad redundant. Really, I would be happier to listen for any changes anywhere in the model—not just changes to those individual properties. Something like that might be expressed as:
@CustomTag('x-pizza')
class XPizza extends PolymerElement {
  // ...
  XPizza.created(): super.created() {
    model = new Pizza()
      ..changes.listen(updatePizzaState);
    // ...
  }
  // ...
}
I have to admit that this sort of feels like something that an Observable class ought to give me for free. That is, if any observable properties in an observable class change, then the instance changes too. But that is not the case, so I am going to need to manually do this for my Pizza class:
class Pizza extends Observable {
  ObservableList<String> firstHalfToppings = toObservable([]);
  ObservableList<String> secondHalfToppings = toObservable([]);
  ObservableList<String> wholeToppings = toObservable([]);
}
(I am not sure the word “observable” is in there enough)

Thankfully, manually notifying that the model has changed in Polymer is directly supported via the notifyChange() method. So, in the Pizza constructor, I listen to the change stream on the individual properties and, whenever such an event occurs, I notify the object itself that a change has occurred:
class Pizza extends Observable {
  ObservableList<String> firstHalfToppings = toObservable([]);
  ObservableList<String> secondHalfToppings = toObservable([]);
  ObservableList<String> wholeToppings = toObservable([]);

  Pizza() {
    firstHalfToppings.changes.listen((_)=> notifyChange(_));
    secondHalfToppings.changes.listen((_)=> notifyChange(_));
    wholeToppings.changes.listen((_)=> notifyChange(_));
  }
}
I think that is pretty close to the spirit of what I want to accomplish.

Still, I am surprised that there is nothing closer to observe blocks in the the JavaScript version of Polymer. These let me observe properties via dot notation, invoking the supplied callback when changes are observed:
  observe: {
    'model.firstHalfToppings': 'updatePizzaState',
    'model.secondHalfToppings': 'updatePizzaState',
    'model.wholeToppings': 'updatePizzaState'
  },
But if that exists, I am unable to find it.


Day #998

Tuesday, January 14, 2014

Day 996: Model Driven Views in Polymer (JS)


I adore switching back and forth between the JavaScript version of Polymer and Polymer.dart. It has been a fantastic feedback mechanism for validating or, more frequently, disproving my thinking about Polymer. Tonight, I switch back to the JavaScript in the hopes that I can solidify my thinking on model driven views in Polymer.

In my Dart version, I have a <x-pizza> Polymer that builds up a pizza for order. It looks something like:



And it works. More or less.

One thing that I was not quite able to figure out was why model attributes—specifically list model attributes—were not updating the values bound in templates. For instance, the model.firstHalfToppings and model.secondHalfToppings values are not being updated in the template when they change in the model:
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>
{{model.firstHalfToppings}}
{{model.secondHalfToppings}}</pre>
    <!-- ... -->
</template>
So let's see if I can make that work in a JavaScript version. Starting with the index.html page that contains my soon-to-exist-js-polymer:
<head>
  <!-- 1. Load Polymer before any code that touches the DOM. -->
  <script src="bower_components/platform/platform.js"></script>
  <!-- 2. Load component(s) -->
  <link rel="import" href="elements/x-pizza.html">
</head>
<body>
  <div class="container">
    <h1>JS Bros. Pizza Builder</h1>
    <x-pizza></x-pizza>
  </div>
</body>
The template that goes into the imported x-pizza-html is nearly identical to the Dart version. What changes is, obviously, the script tag—which now points to a JavaScript version of the Polymer class:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>
{{model.firstHalfToppings}}
{{model.secondHalfToppings}}</pre>
  <!-- ... -->
  </template>
  <script src="x_pizza.js"></script>
</polymer-element>
There is nothing too out of the ordinary in that backing Polymer class—just the model assignment in the ready() lifecycle method:
Polymer('x-pizza', {
  ready: function() {
    this.model = {
      firstHalfToppings: []
    };
  },
  addFirstHalf: function() {
    this.model.firstHalfToppings.push(this.currentFirstHalf);
  },
  // ...
});
The Polymer documentation recommends binding the model in ready() to avoid shared prototype state (ah, JavaScript).

In addition to assigning the model, I also define a bound method that adds records to the firstHalfToppings list property of the model. Whenever this function is called, the model's firstHalfToppings should change, which should update in the template. Only it did not in the Dart version of the Polymer.

And it does not in the JavaScript version either:



No matter how often I click that button, the variable bound in that template never updates.

Interestingly, if I create a string attribute directly on my Polymer and update it whenever the addFirstHalf() method is called:
Polymer('x-pizza', {
  ready: function() {
    this.model = {
      firstHalfToppings: [],
      secondHalfToppings: []
    };
  },
  pizzaState: '',
  addFirstHalf: function() {
    this.model.firstHalfToppings.push(this.currentFirstHalf);
    this.pizzaState = this.model.firstHalfToppings.join(',');
  },
  // ...
});
And if I bind this variable into the template inside the same tag as my list model attribute:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>
{{pizzaState}}
{{model.firstHalfToppings}}
{{model.secondHalfToppings}}</pre>
    <!-- ... -->
  </template>
  <script src="x_pizza.js"></script>
</polymer-element>
Then, not only is the new bound variable updated in the template each time that method is called, but the list model attribute is also updated:



So it seems that the Dart version was behaving correctly. Or at least consistently.

The last thing that I try tonight is observing the list properties of the model. If nothing else, I would like to have single place to accumulate changes. This turns out to be fairly easy with Polymer's observe property:
Polymer('x-pizza', {
  ready: function() {
    this.model = {
      firstHalfToppings: [],
      secondHalfToppings: []
    };
  },
  observe: {
    'model.firstHalfToppings': 'updatePizzaState',
    'model.secondHalfToppings': 'updatePizzaState'
  },
  updatePizzaState: function() {
    this.pizzaState = this.model.firstHalfToppings.join(',') + "\n" +
      this.model.secondHalfToppings.join(',');
  },
  // ...
});
The path expressions that serve as keys in the observe property establish change listeners for the specified properties. Polymer is able to resolve those strings into objects and properties, listening to the appropriate event source for a change. And, when a change does occur, the updatePizzaStat method is invoked.

That works just fine. I think I am fairly close to fully understanding this approach and how Polymer wants it done. I may switch back to Polymer tomorrow to make certain.


Day #996