draiodoir 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,74 @@
1
+ Draíodóir
2
+ =========
3
+
4
+ A javascript that turns web-pages into Wizards.
5
+
6
+
7
+ Usage
8
+ -----
9
+
10
+ - Include (in a `<script>` tag) on your *HTML5* page
11
+ - Make the form a Wizard, with `new Wizard("my_form");`
12
+
13
+ Example
14
+ -------
15
+
16
+ <!DOCTYPE html>
17
+ <html>
18
+ <head>
19
+ <meta content-type='UTF-8'>
20
+ <script type='text/javascript' src='wizard.js'></src>
21
+ <script>
22
+ addEventListener('load', function(e) {
23
+ new Wizard('my_form');
24
+ }, false);
25
+ </script>
26
+ <title>Draíodóir Test</title>
27
+ </head>
28
+ <body>
29
+ <form id='my_form' action='javascript:alert("Submitting form");'>
30
+ <fieldset>
31
+ <label for='name'>Name</label>
32
+ <input id='name' type='text' required>
33
+ </fieldset>
34
+ <fieldset>
35
+ <label for='age'>Age</label>
36
+ <input id='age' type='number' minimum='18' maximum='80' required>
37
+ </fieldset>
38
+ <input type='submit' value='Go'>
39
+ </form>
40
+ </body>
41
+ </html>
42
+
43
+ Caveats
44
+ -------
45
+
46
+ *Draíodóir* relies on [validation][validation] being fully implemented by
47
+ the browser. This is almost never the case (as of this writing), so you'll
48
+ probably need to include another library which performs validation properly,
49
+ such as [Bailitheoir][bailitheoir].
50
+
51
+ The library is targeted, therefore, at the [more][firefox] [modern][chrome]
52
+ [browsers][safari], so don't expect it to work out of the box in [those][ie]
53
+ that are, err, less [standards][w3c] compliant.
54
+
55
+ Author(s)
56
+ ---------
57
+
58
+ - JJ Buckley <jj@bjjb.org>
59
+
60
+ Copyright
61
+ ---------
62
+
63
+ This software is released under the [GPL][gpl], so feel free to steal and
64
+ destroy - just comply with the terms of that license. I accept no
65
+ responsibilty for anything.
66
+
67
+ [validation]: http://www.w3.org/TR/html5/association-of-controls-and-forms.html#constraints
68
+ [bailitheoir]: http://jjbuckley.github.com/bailitheoir
69
+ [firefox]: http://www.mozilla.com/firefox
70
+ [chrome]: http://www.google.com/chrome
71
+ [safari]: http://www.apple.com/safari
72
+ [ie]: http://www.microsoft.com/ie
73
+ [w3c]: http://www.w3.org
74
+ [gpl]: http://www.gnu.org/licenses/gpl.html
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "draiodoir/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "draiodoir"
7
+ s.version = Draiodoir::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["JJ Buckley"]
10
+ s.email = ["jj@bjjb.org"]
11
+ s.homepage = "http://jjbuckley.github.com/draiodoir"
12
+ s.summary = %q{A JavaScript multi-step form helper}
13
+ s.description = %q{Draíodóir lets you take a regular HTML5 page, and turn it
14
+ into a multi-step wizard.}
15
+
16
+ s.rubyforge_project = "draiodoir"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+ end
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Example account signup form.
3
+ */
4
+ body {
5
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
6
+ min-width: 620px;
7
+ }
8
+ form {
9
+ display: block;
10
+ width: 500px;
11
+ padding: 50px;
12
+ margin: auto;
13
+
14
+ background-color: #ccddaa;
15
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(255,255,235,0.9)), to(rgba(20,20,20,0.0)));
16
+ background-image: -moz-linear-gradient(90deg, rgba(255,255,235,0.9), rgba(20,20,0,0.0));
17
+
18
+ border: 5px solid #889966;
19
+ -webkit-border-radius: 10px;
20
+ -moz-border-radius: 10px;
21
+ -o-border-radius: 1em;
22
+ border-radius: 10px;
23
+ -webkit-box-shadow: 0 0 5px #112200;
24
+ -moz-box-shadow: 0 0 0.5em #112200;
25
+ -o-box-shadow: 0 0 5px #112200;
26
+ box-shadow: 0 0 5px #112200;
27
+ }
28
+
29
+ label {
30
+ float: left;
31
+ height: 2em;
32
+ line-height: 2em;
33
+ width: 150px;
34
+ text-align: right;
35
+ }
36
+
37
+ input,select {
38
+ margin-left: 170px;
39
+ display: block;
40
+ clear: right;
41
+ padding: 5px;
42
+ font-weight: bold;
43
+ }
44
+
45
+ input:invalid,select:invalid {
46
+ color: red;
47
+ -webkit-box-shadow: inset 1px 1px 7px red;
48
+ -moz-box-shadow: inset 0 0 7px red;
49
+ box-shadow: inset 0 0 7px red;
50
+ }
51
+
52
+ fieldset {
53
+ border: none;
54
+ margin: 0;
55
+ padding: 0;
56
+ position: relative;
57
+ display: block;
58
+ }
59
+ fieldset[hidden] {
60
+ display: none;
61
+ }
62
+ legend {
63
+ display: block;
64
+ padding: 5px;
65
+ border: 5px solid #889966;
66
+ border: 5px solid #889966;
67
+ -webkit-border-radius: 10px;
68
+ -moz-border-radius: 10px;
69
+ -o-border-radius: 1em;
70
+ border-radius: 10px;
71
+ position: absolute;
72
+ font-size: 30px;
73
+ font-weight: bold;
74
+ color: #334400;
75
+ text-shadow: 0px 0px 4px #999999;
76
+ top: -77px;
77
+ background-color: #ccddaa;
78
+ }
@@ -0,0 +1,76 @@
1
+ <html>
2
+ <head>
3
+ <title>New Account</title>
4
+ <script type="text/javascript" src="validator.js"></script>
5
+ <script type="text/javascript" src="../draiodoir.js"></script>
6
+ <script type="text/javascript">
7
+ addEventListener('load', function(event) {
8
+ var form = document.getElementById('new_account');
9
+ new Validator(form);
10
+ new Wizard(form, {
11
+ labels: {
12
+ next: {
13
+ personal: "That's me...",
14
+ tastes: "That all I'm telling you."
15
+ }
16
+ }
17
+ });
18
+ }, false);
19
+ </script>
20
+ <link rel="stylesheet" type="text/css" href="account_wizard.css">
21
+ </head>
22
+ <body>
23
+ <h1>New Account</h1>
24
+ <form id="new_account" name="account" method="POST"
25
+ action="javascript:alert('Submitting form!');"
26
+ enctype="application/x-www-form-urlencoded">
27
+
28
+ <!-- The fields here are required, so the submit button does nothing
29
+ (except retrigger validation) until validation passes. -->
30
+ <fieldset name="personal">
31
+ <legend>Personal Details</legend>
32
+
33
+ <label for="account_name">Name</label>
34
+ <input type="text" id="account_name" name="account[name]"
35
+ placeholder="Your full name" required="required" />
36
+ <br />
37
+ <label for="account_dob">Date of Birth</label>
38
+ <input type="date" id="account_dob" name="account[dob]"
39
+ placeholder="YYYY-mm-dd" required="required"
40
+ min="1900-01-01" max="1990-12-12" />
41
+ </fieldset>
42
+
43
+ <!-- Everything in this fieldset is optional. It will be shown, but the
44
+ submit button should be enabled already. -->
45
+ <fieldset name="tastes">
46
+ <legend>Some additional info</legend>
47
+ <label for="account_favourite_film">Your favourite film</label>
48
+ <select id="account_favourite_film" name="account[favourite_film]">
49
+ <option value="">Pick one...</option>
50
+ <option value="1">Fight Club</option>
51
+ <option value="2">True Romance</option>
52
+ </select>
53
+
54
+ <label for="account_favourite_music">Your favourite music</label>
55
+ <select id="account_favourite_music" name="account[favourite_music]">
56
+ <option value="">Select from...</option>
57
+ <option value="1">Skip James</option>
58
+ <option value="2">Pond</option>
59
+ <option value="3">Antonio Vivaldi</option>
60
+ </select>
61
+ </fieldset>
62
+
63
+ <!-- This is the last step, so it should trigger the submit! -->
64
+ <fieldset name='plan'>
65
+ <legend>Select your plan</legend>
66
+ <select id="account_plan" name="account[plan]" required='required'>
67
+ <option value="">Please Choose...</option>
68
+ <option value="1">Bronze</option>
69
+ <option value="2">Silver</option>
70
+ <option value="3">Gold</option>
71
+ </select>
72
+ </fieldset>
73
+ <input type='submit' value="Continue" />
74
+ </form>
75
+ </body>
76
+ </html>
@@ -0,0 +1,92 @@
1
+ /**
2
+ * A basic implementation of the W3's HTML5 constraints.
3
+ * (http://www.w3.org/TR/html5/association-of-controls-and-forms.html#constraints)
4
+ *
5
+ * Usage
6
+ * -----
7
+ *
8
+ * new Validator(form);
9
+ * form.addEventListener('invalid', function() {
10
+ * alert("Detected an invalid field!");
11
+ * }, false);
12
+ *
13
+ * Copyright
14
+ * ---------
15
+ *
16
+ * Copyright (c) 2010 JJ Buckley (jj@bjjb.org)
17
+ *
18
+ * This program is free software: you can redistribute it and/or modify
19
+ * it under the terms of the GNU General Public License as published by
20
+ * the Free Software Foundation, either version 3 of the License, or
21
+ * (at your option) any later version.
22
+ *
23
+ * This program is distributed in the hope that it will be useful,
24
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
+ * GNU General Public License for more details.
27
+ *
28
+ * You should have received a copy of the GNU General Public License
29
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
30
+ */
31
+ function Validator(form) {
32
+ var fields = [];
33
+ function Validator(field) {
34
+ function Validity() {
35
+ this.valueMissing = false;
36
+ this.typeMismatch = false;
37
+ this.patternMismatch = false;
38
+ this.tooLong = false;
39
+ this.rangeUnderflow = false;
40
+ this.rangeOverflow = false;
41
+ this.stepMismatch = false;
42
+ this.customError = false;
43
+ this.valid = true;
44
+ };
45
+ field.willValidate = !!(~['INPUT', 'SELECT', 'TEXTAREA'].indexOf(field.tagName));
46
+ field.setCustomValidity = function(message) {
47
+ if (message == "") {
48
+ field.validity.customError = false;
49
+ }
50
+ else {
51
+ field.validity.customError = true;
52
+ field.validationMessage = message;
53
+ }
54
+ };
55
+ field.checkValidity = function() {
56
+ if (this.willValidate) {
57
+ this.validity.valueMissing = (this.hasAttribute('required') && this.value.trim() == "");
58
+ this.validity.typeMismatch = ((this.type == 'email' && this.value && !this.value.match(/.+@.+\..+/)) ||
59
+ (this.type == 'number' && this.value && !this.value.match(/^\d+/)) ||
60
+ (this.type == 'date' && false && this.value && !this.value.match(/\d{4}-\d{2}-\d{2}/)) ||
61
+ (this.type == 'url' && this.value && !this.value.match(/.+\..+/)));
62
+ this.validity.tooLong = (this.hasAttribute('maxlength') && this.value > this.getAttribute('maxlength'));
63
+ this.validity.rangeUnderflow = (this.hasAttribute('min') && this.value < this.getAttribute('min'));
64
+ this.validity.rangeOverflow = (this.type == 'number' && this.hasAttribute('max') && this.value < this.getAttribute('max'));
65
+ this.validity.stepMismatch = false; // TODO
66
+ this.valid = !(this.validity.valueMissing ||
67
+ this.validity.typeMismatch ||
68
+ this.validity.tooLong ||
69
+ this.validity.rangeUnderflow ||
70
+ this.validity.rangeOverflow ||
71
+ this.validity.stepMismatch ||
72
+ this.validity.customError);
73
+ if (!this.valid) {
74
+ var event = document.createEvent('Events');
75
+ event.initEvent('invalid', true, true);
76
+ field.dispatchEvent(event);
77
+ return false;
78
+ }
79
+ }
80
+ return true;
81
+ };
82
+ field.validity = new Validity();
83
+ console.debug("Added a validator (%o) on %o", this, field);
84
+ }
85
+ ['input', 'select', 'textarea'].forEach(function(tag) {
86
+ var elements = form.getElementsByTagName(tag);
87
+ for (var i = 0; i < elements.length; i++) {
88
+ new Validator(elements[i]);
89
+ }
90
+ });
91
+ }
92
+
@@ -0,0 +1,2 @@
1
+ module Draiodoir
2
+ end
@@ -0,0 +1,3 @@
1
+ module Draiodoir
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Draíodóir
3
+ * =========
4
+ *
5
+ * Turns a HTML5 <form> into a wizard.
6
+ *
7
+ * The form *must* be marked up in a particular way, but it's pretty standard
8
+ * HTML. Basically, each fieldset is a step in the form, and (by default) the
9
+ * submit button is used as the control for moving forward. If the elements in
10
+ * a fieldset don't validate (either on a modern browser, or using a compatible
11
+ * JavaScript validation library, such as
12
+ * bailitheoir(http://jjbuckley.github.com/bailitheoir), then the Wizard won't
13
+ * allow you to progress.
14
+ *
15
+ * To hide all steps except the current, *Draíodóir* sets the "hidden"
16
+ * attribute on the fieldset. Since most browsers can't handle this properly
17
+ * yet, you need to either add a CSS rule such as
18
+ *
19
+ * fieldset[hidden] { display: none; }
20
+ *
21
+ * or listen for the `wizard:step:show` and `wizard:step:hide` events, and
22
+ * take appropriate action, such as setting a style attribute, on the event's
23
+ * target.
24
+ *
25
+ * Usage
26
+ * -----
27
+ *
28
+ * form = document.getElementById('my_form');
29
+ * new Wizard(form);
30
+ *
31
+ * A new Wizard immediately hides all steps except the first one.
32
+ *
33
+ * You can pass in an options object as a second argumens (all optional):
34
+ *
35
+ * Options
36
+ * -------
37
+ *
38
+ * next:: Identifies the control used to move forwards in the form. The
39
+ * default is the first "submit".
40
+ * previous:: Identifies the control to be used to move backwards in the form.
41
+ * If a suitable element can't be found, it is created (as a
42
+ * <button>), and given the value "Back" (though you can override
43
+ * this with a label for any or all steps). An auto-generated
44
+ * previous control is inserted immediately before the "next"
45
+ * control.
46
+ * labels:: Used to display text (or HTML) in certain elements at various
47
+ * steps. Valid values are "next" and "previous", which indicate the
48
+ * next and previous controls. Each value can either be a string
49
+ * (which is applied to the control for every step), or an object
50
+ * containing keys for the step names. For example:
51
+ *
52
+ * new Wizard(form, {
53
+ * previous: 'back_button',
54
+ * labels: {
55
+ * next: {
56
+ * step1: "To Step 2", step2: "Step 3, please", step3: "Go!",
57
+ * },
58
+ * previous: "Back"
59
+ * }
60
+ * });
61
+ *
62
+ * The wizard has 3 steps, fieldsets with names "step1", "step2" and
63
+ * step3. On step1, the next button says "To Step 2", and on step2,
64
+ * it says "Step 3, please". On the last step, it says "Go!". If this
65
+ * were left blank, it would revert to the original value of the
66
+ * submit field.
67
+ * On all steps (besides the first), the 'previous' control says
68
+ * "Back". It's hidden and disabled on the first step.
69
+ *
70
+ * Hook into the wizard by listening for the events it generates, or by using
71
+ * its "next()", "previous()", "first()" and "last()" methods.
72
+ *
73
+ * Caveats
74
+ * -------
75
+ *
76
+ * *Draíodóir* has been tested on Google Chrome 6.0.472.63, Mozilla Firefox
77
+ * 3.6.10, and Sarari on iPhone OS v4.1. I hardly expect it to work in any
78
+ * current version of Microsoft Explorer, but that will hopefully change.
79
+ * For more information, see the *Draíodóir* homepage at
80
+ * http://jjbuckley.github.com/draiodoir. And feel free to fork the project,
81
+ * and improve it!
82
+ *
83
+ * Events
84
+ * ------
85
+ *
86
+ * These Events are fired by a Wizard:
87
+ *
88
+ * wizard:load:: the wizard is set up
89
+ * wizard:next:: the next step is being shown
90
+ * wizard:previous:: the previous step is being shown
91
+ * wizard:first:: the first step is being shown
92
+ * wizard:last:: the last step is being shown
93
+ * wizard:invalid:: validation failed on the current step
94
+ * wizard:valid:: validation passed on all steps
95
+ * wizard:submit:: the form is being submitted
96
+ * wizard:step:show:: a step is being shown
97
+ * wizard:step:hide:: a step is being hidden
98
+ *
99
+ * Copyright
100
+ * ---------
101
+ *
102
+ * Copyright (c) 2010 JJ Buckley (jj@bjjb.org)
103
+ *
104
+ * This program is free software: you can redistribute it and/or modify
105
+ * it under the terms of the GNU General Public License as published by
106
+ * the Free Software Foundation, either version 3 of the License, or
107
+ * (at your option) any later version.
108
+ *
109
+ * This program is distributed in the hope that it will be useful,
110
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
111
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
112
+ * GNU General Public License for more details.
113
+ *
114
+ * You should have received a copy of the GNU General Public License
115
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
116
+ */
117
+ function Wizard(form, options) {
118
+ if (typeof(form) == "string") {
119
+ form = document.getElementById(form);
120
+ }
121
+
122
+ var self = this;
123
+ form.addEventListener('submit', function(event) {
124
+ if (!self.submit()) {
125
+ event.preventDefault();
126
+ return false;
127
+ }
128
+ }, false);
129
+
130
+ form.addEventListener('wizard:step:hide', function(event) {
131
+ // A step has been hidden.
132
+ }, false);
133
+
134
+ form.addEventListener('wizard:step:show', function(event) {
135
+ for (var i = 0; i < steps.length; i++) {
136
+ if (i !== index) {
137
+ steps[i].hide();
138
+ }
139
+ }
140
+ }, false);
141
+
142
+ function getSubmit() {
143
+ elements = form.getElementsByTagName('input');
144
+ for (var i = 0; i < elements.length; i++) {
145
+ if (elements[i].type == 'submit') {
146
+ return elements[i];
147
+ }
148
+ }
149
+ }
150
+
151
+ function Step(fieldset, options) {
152
+ this.name = fieldset.getAttribute('name');
153
+ this.toString = function() {
154
+ return "Step:" + this.name;
155
+ };
156
+ var fields = [];
157
+ ['input', 'select', 'textarea'].forEach(function(tag) {
158
+ elements = fieldset.getElementsByTagName(tag);
159
+ for (var i = 0; i < elements.length; i++) {
160
+ elements[i].addEventListener('invalid', function(event) {
161
+ console.debug("%o is invalid! (%o)", this, this.validity);
162
+ fieldset.valid = false;
163
+ }, false);
164
+ fields.push(elements[i]);
165
+ }
166
+ });
167
+ this.hide = function() {
168
+ fieldset.setAttribute('hidden', 'hidden');
169
+ var event = document.createEvent('Events');
170
+ event.initEvent('wizard:step:hide', true, true);
171
+ event.step = this;
172
+ return fieldset.dispatchEvent(event);
173
+ };
174
+ this.show = function() {
175
+ fieldset.removeAttribute('hidden');
176
+ var event = document.createEvent('Events');
177
+ event.initEvent('wizard:step:show', true, true);
178
+ event.step = this;
179
+ return fieldset.dispatchEvent(event);
180
+ };
181
+ this.validate = function() {
182
+ return fields.every(function(field) {
183
+ return field.checkValidity();
184
+ });
185
+ };
186
+ }
187
+
188
+ var steps = [];
189
+ var index = 0;
190
+ var submit = getSubmit();
191
+ var submit_text = submit.value;
192
+
193
+ var fieldsets = form.getElementsByTagName('FIELDSET');
194
+ for (var i = 0; i < fieldsets.length; i++) {
195
+ var step = new Step(fieldsets[i]);
196
+ steps.push(step);
197
+ }
198
+
199
+ function showStep(index) {
200
+ // TODO - set the label
201
+ if (steps[index]) {
202
+ steps[index].show();
203
+ }
204
+ };
205
+ showStep(0);
206
+
207
+ this.next = function() {
208
+ if (steps[index]) {
209
+ if (steps[index].validate()) {
210
+ var event = document.createEvent('Events');
211
+ event.initEvent('wizard:next', true, true);
212
+ event.step = steps[index];
213
+ event.index = index;
214
+ event.wizard = this;
215
+ form.dispatchEvent(event);
216
+ showStep(++index);
217
+ }
218
+ else {
219
+ var event = document.createEvent('Events');
220
+ event.initEvent('wizard:complete', true, true);
221
+ event.step = steps[index];
222
+ event.index = index;
223
+ event.wizard = this;
224
+ form.dispatchEvent(event);
225
+ }
226
+ }
227
+ else {
228
+ var event = document.createEvent('Events');
229
+ event.initEvent('wizard:invalid', true, true);
230
+ event.step = steps[index];
231
+ event.index = index;
232
+ event.wizard = this;
233
+ form.dispatchEvent(event);
234
+ return false;
235
+ }
236
+ };
237
+
238
+ this.submit = function() {
239
+ this.next();
240
+ if (index == steps.length) {
241
+ var event = document.createEvent('Events');
242
+ event.initEvent('wizard:submit', true, true);
243
+ event.wizard = this;
244
+ event.index = index;
245
+ event.step = steps[index];
246
+ form.dispatchEvent(event);
247
+ return true;
248
+ }
249
+ return false;
250
+ };
251
+ };
252
+