wrapper.fhtml: Add `license' relationship to the AGPL link.
[chopwood] / chpwd.js
1 /* -*-js-*-
2 *
3 * Common JavaScript code for Chopwood
4 *
5 * (c) 2013 Mark Wooding
6 */
7
8 /*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of Chopwood: a password-changing service.
11 *
12 * Chopwood is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Affero General Public License as
14 * published by the Free Software Foundation; either version 3 of the
15 * License, or (at your option) any later version.
16 *
17 * Chopwood is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU Affero General Public License for more details.
21 *
22 * You should have received a copy of the GNU Affero General Public
23 * License along with Chopwood; if not, see
24 * <http://www.gnu.org/licenses/>.
25 */
26
27 /*----- Some utilities ----------------------------------------------------*/
28
29 function elt(id) {
30 /* Return the element with the requested ID. */
31 return document.getElementById(id);
32 }
33
34 function map(func, list) {
35 /* Apply FUNC to each element of LIST, which may actually be any object;
36 * return a new object mapping the same keys to the images of the values in
37 * the function FUNC.
38 */
39 var i;
40 var out = {};
41 for (i in list) out[i] = func(list[i]);
42 return out;
43 }
44
45 /*----- Form validation ---------------------------------------------------*/
46
47 var FORMS = {};
48 /* A map of form names to information about them. Each form is an object
49 * with the following slots:
50 *
51 * * elts: A list of element-ids for the form widgets. These widgets will
52 * be checked periodically to see whether the input data is acceptable.
53 *
54 * * check: A function of no arguments, which returns either `null' if
55 * everything is OK, or an error message as a string.
56 *
57 * Form names aren't just for show. Some element-ids are constructed using
58 * the form name as a base:
59 *
60 * * FORM-whinge: An output element in which to display an error message if
61 * the form's input is unacceptable.
62 *
63 * * FORM-submit: The Submit button, which needs hooking to inhibit
64 * submitting a form with invalid data.
65 */
66
67 function update(obj, slot, value) {
68 /* Update an object slot only if we're gping to change its value. */
69 if (obj[slot] !== value) obj[slot] = value;
70 }
71
72 function check() {
73 /* Check through the various forms to make sure they're filled in OK. If
74 * not, set the `F-whinge' elements, and disable `F-submit'.
75 */
76 var f, form, whinge;
77
78 for (f in FORMS) {
79 form = FORMS[f];
80 we = elt(f + '-whinge');
81 sb = elt(f + '-submit');
82 whinge = form.check();
83 if (sb !== null) update(sb, 'disabled', whinge !== null);
84 if (we !== null) {
85 update(we, 'textContent', whinge || 'OK');
86 update(we, 'className', whinge === null ? 'whinge' : 'whinge wrong');
87 }
88 }
89
90 // We can't catch all possible change events: in particular, it seems
91 // really hard to capture changes as a result of selections from a menu --
92 // e.g., delete or paste. Accept this, and just recheck periodically.
93 check_again(1000);
94 }
95
96 var timer = null;
97 /* The timer for the periodic validation job. */
98
99 function check_again(when) {
100 /* Arrange to check the forms again in WHEN milliseconds. */
101 if (timer !== null) clearTimeout(timer);
102 timer = setTimeout(check, when);
103 }
104
105 var Q = 0;
106 function check_soon(ev) {
107 /* Arrange to check the forms again very soon. */
108 check_again(50);
109 }
110
111 function check_presubmit(ev, f) {
112 /* Check the form F now, popping up an alert and preventing the event EV if
113 * there's something wrong.
114 */
115 var whinge = FORMS[f].check();
116
117 if (whinge !== null) {
118 ev.preventDefault();
119 alert(whinge);
120 }
121 }
122
123 function init() {
124 /* Attach event handlers to the various widgets so that we can keep track
125 * of how well things are being filled in.
126 */
127 var f, form, w, e;
128
129 // Start watching for changes.
130 check_soon();
131
132 for (f in FORMS) (function (f, form) {
133
134 // Ugh. We have to lambda-bind `f' here so that we can close over it
135 // properly.
136 for (w in form.elts) {
137 if ((e = elt(f + '-' + form.elts[w])) === null) continue;
138 e.addEventListener('click', check_soon, false);
139 e.addEventListener('change', check_soon, false);
140 e.addEventListener('keypress', check_soon, false);
141 e.addEventListener('blur', check_soon, false);
142 }
143 if ((e = elt(f + '-submit')) !== null) {
144 e.addEventListener('click', function (ev) {
145 return check_presubmit(ev, f)
146 }, false);
147 }
148 })(f, FORMS[f]);
149 }
150
151 window.addEventListener('load', init, false);
152
153 /*----- That's all, folks -------------------------------------------------*/