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

No comments:

Post a Comment