1 | define([ |
---|
2 | "dojo/_base/array", |
---|
3 | "dojo/_base/lang", |
---|
4 | "dojo/dom-attr", |
---|
5 | "dojo/i18n", |
---|
6 | "dojo/query", |
---|
7 | "dojo/keys", |
---|
8 | "dijit/form/_FormValueWidget", |
---|
9 | "dijit/form/ValidationTextBox", |
---|
10 | "dojo/text!./resources/PasswordValidator.html", |
---|
11 | "dojo/i18n!./nls/PasswordValidator", |
---|
12 | "dojo/_base/declare" |
---|
13 | ], function(array, lang, domAttr, i18n, query, keys, FormValueWidget, ValidationTextBox, template, formNlsPasswordValidator, declare){ |
---|
14 | |
---|
15 | var _ChildTextBox = declare("dojox.form._ChildTextBox", ValidationTextBox, { |
---|
16 | // summary: |
---|
17 | // A class that is shared between all our children - extends |
---|
18 | // ValidationTextBox and provides some shared functionality |
---|
19 | |
---|
20 | // containerWidget: widget |
---|
21 | // Our parent (the PasswordValidator) |
---|
22 | containerWidget: null, |
---|
23 | |
---|
24 | // type: string |
---|
25 | // Don't override this - we are all "password" types |
---|
26 | type: "password", |
---|
27 | |
---|
28 | reset: function(){ |
---|
29 | // summary: |
---|
30 | // Force-set to empty string (we don't save passwords EVER)...and |
---|
31 | // since _OldPWBox overrides _setValueAttr to check for empty string, |
---|
32 | // call our parent class directly (not this.inherited()) |
---|
33 | ValidationTextBox.prototype._setValueAttr.call(this, "", true); |
---|
34 | this._hasBeenBlurred = false; |
---|
35 | }, |
---|
36 | |
---|
37 | postCreate: function(){ |
---|
38 | // summary: |
---|
39 | // We want to remove the "name" attribute from our focus node if |
---|
40 | // we don't have one set - this prevents all our extra values |
---|
41 | // from being posted on submit |
---|
42 | this.inherited(arguments); |
---|
43 | if(!this.name){ |
---|
44 | domAttr.remove(this.focusNode, "name"); |
---|
45 | } |
---|
46 | this.connect(this.focusNode, "onkeypress", "_onChildKeyPress"); |
---|
47 | }, |
---|
48 | |
---|
49 | _onChildKeyPress: function(e){ |
---|
50 | // Check if we pressed <enter> - if so, set our blur value so that |
---|
51 | // the parent widget will be updated correctly. |
---|
52 | if(e && e.keyCode == keys.ENTER){ |
---|
53 | this._setBlurValue(); |
---|
54 | } |
---|
55 | } |
---|
56 | }); |
---|
57 | |
---|
58 | |
---|
59 | |
---|
60 | var _OldPWBox = declare("dojox.form._OldPWBox", _ChildTextBox, { |
---|
61 | // summary: |
---|
62 | // A class representing our "old password" box. |
---|
63 | // |
---|
64 | // _isPWValid: boolean |
---|
65 | // Whether or not the password is valid |
---|
66 | _isPWValid: false, |
---|
67 | |
---|
68 | _setValueAttr: function(/*anything*/ newVal, /*Boolean?*/ priority){ |
---|
69 | // summary: |
---|
70 | // Updates _isPWValid if this isn't our initial update by calling |
---|
71 | // our PasswordValidator's pwCheck function |
---|
72 | if(newVal === ""){ |
---|
73 | newVal = _OldPWBox.superclass.attr.call(this, "value"); |
---|
74 | } |
---|
75 | if(priority !== null){ |
---|
76 | // Priority is passed in as null, explicitly when this is an |
---|
77 | // update (not initially set). We want to check our password now. |
---|
78 | this._isPWValid = this.containerWidget.pwCheck(newVal); |
---|
79 | } |
---|
80 | this.inherited(arguments); |
---|
81 | // Trigger the containerWidget to recheck its value, if needed |
---|
82 | this.containerWidget._childValueAttr(this.containerWidget._inputWidgets[1].get("value")); |
---|
83 | }, |
---|
84 | |
---|
85 | isValid: function(/*Boolean*/ isFocused){ |
---|
86 | // Take into account the isPWValid setting |
---|
87 | return this.inherited("isValid", arguments) && this._isPWValid; |
---|
88 | }, |
---|
89 | |
---|
90 | _update: function(/*Event*/ e){ |
---|
91 | // Only call validate() if we've been blurred or else we get popups |
---|
92 | // too early. |
---|
93 | if(this._hasBeenBlurred){ this.validate(true); } |
---|
94 | this._onMouse(e); |
---|
95 | }, |
---|
96 | |
---|
97 | _getValueAttr: function(){ |
---|
98 | if(this.containerWidget._started && this.containerWidget.isValid()){ |
---|
99 | return this.inherited(arguments); |
---|
100 | } |
---|
101 | return ""; |
---|
102 | }, |
---|
103 | |
---|
104 | _setBlurValue: function(){ |
---|
105 | // TextBox._setBlurValue calls this._setValueAttr(this.get('value'), ...) |
---|
106 | // Because we are overridding _getValueAttr to return "" when the containerWidget |
---|
107 | // is not valid, TextBox._setBlurValue will cause OldPWBox's value to be set to "" |
---|
108 | // |
---|
109 | // So, we directly call ValidationTextBox._getValueAttr to bypass our _getValueAttr |
---|
110 | var value = ValidationTextBox.prototype._getValueAttr.call(this); |
---|
111 | this._setValueAttr(value, (this.isValid ? this.isValid() : true)); |
---|
112 | } |
---|
113 | }); |
---|
114 | |
---|
115 | |
---|
116 | var _NewPWBox = declare("dojox.form._NewPWBox", _ChildTextBox, { |
---|
117 | // summary: |
---|
118 | // A class representing our new password textbox |
---|
119 | |
---|
120 | // required: boolean |
---|
121 | // Whether or not this widget is required (default: true) |
---|
122 | required: true, |
---|
123 | |
---|
124 | onChange: function(){ |
---|
125 | // summary: |
---|
126 | // Validates our verify box - to make sure that a change to me is |
---|
127 | // reflected there |
---|
128 | this.containerWidget._inputWidgets[2].validate(false); |
---|
129 | this.inherited(arguments); |
---|
130 | } |
---|
131 | }); |
---|
132 | |
---|
133 | var _VerifyPWBox = declare("dojox.form._VerifyPWBox", _ChildTextBox, { |
---|
134 | // summary: |
---|
135 | // A class representing our verify textbox |
---|
136 | |
---|
137 | isValid: function(isFocused){ |
---|
138 | // summary: |
---|
139 | // Validates that we match the "real" password |
---|
140 | return this.inherited("isValid", arguments) && |
---|
141 | (this.get("value") == this.containerWidget._inputWidgets[1].get("value")); |
---|
142 | } |
---|
143 | }); |
---|
144 | |
---|
145 | return declare("dojox.form.PasswordValidator", FormValueWidget, { |
---|
146 | // summary: |
---|
147 | // A password validation widget that simplifies the "old/new/verify" |
---|
148 | // style of requesting passwords. You will probably want to override |
---|
149 | // this class and implement your own pwCheck function. |
---|
150 | |
---|
151 | // required: boolean |
---|
152 | // Whether or not it is required for form submission |
---|
153 | required: true, |
---|
154 | |
---|
155 | // inputWidgets: TextBox[] |
---|
156 | // An array of text boxes that are our components |
---|
157 | _inputWidgets: null, |
---|
158 | |
---|
159 | // oldName: string? |
---|
160 | // The name to send our old password as (when form is posted) |
---|
161 | oldName: "", |
---|
162 | |
---|
163 | templateString: template, |
---|
164 | |
---|
165 | _hasBeenBlurred: false, |
---|
166 | |
---|
167 | isValid: function(/*Boolean*/ isFocused){ |
---|
168 | // summary: |
---|
169 | // we are valid if ALL our children are valid |
---|
170 | return array.every(this._inputWidgets, function(i){ |
---|
171 | if(i && i._setStateClass){ i._setStateClass(); } |
---|
172 | return (!i || i.isValid()); |
---|
173 | }); |
---|
174 | }, |
---|
175 | |
---|
176 | validate: function(/*Boolean*/ isFocused){ |
---|
177 | // summary: |
---|
178 | // Validating this widget validates all our children |
---|
179 | return array.every(array.map(this._inputWidgets, function(i){ |
---|
180 | if(i && i.validate){ |
---|
181 | i._hasBeenBlurred = (i._hasBeenBlurred || this._hasBeenBlurred); |
---|
182 | return i.validate(); |
---|
183 | } |
---|
184 | return true; |
---|
185 | }, this), function(item){ return item; }); |
---|
186 | }, |
---|
187 | |
---|
188 | reset: function(){ |
---|
189 | // summary: |
---|
190 | // Resetting this widget resets all our children |
---|
191 | this._hasBeenBlurred = false; |
---|
192 | array.forEach(this._inputWidgets, function(i){ |
---|
193 | if(i && i.reset){ i.reset(); } |
---|
194 | }, this); |
---|
195 | }, |
---|
196 | |
---|
197 | _createSubWidgets: function(){ |
---|
198 | // summary: |
---|
199 | // Turns the inputs inside this widget into "real" validation |
---|
200 | // widgets - and sets up the needed connections. |
---|
201 | var widgets = this._inputWidgets, |
---|
202 | msg = i18n.getLocalization("dojox.form", "PasswordValidator", this.lang); |
---|
203 | array.forEach(widgets, function(i, idx){ |
---|
204 | if(i){ |
---|
205 | var p = {containerWidget: this}, c; |
---|
206 | if(idx === 0){ |
---|
207 | p.name = this.oldName; |
---|
208 | p.invalidMessage = msg.badPasswordMessage; |
---|
209 | c = _OldPWBox; |
---|
210 | }else if(idx === 1){ |
---|
211 | p.required = this.required; |
---|
212 | c = _NewPWBox; |
---|
213 | }else if(idx === 2){ |
---|
214 | p.invalidMessage = msg.nomatchMessage; |
---|
215 | c = _VerifyPWBox; |
---|
216 | } |
---|
217 | widgets[idx] = new c(p, i); |
---|
218 | } |
---|
219 | }, this); |
---|
220 | }, |
---|
221 | |
---|
222 | pwCheck: function(/*String*/ password){ |
---|
223 | // summary: |
---|
224 | // Overridable function for validation of the old password box. |
---|
225 | // |
---|
226 | // This function is called and passed the old password. Return |
---|
227 | // true if it's OK to continue, and false if it is not. |
---|
228 | // |
---|
229 | // IMPORTANT SECURITY NOTE: Do NOT EVER EVER EVER check this in |
---|
230 | // HTML or JavaScript!!! |
---|
231 | // |
---|
232 | // You will probably want to override this function to callback |
---|
233 | // to a server to verify the password (the callback will need to |
---|
234 | // be synchronous) - and it's probably a good idea to validate |
---|
235 | // it again on form submission before actually doing |
---|
236 | // anything destructive - that's why the "oldName" value |
---|
237 | // is available. |
---|
238 | // |
---|
239 | // And don't just fetch the password from the server |
---|
240 | // either :) Send the test password (probably hashed, for |
---|
241 | // security) and return from the server a status instead. |
---|
242 | // |
---|
243 | // Again - DON'T BE INSECURE!!! Security is left as an exercise |
---|
244 | // for the reader :) |
---|
245 | return false; |
---|
246 | }, |
---|
247 | |
---|
248 | postCreate: function(){ |
---|
249 | // summary: |
---|
250 | // Sets up the correct widgets. You *MUST* specify one child |
---|
251 | // text box (a simple HTML `<input>` element) with pwType="new" |
---|
252 | // *and* one child text box with pwType="verify". You *MAY* |
---|
253 | // specify a third child text box with pwType="old" in order to |
---|
254 | // prompt the user to enter in their old password before the |
---|
255 | // widget returns that it is valid. |
---|
256 | |
---|
257 | this.inherited(arguments); |
---|
258 | |
---|
259 | // Turn my inputs into the correct stuff.... |
---|
260 | var widgets = this._inputWidgets = []; |
---|
261 | array.forEach(["old","new","verify"], function(i){ |
---|
262 | widgets.push(query("input[pwType=" + i + "]", this.containerNode)[0]); |
---|
263 | }, this); |
---|
264 | if(!widgets[1] || !widgets[2]){ |
---|
265 | throw new Error("Need at least pwType=\"new\" and pwType=\"verify\""); |
---|
266 | } |
---|
267 | if(this.oldName && !widgets[0]){ |
---|
268 | throw new Error("Need to specify pwType=\"old\" if using oldName"); |
---|
269 | } |
---|
270 | this.containerNode = this.domNode; |
---|
271 | this._createSubWidgets(); |
---|
272 | this.connect(this._inputWidgets[1], "_setValueAttr", "_childValueAttr"); |
---|
273 | this.connect(this._inputWidgets[2], "_setValueAttr", "_childValueAttr"); |
---|
274 | }, |
---|
275 | |
---|
276 | _childValueAttr: function(v){ |
---|
277 | this.set("value", this.isValid() ? v : ""); |
---|
278 | }, |
---|
279 | |
---|
280 | _setDisabledAttr: function(value){ |
---|
281 | this.inherited(arguments); |
---|
282 | array.forEach(this._inputWidgets, function(i){ |
---|
283 | if(i && i.set){ i.set("disabled", value);} |
---|
284 | }); |
---|
285 | }, |
---|
286 | |
---|
287 | _setRequiredAttribute: function(value){ |
---|
288 | this.required = value; |
---|
289 | domAttr.set(this.focusNode, "required", value); |
---|
290 | this.focusNode.setAttribute("aria-required", value); |
---|
291 | this._refreshState(); |
---|
292 | array.forEach(this._inputWidgets, function(i){ |
---|
293 | if(i && i.set){ i.set("required", value);} |
---|
294 | }); |
---|
295 | }, |
---|
296 | |
---|
297 | _setValueAttr: function(v){ |
---|
298 | this.inherited(arguments); |
---|
299 | domAttr.set(this.focusNode, "value", v); |
---|
300 | }, |
---|
301 | |
---|
302 | _getValueAttr: function(){ |
---|
303 | // Make sure we don't return undefined.... maybe should do conversion in _setValueAttr() instead? |
---|
304 | return this.value||""; |
---|
305 | }, |
---|
306 | |
---|
307 | focus: function(){ |
---|
308 | // summary: |
---|
309 | // places focus on the first invalid input widget - if all |
---|
310 | // input widgets are valid, the first widget is focused. |
---|
311 | var f = false; |
---|
312 | array.forEach(this._inputWidgets, function(i){ |
---|
313 | if(i && !i.isValid() && !f){ |
---|
314 | i.focus(); |
---|
315 | f = true; |
---|
316 | } |
---|
317 | }); |
---|
318 | if(!f){ this._inputWidgets[1].focus(); } |
---|
319 | } |
---|
320 | }); |
---|
321 | }); |
---|