Merge lp:~methanal-developers/methanal/808474-liveform-validation-error-list into lp:methanal

Proposed by Jonathan Jacobs
Status: Merged
Approved by: Tristan Seligmann
Approved revision: 181
Merged at revision: 182
Proposed branch: lp:~methanal-developers/methanal/808474-liveform-validation-error-list
Merge into: lp:methanal
Diff against target: 278 lines (+147/-9)
4 files modified
methanal/js/Methanal/Tests/TestUtil.js (+23/-0)
methanal/js/Methanal/Util.js (+31/-5)
methanal/js/Methanal/View.js (+78/-3)
methanal/view.py (+15/-1)
To merge this branch: bzr merge lp:~methanal-developers/methanal/808474-liveform-validation-error-list
Reviewer Review Type Date Requested Status
Tristan Seligmann Approve
Review via email: mp+67601@code.launchpad.net
To post a comment you must log in.
181. By Jonathan Jacobs

Docstring.

Revision history for this message
Tristan Seligmann (mithrandi) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'methanal/js/Methanal/Tests/TestUtil.js'
2--- methanal/js/Methanal/Tests/TestUtil.js 2011-03-08 08:47:01 +0000
3+++ methanal/js/Methanal/Tests/TestUtil.js 2011-07-12 10:29:32 +0000
4@@ -517,6 +517,29 @@
5 self.assertIdentical(
6 Methanal.Util.unapply(f)(1, 2, 3),
7 6);
8+ },
9+
10+
11+ /**
12+ * Pluralise a word.
13+ */
14+ function test_plural(self) {
15+ var plural = Methanal.Util.plural;
16+ self.assertIdentical(
17+ plural(1, 'hammer'),
18+ 'hammer');
19+ self.assertIdentical(
20+ plural(0, 'hammer'),
21+ 'hammers');
22+ self.assertIdentical(
23+ plural(2, 'hammer'),
24+ 'hammers');
25+ self.assertIdentical(
26+ plural(-1, 'hammer'),
27+ 'hammers');
28+ self.assertIdentical(
29+ plural(2, 'fix', 'fixes'),
30+ 'fixes');
31 });
32
33
34
35=== modified file 'methanal/js/Methanal/Util.js'
36--- methanal/js/Methanal/Util.js 2011-07-04 14:15:27 +0000
37+++ methanal/js/Methanal/Util.js 2011-07-12 10:29:32 +0000
38@@ -1373,7 +1373,8 @@
39 var orientationClass = POINTER_ORIENTATIONS[self.orientation] || '';
40 self.node = D('span', {'class': self.extraClassName}, [
41 D('div', {'class': 'hover-tooltip ' + orientationClass}, [
42- text, D('div', {'class': 'hover-tooltip-arrow'})])]);
43+ text, D('div', {'class': 'hover-tooltip-arrow'})]),
44+ D('div', {'class': 'terminator'})]);
45 if (self._hidden) {
46 self.hide();
47 }
48@@ -1383,19 +1384,44 @@
49
50
51 /**
52- * Tooltip "pointer" (the tail end of the tooltip) orientations::
53+ * Tooltip "pointer" (the tail end of the tooltip) orientations:
54 *
55- * none:
56+ * - none:
57 * Tooltip has no tail.
58 *
59- * left:
60+ * - left:
61 * Tail comes from the left edge.
62 *
63- * bottom:
64+ * - bottom:
65 * Tail comes from the bottom edge.
66+ *
67+ * - top:
68+ * Tail comes from the top edge.
69 */
70 Methanal.Util.Tooltip.POINTER_ORIENTATIONS = {
71 'none': '',
72 'left': 'hover-tooltip-left',
73 'bottom': 'hover-tooltip-bottom',
74 'top': 'hover-tooltip-top'};
75+
76+
77+
78+/**
79+ * Pluralise a word.
80+ *
81+ * @type n: C{Number}
82+ * @param n: Count.
83+ *
84+ * @type word: C{String}
85+ * @param word: Word to pluralise.
86+ *
87+ * @type pluralForm: C{String}
88+ * @param pluralForm: Plural form of L{word}, defaults to C{word + 's'}.
89+ *
90+ * @rtype: C{String}
91+ * @return: Plural form of C{word} it C{n} indicates it should be a plural.
92+ */
93+Methanal.Util.plural = function plural(n, word, pluralForm/*=undefined*/) {
94+ pluralForm = pluralForm || word + 's';
95+ return n == 1 ? word : pluralForm;
96+};
97
98=== modified file 'methanal/js/Methanal/View.js'
99--- methanal/js/Methanal/View.js 2011-07-11 17:14:43 +0000
100+++ methanal/js/Methanal/View.js 2011-07-12 10:29:32 +0000
101@@ -443,14 +443,19 @@
102 return;
103 }
104
105+ var invalidControls = [];
106 for (var controlName in self.controls) {
107 var control = self.getControl(controlName);
108 if (control.active && control.error) {
109- self.setInvalid();
110- return;
111+ invalidControls.push(control);
112 }
113 }
114
115+ if (invalidControls.length) {
116+ self.setInvalid(invalidControls);
117+ return;
118+ }
119+
120 for (var name in self.subforms) {
121 if (!self.subforms[name].valid) {
122 self.setInvalid();
123@@ -958,6 +963,10 @@
124 * @ivar hideModificationIndicator: Hide the modification indicator for this
125 * form? Defaults to C{false}.
126 *
127+ * @type hideValidationErrorIndicator: C{boolean}
128+ * @ivar hideValidationErrorIndicator: Hide the validation error indicator for
129+ * this form? Defaults to C{false}.
130+ *
131 * @type controlNames: C{object} of C{String}
132 * @ivar controlNames: Names of form inputs as a mapping
133 *
134@@ -986,6 +995,7 @@
135 }
136 self.viewOnly = args.viewOnly;
137 self.hideModificationIndicator = args.hideModificationIndicator;
138+ self.hideValidationErrorIndicator = args.hideValidationErrorIndicator;
139 if (!(controlNames instanceof Array)) {
140 throw new Error('"controlNames" must be an Array of control names');
141 }
142@@ -999,6 +1009,9 @@
143
144 function nodeInserted(self) {
145 self._formErrorNode = self.nodeById('form-error');
146+ self._validationErrorTooltip = Methanal.Util.Tooltip(
147+ self.node, null, 'top', 'error-tooltip submission-error-tooltip ' +
148+ 'form-validation-error-tooltip');
149 },
150
151
152@@ -1225,15 +1238,58 @@
153 function setValid(self) {
154 self.valid = true;
155 self.actions.enable();
156+ self._validationErrorTooltip.hide();
157+ },
158+
159+
160+ /**
161+ * Create and display the form validation error tooltip if necessary.
162+ */
163+ function _showFormValidationErrorTooltip(self, invalidControls) {
164+ if (self.hideValidationErrorIndicator) {
165+ return;
166+ }
167+
168+ function focusControl(control) {
169+ return function () {
170+ control.focus(true);
171+ return false;
172+ }
173+ }
174+
175+ var D = Methanal.Util.DOMBuilder(self.node.ownerDocument);
176+ var errors = [];
177+ for (var i = 0; i < invalidControls.length; ++i) {
178+ var control = invalidControls[i];
179+ var label = control.label || control.name;
180+ var a = D('a', {'href': '#', 'title': control.error},
181+ [label.replace(/\s/g, '\xa0')]);
182+ a.onclick = focusControl(control);
183+ errors.push(D('span', {}, [a, ' ']));
184+ }
185+
186+ var numErrors = errors.length;
187+ if (numErrors) {
188+ var errorText = D('span', {}, [
189+ D('h2', {}, [
190+ errors.length.toString() + ' validation ' +
191+ Methanal.Util.plural(numErrors, 'error')]),
192+ D('p', {}, errors)]);
193+ self._validationErrorTooltip.setText(errorText);
194+ self._validationErrorTooltip.show();
195+ }
196 },
197
198
199 /**
200 * Disable form submission.
201 */
202- function setInvalid(self) {
203+ function setInvalid(self, invalidControls) {
204 self.valid = false;
205 self.actions.disable();
206+ if (invalidControls !== undefined && invalidControls.length) {
207+ self._showFormValidationErrorTooltip(invalidControls);
208+ }
209 });
210
211
212@@ -1539,6 +1595,25 @@
213
214
215 /**
216+ * Give keyboard focus to the form input.
217+ */
218+ function focus(self, scrollIntoView/*=true*/) {
219+ if (!self.inputNode) {
220+ return;
221+ }
222+
223+ if (self.inputNode.focus !== undefined) {
224+ self.inputNode.focus();
225+ }
226+ if (scrollIntoView === undefined || scrollIntoView) {
227+ if (self.inputNode.scrollIntoView) {
228+ self.inputNode.scrollIntoView(true);
229+ }
230+ }
231+ },
232+
233+
234+ /**
235 * Has this input finished loading?
236 */
237 function isLoaded(self) {
238
239=== modified file 'methanal/view.py'
240--- methanal/view.py 2011-06-12 22:16:10 +0000
241+++ methanal/view.py 2011-07-12 10:29:32 +0000
242@@ -240,6 +240,14 @@
243 @ivar viewOnly: Flag indicating whether model values are written back when
244 invoked.
245
246+ @type hideModificationIndicator: C{bool}
247+ @ivar hideModificationIndicator: Hide the modification indicator for this
248+ form? Defaults to C{False}.
249+
250+ @type hideValidationErrorIndicator: C{bool}
251+ @ivar hideValidationErrorIndicator: Hide the validation error indicator for
252+ this form? Defaults to C{False}.
253+
254 @type actions: L{ActionContainer}
255
256 @type doc: C{unicode}
257@@ -262,6 +270,9 @@
258 self.actions = actions
259 self.doc = doc
260
261+ self.hideModificationIndicator = False
262+ self.hideValidationErrorIndicator = False
263+
264
265 def getInitialArguments(self):
266 args = super(LiveForm, self).getInitialArguments()
267@@ -269,7 +280,10 @@
268
269
270 def getArgs(self):
271- return {u'viewOnly': self.viewOnly}
272+ return {
273+ u'viewOnly': self.viewOnly,
274+ u'hideModificationIndicator': self.hideModificationIndicator,
275+ u'hideValidationErrorIndicator': self.hideValidationErrorIndicator}
276
277
278 @renderer

Subscribers

People subscribed via source and target branches

to all changes: