Polymer

Elements can serve as a generic package for reusable functionality. Custom Elements pave a path to Polymer’s “Everything is an element” philosophy. Elements can be classified into UI Element and Non-UI Element.

What is Polymer

The future to create Web Components. Polymer 利用 HTML 为基础,以组件的形式集成针对该组件的:Model, View, Controller。

它的设计思想类似于:聚合物,从最小的组件聚合到最大的组件从而构成一个 App. Polymer 在层次体系中实现层次通信,一般是隔层的通信,因而感觉写法和结构是比较固定的。

  • Web Components Standard
  • Polymer Library to define custom elements
  • Elements can be a comprehensive set of UI and non-UI elements

Shadow DOM

Shadow DOM separates content from presentation thereby eliminating naming conflicts and improving code expression.

Light DOM and Shadow DOM

This is a light DOM

  1. <my-custom-element>
  2. <q>Hello World</q> <!-- part of my-custom-element's light DOM -->
  3. </my-custom-element>

This is a shadow DOM

<polymer-element name="my-custom-element" noscript>
  <template>
    <span>People say: </span>
      <content select="q"></content> 
    <footer>sometimes</footer>
  </template>
</polymer-element>

The span, content, and footer elements are encapsulated within the custom element and hidden from the rest of the page.

The composed DOM is what the browser actually renders. For rendering, the light DOM is distributed into the shadow DOM to produce the composed DOM.

This is a Composed DOM

<my-custom-element>
  <span>People say: <q>Hello World</q></span>
  <footer>sometimes</footer>
</my-custom-element>

Web components polyfills

Web Components:

  • Shadow DOM. Encapsulate DOM subtrees and the associated CSS.
  • HTML Imports. Load element definitions and other resources declaratively.
  • Custom Elements. Define new elements in HTML.

Dependencies required by the Web Components polyfills:

  • WeakMap. Shim for ES6 WeakMap type.
  • Mutation Observers. Efficiently watch for changes in the DOM.

Why use Polymer

个人觉得 Polymer 比较适合内容发布式的 Web 应用,适合重复组件比较多的,或者结构清晰,顺序分明的 App 结构。同时也适合多终端结构的 Web App 开发。

Brief API

Reference of Polymer

Element

<link rel="import"
      href="../bower_components/polymer/polymer.html">
<polymer-element 
    name="tag-name" 
    constructor="TagName" 
    noscript 
    extends="TagName"
    attributes="foo bar baz"
>
  <template>
    <!-- A shadow element renders the extended
         element's shadow DOM here. -->
    <shadow></shadow> <!-- "You are cool Matt" -->

    <!-- shadow DOM here -->
    <style>input {box-sizing:border-box;width:100%;}</style>
    <input type="number" value="{{num}}"><br>
    <em>{{num}}^2 = {{square}}</em>
    <input id="nameInput" placeholder="Name">
    <button on-click="">Say hello</button>
  </template>
  <script>
  (function() {
      // Run once. Private and static to the element.
      var foo_ = new Foo();

      // these variables are shared by all instances of this element
      var values = {
        firstName: 'John',
        lastName: 'Smith'
      }

      // created() is run for every instance of the element that is created.
      Polymer({
        get foo() { return foo_; }
        created: function() { 
            // Overwrite the supper method
            this.super([arg1, arg2]);

            // Observing changes to light DOM children
            this.onMutation(domElement, someCallback);

            // async(inMethod, inArgs, inTimeout)
            this.async(function() {
            this.foo = 3;

            // Dynamic import
            Polymer.import(['dynamic-element.html'], function() {
                document.querySelector('dynamic-element')
                .description = 'a dynamic import';
            });

            // Mixin methods
            Polymer(Polymer.mixin({
                // my-element prototype
            }, myMixin));
        }
        publish: { // publish attributes
            foo: 'bar' 
        }

        // Set a Reflect Property 
        // If the property value is an object, array, or function, 
        // the value is never reflected, whether or not reflect is true.
        propertyName: {
          value: defaultValue,
          reflect: true
        },

        // In the case of property changes that result 
        // in DOM modifications
        propChanged: function() {
          // If "prop" changing results in our DOM changing,
          // schedule an update after the new micro task.
          this.async(this.updateValues);
        },

        responseChanged: function() { ... },

        // UI Events Binding
        keypressHandler: function(event, detail, sender) { ...},
        buttonClick: function(event, detail, sender) {
            // fire(type, detail, targetNode, bubbles?, cancelable?)
            this.fire('ouch', {msg: 'That hurt!'}); 
        },

        // Automatic node finding
        sayHello: function() {
          alert("Hello " + this.$.nameInput.value);
          this.$.container.querySelector('#inner');
        },

        // Watcher
        bar: 'bar',
        observe: {
          bar: 'validate'
        },
        barChanged: function(oldValue, newValue) {
          console.log("I'm not called");
        },
        validate: function(oldValue, newValue) {
          console.log("I'm called instead");
        }
    });
  })();

    // Element life-cycle methods
    Polymer('tag-name', {
      created: function() { ... },
      ready: function() { ... },
      attached: function () { ... },
      domReady: function() { ... },
      detached: function() {
        // Keep bindings active when this element is removed
        this.cancelUnbindAll();
      },
      attributeChanged: function(attrName, oldVal, newVal) {
        //var newVal = this.getAttribute(attrName);
        console.log(attrName, 'old: ' + oldVal, 'new:', newVal);
    },
  });
  </script>
  <script src="path/to/tagname.js"></script>
</polymer-element>

Polymer elements prepare themselves automatically in the following cases:

  • when they’re created in a document that has a defaultView (the main document)
  • when they receive the attached callback
  • when they’re created in the shadowRoot of another element that is preparing itself
  • In addition, if the .alwaysPrepare property is set to true, Polymer elements prepare themselves even when they do not satisfy the above rules.
  • Registration an element: Polymer([ tag-name, ] [prototype]);

Data Binding

Polymer adds declarative, two-way data binding to templates. Data binding lets you assign, or bind, a JavaScript object as the template’s data model.

Polymer data binding makes the smallest changes to the DOM necessary.

// if
<template>
  <template bind="{{myList as list}}">
    <template repeat="{{item in list.items}}" if="{{list.showItems}}">
      <li>{{item.name}}</li>
    </template>
  </template>
</template>

// Expression scope
<template>
  <!-- outermost template -- element's properties available -->
  <template bind="{{organization as organization}}">
    <!-- organization.* available -->
    <template bind="{{organization.contact as contact}}">
      <!-- organization.* & contact.* available -->
      <template bind="{{contact.address}}">
        <!-- only properties of address are available -->
        <template bind="{{streetAddress as streetAddress}}">
          <!-- streetAddress.* and properties of address are available.
               NOT organization.* or contact.* -->
        </template>
      </template>
    </template>
  </template>
</template>

// Filter
{{myNumber | toFixed(2)}}
{{myNumber | toHex | toUpperCase}}
PolymerExpressions.prototype.uppercase = function(input) {
  if (input) {
    return input.toUpperCase();
  }
};

Events

  • Polymer Ready Events
window.addEventListener('polymer-ready', function(e) {
 var xFoo = document.querySelector('x-foo');
 xFoo.barProperty = 'baz';
});

Layout Attributes


version 1.0 via YQN 2015-03-20