Merge lp:~gmb/lazr-js/wizard-widget into lp:lazr-js

Proposed by Graham Binns on 2010-09-17
Status: Merged
Approved by: Graham Binns on 2010-09-23
Approved revision: 211
Merged at revision: 186
Proposed branch: lp:~gmb/lazr-js/wizard-widget
Merge into: lp:lazr-js
Diff against target: 1342 lines (+1280/-3)
8 files modified
examples/wizard/index.html (+110/-0)
setup.py (+6/-3)
src-js/lazrjs/formoverlay/formoverlay.js (+4/-0)
src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css (+104/-0)
src-js/lazrjs/wizard/assets/wizard-core.css (+50/-0)
src-js/lazrjs/wizard/tests/wizard.html (+34/-0)
src-js/lazrjs/wizard/tests/wizard.js (+752/-0)
src-js/lazrjs/wizard/wizard.js (+220/-0)
To merge this branch: bzr merge lp:~gmb/lazr-js/wizard-widget
Reviewer Review Type Date Requested Status
Paul Hummer (community) js 2010-09-17 Approve on 2010-09-21
Brad Crittenden (community) code Approve on 2010-09-17
Review via email: mp+35827@code.launchpad.net

Commit Message

Add a Wizard widget.

Description of the Change

This branch complete's Paul's work on adding a Wizard (multi-step FormOverlay) widget to LAZR-JS.

The Widget is a fairly simple extension to lazr.FormOverlay. It contains next() and previous() methods which allow us to move through its steps. It's up to whatever uses the Wizard to call those methods.

The Step class isn't really a class so much as a container for logic. It has a form_content attribute, which contains whatever HTML should be displayed when the step is loaded, and callbacks funcLoad and funcCleanUp, which are called when the step is loaded and when it is superseded by another step respectively.

To post a comment you must log in.
lp:~gmb/lazr-js/wizard-widget updated on 2010-09-17
207. By Graham Binns on 2010-09-17

Minor tweak.

208. By Graham Binns on 2010-09-17

Fixed brokenness.

209. By Graham Binns on 2010-09-17

Added a comment to make the horrendous testing of erroring tests nonsense a little bit clearer.

210. By Graham Binns on 2010-09-17

Removed unnecessary check for steps.

Brad Crittenden (bac) wrote :
Download full text (14.4 KiB)

Hi Graham,

Thanks for the feedback on IRC.

This branch looks good but I've noted some issues, most of them trivial. I do encourage you to have a more experienced JS reviewer look at this branch but I'm glad I reviewed it as I learned quite a bit.

--Brad

> === added directory 'examples/wizard'
> === added file 'examples/wizard/index.html'
> --- examples/wizard/index.html 1970-01-01 00:00:00 +0000
+++ examples/wizard/index.html 2010-09-17 14:41:35 +0000

Thanks for fixing the issue we discussed on IRC. It is good to have a
working example even if it isn't as nice as you'd like.

> @@ -0,0 +1,102 @@
> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
> + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
> +<head>
> + <title>LAZR JS Examples: lazr.wizard</title>
> +
> + <link rel="stylesheet" type="text/css" href="../../build/cssreset/reset-min.css" />
> + <link rel="stylesheet" type="text/css" href="../../build/cssfonts/fonts-min.css" />
> + <link rel="stylesheet" type="text/css" href="../../build/cssbase/base-min.css" />
> + <meta http-equiv="content-type" content="text/html;charset=utf-8" />
> +
> + <script type="text/javascript" src="../../build/yui/yui-min.js"></script>
> + <script type="text/javascript" src="../../build/lazr/lazr-meta.js"></script>
> + <script type="text/javascript">
> + var LAZR_YUI_CONFIG = {
> + filter: "min",
> + base: "../../build/",
> + modules: LAZR_MODULES,
> + };
> + </script>
> +</head>
> +
> +<body class="yui3-skin-sam">
> +<h1>The lazr.wizard module</h1>
> +
> +The lazr.wizard module has two widgets in it: <code>Wizard</code> and
> +<code>Step</code>.
> +
> +<h2>Wizard</h2>
> +A wizard can have multiple steps. You can add steps by calling Wizard.addStep.
> +
> +<h2>Step</h2>
> +A step is not really a widget. It merely has a load() and a cleanup() that are
> +called by the Wizard when it moves through its steps.
> +
> +<h2>Demonstration</h2>
> +<p><a href="#" id="re-open">Re-open the wizard to test again</a></p>
> +
> +<script type="text/javascript">
> +YUI(LAZR_YUI_CONFIG).use('cssreset', 'cssfonts', 'cssbase',
> + 'lazr.wizard', 'node', 'event',
> + 'dump', function(Y) {
> +
> + var wizardSteps = [];
> + wizardSteps.push(new Y.lazr.wizard.Step({
> + title: "Step One",
> + form_content: [
> + '<p>You are now on step 1.</p>',
> + '<p>',
> + ' <input id="wizard-next-step" type="button" value="Next" />',
> + '</p>'
> + ].join("\n"),
> + funcLoad: function(wizard) {
> + Y.log("Step One load");
> + var form_node = wizard.form_node;
> + form_node.query('input#wizard-next-step').on(
> + 'click', function(e) {
> + e.stopPropagation();
> + wizard.next();
> + });
> + },
> + funcCleanUp: function() { Y.log("Step One cleanup"); }
> + }));
> + wizardSteps.push(new Y.lazr.wizard.Step({
> + title: "Step Two",
> + form_content: [
> + '<p>...

review: Approve (code)
Paul Hummer (rockstar) wrote :

So, we discussed on the phone that we should have Wizard.destroy() handling the destruction of the whole widget, and we also discussed actually firing the event instead of calling the function we set up to be the event default. Fix those and land it.

review: Approve (js)
Graham Binns (gmb) wrote :
Download full text (8.3 KiB)

Hi Brad,

> > === modified file 'src-js/lazrjs/formoverlay/formoverlay.js'
> > --- src-js/lazrjs/formoverlay/formoverlay.js 2010-04-02 21:44:29 +0000
> > +++ src-js/lazrjs/formoverlay/formoverlay.js 2010-09-17 14:41:35 +0000
> > @@ -190,6 +190,9 @@
> >
> > Y.extend(FormOverlay, Y.lazr.PrettyOverlay, {
> >
> > + initializer: function() {
> > + /* Do nothing */
>
> A comment here that the method is meant to be overridden by subclasses
> might be useful.
>
> The comment should be a complete sentence with punctuation.
>

Done.

> > + },
> > /**
> > * Create the nodes for the form and add them to the contentBody.
> > * <p>
>
> > === added directory 'src-js/lazrjs/wizard'
> > === added directory 'src-js/lazrjs/wizard/assets'
> > === added directory 'src-js/lazrjs/wizard/assets/skins'
> > === added directory 'src-js/lazrjs/wizard/assets/skins/sam'
> > === added file 'src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css'
> > --- src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css 1970-01-01 00:00:00 +0000
> > +++ src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css 2010-09-17 14:41:35 +0000
>
> sam?

No idea.

> > @@ -0,0 +1,104 @@
> > +/* Copyright (c) 2009, Canonical Ltd. All rights reserved. */
>
> 2010
>
> > +.yui3-wizard .step-off,
> > +.yui3-wizard .step-offb {
> > + background: gray url('images/bg_steps-estatus.gif') bottom repeat-x;
>
> What an odd file name.

Yep. Again, not a clue.

> > === added directory 'src-js/lazrjs/wizard/tests'
> > === added file 'src-js/lazrjs/wizard/tests/wizard.html'
> > --- src-js/lazrjs/wizard/tests/wizard.html 1970-01-01 00:00:00 +0000
> > +++ src-js/lazrjs/wizard/tests/wizard.html 2010-09-17 14:41:35 +0000
> > @@ -0,0 +1,34 @@
> > +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
> > +<html>
> > + <head>
> > + <title>Form Overlay</title>
> > +
> > + <!-- YUI 3.0 Setup -->
> > + <script type="text/javascript" src="../../yui/yui/yui.js"></script>
> > + <link rel="stylesheet" href="../../yui/cssreset/reset.css"/>
> > + <link rel="stylesheet" href="../../yui/cssfonts/fonts.css"/>
> > + <link rel="stylesheet" href="../../yui/cssbase/base.css"/>
> > + <link rel="stylesheet" href="../../testing/assets/testlogger.css"/>
> > +
> > + <!-- dependent modules from lazr-->
>
> Capitalize the comment and use punctuation. Please apply to all comments.

Done.

> > + <script type="text/javascript" src="../../lazr/lazr.js"></script>
> > + <script type="text/javascript" src="../../overlay/overlay.js"></script>
> > + <script type="text/javascript" src="../../testing/testing.js"></script>
> > + <script type="text/javascript" src="../../formoverlay/formoverlay.js"></script>
> > +
> > + <!-- The module under test -->
> > + <script type="text/javascript" src="../../wizard/wizard.js"></script>
> > +
> > + <!-- Testing helpers -->
> > + <script type="text/javascript" src="../../testing/mockio.js"></script>
> > +
> > + <!-- The test suite -->
> > + <script type="text/javascript" src="wizard.js"></script>
> > +
> > +</head>
> > +<body class="yui3-skin-sam">
> > + <div id="form_overlay_example">
> > + </div>
> > + <div id="log...

Read more...

lp:~gmb/lazr-js/wizard-widget updated on 2010-09-23
211. By Graham Binns on 2010-09-23

Review changes for Brad.

212. By Graham Binns on 2010-09-23

Fixed addStep.

213. By Graham Binns on 2010-09-23

Added a destructor.

214. By Graham Binns on 2010-09-23

Typo fix.

Paul Hummer (rockstar) wrote :

On Thu, 23 Sep 2010 13:47:41 -0000
Graham Binns <email address hidden> wrote:
> > > + },
> > > /**
> > > * Create the nodes for the form and add them to the
> > > contentBody.
> > > * <p>
> >
> > > === added directory 'src-js/lazrjs/wizard'
> > > === added directory 'src-js/lazrjs/wizard/assets'
> > > === added directory 'src-js/lazrjs/wizard/assets/skins'
> > > === added directory 'src-js/lazrjs/wizard/assets/skins/sam'
> > > === added file
> > > 'src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css' ---
> > > src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css
> > > 1970-01-01 00:00:00 +0000 +++
> > > src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css
> > > 2010-09-17 14:41:35 +0000
> >
> > sam?
>
> No idea.
>

This is the default skin name for YUI. I hate that we use it, but we
do. :( I'm not sure why we can't have a lazrjs skin name that we
use. It would make more sense.

> > > @@ -0,0 +1,104 @@
> > > +/* Copyright (c) 2009, Canonical Ltd. All rights reserved. */
> >
> > 2010
> >
> > > +.yui3-wizard .step-off,
> > > +.yui3-wizard .step-offb {
> > > + background: gray url('images/bg_steps-estatus.gif') bottom
> > > repeat-x;
> >
> > What an odd file name.
>
> Yep. Again, not a clue.
>

Please file a bug about this. It makes no sense, but it's legacy.

A lot of the lazr-js codebase is proof that when it comes to
javascript, we're still fumbling around a lot. I hope to get some
standards set up soon, but the simple fact is that there's lots of
hidden (and some not so hidden) oddities about lazr-js.

Cheers,
Paul

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'examples/wizard'
2=== added file 'examples/wizard/index.html'
3--- examples/wizard/index.html 1970-01-01 00:00:00 +0000
4+++ examples/wizard/index.html 2010-09-23 14:58:56 +0000
5@@ -0,0 +1,110 @@
6+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
7+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
8+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
9+<head>
10+ <title>LAZR JS Examples: lazr.wizard</title>
11+
12+ <link rel="stylesheet" type="text/css" href="../../build/cssreset/reset-min.css" />
13+ <link rel="stylesheet" type="text/css" href="../../build/cssfonts/fonts-min.css" />
14+ <link rel="stylesheet" type="text/css" href="../../build/cssbase/base-min.css" />
15+ <meta http-equiv="content-type" content="text/html;charset=utf-8" />
16+
17+ <script type="text/javascript" src="../../build/yui/yui-min.js"></script>
18+ <script type="text/javascript" src="../../build/lazr/lazr-meta.js"></script>
19+ <script type="text/javascript">
20+ var LAZR_YUI_CONFIG = {
21+ filter: "min",
22+ base: "../../build/",
23+ modules: LAZR_MODULES,
24+ };
25+ </script>
26+</head>
27+
28+<body class="yui3-skin-sam">
29+<h1>The lazr.wizard module</h1>
30+
31+The lazr.wizard module has two widgets in it: <code>Wizard</code> and
32+<code>Step</code>.
33+
34+<h2>Wizard</h2>
35+A wizard can have multiple steps. You can add steps by calling Wizard.addStep.
36+
37+<h2>Step</h2>
38+A step is not really a widget. It merely has a load() and a cleanup() that are
39+called by the Wizard when it moves through its steps.
40+
41+<h2>Demonstration</h2>
42+<p><a href="#" id="re-open">Re-open the wizard to test again</a></p>
43+
44+<script type="text/javascript">
45+YUI(LAZR_YUI_CONFIG).use('cssreset', 'cssfonts', 'cssbase',
46+ 'lazr.wizard', 'node', 'event',
47+ 'dump', function(Y) {
48+
49+ var wizardSteps = [];
50+ wizardSteps.push(new Y.lazr.wizard.Step({
51+ title: "Step One",
52+ form_content: [
53+ '<p>You are now on step 1.</p>',
54+ '<p>',
55+ ' <input id="wizard-next-step" type="button" value="Next" />',
56+ '</p>'
57+ ].join("\n"),
58+ funcLoad: function(wizard) {
59+ Y.log("Step One load");
60+ var form_node = wizard.form_node;
61+ form_node.query('input#wizard-next-step').on(
62+ 'click', function(e) {
63+ e.stopPropagation();
64+ wizard.next();
65+ });
66+ },
67+ funcCleanUp: function() { Y.log("Step One cleanup"); }
68+ }));
69+ wizardSteps.push(new Y.lazr.wizard.Step({
70+ title: "Step Two",
71+ form_content: [
72+ '<p>You are now on step 2.</p>',
73+ '<p>',
74+ ' <input id="wizard-prev-step" type="button" value="Prev" />',
75+ ' <input id="wizard-destructor" type="button" value="Destroy!" />',
76+ '</p>'
77+ ].join("\n"),
78+ funcLoad: function(wizard) {
79+ Y.log("Step Two load");
80+ var form_node = wizard.form_node;
81+ form_node.query('input#wizard-prev-step').on(
82+ 'click', function(e) {
83+ e.stopPropagation();
84+ wizard.previous();
85+ });
86+ form_node.query('input#wizard-destructor').on(
87+ 'click', function(e) {
88+ wizard.destroy();
89+ });
90+ },
91+ funcCleanUp: function() { Y.log("Step two cleanup"); }
92+ }));
93+
94+ var wizard = new Y.lazr.wizard.Wizard({
95+ form_content: 'hello',
96+ headerContent: '<h2>Wizard</h2>',
97+ centered: true,
98+ progressbar: true,
99+ visible: false,
100+ steps: wizardSteps
101+ });
102+ wizard.render();
103+ wizard.show();
104+ wizard.on("wizard:stepChange", function(e) {
105+ Y.log("Step has changed");
106+ });
107+
108+ Y.on('click', function(e) {
109+ wizard.show();
110+ }, '#re-open');
111+});
112+</script>
113+
114+</body>
115+</html>
116
117=== modified file 'setup.py'
118--- setup.py 2010-09-09 17:15:22 +0000
119+++ setup.py 2010-09-23 14:58:56 +0000
120@@ -3,11 +3,14 @@
121 import distribute_setup
122 distribute_setup.use_setuptools()
123
124-from bzrlib import branch
125 from setuptools import setup
126
127-revno = branch.Branch.open('.').revno()
128-__version__ = '1.5DEVr%s' % (revno)
129+__version__ = '1.5DEV'
130+try:
131+ from bzrlib import branch
132+ __version__ += '-r%s' % branch.Branch.open('.').revno()
133+except ImportError:
134+ pass
135
136
137 setup(
138
139=== modified file 'src-js/lazrjs/formoverlay/formoverlay.js'
140--- src-js/lazrjs/formoverlay/formoverlay.js 2010-04-02 21:44:29 +0000
141+++ src-js/lazrjs/formoverlay/formoverlay.js 2010-09-23 14:58:56 +0000
142@@ -190,6 +190,10 @@
143
144 Y.extend(FormOverlay, Y.lazr.PrettyOverlay, {
145
146+ initializer: function() {
147+ // This function is intentionally blank as it's not defined by
148+ // PrettyOverlay but is required by YUI.
149+ },
150 /**
151 * Create the nodes for the form and add them to the contentBody.
152 * <p>
153
154=== added directory 'src-js/lazrjs/wizard'
155=== added directory 'src-js/lazrjs/wizard/assets'
156=== added directory 'src-js/lazrjs/wizard/assets/skins'
157=== added directory 'src-js/lazrjs/wizard/assets/skins/sam'
158=== added file 'src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css'
159--- src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css 1970-01-01 00:00:00 +0000
160+++ src-js/lazrjs/wizard/assets/skins/sam/wizard-skin.css 2010-09-23 14:58:56 +0000
161@@ -0,0 +1,104 @@
162+/* Copyright (c) 2010, Canonical Ltd. All rights reserved. */
163+/* Modal Box */
164+
165+.yui3-wizard {
166+ border: none;
167+ position: absolute;
168+ top: 50px;
169+ padding: 0;
170+ margin: 0;
171+ left: 405px;
172+ min-width: 40%;
173+}
174+
175+.yui3-wizard #yui3-wizard-modal {
176+ margin: 7px 0px;
177+ padding: 0;
178+ font: normal normal 12px/normal;
179+ color: #484848;
180+ background: #fff;
181+}
182+
183+.yui3-wizard .close {
184+ margin: 0;
185+ padding: 0 5px;
186+ font: normal normal 10px/normal;
187+ color: #484848;
188+ background: #fff;
189+}
190+
191+.yui3-wizard .close a {
192+ float: right;
193+ width: 15px;
194+ height: 15px;
195+ background: url('images/close.gif');
196+ display: block;
197+ margin-top: 4px;
198+}
199+
200+.yui3-wizard .close .clear {
201+ clear: both;
202+}
203+
204+.yui3-wizard #yui3-wizard-modal h1,
205+.yui3-wizard #yui3-wizard-modal h2 {
206+ font: normal normal 18px/normal;
207+ color: #000;
208+ text-indent: 15px;
209+ margin: 0;
210+ padding: 0;
211+}
212+
213+.yui3-wizard #yui3-wizard-modal h2 {
214+ height: 30px;
215+ font: normal normal 18px;
216+ color: #000;
217+}
218+
219+.yui3-wizard #yui3-wizard-modal .steps h2 {
220+ height: 30px;
221+ font: normal normal 14px/30px;
222+ color: #666;
223+}
224+
225+.yui3-wizard #yui3-wizard-modal h2 strong {
226+ color: #000;
227+ font-weight: normal;
228+}
229+
230+.yui3-wizard .steps {
231+ width: 100%;
232+ height: 3px;
233+ color: #666;
234+ background: #f0f0f0 url('images/bg_steps.gif') bottom repeat-x;
235+ border-top: 1px solid #e6e6e6;
236+}
237+
238+.yui3-wizard .contains-steptitle {
239+ height: 33px;
240+}
241+
242+.yui3-wizard .step-on,
243+.yui3-wizard .step-onb,
244+.yui3-wizard .step-off,
245+.yui3-wizard .step-offb {
246+ width: 100%;
247+ height: 3px;
248+ background: green url('images/bg_steps-estatus.gif') top repeat-x;
249+}
250+
251+.yui3-wizard .step-onb,
252+.yui3-wizard .step-offb {
253+ border-bottom: 1px solid #e6e6e6;
254+ margin-bottom: 15px;
255+}
256+
257+.yui3-wizard .step-off,
258+.yui3-wizard .step-offb {
259+ background: gray url('images/bg_steps-estatus.gif') bottom repeat-x;
260+}
261+
262+.yui3-wizard .yui3-widget-bd {
263+ margin-left: 1em;
264+ margin-right: 1em;
265+}
266
267=== added file 'src-js/lazrjs/wizard/assets/wizard-core.css'
268--- src-js/lazrjs/wizard/assets/wizard-core.css 1970-01-01 00:00:00 +0000
269+++ src-js/lazrjs/wizard/assets/wizard-core.css 2010-09-23 14:58:56 +0000
270@@ -0,0 +1,50 @@
271+.yui3-lazr-wizard-hidden {
272+ visibility: hidden;
273+}
274+
275+.yui3-lazr-wizard-form th, .yui3-lazr-wizard-form td {
276+ /* The same as the Launchpad style, so the example represents
277+ * how it will look.
278+ */
279+ padding-bottom: 1em;
280+}
281+
282+.yui3-lazr-wizard-form div.yui3-lazr-wizard-actions {
283+ padding-top: 0;
284+ padding-bottom: 0;
285+ text-align: right;
286+}
287+
288+.yui3-lazr-wizard-form .yui3-lazr-wizard-errors {
289+ padding-top: 0;
290+ padding-bottom: 0;
291+ color: red;
292+}
293+
294+.yui3-lazr-wizard-form table {
295+ /* This gets rid of the 12px margin-bottom that yui specifies
296+ * in its base.css.
297+ */
298+ margin-bottom: 0;
299+}
300+
301+.yui3-lazr-wizard-form {
302+ /* The display:table is necessary to make the div's width
303+ * shrink to fit its contents as opposed to expanding to
304+ * fill its container. It is also easier to center without
305+ * affecting the form_header than display:inline-block.
306+ */
307+ display: table;
308+ margin-bottom: 2em;
309+ /* Center the table in the widget. */
310+ margin-left: auto;
311+ margin-right: auto;
312+}
313+
314+.yui3-lazr-wizard-form-header {
315+ margin-top: 1em;
316+}
317+.yui3-lazr-wizard a.close-button {
318+ visibility: hidden;
319+}
320+
321
322=== added directory 'src-js/lazrjs/wizard/tests'
323=== added file 'src-js/lazrjs/wizard/tests/wizard.html'
324--- src-js/lazrjs/wizard/tests/wizard.html 1970-01-01 00:00:00 +0000
325+++ src-js/lazrjs/wizard/tests/wizard.html 2010-09-23 14:58:56 +0000
326@@ -0,0 +1,34 @@
327+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
328+<html>
329+ <head>
330+ <title>Form Overlay</title>
331+
332+ <!-- YUI 3.0 Setup. -->
333+ <script type="text/javascript" src="../../yui/yui/yui.js"></script>
334+ <link rel="stylesheet" href="../../yui/cssreset/reset.css"/>
335+ <link rel="stylesheet" href="../../yui/cssfonts/fonts.css"/>
336+ <link rel="stylesheet" href="../../yui/cssbase/base.css"/>
337+ <link rel="stylesheet" href="../../testing/assets/testlogger.css"/>
338+
339+ <!-- Dependent modules from lazr. -->
340+ <script type="text/javascript" src="../../lazr/lazr.js"></script>
341+ <script type="text/javascript" src="../../overlay/overlay.js"></script>
342+ <script type="text/javascript" src="../../testing/testing.js"></script>
343+ <script type="text/javascript" src="../../formoverlay/formoverlay.js"></script>
344+
345+ <!-- The module under test. -->
346+ <script type="text/javascript" src="../../wizard/wizard.js"></script>
347+
348+ <!-- Testing helpers. -->
349+ <script type="text/javascript" src="../../testing/mockio.js"></script>
350+
351+ <!-- The test suite. -->
352+ <script type="text/javascript" src="wizard.js"></script>
353+
354+</head>
355+<body class="yui3-skin-sam">
356+ <div id="form_overlay_example">
357+ </div>
358+ <div id="log"></div>
359+</body>
360+</html>
361
362=== added file 'src-js/lazrjs/wizard/tests/wizard.js'
363--- src-js/lazrjs/wizard/tests/wizard.js 1970-01-01 00:00:00 +0000
364+++ src-js/lazrjs/wizard/tests/wizard.js 2010-09-23 14:58:56 +0000
365@@ -0,0 +1,752 @@
366+/* Copyright (c) 2010, Canonical Ltd. All rights reserved. */
367+
368+YUI({
369+ base: '../../yui/',
370+ filter: 'raw',
371+ combine: false
372+ }).use('lazr.wizard', 'lazr.testing.runner',
373+ 'lazr.testing.mockio', 'node', 'event', 'event-simulate',
374+ 'dump', 'console', function(Y) {
375+
376+var Assert = Y.Assert; // For easy access to isTrue(), etc.
377+
378+
379+/*
380+ * A wrapper for the Y.Event.simulate() function. The wrapper accepts
381+ * CSS selectors and Node instances instead of raw nodes.
382+ */
383+function simulate(widget, selector, evtype, options) {
384+ var rawnode = Y.Node.getDOMNode(widget.one(selector));
385+ Y.Event.simulate(rawnode, evtype, options);
386+}
387+
388+/* Helper function to cleanup and destroy a form wizard instance */
389+function cleanup_wizard(wizard) {
390+ if (wizard.get('rendered')) {
391+ var bb = wizard.get('boundingBox');
392+ if (Y.Node.getDOMNode(bb)){
393+ bb.get('parentNode').removeChild(bb);
394+ }
395+ }
396+
397+ // Kill the widget itself.
398+ wizard.destroy();
399+}
400+
401+/* Helper function that creates a new form wizard instance. */
402+function make_wizard(cfg) {
403+ var wizard = new Y.lazr.wizard.Wizard(cfg);
404+ wizard.render();
405+ return wizard;
406+}
407+
408+/* Helper array of steps for creating a wizard */
409+var default_steps = [
410+ new Y.lazr.wizard.Step({
411+ form_content: "Hello."
412+ })
413+];
414+
415+var suite = new Y.Test.Suite("Wizard Tests");
416+
417+suite.add(new Y.Test.Case({
418+
419+ name: 'wizard_basics',
420+
421+ setUp: function() {
422+ wizard_steps = [
423+ new Y.lazr.wizard.Step({
424+ title: "Step One",
425+ form_content: [
426+ 'Here is an input: ',
427+ '<input type="text" name="field1" id="field1" />',
428+ 'Here is another input: ',
429+ '<input type="text" name="field2" id="field2" />'
430+ ].join(""),
431+ funcLoad: function() {},
432+ funcCleanUp: function() {}
433+ })
434+ ]
435+ this.wizard = make_wizard({
436+ headerContent: 'Form for testing',
437+ steps: wizard_steps,
438+ xy: [0, 0]
439+ });
440+
441+ // Ensure window size is constant for tests
442+ this.width = window.top.outerWidth;
443+ this.height = window.top.outerHeight;
444+ window.top.resizeTo(800, 600);
445+ },
446+
447+ tearDown: function() {
448+ window.top.resizeTo(this.width, this.height);
449+ cleanup_wizard(this.wizard);
450+ },
451+
452+ _should: {
453+ // The following tests will only be deemed to have passed if
454+ // they raise an error.
455+ error: {
456+ test_wizard_needs_steps: true,
457+ test_cant_go_back_when_at_first_step: true,
458+ test_cant_go_forward_when_at_last_step: true
459+ }
460+ },
461+
462+ test_wizard_can_be_instantiated: function() {
463+ var wizard = new Y.lazr.wizard.Wizard({
464+ steps: default_steps
465+ });
466+ Assert.isInstanceOf(
467+ Y.lazr.wizard.Wizard,
468+ wizard,
469+ "Wizard could not be instantiated.");
470+ cleanup_wizard(wizard);
471+ },
472+
473+ test_wizard_needs_steps: function() {
474+ // The wizard will raise an error if no steps are provided. Note
475+ // that this test will raise the error; this is expected
476+ // behaviour and is declared in the _should object, above.
477+ var wizard = new Y.lazr.wizard.Wizard();
478+ Assert.isInstanceOf(
479+ Y.lazr.wizard.Wizard,
480+ wizard,
481+ "Wizard could not be instantiated.");
482+ cleanup_wizard(wizard);
483+ },
484+
485+ test_body_content_is_single_node: function() {
486+ Assert.areEqual(
487+ 1,
488+ this.wizard.getStdModNode("body").size(),
489+ "The body content should be a single node, not a node list.");
490+ },
491+
492+ test_form_content_in_body_content: function() {
493+ // The form_content should be included in the body of the
494+ // wizard during initialization.
495+ var body_content = this.wizard.getStdModNode("body");
496+
497+ // Ensure the body_content contains our form node.
498+ Assert.isTrue(
499+ body_content.contains(this.wizard.form_node),
500+ "The form node is part of the body content.");
501+
502+ // And then make sure that the user-supplied form_content is
503+ // included in the form node:
504+ Assert.areNotEqual(
505+ body_content.get("innerHTML").search(
506+ this.wizard.get("form_content")));
507+ },
508+
509+ test_first_input_has_focus: function() {
510+ // The first input element in the form content should have
511+ // focus.
512+ var first_input = this.wizard.form_node.one('#field1');
513+
514+ // Hide the wizard and ensure that the first input does not
515+ // have the focus.
516+ this.wizard.hide();
517+ first_input.blur();
518+
519+ var test = this;
520+ var focused = false;
521+
522+ var onFocus = function(e) {
523+ focused = true;
524+ };
525+
526+ first_input.on('focus', onFocus);
527+
528+ this.wizard.show();
529+ Assert.isTrue(focused,
530+ "The form wizard's first input field receives focus " +
531+ "when the wizard is shown.");
532+ },
533+
534+ test_form_submit_in_body_content: function() {
535+ // The body content should include the submit button.
536+ var body_content = this.wizard.getStdModNode("body");
537+ Assert.isTrue(
538+ body_content.contains(
539+ this.wizard.get("form_submit_button")),
540+ "The body content includes the form_submit_button.");
541+ },
542+
543+ test_users_submit_button_in_body_content: function() {
544+ // If a user supplies a custom submit button, it should be included
545+ // in the form instead of the default one.
546+ var submit_button = Y.Node.create(
547+ '<input type="submit" value="Hit me!" />');
548+ var wizard = new Y.lazr.wizard.Wizard({
549+ steps: [
550+ new Y.lazr.wizard.Step({
551+ form_content: 'Here is an input: ' +
552+ '<input type="text" name="field1" id="field1" />'
553+ })
554+ ],
555+ form_submit_button: submit_button
556+ });
557+ wizard.render();
558+
559+ // Ensure the button has been used in the form:
560+ Assert.isTrue(
561+ wizard.form_node.contains(submit_button),
562+ "The form should include the users submit button.");
563+
564+ cleanup_wizard(wizard);
565+ },
566+
567+ test_form_cancel_in_body_content: function() {
568+ // The body content should include the cancel button.
569+ var body_content = this.wizard.getStdModNode("body");
570+ Assert.isTrue(
571+ body_content.contains(
572+ this.wizard.get("form_cancel_button")),
573+ "The body content includes the form_cancel_button.");
574+ },
575+
576+ test_users_cancel_button_in_body_content: function() {
577+ // If a user supplies a custom cancel button, it should be included
578+ // in the form instead of the default one.
579+ var cancel_button = Y.Node.create(
580+ '<button type="" value="cancel" />');
581+ var wizard = new Y.lazr.wizard.Wizard({
582+ steps: [
583+ new Y.lazr.wizard.Step({
584+ form_content: 'Here is an input: ' +
585+ '<input type="text" name="field1" id="field1" />'
586+ })
587+ ],
588+ form_cancel_button: cancel_button
589+ });
590+ wizard.render();
591+
592+ // Ensure the button has been used in the form:
593+ Assert.isTrue(
594+ wizard.form_node.contains(cancel_button),
595+ "The form should include the users cancel button.");
596+
597+ cleanup_wizard(wizard);
598+ },
599+
600+ test_hide_when_cancel_clicked: function() {
601+ // The form wizard should hide when the cancel button is clicked.
602+
603+ var bounding_box = this.wizard.get('boundingBox');
604+ Assert.isFalse(
605+ bounding_box.hasClass('yui3-lazr-wizard-hidden'),
606+ "The form is not hidden initially.");
607+
608+ simulate(
609+ this.wizard.form_node,
610+ "button[type=button]",
611+ 'click');
612+
613+ Assert.isTrue(
614+ bounding_box.hasClass('yui3-lazr-wizard-hidden'),
615+ "The form is hidden after cancel is clicked.");
616+ },
617+
618+ test_error_displayed_on_showError: function() {
619+ // The error message should be in the body content.
620+
621+ this.wizard.showError("My special error");
622+
623+ var body_content = this.wizard.getStdModNode("body");
624+ Assert.areNotEqual(
625+ body_content.get("innerHTML").search("My special error"),
626+ -1,
627+ "The error text was included in the body content.");
628+ },
629+
630+ test_tags_stripped_from_errors: function() {
631+ // Any tags in error messages will be stripped out.
632+ // That is, as long as they begin and end with ASCII '<' and '>'
633+ // chars. Not sure what to do about unicode, for example.
634+ this.wizard.showError("<h2>My special error</h2>");
635+
636+ var body_content = this.wizard.getStdModNode("body");
637+ Assert.areEqual(
638+ -1,
639+ body_content.get("innerHTML").search("<h2>"),
640+ "The tags were stripped from the error message.");
641+ },
642+
643+ test_error_cleared_on_clearError: function() {
644+ // The error message should be cleared from the body content.
645+ this.wizard.showError("My special error");
646+ this.wizard.clearError();
647+ var body_content = this.wizard.getStdModNode("body");
648+ Assert.areEqual(
649+ body_content.get("innerHTML").search("My special error"),
650+ -1,
651+ "The error text is cleared from the body content.");
652+ },
653+
654+ test_wizard_centered_when_shown: function() {
655+ // If the 'centered' attribute is set, the wizard should be
656+ // centered in the viewport when shown.
657+ Assert.areEqual('[0, 0]', Y.dump(this.wizard.get('xy')),
658+ "Position is initially 0,0.");
659+ this.wizard.show();
660+ Assert.areEqual('[0, 0]', Y.dump(this.wizard.get('xy')),
661+ "Position is not updated if widget not centered.");
662+ this.wizard.hide();
663+
664+ this.wizard.set('centered', true);
665+ this.wizard.show();
666+ var centered_pos_before_resize = this.wizard.get('xy');
667+ Assert.areNotEqual('[0, 0]', Y.dump(centered_pos_before_resize),
668+ "Position is updated when centered attr set.");
669+ this.wizard.hide();
670+
671+ var centered = false;
672+ function watch_centering() {
673+ centered = true;
674+ }
675+ Y.Do.after(watch_centering, this.wizard, 'centered');
676+
677+ // The position is updated after resizing the window and re-showing:
678+ window.top.resizeTo(850, 550);
679+ this.wizard.show();
680+
681+ Assert.isTrue(centered,
682+ "The wizard centers itself when it is shown with the centered " +
683+ "attribute set.");
684+ },
685+
686+ test_form_content_as_node: function() {
687+ // The form content can also be passed as a node, rather than
688+ // a string of HTML.
689+ var form_content_div = Y.Node.create("<div />");
690+ var input_node = Y.Node.create(
691+ '<input type="text" name="field1" value="val1" />');
692+ form_content_div.appendChild(input_node);
693+
694+ var wizard = make_wizard({
695+ headerContent: 'Form for testing',
696+ steps: [
697+ new Y.lazr.wizard.Step({
698+ form_content: form_content_div
699+ })
700+ ]
701+ });
702+
703+ Assert.isTrue(
704+ wizard.form_node.contains(input_node),
705+ "Failed to pass the form content as a Y.Node instance.");
706+ cleanup_wizard(wizard);
707+ },
708+
709+ test_first_step_load_called_on_wizard_load: function() {
710+ // The load() function of the first step will be called when the
711+ // wizard is instantiated.
712+ var load_called = false;
713+ var wizard_steps = [
714+ new Y.lazr.wizard.Step({
715+ form_content: "Nothing to see here.",
716+ funcLoad: function() {
717+ load_called = true;
718+ }
719+ })
720+ ]
721+ var wizard = make_wizard({
722+ steps: wizard_steps
723+ });
724+ wizard.render();
725+
726+ Assert.isTrue(
727+ load_called,
728+ "The funcLoad callback of the first step was not called.");
729+ },
730+
731+ test_step_load_called_on_wizard_next: function() {
732+ // When wizard.next() is called, the next Step's funcLoad
733+ // callback will be called.
734+ var load_called = false;
735+ var wizard_steps = [
736+ new Y.lazr.wizard.Step({
737+ form_content: "Nothing to see here."
738+ }),
739+ new Y.lazr.wizard.Step({
740+ form_content: "Still nothing to see here.",
741+ funcLoad: function() {
742+ load_called = true;
743+ }
744+ })
745+ ]
746+ var wizard = make_wizard({
747+ steps: wizard_steps
748+ });
749+ wizard.render();
750+ wizard.next();
751+
752+ Assert.isTrue(
753+ load_called,
754+ "The funcLoad callback of the next step was not called.");
755+ },
756+
757+ test_step_load_called_on_wizard_prev: function() {
758+ // When wizard.previous() is called, the previous Step's funcLoad
759+ // callback will be called.
760+ var load_called = false;
761+ var wizard_steps = [
762+ new Y.lazr.wizard.Step({
763+ form_content: "Nothing to see here.",
764+ funcLoad: function() {
765+ load_called = true;
766+ }
767+ }),
768+ new Y.lazr.wizard.Step({
769+ form_content: "Still nothing to see here."
770+ })
771+ ]
772+ var wizard = make_wizard({
773+ steps: wizard_steps
774+ });
775+ wizard.render();
776+ wizard.next();
777+ load_called = false;
778+ wizard.previous();
779+
780+ Assert.isTrue(
781+ load_called,
782+ "The funcLoad callback of the first step was not called.");
783+ },
784+
785+ test_cant_go_back_when_at_first_step: function() {
786+ // When a wizard is on its first step, calling wizard.previous()
787+ // will raise an error.
788+ var wizard_steps = [
789+ new Y.lazr.wizard.Step({
790+ form_content: "Nothing to see here."
791+ })
792+ ]
793+ var wizard = make_wizard({
794+ steps: wizard_steps
795+ });
796+ wizard.render();
797+ wizard.previous();
798+ },
799+
800+ test_cant_go_forward_when_at_last_step: function() {
801+ // When a wizard is on its last step, calling wizard.next()
802+ // will raise an error.
803+ var wizard_steps = [
804+ new Y.lazr.wizard.Step({
805+ form_content: "Nothing to see here."
806+ })
807+ ]
808+ var wizard = make_wizard({
809+ steps: wizard_steps
810+ });
811+ wizard.render();
812+ wizard.next();
813+ },
814+
815+ test_hasNextStep_returns_true_with_next_step: function() {
816+ // Wizard.hasNextStep() will return True if there are more
817+ // steps after the current one.
818+ var wizard_steps = [
819+ new Y.lazr.wizard.Step({
820+ form_content: "Nothing to see here."
821+ }),
822+ new Y.lazr.wizard.Step({
823+ form_content: "Still nothing to see here."
824+ })
825+ ]
826+ var wizard = make_wizard({
827+ steps: wizard_steps
828+ });
829+ wizard.render();
830+
831+ Assert.isTrue(
832+ wizard.hasNextStep(),
833+ "Wizard.hasNextStep() should return true.");
834+ },
835+
836+ test_hasNextStep_returns_false_with_no_next_step: function() {
837+ // Wizard.hasNextStep() will return false if there are no more
838+ // steps after the current one.
839+ var wizard_steps = [
840+ new Y.lazr.wizard.Step({
841+ form_content: "Nothing to see here."
842+ })
843+ ]
844+ var wizard = make_wizard({
845+ steps: wizard_steps
846+ });
847+ wizard.render();
848+
849+ Assert.isFalse(
850+ wizard.hasNextStep(),
851+ "Wizard.hasNextStep() should return false.");
852+ },
853+
854+ test_hasPreviousStep_returns_true_with_prev_step: function() {
855+ // Wizard.hasPreviousStep() will return True if there are steps
856+ // before the current one.
857+ var wizard_steps = [
858+ new Y.lazr.wizard.Step({
859+ form_content: "Nothing to see here."
860+ }),
861+ new Y.lazr.wizard.Step({
862+ form_content: "Still nothing to see here."
863+ })
864+ ]
865+ var wizard = make_wizard({
866+ steps: wizard_steps
867+ });
868+ wizard.render();
869+ wizard.next();
870+
871+ Assert.isTrue(
872+ wizard.hasPreviousStep(),
873+ "Wizard.hasPreviousStep() should return true.");
874+ },
875+
876+ test_hasPreviousStep_returns_false_with_no_prev_step: function() {
877+ // Wizard.hasPreviousStep() will return false if there are no
878+ // steps before the current one.
879+ var wizard_steps = [
880+ new Y.lazr.wizard.Step({
881+ form_content: "Nothing to see here."
882+ })
883+ ]
884+ var wizard = make_wizard({
885+ steps: wizard_steps
886+ });
887+ wizard.render();
888+
889+ Assert.isFalse(
890+ wizard.hasPreviousStep(),
891+ "Wizard.hasPreviousStep() should return false.");
892+ },
893+
894+ test_stepChange_fired_on_step_change: function() {
895+ // Changing the step will fire a wizard:stepChange event.
896+ var stepChange_fired = false;
897+ var wizard_steps = [
898+ new Y.lazr.wizard.Step({
899+ form_content: "Nothing to see here."
900+ }),
901+ new Y.lazr.wizard.Step({
902+ form_content: "Still nothing to see here."
903+ })
904+ ]
905+ var wizard = make_wizard({
906+ steps: wizard_steps
907+ });
908+ wizard.on("wizard:stepChange", function() {
909+ stepChange_fired = true;
910+ });
911+ wizard.render();
912+ wizard.next();
913+
914+ Assert.isTrue(
915+ stepChange_fired, "wizard:stepChange did not fire.");
916+ },
917+
918+ test_addStep_adds_step: function() {
919+ // Wizard.addStep() adds a step to the Wizard.
920+ var wizard_steps = [
921+ new Y.lazr.wizard.Step({
922+ form_content: "Nothing to see here."
923+ })
924+ ]
925+ var wizard = make_wizard({
926+ steps: wizard_steps
927+ });
928+ wizard.render();
929+ // There's only one step at this point, so hasNextStep() returns
930+ // False.
931+ Assert.isFalse(
932+ wizard.hasNextStep(),
933+ "Wizard.hasNextStep() should return false.");
934+ wizard.addStep(new Y.lazr.wizard.Step({
935+ form_content: "Still nothing. Move along."
936+ }));
937+ Assert.isTrue(
938+ wizard.hasNextStep(),
939+ "Wizard.hasNextStep() should return true.");
940+ },
941+
942+ test_destructor_deletes_steps: function() {
943+ // Wizard.destructor() deletes all the of the Wizard's Steps.
944+ var wizard_steps = [
945+ new Y.lazr.wizard.Step({
946+ form_content: "Nothing to see here."
947+ })
948+ ]
949+ var wizard = make_wizard({
950+ steps: wizard_steps
951+ });
952+ wizard.render();
953+ wizard.destructor();
954+
955+ var steps = wizard.get("steps");
956+ Assert.areEqual(
957+ 0, steps.length, "There should be no steps left.");
958+ }
959+
960+
961+}));
962+
963+suite.add(new Y.Test.Case({
964+
965+ name: 'wizard_data',
966+
967+ test_submit_callback_called_on_submit: function() {
968+ // Set an expectation that the form_submit_callback will be
969+ // called with the correct data:
970+ var callback_called = false;
971+ var submit_callback = function(ignore){
972+ callback_called = true;
973+ };
974+ var wizard = make_wizard({
975+ steps: [
976+ new Y.lazr.wizard.Step({
977+ form_content:
978+ '<input type="text" name="field1" value="val1" />',
979+ })
980+ ],
981+ form_submit_callback: submit_callback
982+ });
983+ simulate(
984+ wizard.form_node,
985+ "input[type=submit]",
986+ 'click');
987+
988+ Assert.isTrue(
989+ callback_called,
990+ "The form_submit_callback should be called.");
991+ cleanup_wizard(wizard);
992+ },
993+
994+ test_submit_with_callback_prevents_propagation: function() {
995+ // The onsubmit event is not propagated when user provides
996+ // a callback.
997+
998+ var wizard = make_wizard({
999+ steps: [
1000+ new Y.lazr.wizard.Step({
1001+ form_content:
1002+ '<input type="text" name="field1" value="val1" />',
1003+ })
1004+ ],
1005+ form_submit_callback: function() {}
1006+ });
1007+
1008+ var event_was_propagated = false;
1009+ var test = this;
1010+ var onSubmit = function(e) {
1011+ event_was_propagated = true;
1012+ e.preventDefault();
1013+ };
1014+ Y.on('submit', onSubmit, wizard.form_node);
1015+
1016+ simulate(wizard.form_node, "input[type=submit]", 'click');
1017+
1018+ Assert.isFalse(
1019+ event_was_propagated,
1020+ "The onsubmit event should not be propagated.");
1021+ cleanup_wizard(wizard);
1022+ },
1023+
1024+ test_submit_without_callback: function() {
1025+ // The form should submit as a normal form if no callback
1026+ // was provided.
1027+ var wizard = make_wizard({
1028+ steps: [
1029+ new Y.lazr.wizard.Step({
1030+ form_content:
1031+ '<input type="text" name="field1" value="val1" />',
1032+ })
1033+ ],
1034+ });
1035+
1036+ var event_was_propagated = false;
1037+ var test = this;
1038+ var onSubmit = function(e) {
1039+ event_was_propagated = true;
1040+ e.preventDefault();
1041+ };
1042+
1043+ Y.on('submit', onSubmit, wizard.form_node);
1044+
1045+ simulate(
1046+ wizard.form_node,
1047+ "input[type=submit]",
1048+ 'click');
1049+ Assert.isTrue(event_was_propagated,
1050+ "The normal form submission event is propagated as " +
1051+ "normal when no callback is provided.");
1052+ cleanup_wizard(wizard);
1053+ },
1054+
1055+ test_getFormData_returns_correct_data_for_simple_inputs: function() {
1056+ // The getFormData method should return the values of simple
1057+ // inputs correctly.
1058+
1059+ var wizard = make_wizard({
1060+ headerContent: 'Form for testing',
1061+ steps: [
1062+ new Y.lazr.wizard.Step({
1063+ form_content: [
1064+ 'Here is an input: ',
1065+ '<input type="text" name="field1" value="val1" />',
1066+ '<input type="text" name="field2" value="val2" />',
1067+ '<input type="text" name="field3" value="val3" />'
1068+ ].join("")
1069+ })
1070+ ],
1071+ });
1072+ Assert.areEqual(
1073+ '{field1 => [val1], field2 => [val2], field3 => [val3]}',
1074+ Y.dump(wizard.getFormData()),
1075+ "The getFormData method returns simple input data correctly.");
1076+ cleanup_wizard(wizard);
1077+ },
1078+
1079+ test_getFormData_returns_inputs_nested_several_levels: function() {
1080+ // The getFormData method should return the values of inputs
1081+ // even when they are several levels deep in the form node
1082+ var wizard = make_wizard({
1083+ headerContent: 'Form for testing',
1084+ steps: [
1085+ new Y.lazr.wizard.Step({
1086+ form_content: [
1087+ 'Here is an input: ',
1088+ '<div>',
1089+ ' <input type="text" name="field1" value="val1" />',
1090+ ' <div>',
1091+ ' <input type="text" name="field2" value="val2" ',
1092+ ' />',
1093+ ' <div>',
1094+ ' <input type="text" name="field3" ',
1095+ ' value="val3" />',
1096+ ' </div>',
1097+ ' </div>',
1098+ '</div>'
1099+ ].join("")
1100+ })
1101+ ]
1102+ });
1103+
1104+ Assert.areEqual(
1105+ '{field1 => [val1], field2 => [val2], field3 => [val3]}',
1106+ Y.dump(wizard.getFormData()),
1107+ "The getFormData method returns simple input data correctly.");
1108+ cleanup_wizard(wizard);
1109+
1110+ },
1111+
1112+}));
1113+
1114+Y.lazr.testing.Runner.add(suite);
1115+Y.lazr.testing.Runner.run();
1116+
1117+});
1118
1119=== added file 'src-js/lazrjs/wizard/wizard.js'
1120--- src-js/lazrjs/wizard/wizard.js 1970-01-01 00:00:00 +0000
1121+++ src-js/lazrjs/wizard/wizard.js 2010-09-23 14:58:56 +0000
1122@@ -0,0 +1,220 @@
1123+/* Copyright (c) 2010, Canonical Ltd. All rights reserved.
1124+ *
1125+ * Display a functioning form in a lazr.formoverlay.
1126+ *
1127+ * @module lazr.wizard
1128+ */
1129+YUI.add('lazr.wizard', function(Y) {
1130+
1131+ var namespace = Y.namespace("lazr.wizard");
1132+
1133+ /**
1134+ * The Step class isn't so much a Widget as it is just an Object with
1135+ * references to perform certain functions. They are used to tell the
1136+ * Wizard what to do next.
1137+ *
1138+ * @class Step
1139+ * @namespace lazr.wizard
1140+ */
1141+ function Step(config) {
1142+ Step.superclass.constructor.apply(this, arguments);
1143+ }
1144+ Step.NAME = "step";
1145+ Step.ATTRS = {
1146+ title: {
1147+ value: ''
1148+ },
1149+ form_content: {
1150+ value: null
1151+ },
1152+ funcLoad: {
1153+ value: function() {}
1154+ },
1155+ funcCleanUp: {
1156+ value: function() {}
1157+ }
1158+ };
1159+
1160+ Y.extend(Step, Y.Widget, {
1161+ load: function() {
1162+ this.get("funcLoad").apply(this, arguments);
1163+ }
1164+ });
1165+ namespace.Step = Step;
1166+
1167+
1168+ /**
1169+ * The Wizard class builds on the lazr.FormOverlay class
1170+ * to display form content and extract form data for the callsite.
1171+ *
1172+ * @class Wizard
1173+ * @namespace lazr.wizard
1174+ */
1175+ function Wizard(config) {
1176+ Wizard.superclass.constructor.apply(this, arguments);
1177+
1178+ if (this.get("steps").length == 0) {
1179+ throw "Cannot create a Wizard with no steps.";
1180+ }
1181+ Y.after(this._renderUIWizard, this, "renderUI");
1182+ Y.after(this._bindUIWizard, this, "bindUI");
1183+ }
1184+
1185+ Wizard.NAME = "lazr-wizard";
1186+ Wizard.ATTRS = {
1187+ current_step_index: {
1188+ value: -1
1189+ },
1190+ previous_step_index: {
1191+ value: -1
1192+ },
1193+ next_step_index: {
1194+ value: 0
1195+ },
1196+ steps: {
1197+ value: []
1198+ }
1199+ }
1200+
1201+ Y.extend(Wizard, Y.lazr.FormOverlay, {
1202+
1203+ initializer: function() {
1204+ /* Do nothing */
1205+ },
1206+
1207+ _renderUIWizard: function() {
1208+ this.next();
1209+ },
1210+
1211+ _bindUIWizard: function() {
1212+ Y.on("click",
1213+ Y.bind(function(e){ this.hide();}, this),
1214+ this.get("form_cancel_button"));
1215+ },
1216+
1217+ /**
1218+ * Add a step to the end of the steps array.
1219+ *
1220+ * @method addStep
1221+ */
1222+ addStep: function(step) {
1223+ this.get("steps").push(step);
1224+ // If the widget is currently on its final step, update the
1225+ // next_step_index to reflect the fact that we've just added
1226+ // a new one.
1227+ if (!this.hasNextStep()) {
1228+ var current_step_index = this.get("current_step_index");
1229+ this.set("next_step_index", current_step_index + 1);
1230+ }
1231+ },
1232+
1233+ /**
1234+ * Transition to the step at a given index.
1235+ *
1236+ * @method _transitionToStep
1237+ * @private
1238+ * @param step_index The index of the step to transition to.
1239+ */
1240+ _transitionToStep: function(step_index) {
1241+ var step = this.get("steps")[step_index];
1242+ this.set("steptitle", step.get("title"));
1243+
1244+ var step_form_content = step.get("form_content");
1245+ if (Y.Lang.isValue(step_form_content)) {
1246+ this.set("form_content", step_form_content);
1247+ this._setFormContent()
1248+ }
1249+
1250+ step.load(this);
1251+ this.fire("wizard:stepChange");
1252+ this.set("current_step", step);
1253+ this._updateStepIndices(step_index);
1254+ },
1255+
1256+ /**
1257+ * Transition to the next step in the steps array.
1258+ *
1259+ * @method next
1260+ */
1261+ next: function() {
1262+ var step_index = this.get("next_step_index");
1263+ if (step_index < 0) {
1264+ throw "Wizard is already on its last step.";
1265+ }
1266+ this._transitionToStep(step_index);
1267+ },
1268+
1269+ /**
1270+ * Transition to the previous step in the steps array.
1271+ *
1272+ * @method next
1273+ */
1274+ previous: function() {
1275+ var step_index = this.get("previous_step_index");
1276+ if (step_index < 0) {
1277+ throw "Wizard is already on its first step.";
1278+ }
1279+ this._transitionToStep(step_index);
1280+ },
1281+
1282+ /**
1283+ * Update the step indices based on the current step index.
1284+ *
1285+ * @method _updateStepIndices
1286+ * @private
1287+ * @param current_step_index The index of the current step.
1288+ */
1289+ _updateStepIndices: function(current_step_index) {
1290+ if (current_step_index > 0) {
1291+ this.set("previous_step_index", current_step_index - 1);
1292+ } else {
1293+ this.set("previous_step_index", -1);
1294+ }
1295+
1296+ if (this.get("steps").length > current_step_index + 1) {
1297+ this.set("next_step_index", current_step_index + 1);
1298+ } else {
1299+ this.set("next_step_index", -1);
1300+ }
1301+
1302+ this.set("current_step_index", current_step_index + 1);
1303+ },
1304+
1305+ /**
1306+ * Return true if there's another step after the current one.
1307+ *
1308+ * @method hasNextStep
1309+ */
1310+ hasNextStep: function() {
1311+ var next_step_index = this.get("next_step_index");
1312+ return (next_step_index > 0)
1313+ },
1314+
1315+ /**
1316+ * Return true if there's a step before the current one.
1317+ *
1318+ * @method hasPreviousStep
1319+ */
1320+ hasPreviousStep: function() {
1321+ var previous_step_index = this.get("previous_step_index");
1322+ return (previous_step_index > -1)
1323+ },
1324+
1325+ /**
1326+ * Destroy all the Steps of the widget.
1327+ *
1328+ * @method destructor
1329+ */
1330+ destructor: function() {
1331+ // Loop over all the steps and delete them.
1332+ while(this.get("steps").length > 0) {
1333+ var step = this.get("steps").pop();
1334+ delete step;
1335+ }
1336+ }
1337+
1338+ });
1339+
1340+ namespace.Wizard = Wizard;
1341+
1342+}, "0.1", {"skinnable": true, "requires": ["lazr.formoverlay"]});

Subscribers

People subscribed via source and target branches