分类:
2006-06-29 13:41:15
form.js 代码:
1 | /** |
2 | |
3 | * 针对 页面元素对象 的工具类,提供一些简单静态方法 |
4 | |
5 | */ |
6 | |
7 | var Field = { |
8 | |
9 | /** |
10 | |
11 | * 清除参数引用对象的值 |
12 | |
13 | */ |
14 | |
15 | clear: function() { |
16 | |
17 | for (var i = 0; i < arguments.length; i++) |
18 | |
19 | $(arguments[i]).value = ''; |
20 | |
21 | }, |
22 | |
23 | |
24 | /** |
25 | |
26 | * 使参数引用对象获取焦点 |
27 | |
28 | */ |
29 | |
30 | focus: function(element) { |
31 | |
32 | $(element).focus(); |
33 | |
34 | }, |
35 | |
36 | |
37 | /** |
38 | |
39 | * 判断参数引用对象值是否为空,如为空,返回false, 反之true |
40 | |
41 | */ |
42 | |
43 | present: function() { |
44 | |
45 | for (var i = 0; i < arguments.length; i++) |
46 | |
47 | if ($(arguments[i]).value == '') return false; |
48 | |
49 | return true; |
50 | |
51 | }, |
52 | |
53 | |
54 | /** |
55 | |
56 | * 使选中参数引用对象 |
57 | |
58 | */ |
59 | |
60 | select: function(element) { |
61 | |
62 | $(element).select(); |
63 | |
64 | }, |
65 | |
66 | |
67 | /** |
68 | |
69 | * 使参数引用对象处于可编辑状态 |
70 | |
71 | */ |
72 | |
73 | activate: function(element) { |
74 | |
75 | $(element).focus(); |
76 | |
77 | $(element).select(); |
78 | |
79 | } |
80 | |
81 | } |
82 | |
83 | |
84 | /*-----------------------------------------------------------------*/ |
85 | |
86 | |
87 | /** |
88 | |
89 | * 表单工具类 |
90 | |
91 | */ |
92 | |
93 | var Form = { |
94 | |
95 | /** |
96 | |
97 | * 将表单元素序列化后的值组合成 QueryString 的形式 |
98 | |
99 | */ |
100 | |
101 | serialize: function(form) { |
102 | |
103 | var elements = Form.getElements($(form)); |
104 | |
105 | var queryComponents = new Array(); |
106 | |
107 | |
108 | for (var i = 0; i < elements.length; i++) { |
109 | |
110 | var queryComponent = Form.Element.serialize(elements[i]); |
111 | |
112 | if (queryComponent) |
113 | |
114 | queryComponents.push(queryComponent); |
115 | |
116 | } |
117 | |
118 | |
119 | return queryComponents.join('&'); |
120 | |
121 | }, |
122 | |
123 | |
124 | /** |
125 | |
126 | * 得到表单的所有元素对象 |
127 | |
128 | */ |
129 | |
130 | getElements: function(form) { |
131 | |
132 | form = $(form); |
133 | |
134 | var elements = new Array(); |
135 | |
136 | |
137 | for (tagName in Form.Element.Serializers) { |
138 | |
139 | var tagElements = form.getElementsByTagName(tagName); |
140 | |
141 | for (var j = 0; j < tagElements.length; j++) |
142 | |
143 | elements.push(tagElements[j]); |
144 | |
145 | } |
146 | |
147 | return elements; |
148 | |
149 | }, |
150 | |
151 | |
152 | /** |
153 | |
154 | * 将指定表单的元素置于不可用状态 |
155 | |
156 | */ |
157 | |
158 | disable: function(form) { |
159 | |
160 | var elements = Form.getElements(form); |
161 | |
162 | for (var i = 0; i < elements.length; i++) { |
163 | |
164 | var element = elements[i]; |
165 | |
166 | element.blur(); |
167 | |
168 | element.disable = 'true'; |
169 | |
170 | } |
171 | |
172 | }, |
173 | |
174 | |
175 | /** |
176 | |
177 | * 使表单的第一个非 hidden 类型而且处于可用状态的元素获得焦点 |
178 | |
179 | */ |
180 | |
181 | focusFirstElement: function(form) { |
182 | |
183 | form = $(form); |
184 | |
185 | var elements = Form.getElements(form); |
186 | |
187 | for (var i = 0; i < elements.length; i++) { |
188 | |
189 | var element = elements[i]; |
190 | |
191 | if (element.type != 'hidden' && !element.disabled) { |
192 | |
193 | Field.activate(element); |
194 | |
195 | break; |
196 | |
197 | } |
198 | |
199 | } |
200 | |
201 | }, |
202 | |
203 | |
204 | /* |
205 | |
206 | * 重置表单 |
207 | |
208 | */ |
209 | |
210 | reset: function(form) { |
211 | |
212 | $(form).reset(); |
213 | |
214 | } |
215 | |
216 | } |
217 | |
218 | |
219 | /** |
220 | |
221 | * 表单元素工具类 |
222 | |
223 | */ |
224 | |
225 | Form.Element = { |
226 | |
227 | /** |
228 | |
229 | * 返回表单元素的值先序列化再进行 URL 编码后的值 |
230 | |
231 | */ |
232 | |
233 | serialize: function(element) { |
234 | |
235 | element = $(element); |
236 | |
237 | var method = element.tagName.toLowerCase(); |
238 | |
239 | var parameter = Form.Element.Serializers[method](element); |
240 | |
241 | |
242 | if (parameter) |
243 | |
244 | return encodeURIComponent(parameter[0]) + '=' + |
245 | |
246 | encodeURIComponent(parameter[1]); |
247 | |
248 | }, |
249 | |
250 | |
251 | /** |
252 | |
253 | * 返回表单元素序列化后的值 |
254 | |
255 | */ |
256 | |
257 | getValue: function(element) { |
258 | |
259 | element = $(element); |
260 | |
261 | var method = element.tagName.toLowerCase(); |
262 | |
263 | var parameter = Form.Element.Serializers[method](element); |
264 | |
265 | |
266 | if (parameter) |
267 | |
268 | return parameter[1]; |
269 | |
270 | } |
271 | |
272 | } |
273 | |
274 | |
275 | /** |
276 | |
277 | * prototype 的所谓序列化其实就是将表单的名字和值组合成一个数组 |
278 | |
279 | */ |
280 | |
281 | Form.Element.Serializers = { |
282 | |
283 | input: function(element) { |
284 | |
285 | switch (element.type.toLowerCase()) { |
286 | |
287 | case 'hidden': |
288 | |
289 | case 'password': |
290 | |
291 | case 'text': |
292 | |
293 | return Form.Element.Serializers.textarea(element); |
294 | |
295 | case 'checkbox': |
296 | |
297 | case 'radio': |
298 | |
299 | return Form.Element.Serializers.inputSelector(element); |
300 | |
301 | } |
302 | |
303 | return false; |
304 | |
305 | }, |
306 | |
307 | |
308 | inputSelector: function(element) { |
309 | |
310 | if (element.checked) |
311 | |
312 | return [element.name, element.value]; |
313 | |
314 | }, |
315 | |
316 | |
317 | textarea: function(element) { |
318 | |
319 | return [element.name, element.value]; |
320 | |
321 | }, |
322 | |
323 | |
324 | /** |
325 | |
326 | * 看样子,也不支持多选框(select-multiple) |
327 | |
328 | */ |
329 | |
330 | select: function(element) { |
331 | |
332 | var index = element.selectedIndex; |
333 | |
334 | var value = element.options[index].value || element.options[index].text; |
335 | |
336 | return [element.name, (index >= 0) ? value : '']; |
337 | |
338 | } |
339 | |
340 | } |
341 | |
342 | |
343 | /*--------------------------------------------------------------------------*/ |
344 | |
345 | |
346 | /** |
347 | |
348 | * Form.Element.getValue 也许会经常用到,所以做了一个快捷引用 |
349 | |
350 | */ |
351 | |
352 | var $F = Form.Element.getValue; |
353 | |
354 | |
355 | /*--------------------------------------------------------------------------*/ |
356 | |
357 | |
358 | /** |
359 | |
360 | * Abstract.TimedObserver 也没有用 Class.create() 来创建, |
361 | |
362 | * 和Ajax.Base 意图应该一样 |
363 | |
364 | * Abstract.TimedObserver 顾名思义, |
365 | |
366 | * 是套用Observer设计模式来跟踪指定表单元素, |
367 | |
368 | * 当表单元素的值发生变化的时候,就执行回调函数 |
369 | |
370 | * |
371 | |
372 | * 我想 Observer 与注册onchange事件相似, |
373 | |
374 | * 不同点在于 onchange 事件是在元素失去焦点 |
375 | |
376 | * 的时候才激发。 |
377 | |
378 | * 同样的与 onpropertychange 事件也相似, |
379 | |
380 | * 不过它只关注表单元素的值的变化,而且提供timeout的控制。 |
381 | |
382 | * |
383 | |
384 | * 除此之外,Observer 的好处大概就在与更面向对象,另外可以动态的更换回调函数, |
385 | |
386 | * 这就比注册事件要灵活一些。 |
387 | |
388 | * Observer 应该可以胜任动态数据校验,或者多个关联下拉选项列表的连动等等 |
389 | |
390 | * |
391 | |
392 | */ |
393 | |
394 | Abstract.TimedObserver = function() {} |
395 | |
396 | |
397 | /** |
398 | |
399 | * 这个设计和 PeriodicalExecuter 一样,bind 方法是实现的核心 |
400 | |
401 | */ |
402 | |
403 | Abstract.TimedObserver.prototype = { |
404 | |
405 | initialize: function(element, frequency, callback) { |
406 | |
407 | this.frequency = frequency; |
408 | |
409 | this.element = $(element); |
410 | |
411 | this.callback = callback; |
412 | |
413 | |
414 | this.lastValue = this.getValue(); |
415 | |
416 | this.registerCallback(); |
417 | |
418 | }, |
419 | |
420 | |
421 | registerCallback: function() { |
422 | |
423 | setTimeout(this.onTimerEvent.bind(this), this.frequency * 1000); |
424 | |
425 | }, |
426 | |
427 | |
428 | onTimerEvent: function() { |
429 | |
430 | var value = this.getValue(); |
431 | |
432 | if (this.lastValue != value) { |
433 | |
434 | this.callback(this.element, value); |
435 | |
436 | this.lastValue = value; |
437 | |
438 | } |
439 | |
440 | |
441 | this.registerCallback(); |
442 | |
443 | } |
444 | |
445 | } |
446 | |
447 | |
448 | /** |
449 | |
450 | * Form.Element.Observer 和 Form.Observer 其实是一样的 |
451 | |
452 | * 注意 Form.Observer 并不是用来跟踪整个表单的,我想大概只是 |
453 | |
454 | * 为了减少书写(这是Ruby的一个设计原则) |
455 | |
456 | */ |
457 | |
458 | Form.Element.Observer = Class.create(); |
459 | |
460 | Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ |
461 | |
462 | getValue: function() { |
463 | |
464 | return Form.Element.getValue(this.element); |
465 | |
466 | } |
467 | |
468 | }); |
469 | |
470 | |
471 | Form.Observer = Class.create(); |
472 | |
473 | Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ |
474 | |
475 | getValue: function() { |
476 | |
477 | return Form.serialize(this.element); |
478 | |
479 | } |
480 | |
481 | }); |
view plain | print | copy to clipboard | ? |
dom.js 代码:
1 | /** |
2 | |
3 | * 根据 class attribute 的名字得到对象数组,支持 multiple class |
4 | |
5 | * |
6 | |
7 | */ |
8 | |
9 | document.getElementsByClassName = function(className) { |
10 | |
11 | var children = document.getElementsByTagName('*') || document.all; |
12 | |
13 | var elements = new Array(); |
14 | |
15 | |
16 | for (var i = 0; i < children.length; i++) { |
17 | |
18 | var child = children[i]; |
19 | |
20 | var classNames = child.className.split(' '); |
21 | |
22 | for (var j = 0; j < classNames.length; j++) { |
23 | |
24 | if (classNames[j] == className) { |
25 | |
26 | elements.push(child); |
27 | |
28 | break; |
29 | |
30 | } |
31 | |
32 | } |
33 | |
34 | } |
35 | |
36 | |
37 | return elements; |
38 | |
39 | } |
40 | |
41 | |
42 | /*--------------------------------------------------------------------------*/ |
43 | |
44 | |
45 | /** |
46 | |
47 | * Element 就象一个 java 的工具类,主要用来 隐藏/显示/销除 对象, |
48 | |
49 | * 以及获取对象的简单属性。 |
50 | |
51 | * |
52 | |
53 | */ |
54 | |
55 | var Element = { |
56 | |
57 | toggle: function() { |
58 | |
59 | for (var i = 0; i < arguments.length; i++) { |
60 | |
61 | var element = $(arguments[i]); |
62 | |
63 | element.style.display = |
64 | |
65 | (element.style.display == 'none' ? '' : 'none'); |
66 | |
67 | } |
68 | |
69 | }, |
70 | |
71 | |
72 | hide: function() { |
73 | |
74 | for (var i = 0; i < arguments.length; i++) { |
75 | |
76 | var element = $(arguments[i]); |
77 | |
78 | element.style.display = 'none'; |
79 | |
80 | } |
81 | |
82 | }, |
83 | |
84 | |
85 | show: function() { |
86 | |
87 | for (var i = 0; i < arguments.length; i++) { |
88 | |
89 | var element = $(arguments[i]); |
90 | |
91 | element.style.display = ''; |
92 | |
93 | } |
94 | |
95 | }, |
96 | |
97 | |
98 | remove: function(element) { |
99 | |
100 | element = $(element); |
101 | |
102 | element.parentNode.removeChild(element); |
103 | |
104 | }, |
105 | |
106 | |
107 | getHeight: function(element) { |
108 | |
109 | element = $(element); |
110 | |
111 | return element.offsetHeight; |
112 | |
113 | } |
114 | |
115 | } |
116 | |
117 | |
118 | /** |
119 | |
120 | * 为 Element.toggle 做了一个符号连接,大概是兼容性的考虑 |
121 | |
122 | */ |
123 | |
124 | var Toggle = new Object(); |
125 | |
126 | Toggle.display = Element.toggle; |
127 | |
128 | |
129 | /*--------------------------------------------------------------------------*/ |
130 | |
131 | |
132 | /** |
133 | |
134 | * 动态插入内容的实现,MS的Jscript实现中对象有一个 insertAdjacentHTML 方法 |
135 | |
136 | * |
137 | |
138 | * author/dhtml/reference/methods/insertadjacenthtml.asp |
139 | |
140 | * 这里算是一个对象形式的封装。 |
141 | |
142 | */ |
143 | |
144 | Abstract.Insertion = function(adjacency) { |
145 | |
146 | this.adjacency = adjacency; |
147 | |
148 | } |
149 | |
150 | |
151 | Abstract.Insertion.prototype = { |
152 | |
153 | initialize: function(element, content) { |
154 | |
155 | this.element = $(element); |
156 | |
157 | this.content = content; |
158 | |
159 | |
160 | if (this.adjacency && this.element.insertAdjacentHTML) { |
161 | |
162 | this.element.insertAdjacentHTML(this.adjacency, this.content); |
163 | |
164 | } else { |
165 | |
166 | /** |
167 | |
168 | * gecko 不支持 insertAdjacentHTML 方法,但可以用如下代码代替 |
169 | |
170 | */ |
171 | |
172 | this.range = this.element.ownerDocument.createRange(); |
173 | |
174 | /** |
175 | |
176 | * 如果定义了 initializeRange 方法,则实行, |
177 | |
178 | * 这里相当与定义了一个抽象的 initializeRange 方法 |
179 | |
180 | */ |
181 | |
182 | if (this.initializeRange) this.initializeRange(); |
183 | |
184 | this.fragment = this.range.createContextualFragment(this.content); |
185 | |
186 | |
187 | /** |
188 | |
189 | * insertContent 也是一个抽象方法,子类必须实现 |
190 | |
191 | */ |
192 | |
193 | this.insertContent(); |
194 | |
195 | } |
196 | |
197 | } |
198 | |
199 | } |
200 | |
201 | |
202 | /** |
203 | |
204 | * prototype 加深了我的体会,就是写js 如何去遵循 |
205 | |
206 | * Don’t Repeat Yourself (DRY) 原则 |
207 | |
208 | * 上文中 Abstract.Insertion 算是一个抽象类, |
209 | |
210 | * 定义了名为 initializeRange 的一个抽象方法 |
211 | |
212 | * var Insertion = new Object() 建立一个命名空间 |
213 | |
214 | * Insertion.Before|Top|Bottom|After 就象是四个java中 |
215 | |
216 | * 的四个静态内部类,而它们分别继承于 |
217 | |
218 | * Abstract.Insertion,并实现了initializeRange方法。 |
219 | |
220 | */ |
221 | |
222 | var Insertion = new Object(); |
223 | |
224 | |
225 | Insertion.Before = Class.create(); |
226 | |
227 | Insertion.Before.prototype = |
228 | |
229 | (new Abstract.Insertion('beforeBegin')).extend({ |
230 | |
231 | initializeRange: function() { |
232 | |
233 | this.range.setStartBefore(this.element); |
234 | |
235 | }, |
236 | |
237 | |
238 | /** |
239 | |
240 | * 将内容插入到指定节点的前面, 与指定节点同级 |
241 | |
242 | */ |
243 | |
244 | insertContent: function() { |
245 | |
246 | this.element.parentNode.insertBefore(this.fragment, this.element); |
247 | |
248 | } |
249 | |
250 | }); |
251 | |
252 | |
253 | Insertion.Top = Class.create(); |
254 | |
255 | Insertion.Top.prototype = |
256 | |
257 | (new Abstract.Insertion('afterBegin')).extend({ |
258 | |
259 | initializeRange: function() { |
260 | |
261 | this.range.selectNodeContents(this.element); |
262 | |
263 | this.range.collapse(true); |
264 | |
265 | }, |
266 | |
267 | |
268 | /** |
269 | |
270 | * 将内容插入到指定节点的第一个子节点前,于是内容变为该节点的第一个子节点 |
271 | |
272 | */ |
273 | |
274 | insertContent: function() { |
275 | |
276 | this.element.insertBefore(this.fragment, this.element.firstChild); |
277 | |
278 | } |
279 | |
280 | }); |
281 | |
282 | |
283 | Insertion.Bottom = Class.create(); |
284 | |
285 | Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ |
286 | |
287 | initializeRange: function() { |
288 | |
289 | this.range.selectNodeContents(this.element); |
290 | |
291 | this.range.collapse(this.element); |
292 | |
293 | }, |
294 | |
295 | |
296 | /** |
297 | |
298 | * 将内容插入到指定节点的最后,于是内容变为该节点的最后一个子节点 |
299 | |
300 | */ |
301 | |
302 | insertContent: function() { |
303 | |
304 | this.element.appendChild(this.fragment); |
305 | |
306 | } |
307 | |
308 | }); |
309 | |
310 | |
311 | |
312 | Insertion.After = Class.create(); |
313 | |
314 | Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ |
315 | |
316 | initializeRange: function() { |
317 | |
318 | this.range.setStartAfter(this.element); |
319 | |
320 | }, |
321 | |
322 | |
323 | /** |
324 | |
325 | * 将内容插入到指定节点的后面, 与指定节点同级 |
326 | |
327 | */ |
328 | |
329 | insertContent: function() { |
330 | |
331 | this.element.parentNode.insertBefore(this.fragment, |
332 | |
333 | this.element.nextSibling); |
334 | |
335 | } |
336 | |
337 | }); |
view plain | print | copy to clipboard | ? |
其他代码:
prototype 还有两个源码文件 effects.js compat.js 就不贴出来了。两者并不常用,effects.js 看example 做花哨的效果还不错,不过代码中没有太多新鲜的东西。
需要指出的就是
compat.js 中 Funcation.prototype.apply 的实现有两个错误(应该是拼写错误), 我分别贴出来,大家比较一下就清楚了。
1 | /* 这是包含错误的原版本 |
2 | |
3 | if (!Function.prototype.apply) { |
4 | |
5 | // Based on code from |
6 | |
7 | Function.prototype.apply = function(object, parameters) { |
8 | |
9 | var parameterStrings = new Array(); |
10 | |
11 | if (!object) object = window; |
12 | |
13 | if (!parameters) parameters = new Array(); |
14 | |
15 | |
16 | for (var i = 0; i < parameters.length; i++) |
17 | |
18 | parameterStrings[i] = 'x[' + i + ']'; //Error 1 |
19 | |
20 | |
21 | object.__apply__ = this; |
22 | |
23 | var result = eval('obj.__apply__(' + //Error 2 |
24 | |
25 | parameterStrings[i].join(', ') + ')'); |
26 | |
27 | object.__apply__ = null; |
28 | |
29 | |
30 | return result; |
31 | |
32 | } |
33 | |
34 | } |
35 | |
36 | */ |
37 | |
38 | |
39 | if (!Function.prototype.apply) { |
40 | |
41 | Function.prototype.apply = function(object, parameters) { |
42 | |
43 | var parameterStrings = new Array(); |
44 | |
45 | if (!object) object = window; |
46 | |
47 | if (!parameters) parameters = new Array(); |
48 | |
49 | |
50 | for (var i = 0; i < parameters.length; i++) |
51 | |
52 | parameterStrings[i] = 'parameters[' + i + ']'; |
53 | |
54 | |
55 | object.__apply__ = this; |
56 | |
57 | var result = eval('object.__apply__(' + parameterStrings.join(', ') + ')'); |
58 | |
59 | object.__apply__ = null; |
60 | |
61 | |
62 | return result; |
63 | |
64 | } |
65 | |
66 | } |
view plain | print | copy to clipboard | ? |
接下来是我模仿着编写的一个 Effect 的一个子类,用来实现闪烁的效果。
1 | Effect.Blink = Class.create(); |
2 | |
3 | Effect.Blink.prototype = { |
4 | |
5 | initialize: function(element, frequency) { |
6 | |
7 | this.element = $(element); |
8 | |
9 | this.frequency = frequency?frequency:1000; |
10 | |
11 | this.element.effect_blink = this; |
12 | |
13 | this.blink(); |
14 | |
15 | }, |
16 | |
17 | |
18 | blink: function() { |
19 | |
20 | if (this.timer) clearTimeout(this.timer); |
21 | |
22 | try { |
23 | |
24 | this.element.style.visibility = |
25 | |
26 | this.element.style.visibility == 'hidden'?'visible':'hidden'; |
27 | |
28 | } catch (e) {} |
29 | |
30 | this.timer = setTimeout(this.blink.bind(this), this.frequency); |
31 | |
32 | } |
33 | |
34 | }; |
view plain | print | copy to clipboard | ? |
使用也很简单, 调用 new Effect.Blink(elementId) 就好了。