Creating a simple component

Daniel Busłowicz edited this page Jul 23, 2017 · 1 revision

TWC does not add any funky logic to Polymer, so concepts are pretty much the same. All it requires additionally, is typescript installed (globally or locally). The following tutorial will guide you through creating a simple component in Polymer V2, which lets you select an online state (online or offline) and reflect the change on a host attribute. There are a lot of ways you can take, but here we will take a single simple approach.

What you will need

  • Code editor
  • Node.js
  • Bower
  • Polymer CLI
  • TWC

TL;DR

Nothing comes out of the box (at least in most cases), so first you will need to get familiar with some general development terms and tools. The most important things are a source code editor and a terminal/command line. Next in a row is having Node.js and Bower installed globally. These are package repositories with software that you will need. Next you need you install polymer-cli and twc (both globally).

Installing

npm init
tsc --init
npm install typescript twc --save-dev

Please note. To have types properly handled, add node_modules/twc/types/polymer.decorators.d.ts to include section in your tsconfig.json!

TL;DR

Let us begin with installing core stuff. First fire up a terminal within a folder you want to develop in, this will be the root of your project. Now run npm init (answer all the questions) and tsc --init. This will create two additional files: package.json and tsconfig.json. The first one is a configuration file for npm repository (for twc and TypeScript), the second one hold TypeScript project configuration. Now let's install developer dependencies: npm install typescript twc --save-dev. This will install typescript and twc locally (for this project only) and add them as development dependencies. You can now see that package.json file has changed and now has devDependencies inside. Now you are ready to create a component!

Starting a scaffold (using Polymer CLI)

polymer init --name polymer-2-element

Please note. In this tutorial, component will be named online-state

TL;DR

Next thing you need to do is creating a project using polymer-cli. To do that, go to your terminal and run the following: polymer init and select polymer-2-element as a starter template. Now you have a valid Polymer 2 sample component! To see how it looks, run polymer serve and in the web browser open this link http://127.0.0.1:8081/components/COMPONENT-NAME (replace COMPONENT-NAME with the name of your component).

Creating a basic TWC component

Lets analyze the sample Polymer component:

<link rel="import" href="../polymer/polymer-element.html">

<dom-module id="online-state">
  <template>
    <style>
      :host {
        display: block;
      }
    </style>
    <h2>Hello [[prop1]]!</h2>
  </template>

  <script>
    /**
     * `online-state`
     * Lets you select an online state (online or offline) and reflect the change on a host attribute.
     *
     * @customElement
     * @polymer
     * @demo demo/index.html
     */
    class OnlineState extends Polymer.Element {
      static get is() { return 'online-state'; }
      static get properties() {
        return {
          prop1: {
            type: String,
            value: 'online-state'
          }
        };
      }
    }

    window.customElements.define(OnlineState.is, OnlineState);
  </script>
</dom-module>

What we have here is an import and a <dom-module> declaration.

Please note! The path is generated to work with Polymer CLI and looks like it in an own folder within bower_components.

Within the <dom-module> we have a <template> and <script> tags. First one holds a template and styles, while the second one holds the actual component declaration. Declaration itself consists of a JSDoc comment, and a class with a tag name (string returned from static get is) and properties.

Lets use this information to scaffold a sample typescript class to be our base. Change component file extension from .html to .ts and replace the contents to this:

import { CustomElement } from "twc/polymer";
import "bower:polymer/polymer-element.html";

/**
 * `online-state`
 * Lets you select an online state (online or offline) and reflect the change on a host attribute.
 *
 * @customElement
 * @polymer
 * @demo demo/index.html
 */
@CustomElement()
class OnlineState extends Polymer.Element {
  prop1: string = "online-state";

  template() {
    return `
      <style>
        :host {
          display: block;
        }
      </style>
      <h2>Hello [[prop1]]!</h2>
    `;
  }
}

The above code contains the same details. It imports polymer/polymer-element.html, contains the exactly same JSDoc comment, a class with the same name and heritage, prop1 string property with default value, style and a template. It looks different though, so lets get through it.

Imports

It now skips the relative path (which sometimes is troublesome) and tells "import polymer/polymer-element.html from bower repository".

Properties

Instead of declaring an object with properties config, we use a TypeScript properties declaration. It only requires to list properties within a class body, add a type and optionally assign a default value. As easy as that!

Template

This is almost identical to original code. The only difference is that instead of declaring it within <template> tags, we return it from a template() function.

What's new, what's missing

The only thing added is a @CustomElement() decorator imported from twc/polymer package. This registers the component as a Custom Element and removes the need to register it manually using customElements.define().

Make it be OnlineStatus

So what next? First of all we need a template with 3 things:

  • container reflecting current status
  • select with options
  • button to disable the select (because we can!)

This is how your template() method should look like:

  template() {
    return `
      <style>
        :host {
          display: block;
        }
      </style>
      <strong>You are [[status]]</strong>
      <select value="{{currentIndex::change}}" disabled="[[selectDisabled]]">
        <template is="dom-repeat" items="{{statusList}}">
          <option value="[[index]]">[[item]]</option>
        </template>
      </select>
      <button type="button" on-click="toggleEdit">Toggle disable</button>`;
  }

This is what normal Polymer template looks like, and this is how it looks with twc, no magic here. Now more interesting part - lets make it work! From the template we can see that we need a status, currentIndex, selectDisabled and statusList properties and a toggleEdit() method. To explain a bit:

  • currentIndex - a number taking an index of current a option
  • selectDisabled - a boolean indicating whether select should or should not be disabled
  • statusList - an array of available statuses
  • status - a computed property, taking a current index and list of available statuses to return a status
  • toggleEdit() - a method flipping selectDisabled property

Here is how it looks like:

class OnlineStatus extends Polymer.Element {
  readonly statusList = [ "online", "offline" ];
  currentIndex = 0;
  selectDisabled = false;

  @compute((statusList, currentIndex) => statusList[currentIndex]) @attr() @notify() status: string;

  toggleEdit() {
    this.selectDisabled = !this.selectDisabled;
  }

  template() {
    return `
      <style>
        :host {
          display: block;
        }
      </style>
      <strong>You are [[status]]</strong>
      <select value="{{currentIndex::change}}" disabled="[[selectDisabled]]">
        <template is="dom-repeat" items="{{statusList}}">
          <option value="[[index]]">[[item]]</option>
        </template>
      </select>
      <button type="button" on-click="toggleEdit">Toggle disable</button>`;
  }

As you can see, the statusList is a read only property and has an initial value of an array. You might know that non-primitive default values should be wrapped with a function, but you don't need to worry about that here, twc will do that for you. currentIndex and selectDisabled have assigned primitive values, but as with statusList, there is no type declaration. How so? Twc analyses the assigned value and fetches the type out of them, if not provided explicitly. Next thing to notice is status property. It has an explicit type of a string and 3 decorators:

  • @attr() sets reflectToAttribute to true
  • @notify() sets notify to true
  • @compute() creates a resolver method and sets the property to be a computed property

A bit more on @compute(). We provided it with an array function: (statusList, currentIndex) => statusList[currentIndex] and did nothing more. It works pretty simple: argument names are properties it relies on, and the function itself is created as a class method and assigned to the prototype. The name given to the method is then set to computed config of the property and arguments are passed to it.

The output

Once you are with the code, head to the terminal and run twc. It works almost identical to tsc, but generates .html file instead of .js. Just as tsc it reads configuration from tsconfig.json file (like rootDir, outDir, target, etc.), so for more details head over here. With the following config:

{
  "compilerOptions": {
    "target": "es6",
    "experimentalDecorators": true
  },
  "include": [
    "node_modules/twc/types/polymer.decorators.d.ts"
  ],
  "files": [
    "online-status.ts"
  ]
}

running the twc should create online-status.html file at your projects root, which should look roughly like this:

<link rel="import" href="../polymer/polymer-element.html">
<!--
`online-status`
Lets you select an online state (online or offline) and reflect the change on a host attribute.

@customElement
@polymer
@demo demo/index.html
-->
<dom-module id="online-status">
  <template>

    <style>
       :host {
        display: block;
      }
    </style>
    <strong>You are [[status]]</strong>
    <select value="{{currentIndex::change}}" disabled="[[selectDisabled]]">
      <template is="dom-repeat" items="{{statusList}}">
        <option value="[[index]]">[[item]]</option>
      </template>
    </select>
    <button type="button" on-click="toggleEdit">Toggle disable</button>
  </template>
  <script>
    class OnlineStatus extends Polymer.Element {
      static get is() {
        return "online-status";
      }
      static get properties() {
        return {
          statusList: {
            type: Array,
            value: function() {
              return ["online", "offline"];
            },
            readOnly: true
          },
          currentIndex: {
            type: Number,
            value: 0,
            observer: "statusChanged"
          },
          status: {
            type: String,
            reflectToAttribute: true,
            notify: true,
            computed: "_statusComputed(statusList, currentIndex)"
          },
          selectDisabled: {
            type: Boolean,
            value: false
          }
        };
      }
      _statusComputed(statusList, currentIndex) {
        return statusList[currentIndex];
      }
      statusChanged(newStatus, oldStatus) {
        console.log(`currentIndex has changed ${oldStatus} => ${newStatus}`);
      }
      toggleEdit() {
        this.selectDisabled = !this.selectDisabled;
      }
    }
    customElements.define(OnlineStatus.is, OnlineStatus);
  </script>
</dom-module>

Done!

This is it! This tutorial should explain basic concepts of twc. Further documentation will come with time (How To guides and detailed info of how it works internally, as well as API docs), so please be patient :).

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.