Demo-Driven Development for Polymer wizards
Accelerate development cycles by connecting the internal state of your element to its demo page
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!
Photo: Peter Castleton