Re-usability is the Goal!

When developing modern web applications, we want to build components that are re-usable. The more reusable it is, the better. When things are re-usable we get a lot of benefits going forward with future applications, mainly speed of development. But we also get the ability to update a component in one place and the next update to any app that uses that component, we just get the new version.

One thing that is pretty popular in Domino apps and some modern web apps is a picker. A list of items on the left which you select from, then move the things which are selected into another list on the right. With web components we can create a web component for this and then use that component in multiple applications. In this article I am going to walk you through building a picker component that we can use in any application which needs that sort of functionality. This component will be built with Polymer and be infinitely re-usable. I will not go through the process of setting up your development workstation in this article (maybe that should be a future article).

When creating a component it has a pretty generic pattern of construction. I’ve setup a snippet in Sublime Text to help with this pattern just to help speed things up a bit. The pattern of a polymer web component is as follows:

<link rel="import" href="../polymer/polymer.html">
<dom-module id="now-standalone-picker">
	<template>
		<style is="custom-style">
			:host {
				display: block;
				box-sizing: border-box;
			}
		</style>
		<!-- HTML Goes Here -->
	</template>
	<script>
Polymer({
	is: 'now-standalone-picker'
});
	</script>
</dom-module>

 

All components should start this way. We’ve got a few things going on here:

  1. We import polymer to get the Polymer library
  2. We define our component. The id will be what the tag name we want to use will be
  3. We define a style for our component. This will be constrained to just this component and will not bleed out into other parts of the application
  4. We have a place to enter HTML which will be the construction of our component
  5. Finally we define our component in JavaScript which will contain all the functionality of our component

Next up, we want to define the HTML for our component. This is how it will look. We don’t need to do anything special here, we can use Polymer Elements, plain-ole html, CSS, etc. We’ll replace the ‘HTML Goes Here’ line with our html:

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

<link rel="import" href="../iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../iron-icon/iron-icon.html">
<link rel="import" href="../iron-input/iron-input.html">

<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="../paper-menu/paper-menu.html">

<dom-module id="now-standalone-picker">
	<template>
		<style is="custom-style" include="iron-flex iron-flex-alignment">
			:host {
				display: block;
				box-sizing: border-box;
			}
		</style>
		<div class="layout horizontal">
			<div class="layout vertical flex selectionContainer">
				<div class="searchFieldContainer layout horizontal">
					<input
						id="selectionSearch"
						is="iron-input"
						bind-value="{{searchValue}}"
						name="searchValue"
						placeholder="Search Items">
					</input>
					<span on-tap="_clearSearch" hidden$="{{!searchValue}}">X</span>
					<div id="searchIconContainer" class="searchIconContainer" title="Perform the search">
						<iron-icon icon="search"></iron-icon>
						<paper-ripple></paper-ripple>
					</div>
				</div>
				<div class="selectionList">
					<paper-menu
						id="selectionMenu"
						multi>
						<template is="dom-repeat" items="{{selectionList}}" as="item">
							<paper-item
								value="[[item.title]]">
									[[item.title]]
							</paper-item>
						</template>
					</paper-menu>
				</div>
			</div>
			<div class="layout vertical self-center addRemoveButtonList">
				<paper-icon-button id="addSelected" icon="arrow-forward" on-tap="_addSelected" title="Add Selections"></paper-icon-button>
				<paper-icon-button id="removeSelected" icon="arrow-back" on-tap="_removeSelected" title="Remove Selections"></paper-icon-button>
			</div>
			<div class="layout vertical flex selectedContainer">
				<span class="selectionTitle">
					{{rightSideTitle}}
				</span>
				<div class="selectedList">
					<paper-menu
						id="selectedMenu"
						multi>
						<template is="dom-repeat" items="{{selectedList}}" as="selectedItem">
							<paper-item
								value="[[selectedItem.title]]">
								[[selectedItem.title]]
							</paper-item>
						</template>
					</paper-menu>
				</div>
			</div>
		</div>
	</template>
	<script>
Polymer({
	is: 'now-standalone-picker'
});
	</script>
</dom-module>

Ok, we’ve added the html but there’s some other stuff going on here. I’ve highlighted the pieces we’ve modified:

  1. At the top, notice we have a bunch of link tags with a ‘rel’ of ‘import’? Those are actually including the Polymer components we want to use in our component. We have to load them so that they are available for us to use.
  2. The opening style tag now also includes some weirdness. We’ve added an attribute called ‘include’ with some stuff there. This is importing Polymer’s Flex Layout classes so we can use those in our html also. If you look at the html you’ll find style classes like ‘layout horizontal’, ‘layout vertical’, ‘self-center’, etc. The layout classes provide that functionality for us.
  3. In the html you’ll notice stuff like ‘{{foo}}’ or ‘[[foo]]’. What’s that all about. Well, those will end up being properties of this component. We can bind to those properties and things will just update when they change. Polymer’s Data Binding is one of the biggest reasons to use Polymer in my mind. It’s very similar to Angular’s data binding at least in it’s feel, no clue if they’re similar in structure. Also notice that there are these ‘template’ tags with an ‘is’ attribute of ‘dom-repeat’. Think of these as an XPages repeat component. You give them a list and they’ll just iterate that list stamping out a DOM element for each item in the list. One thing of note with Polymer. The items inside an element that reside inside a template, you can not get to that element using Polymer’s DOM query functions (i.e. this.$.someElemId).
  4. Also notice that there is an ‘on-tap’ attribute on some of the elements? This is another way to define an event handler. In this case, on-tap corresponds to a tap on a touch device or a click from a mouse.

We should now have something that looks like this when we view this component in a browser. Not very exciting huh?

now-standalone-picker-no-styles

Next step is to add some styles, I won’t go over the styles except for the stuff that’s not what you would normally expect to see in CSS.

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

<link rel="import" href="../iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../iron-icon/iron-icon.html">
<link rel="import" href="../iron-input/iron-input.html">

<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="../paper-menu/paper-menu.html">

<dom-module id="now-standalone-picker">
  <template>
    <style is="custom-style" include="iron-flex iron-flex-alignment">
      :host {
        display: block;
        box-sizing: border-box;
      }
      .selectionList {
        border: 1px solid #afafaf;
        margin-right: 5px;
        height: 150px;
        overflow: auto;
        background-color: white;
      }
      .selectionList {
        margin-top: 10px;
      }
      .selectionList paper-menu paper-item, .selectedList paper-menu paper-item {
        font-size: 12px;
        min-height: 20px;
      }
      .selectionList paper-menu paper-item:hover {
        cursor: pointer;
        background-color: #efefef;
      }
      .selectedList {
        border: 1px solid #afafaf;
        height: 150px;
        overflow: auto;
        margin-top: 16px;
        background-color: white;
      }
      .selectionContainer input {
        background-color: white;
        border: 1px solid var(--default-primary-color);
        line-height: 20px;
        padding: 2px 2px 2px 10px;
        width: 100%;
        font-size: 12px;
      }
      .selectionContainer input:focus {
        outline: none;
        border-color: var(--default-primary-color);
        box-shadow: 0 0 10px var(--default-primary-color);
      }
      .selectionContainer input::-webkit-input-placeholder {
        font-size: 12px;
      }
      .selectionContainer input::moz-placeholder {
        font-size: 12px;
      }
      .selectionContainer input:moz-placeholder {
        font-size: 12px;
      }
      .selectionTitle {
        font-size: 12px;
        font-weight: bold;
        line-height: 20px;
      }
      .selectionContainer input:-ms-input-placeholder {
        font-size: 12px;
      }
      .searchFieldContainer span {
        position: absolute;
        display: block;
        top: 23px;
        left: 233px;
        width: 16px;
        height: 16px;
        cursor: pointer;
      }
      .searchIconContainer {
        height: 26px;
        background-color: var(--default-primary-color);
        position: relative;
      }
      .searchIconContainer:hover {
        cursor: pointer;
      }
      .searchIconContainer iron-icon {
        height: inherit;
        background-color: var(--default-primary-color);
        border: 1px solid var(--divider-color);
        border: 0;
        color: white;
      }
    </style>
    <div class="layout horizontal">
      <div class="layout vertical flex selectionContainer">
        <div class="searchFieldContainer layout horizontal">
          <input
            id="selectionSearch"
            is="iron-input"
            bind-value="{{searchValue}}"
            name="searchValue"
            placeholder="Search Replicas">
          </input>
          <span on-tap="_clearSearch" hidden$="{{!searchValue}}">X</span>
          <div id="searchIconContainer" class="searchIconContainer" title="Perform the search">
            <iron-icon icon="search"></iron-icon>
            <paper-ripple></paper-ripple>
          </div>
        </div>
        <div class="selectionList">
          <paper-menu
            id="selectionMenu"
            multi>
            <template is="dom-repeat" items="{{selectionList}}" as="item">
              <paper-item
                value="[[item.title]]">
                  [[item.title]]
              </paper-item>
            </template>
          </paper-menu>
        </div>
      </div>
      <div class="layout vertical self-center addRemoveButtonList">
        <paper-icon-button id="addSelected" icon="arrow-forward" on-tap="_addSelected"></paper-icon-button>
        <paper-icon-button id="removeSelected" icon="arrow-back" on-tap="_removeSelected"></paper-icon-button>
      </div>
      <div class="layout vertical flex selectedContainer">
        <span class="selectionTitle">
          {{rightSideTitle}}
        </span>
        <div class="selectedList">
          <paper-menu
            id="selectedMenu"
            multi>
            <template is="dom-repeat" items="{{selectedList}}" as="selectedItem">
              <paper-item
                value="[[selectedItem.title]]">
                [[selectedItem.title]]
              </paper-item>
            </template>
          </paper-menu>
        </div>
      </div>
    </div>
  </template>
  <script>
Polymer({
  is: 'now-standalone-picker'
});
  </script>
</dom-module>

So pretty straight forward on the CSS we added. But wait a second… what are those ‘var(–default-primary-color)’ statements all about? With Polymer we get the ability to define CSS variables. I can’t find where the ‘default’ variable names are defined by Polymer, it may have just come with using the custom styles. But the variables allow us to define colors (i.e. default-primary-color) assigned to a variable name and then use them anywhere. You can also define mixins but I do not have an example of that here. But it’s very similar to using the variables. So with the styles added we should now see something like this:

now-standalone-picker-styled

Notice that right side box is higher than the left because we’ve left an area to place a title over that right side box. Since there isn’t a title there, the right side has moved up to fill up the space. Once a title is put in there, the boxes will line up perfectly.

We are now ready to define the properties of our component. The properties are what those data binding variables defined in our html will use to display data. We’ll also want to define a keyup listener to our search field along with the method to handle the keyup event. So we should now end up with something like this:

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

<link rel="import" href="../iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../iron-icon/iron-icon.html">
<link rel="import" href="../iron-input/iron-input.html">

<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="../paper-menu/paper-menu.html">

<dom-module id="now-standalone-picker">
  <template>
    <style is="custom-style" include="iron-flex iron-flex-alignment">
      :host {
        display: block;
        box-sizing: border-box;
      }
      .selectionList {
        border: 1px solid #afafaf;
        margin-right: 5px;
        height: 150px;
        overflow: auto;
        background-color: white;
      }
      .selectionList {
        margin-top: 10px;
      }
      .selectionList paper-menu paper-item, .selectedList paper-menu paper-item {
        font-size: 12px;
        min-height: 20px;
      }
      .selectionList paper-menu paper-item:hover {
        cursor: pointer;
        background-color: #efefef;
      }
      .selectedList {
        border: 1px solid #afafaf;
        height: 150px;
        overflow: auto;
        margin-top: 16px;
        background-color: white;
      }
      .selectionContainer input {
        background-color: white;
        border: 1px solid var(--default-primary-color);
        line-height: 20px;
        padding: 2px 2px 2px 10px;
        width: 100%;
        font-size: 12px;
      }
      .selectionContainer input:focus {
        outline: none;
        border-color: var(--default-primary-color);
        box-shadow: 0 0 10px var(--default-primary-color);
      }
      .selectionContainer input::-webkit-input-placeholder {
        font-size: 12px;
      }
      .selectionContainer input::moz-placeholder {
        font-size: 12px;
      }
      .selectionContainer input:moz-placeholder {
        font-size: 12px;
      }
      .selectionTitle {
        font-size: 12px;
        font-weight: bold;
        line-height: 20px;
      }
      .selectionContainer input:-ms-input-placeholder {
        font-size: 12px;
      }
      .searchFieldContainer span {
        position: absolute;
        display: block;
        top: 23px;
        left: 233px;
        width: 16px;
        height: 16px;
        cursor: pointer;
      }
      .searchIconContainer {
        height: 26px;
        background-color: var(--default-primary-color);
        position: relative;
      }
      .searchIconContainer:hover {
        cursor: pointer;
      }
      .searchIconContainer iron-icon {
        height: inherit;
        background-color: var(--default-primary-color);
        border: 1px solid var(--divider-color);
        border: 0;
        color: white;
      }
    </style>
    <div class="layout horizontal">
      <div class="layout vertical flex selectionContainer">
        <div class="searchFieldContainer layout horizontal">
          <input
            id="selectionSearch"
            is="iron-input"
            bind-value="{{searchValue}}"
            name="searchValue"
            placeholder="Search Replicas">
          </input>
          <span on-tap="_clearSearch" hidden$="{{!searchValue}}">X</span>
          <div id="searchIconContainer" class="searchIconContainer" title="Perform the search">
            <iron-icon icon="search"></iron-icon>
            <paper-ripple></paper-ripple>
          </div>
        </div>
        <div class="selectionList">
          <paper-menu
            id="selectionMenu"
            multi>
            <template is="dom-repeat" items="{{selectionList}}" as="item">
              <paper-item
                value="[[item.title]]">
                  [[item.title]]
              </paper-item>
            </template>
          </paper-menu>
        </div>
      </div>
      <div class="layout vertical self-center addRemoveButtonList">
        <paper-icon-button id="addSelected" icon="arrow-forward" on-tap="_addSelected"></paper-icon-button>
        <paper-icon-button id="removeSelected" icon="arrow-back" on-tap="_removeSelected"></paper-icon-button>
      </div>
      <div class="layout vertical flex selectedContainer">
        <span class="selectionTitle">
          {{rightSideTitle}}
        </span>
        <div class="selectedList">
          <paper-menu
            id="selectedMenu"
            multi>
            <template is="dom-repeat" items="{{selectedList}}" as="selectedItem">
              <paper-item
                value="[[selectedItem.title]]">
                [[selectedItem.title]]
              </paper-item>
            </template>
          </paper-menu>
        </div>
      </div>
    </div>
  </template>
  <script>
Polymer({
  is: 'now-standalone-picker',
  properties: {
    selectionList: Array,
    selectedList: {
      type: Array,
      notify: true,
      value: []
    },
    searchValue: String,
    rightSideTitle: String,
    _origSelectionList: Array
  },
  listeners: {
    'selectionSearch.keyup': '_onSearchKeyup'
  },
  _onSearchKeyup: function(evt) {
    if (!this._origSelectionList) {
      this._origSelectionList = this.selectionList.slice();
    }
    if (this.searchValue.length > 2) {
      var searchVal = this.searchValue.toLowerCase();
      this.selectionList = this._origSelectionList.filter(function(item) {
        var title = item.title.toLowerCase();
        return title.indexOf(searchVal) > -1;
      });
    }else if (!this.searchValue) {
      this._clearSearch();
    }
  },
  _clearSearch: function(evt) {
    this.searchValue = null;
    this.selectionList = this._origSelectionList;
    delete this._origSelectionList;
  }
});
  </script>
</dom-module>

When defining properties there are a couple of different ways to do so. One is a propertyName: type (i.e. String, Object, Array, etc) the other is as an object (see selectedList above). Once these properties are defined they are now available to be used both inside this component and outside. Notice that the ‘selectedList’ property has ‘notify’ set to ‘true’. This will allow this property to be propagated up and down the DOM tree and all concerned elements will be notified of it’s change.

Also notice our ‘listeners’ property. Inside that object is a property name whose definition matches the pattern <ElementId>.<eventName> then the method to call when that event fires (‘_onSearchKeyup’).

We can now define our functionality of the right/left arrow buttons and what happens as this thing is used. Our usage pattern will be:

  1. Select an item(s) on the left/right
  2. Click the ‘right’/’left’ arrow icon
  3. This will move the items selected on the left, to the right side or vice-versa.

So we need a function to handle adding to the selection, removing from the selection and a couple of maintenance type methods to find the index of an item, deselecting items and selecting items programmatically.

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

<link rel="import" href="../iron-flex-layout/iron-flex-layout-classes.html">
<link rel="import" href="../iron-icon/iron-icon.html">
<link rel="import" href="../iron-input/iron-input.html">

<link rel="import" href="../paper-icon-button/paper-icon-button.html">
<link rel="import" href="../paper-item/paper-item.html">
<link rel="import" href="../paper-menu/paper-menu.html">

<dom-module id="now-standalone-picker">
  <template>
    <style is="custom-style" include="iron-flex iron-flex-alignment">
      :host {
        display: block;
        box-sizing: border-box;
      }
      .selectionList {
        border: 1px solid #afafaf;
        margin-right: 5px;
        height: 150px;
        overflow: auto;
        background-color: white;
      }
      .selectionList {
        margin-top: 10px;
      }
      .selectionList paper-menu paper-item, .selectedList paper-menu paper-item {
        font-size: 12px;
        min-height: 20px;
      }
      .selectionList paper-menu paper-item:hover {
        cursor: pointer;
        background-color: #efefef;
      }
      .selectedList {
        border: 1px solid #afafaf;
        height: 150px;
        overflow: auto;
        margin-top: 16px;
        background-color: white;
      }
      .selectionContainer input {
        background-color: white;
        border: 1px solid var(--default-primary-color);
        line-height: 20px;
        padding: 2px 2px 2px 10px;
        width: 100%;
        font-size: 12px;
      }
      .selectionContainer input:focus {
        outline: none;
        border-color: var(--default-primary-color);
        box-shadow: 0 0 10px var(--default-primary-color);
      }
      .selectionContainer input::-webkit-input-placeholder {
        font-size: 12px;
      }
      .selectionContainer input::moz-placeholder {
        font-size: 12px;
      }
      .selectionContainer input:moz-placeholder {
        font-size: 12px;
      }
      .selectionTitle {
        font-size: 12px;
        font-weight: bold;
        line-height: 20px;
      }
      .selectionContainer input:-ms-input-placeholder {
        font-size: 12px;
      }
      .searchFieldContainer span {
        position: absolute;
        display: block;
        top: 23px;
        left: 233px;
        width: 16px;
        height: 16px;
        cursor: pointer;
      }
      .searchIconContainer {
        height: 26px;
        background-color: var(--default-primary-color);
        position: relative;
      }
      .searchIconContainer:hover {
        cursor: pointer;
      }
      .searchIconContainer iron-icon {
        height: inherit;
        background-color: var(--default-primary-color);
        border: 1px solid var(--divider-color);
        border: 0;
        color: white;
      }
    </style>
    <div class="layout horizontal">
      <div class="layout vertical flex selectionContainer">
        <div class="searchFieldContainer layout horizontal">
          <input
            id="selectionSearch"
            is="iron-input"
            bind-value="{{searchValue}}"
            name="searchValue"
            placeholder="Search Replicas">
          </input>
          <span on-tap="_clearSearch" hidden$="{{!searchValue}}">X</span>
          <div id="searchIconContainer" class="searchIconContainer" title="Perform the search">
            <iron-icon icon="search"></iron-icon>
            <paper-ripple></paper-ripple>
          </div>
        </div>
        <div class="selectionList">
          <paper-menu
            id="selectionMenu"
            multi>
            <template is="dom-repeat" items="{{selectionList}}" as="item">
              <paper-item
                value="[[item.title]]">
                  [[item.title]]
              </paper-item>
            </template>
          </paper-menu>
        </div>
      </div>
      <div class="layout vertical self-center addRemoveButtonList">
        <paper-icon-button id="addSelected" icon="arrow-forward" on-tap="_addSelected"></paper-icon-button>
        <paper-icon-button id="removeSelected" icon="arrow-back" on-tap="_removeSelected"></paper-icon-button>
      </div>
      <div class="layout vertical flex selectedContainer">
        <span class="selectionTitle">
          {{rightSideTitle}}
        </span>
        <div class="selectedList">
          <paper-menu
            id="selectedMenu"
            multi>
            <template is="dom-repeat" items="{{selectedList}}" as="selectedItem">
              <paper-item
                value="[[selectedItem.title]]">
                [[selectedItem.title]]
              </paper-item>
            </template>
          </paper-menu>
        </div>
      </div>
    </div>
  </template>
  <script>
Polymer({
  is: 'now-standalone-picker',
  properties: {
    selectionList: Array,
    selectedList: {
      type: Array,
      notify: true,
      value: []
    },
    searchValue: String,
    rightSideTitle: String,
    _origSelectionList: Array
  },
  listeners: {
    'selectionSearch.keyup': '_onSearchKeyup'
  },
  _onSearchKeyup: function(evt) {
    if (!this._origSelectionList) {
      this._origSelectionList = this.selectionList.slice();
    }
    if (this.searchValue.length > 2) {
      var searchVal = this.searchValue.toLowerCase();
      this.selectionList = this._origSelectionList.filter(function(item) {
        var title = item.title.toLowerCase();
        return title.indexOf(searchVal) > -1;
      });
    }else if (!this.searchValue) {
      this._clearSearch();
    }
  },
  _clearSearch: function(evt) {
    this.searchValue = null;
    this.selectionList = this._origSelectionList;
    delete this._origSelectionList;
  },
  _addSelected: function(evt, detail) {
    var selectedItems = [];
    selectedItems = this.$.selectionMenu.selectedValues || [];
    var selectionListItems = this.selectionList;
    for (var i = 0; i < selectedItems.length; i++) {
      var pushVal = selectionListItems[selectedItems[i]];
      if (pushVal && this.selectedList.indexOf(pushVal) === -1) {
        this.push('selectedList', pushVal);
      }
    }
  },
  _removeSelected: function(evt, detail) {
    var selectedRemoveMenuValues = [];
    selectedRemoveMenuValues = this.$.selectedMenu.selectedValues || [];
    var selectedRemoveItems = [];
    for (var i = 0; i < selectedRemoveMenuValues.length; i++) {
      var selectedItem = selectedRemoveMenuValues[i];
      if (selectedItem || selectedItem === 0) {
        selectedRemoveItems.push(this.selectedList[selectedItem]);
      }
    }
    for (var j = 0; j < selectedRemoveItems.length; j++) {
      var removeItemIdx = this._findIndex('selected', selectedRemoveItems[j]);
      var removeItem = selectedRemoveItems[j];
      if (removeItemIdx > -1) {
        this.splice('selectedList', removeItemIdx, 1);
      }
      this._deselectRemovedNames(selectedRemoveItems);
      this.$.selectedMenu.selectedValues = [];
    }
  },
  _findIndex: function(menuType, item) {
    var menuItems = null;
    if (menuType === 'selection') {
      menuItems = this.selectionList;
    }else if (menuType === 'selected') {
      menuItems = this.selectedList;
    }
    return menuItems.indexOf(item);
  },
  _deselectRemovedNames: function(removedItems) {
    for (var i = 0; i < removedItems.length; i++) {
      var removeNameIdx = this._findIndex('selection', removedItems[i]);
      this.$.selectionMenu.select(removeNameIdx);
    }
  },
  _selectInSelectionList: function(itemId) {
    var selection = this.selectionList.find(function(item) {
      return item['@id'] === itemId;
    });
    var thisIdx = this._findIndex('selection', selection);
    this.$.selectionMenu.select(thisIdx);
    this._addSelected();
  }
});
  </script>
</dom-module>

We’ve now added functions to find the index of an item in either list and a way to select/deselect things programmatically. The biggest thing to remember here is that ‘this’ is the component. We also

OK, that should finish up our component. Which is all well and good, but how do you use it? Well that’s the great thing about web components. In order to use this thing we’ll just include it in the html of an application somewhere. Below is the html of the demo page for this component.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
    <title>now-standalone-picker Demo</title>
    <script src="../../webcomponentsjs/webcomponents-lite.min.js"></script>
    <link rel="import" href="../now-standalone-picker.html">
    <link rel="import" href="../../iron-demo-helpers/demo-snippet.html">
    <link rel="import" href="../../iron-demo-helpers/demo-pages-shared-styles.html">
  </head>
  <body unresolved>

    <p>An example of <code>&lt;now-standalone-picker&gt;</code>:</p>

    <demo-snippet class="centered-demo">
      <template id="pickerDemo">
        <now-standalone-picker
          right-side-title="Now Picker: Selected Items"
          selected-list="{{selected}}">
        </now-standalone-picker>
        <script>
          var picker = document.querySelector('#pickerDemo');
          picker.selected = [];
          picker.selections = [
            {"id": "123", "title": "Selection Item 1"},
            {"id": "121", "title": "Selection Item 2"},
            {"id": "122", "title": "Selection Item 3"},
            {"id": "124", "title": "Selection Item 4"},
            {"id": "125", "title": "Selection Item 5"},
            {"id": "126", "title": "Selection Item 6"},
            {"id": "127", "title": "Selection Item 7"},
            {"id": "128", "title": "Selection Item 8"},
            {"id": "129", "title": "Selection Item 9"},
            {"id": "120", "title": "Selection Item 10"}
          ];
          var saPicker = document.querySelector('now-standalone-picker');
          saPicker.set('selectionList', picker.selections);
          saPicker.set('selectedList', picker.selected);
        </script>
      </template>
    </demo-snippet>
  </body>
</html>

Notice our ‘now-standalone-picker’ html tag? That’s our component. Also notice that we’ve defined some attributes that closely match our property names for ‘rightSideTitle’ and ‘selectedList’. In our property declarations we defined these properties using camel-case. When we define these properties via an html attribute, we have to use a dash at each capitalized letter. So ‘rightSideTitle’ ends up being ‘right-side-title’.

We now end up with something like below. Notice we’ve now got a selection list. The component so far is working as we expect.

now-standalone-picker-selections

Now we make some selections and click the ‘right’ arrow and it moves them to the right side.

now-standalone-picker-selections-selected

We can also search the selection items, make choices and select them.

now-standalone-picker-search

As we change the selected values around, we can see our changes via data binding by going out to a JavaScript console and typing picker.selected and we should see the items we’ve selected.

The biggest advantage to this is now we can re-use this in any application we want to. We can include it in a dialog to be fired when something else is clicked or just use it as is. Since our properties are public we can populate them via an AJAX request or however we want. There is arguably more functionality to be added here: for example fire an event when the selectedList changes. We could also add the ability to handle single or multiple selections. Currently we only handle multiple selections as I really don’t see a use for this if only a single selection is needed, just use a combo box of some sort.

Polymer web components allow you to build the pieces of an application and then put those pieces together to form the whole app. Each component is aware of itself and any components contained within itself and nothing else. It provides a great way to separate out the logic of our application down to the smallest possible piece. Which totally goes toward the goal of re-usability.

If you would like to use or contribute to this component please visit the repository at Github. To see the documentation and demo visit my github pages site.

Share This: