Demo-Driven Development for Polymer wizards

Accelerate development cycles by connecting the internal state of your element to its demo page

Ronny Roeller
NEXT Engineering
3 min readAug 25, 2017

--

Polymer elements are often better developed based on demos than onunit tests (read full story here). A special case are wizards — or other multi-step elements: Reloading the demo will wipe out the state, and force you to redo all steps, before you can verify your changes.

Control page from demo

Let’s consider this simple wizard based on <iron-pages>:

<iron-pages selected="{{_page}}" attr-for-selected="name">
<div name="intro">
Introduction page
<paper-button on-tap="gotToExecute">Next</paper-button>
</div>
<div name="execute">
Here is the real action
<paper-button on-tap="gotToFinalize">Next</paper-button>
</div>
<div name="finalize">
Thanks and good bye
</div>
</iron-pages>

The full-screen demo would expose the wizard via:

<dom-bind>
<template>
<custom-style>
<style>
my-wizard {
@apply --layout-fit;
}
</style>
</custom-style>
<my-wizard></my-wizard>
</template>
</dom-bind>

In its current state, we would have to press twice the [Next] button to see changes on our finalize page. Wouldn’t it be great to jump directly to the last page from our demo?

To control the page, we first need to expose the underlying property. We’ll keep the underscore in the name _page to indicate that this property isn’t part of the public API (similar to Java’s @ VisibleForTesting):

class MyWizard extends Polymer.Element {
static get is() {
return 'my-wizard';
}
static get properties() {
return {
_page: {
type: String,
value: 'intro',
// For demo
notify: true,
},

};
}
}

Now, we can extend the demo to control the page directly:

<dom-bind>
<template>
<custom-style>
<style>
.page {
@apply --layout-fit;
@apply --layout-vertical;
}
my-wizard {
@apply --layout-flex;
}
.controls {
@apply --layout-horizontal;
border-top: 1px solid #ccc;
}
</style>
</custom-style>
<div class="page">
<my-wizard _page="{{page}}"></my-wizard>
<div class="controls">
<paper-radio-group selected="{{page}}">
<paper-radio-button name="intro">Intro</paper-radio-button>
<paper-radio-button name="execute">Execute</paper-radio-button>
<paper-radio-button name="finalize">Finalize</paper-radio-button>
</paper-radio-group>
</div>
</div>

</template>
</dom-bind>

Wizard within wizards

This pattern can be easily extended. Imagine the execute page of our wizard consists again of different steps:

<iron-pages selected="{{_page}}" attr-for-selected="name">
<div name="intro">
Introduction page
<paper-button on-tap="gotToExecute">Next</paper-button>
</div>
<div name="execute">
<iron-pages selected="{{_step}}">
<div>First step...</div>
<div>Second step...</div>
<div>
<paper-button on-tap="gotToFinalize">Next</paper-button>
</div>
</iron-pages>

</div>
<div name="finalize">
Thanks and good bye
</div>
</iron-pages>

With the _step property being exposed, the demo can control the sub wizard as well:

...<my-wizard _page="{{page}}" _step="{{step}}"></my-wizard><div class="controls">
<paper-radio-group selected="{{page}}">
<paper-radio-button name="intro">Intro</paper-radio-button>
<paper-radio-button name="execute">Execute</paper-radio-button>
<paper-radio-button name="finalize">Finalize</paper-radio-button>
</paper-radio-group>
<paper-radio-group selected="{{step}}" hidden$="[[!eq(page, 'execute')]]">
<paper-radio-button name="1">Step 1</paper-radio-button>
<paper-radio-button name="2">Step 2</paper-radio-button>
<paper-radio-button name="3">Step 3</paper-radio-button>
</paper-radio-group>
</div>
...eq(value1, value2) {
return value1 === value2;
}

Now, the demo allows to jump to a specific page and step. To completely resolve the reload issue, the demo could store the last page/step in a cookie or in local storage, and restore from there.

Happy coding!

--

--

Ronny Roeller
NEXT Engineering

CTO at nextapp.co # Product discovery platform for high performing teams that bring their customers into every decision