chiark / gitweb /
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 -------------------------------------------------*/