|
1 // ARRAY FUNTIONS |
|
2 |
|
3 /** |
|
4 * Test is some array member is equal (===) to tested value. |
|
5 * |
|
6 * @param arr array to test |
|
7 * @param value value to test |
|
8 * @type Boolean |
|
9 */ |
|
10 function inArray(arr, value) { |
|
11 for (var i = 0; i < arr.length; i++) { |
|
12 if (arr[i] === value) { |
|
13 return true; |
|
14 } |
|
15 } |
|
16 return false; |
|
17 } |
|
18 |
|
19 // STRING FUNCTIONS |
|
20 |
|
21 |
|
22 /** |
|
23 * Get relative URL. |
|
24 * |
|
25 * @param url URL string |
|
26 * @return relative part of the URL |
|
27 * @type String |
|
28 */ |
|
29 function getRelativeUrl(url, baseUrl) { |
|
30 if (baseUrl === undefined) { |
|
31 var i = url.lastIndexOf("/"); |
|
32 if (i != -1) { |
|
33 return url.substring(i + 1); |
|
34 } else { |
|
35 return url; |
|
36 } |
|
37 } else { |
|
38 if (url.substring(0, baseUrl.length) === baseUrl) { |
|
39 return url.substring(baseUrl.length); |
|
40 } else { |
|
41 return undefined; |
|
42 } |
|
43 } |
|
44 } |
|
45 |
|
46 /** |
|
47 * Fix file scheme URLs generated by IE. |
|
48 * |
|
49 * @param url file scheme URL to fix |
|
50 */ |
|
51 /* |
|
52 function fixUrl(url) { |
|
53 var _url; |
|
54 if (pathFileUrl.test(url)) { |
|
55 _url = url.replace(pathFileUrl, "file:///$1/$2"); |
|
56 } else { |
|
57 _url = url; |
|
58 } |
|
59 return _url.replace("\\", "/"); |
|
60 |
|
61 } |
|
62 var pathFileUrl = new RegExp("^file://([a-zA-Z]:)[\\\\/](.+)$"); |
|
63 */ |
|
64 |
|
65 /** |
|
66 * Get string value for array where list is in prose. |
|
67 * |
|
68 * @param array array to get the string value for |
|
69 * @type String |
|
70 * @return array string value in prose |
|
71 */ |
|
72 function toProseString(array) { |
|
73 switch (array.length) { |
|
74 case 0: |
|
75 return ""; |
|
76 case 1: |
|
77 return array[0]; |
|
78 case 2: |
|
79 return array[0] + " and " + array[1]; |
|
80 default: |
|
81 var t = ""; |
|
82 for (var i = 0; i < array.length - 1; i++) { |
|
83 t += array[i] + ", "; |
|
84 } |
|
85 return t + "and " + array[array.length - 1]; |
|
86 } |
|
87 } |
|
88 |
|
89 |
|
90 // EVENT FUNCTIONS |
|
91 |
|
92 /** |
|
93 * Stop event. |
|
94 * |
|
95 * @param event event to stop |
|
96 */ |
|
97 function stopEvent(event) { |
|
98 if (event.stopPropagation) { |
|
99 event.stopPropagation(); |
|
100 } else { |
|
101 event.cancelBubble = true; |
|
102 } |
|
103 if (event.preventDefault) { |
|
104 event.preventDefault(); |
|
105 } else { |
|
106 event.returnValue = false; |
|
107 } |
|
108 } |
|
109 |
|
110 /** |
|
111 * Return target node of an event. |
|
112 * |
|
113 * @return target node of the event |
|
114 * @type Node |
|
115 */ |
|
116 function getTargetNode(event) { |
|
117 if (event.target) { |
|
118 return event.target; |
|
119 } else if (event.srcElement) { |
|
120 return event.srcElement; |
|
121 } else { |
|
122 return null; |
|
123 } |
|
124 } |
|
125 |
|
126 /** |
|
127 * Attach event listener to a DOM node. |
|
128 * |
|
129 * @param node DOM node to attach lister to |
|
130 * @param type Type of event to catch |
|
131 * @param func Function to act as the handler |
|
132 */ |
|
133 function attachEventListener(node, type, func) { |
|
134 if (node.addEventListener) { |
|
135 node.addEventListener(type, func, false); |
|
136 } else if (node.attachEvent) { |
|
137 node.attachEvent("on" + type, function() { func.call(this, event); } ); |
|
138 } |
|
139 } |
|
140 |
|
141 |
|
142 // CSS CLASS FUNCTIONS |
|
143 |
|
144 |
|
145 /** |
|
146 * Switch class of an element. |
|
147 * |
|
148 * @param n Element node to change the class of |
|
149 * @param f Class to change from |
|
150 * @param t Class to change to |
|
151 * @param arr Class array (optional) |
|
152 */ |
|
153 function switchClass(n, f, t, arr) { |
|
154 if (arr === undefined) { |
|
155 arr = getClassArray(n.className); |
|
156 } |
|
157 for (var i = 0; i < arr.length; i++) { |
|
158 if (arr[i] == f) { |
|
159 arr[i] = t; |
|
160 } else if (arr[i] == t) { |
|
161 arr[i] = f; |
|
162 } |
|
163 } |
|
164 n.className = arr.join(" "); |
|
165 } |
|
166 |
|
167 /** |
|
168 * Test if element is of class. |
|
169 * |
|
170 * @param n Element node to test |
|
171 * @param c Class to test for |
|
172 * @param arr Class array (optional) |
|
173 * @return boolean value if element is of the tested class |
|
174 */ |
|
175 function isClass(n, c, arr) { |
|
176 if (arr === undefined) { |
|
177 arr = getClassArray(n.className); |
|
178 } |
|
179 for (var i = 0; i < arr.length; i++) { |
|
180 if (arr[i] == c) { |
|
181 return true; |
|
182 } |
|
183 } |
|
184 return false; |
|
185 } |
|
186 |
|
187 /** |
|
188 * Add class to an element. |
|
189 * |
|
190 * @param n Element node to add the class to |
|
191 * @param c Class to add |
|
192 * @param arr Class array (optional) |
|
193 */ |
|
194 function addClass(n, c, arr) { |
|
195 if (arr === undefined) { |
|
196 arr = getClassArray(n.className); |
|
197 } |
|
198 if (!isClass(n, c, arr)) { |
|
199 n.className = n.className + " " + c; |
|
200 } |
|
201 } |
|
202 |
|
203 /** |
|
204 * Remove class |
|
205 * |
|
206 * @param n Element node to remove the class from |
|
207 * @param c Class to remove |
|
208 * @param arr Class array (optional) |
|
209 */ |
|
210 function removeClass(n, c, arr) { |
|
211 if (arr === undefined) { |
|
212 arr = getClassArray(n.className); |
|
213 } |
|
214 if (isClass(n, c, arr)) { |
|
215 var ret = []; |
|
216 for (var i = 0; i < arr.length; i++) { |
|
217 if (arr[i] != c) { |
|
218 ret.push(arr[i]); |
|
219 } |
|
220 } |
|
221 n.className = ret.join(" "); |
|
222 } |
|
223 } |
|
224 |
|
225 /** |
|
226 * Get class names as an array |
|
227 * |
|
228 * @param c class name |
|
229 * @return array of class members |
|
230 * @type Array |
|
231 */ |
|
232 function getClassArray(c) { |
|
233 return String(c).split(" "); |
|
234 } |
|
235 |
|
236 |
|
237 // COLLECTION CLASSES |
|
238 |
|
239 |
|
240 /** |
|
241 * Multi value Map. |
|
242 * |
|
243 * Usage: |
|
244 * |
|
245 * >> var m = new Map(); |
|
246 * >> m.put("a", "first"); |
|
247 * >> m.put("a", "second"); |
|
248 * >> m.size(); |
|
249 * 1 |
|
250 * >> m.get("a"); |
|
251 * ["first", "second"] |
|
252 * |
|
253 * @constructor |
|
254 * @param {Boolean} multi Boolean to define whether map will store dublicate values into an array |
|
255 */ |
|
256 function Map(multi) { |
|
257 this.keys = []; |
|
258 this.values = []; |
|
259 this.multi = (multi === undefined ? false : multi); |
|
260 } |
|
261 Map.prototype.toString = function() { |
|
262 var val = "[Object Map"; |
|
263 if (this.keys.length !== 0) { |
|
264 val += ": {"; |
|
265 for (var i = 0; i < this.keys.length; i++) { |
|
266 if (i !== 0) { |
|
267 val += "; "; |
|
268 } |
|
269 val += this.keys[i] + ":"; |
|
270 for (var j = 0; j < this.values[i].length; j++) { |
|
271 if (j !== 0) { |
|
272 val += ","; |
|
273 } |
|
274 val += " " + this.values[i][j].toString(); |
|
275 } |
|
276 |
|
277 } |
|
278 val += "}"; |
|
279 } |
|
280 val += "]"; |
|
281 return val; |
|
282 }; |
|
283 /** |
|
284 * @param {String} key |
|
285 * @param {Object} value |
|
286 */ |
|
287 Map.prototype.put = function(key, value) { |
|
288 var index = this._find(key); |
|
289 var a; |
|
290 if (index != -1 ) { |
|
291 a = this.values[index]; |
|
292 } else { |
|
293 index = this.keys.length; |
|
294 this.keys[index] = key; |
|
295 a = []; |
|
296 } |
|
297 if (this.multi) { |
|
298 a[a.length] = value; |
|
299 } else { |
|
300 a[0] = value; |
|
301 } |
|
302 this.values[index] = a; |
|
303 }; |
|
304 /** |
|
305 * @param {String} key |
|
306 * @type Object |
|
307 */ |
|
308 Map.prototype.get = function(key) { |
|
309 var index = this._find(key); |
|
310 if (index != -1) { |
|
311 if (this.multi) { |
|
312 return this.values[index]; |
|
313 } else { |
|
314 return this.values[index][0]; |
|
315 } |
|
316 } else { |
|
317 return null; |
|
318 } |
|
319 }; |
|
320 /** |
|
321 * Iterator for the value set. |
|
322 * |
|
323 * @return Iterator for the values |
|
324 * @type ArrayIterator |
|
325 */ |
|
326 Map.prototype.iterator = function(key) { |
|
327 var index = this._find(key); |
|
328 var val; |
|
329 if (index != -1) { |
|
330 val = this.values[index]; |
|
331 } else { |
|
332 val = []; |
|
333 } |
|
334 return new ArrayIterator(val); |
|
335 }; |
|
336 /** |
|
337 * @type Number |
|
338 */ |
|
339 Map.prototype.size = function() { |
|
340 return this.keys.length; |
|
341 }; |
|
342 /** |
|
343 * @param {String} key |
|
344 * @type Number |
|
345 */ |
|
346 Map.prototype._find = function(key) { |
|
347 var index = -1; |
|
348 for (var i = 0; i < this.keys.length; i++) { |
|
349 if (this.keys[i] == key) { |
|
350 index = i; |
|
351 break; |
|
352 } |
|
353 } |
|
354 return index; |
|
355 }; |
|
356 |
|
357 /** |
|
358 * Map. |
|
359 * |
|
360 * Usage: |
|
361 * |
|
362 * >> var m = new SingleValueMap(); |
|
363 * >> m.put("a", "first"); |
|
364 * >> m.put("a", "second"); |
|
365 * >> m.size(); |
|
366 * 1 |
|
367 * >> m.get("a"); |
|
368 * "second" |
|
369 * |
|
370 * @constructor |
|
371 */ |
|
372 function SingleValueMap() { |
|
373 this.keys = []; |
|
374 this.values = []; |
|
375 this.multi = false; |
|
376 } |
|
377 SingleValueMap.prototype = new Map(false); |
|
378 SingleValueMap.prototype.toString = function() { |
|
379 return "[Object SingleValueMap]"; |
|
380 }; |
|
381 |
|
382 /** |
|
383 * Set. |
|
384 * |
|
385 * @constructor |
|
386 */ |
|
387 function Set() { |
|
388 this.keys = []; |
|
389 } |
|
390 Set.prototype.toString = function() { |
|
391 var val = "[object Set"; |
|
392 if (this.keys.length > 0) { |
|
393 val += ":"; |
|
394 for (var i = 0; i < this.keys.length; i++) { |
|
395 if (i !== 0) { |
|
396 val += ","; |
|
397 } |
|
398 val += " " + this.keys[i]; |
|
399 } |
|
400 } |
|
401 val += "]"; |
|
402 return val; |
|
403 }; |
|
404 Set.prototype.add = function(value) { |
|
405 var i = this.m_find(value); |
|
406 if (i == -1) { |
|
407 this.keys[this.keys.length] = value; |
|
408 } |
|
409 }; |
|
410 Set.prototype.iterator = function() { |
|
411 return new ArrayIterator(this.keys); |
|
412 }; |
|
413 Set.prototype.size = Map.prototype.size; |
|
414 Set.prototype.m_find = Map.prototype._find; |
|
415 |
|
416 /** |
|
417 * Array Iterator |
|
418 * |
|
419 * Usage: |
|
420 * |
|
421 * >> var arr = new Array("a", "b", "c"); |
|
422 * >> for (var itr = new ArrayIterator(arr); itr.hasNext();) { |
|
423 * >> alert(itr.next()); |
|
424 * >> } |
|
425 * "a" |
|
426 * "b" |
|
427 * "c" |
|
428 * |
|
429 * @constructor |
|
430 * @param {Array} arrayLike array to iterate through |
|
431 */ |
|
432 function ArrayIterator(arrayLike) { |
|
433 this.arrayLike = arrayLike; |
|
434 this.i = 0; |
|
435 NoSuchElementException = function() {}; |
|
436 NoSuchElementException.prototype = new Error(); |
|
437 NoSuchElementException.prototype.name = 'NoSuchElementException'; |
|
438 NoSuchElementException.prototype.message = 'Iteration has no more elements'; |
|
439 } |
|
440 /** |
|
441 * String value of the object. |
|
442 * |
|
443 * @type String |
|
444 */ |
|
445 ArrayIterator.prototype.toString = function() { |
|
446 return "[Object ArrayIterator]"; |
|
447 }; |
|
448 /** |
|
449 * @type Object |
|
450 */ |
|
451 ArrayIterator.prototype.next = function() { |
|
452 if (!this.hasNext()) { |
|
453 throw new NoSuchElementException(); |
|
454 } |
|
455 if (this.arrayLike.item) { |
|
456 return this.arrayLike.item(this.i++); |
|
457 } else { |
|
458 return this.arrayLike[this.i++]; |
|
459 } |
|
460 }; |
|
461 /*/ |
|
462 * @type Boolean |
|
463 */ |
|
464 ArrayIterator.prototype.hasNext = function() { |
|
465 return this.i < this.arrayLike.length; |
|
466 }; |
|
467 |
|
468 |
|
469 // DOM FUNCTIONS |
|
470 |
|
471 |
|
472 /** |
|
473 * Get next sibling by element name |
|
474 * |
|
475 * @param n current element |
|
476 * @param t element name of the desired sibling |
|
477 * @return next sibling element with a given name or null if not found |
|
478 * @type Element |
|
479 */ |
|
480 function getNextSiblingByTagName(n, t) { |
|
481 if (n != null) { |
|
482 var tag = t.toLowerCase(); |
|
483 var i = n; |
|
484 while (true) { |
|
485 if (i.nodeType == 1 && i.nodeName.toLowerCase() == tag) { |
|
486 return i; |
|
487 } |
|
488 if (i.nextSibling != null) { |
|
489 i = i.nextSibling; |
|
490 } else { |
|
491 return null; |
|
492 } |
|
493 } |
|
494 } |
|
495 return null; |
|
496 } |
|
497 |
|
498 /** |
|
499 * Get child elements by element name. |
|
500 * |
|
501 * @param node parent element |
|
502 * @param name element name of the desired child elements |
|
503 * @return array of child elements |
|
504 * @type Array |
|
505 */ |
|
506 function getChildElementsByTagName(node, name) { |
|
507 var buf = []; |
|
508 var tag = undefined; |
|
509 if (name !== undefined) { |
|
510 tag = name.toLowerCase(); |
|
511 } |
|
512 for (var n = node.firstChild; n != null; n = n.nextSibling) { |
|
513 if (n.nodeType == 1 && (name === undefined || n.nodeName.toLowerCase() == tag)) { |
|
514 buf[buf.length] = n; |
|
515 } |
|
516 } |
|
517 return buf; |
|
518 } |
|
519 |
|
520 /** |
|
521 * Get first child element by element name. |
|
522 * |
|
523 * @param node parent element |
|
524 * @param name element name of the desired child elements |
|
525 * @return DOM Element, null if none found |
|
526 * @type Object |
|
527 */ |
|
528 function getFirstChildElementByTagName(node, name) { |
|
529 var tag = undefined; |
|
530 if (name !== undefined) { |
|
531 tag = name.toLowerCase(); |
|
532 } |
|
533 for (var n = node.firstChild; n != null; n = n.nextSibling) { |
|
534 if (n.nodeType == 1 && (name === undefined || n.nodeName.toLowerCase() == tag)) { |
|
535 return n; |
|
536 } |
|
537 } |
|
538 return null; |
|
539 } |
|
540 |
|
541 |
|
542 /** |
|
543 * Wrap child nodes of an element to wrapper element. |
|
544 * |
|
545 * @param parent element whose child nodes to wrapper |
|
546 * @param wrapper wrapper element |
|
547 */ |
|
548 function wrapChildElements(parent, wrapper) { |
|
549 for (var c = parent.firstChild, n = null; c != null; c = parent.firstChild) { |
|
550 n = parent.removeChild(c); |
|
551 wrapper.appendChild(n); |
|
552 } |
|
553 parent.appendChild(wrapper); |
|
554 } |
|
555 |
|
556 /** |
|
557 * Get elements by class name. |
|
558 * |
|
559 * @param node DOM node whose descendants select |
|
560 * @param cls class name |
|
561 * @param elem element name (optional) |
|
562 * @return array of DOM nodes |
|
563 * @type Array |
|
564 */ |
|
565 function getElementsByClassName(node, cls, elem) { |
|
566 if (elem === undefined) { |
|
567 elem = "*"; |
|
568 } |
|
569 var res = []; |
|
570 for (var el = document.getElementsByTagName(elem), i = 0; i < el.length; i++) { |
|
571 if (isClass(el[i], cls)) { |
|
572 res[res.length] = el[i]; |
|
573 } |
|
574 } |
|
575 return res; |
|
576 } |
|
577 |
|
578 /** |
|
579 * Get first element by class name. |
|
580 * |
|
581 * @param node DOM node whose descendants select |
|
582 * @param cls class name |
|
583 * @param elem element name (optional) |
|
584 * @return DOM Element, null if none found |
|
585 * @type Object |
|
586 */ |
|
587 function getFirstElementByClassName(node, cls, elem) { |
|
588 if (elem === undefined) { |
|
589 elem = "*"; |
|
590 } |
|
591 for (var el = document.getElementsByTagName(elem), i = 0; i < el.length; i++) { |
|
592 if (isClass(el[i], cls)) { |
|
593 return el[i]; |
|
594 } |
|
595 } |
|
596 return null; |
|
597 } |
|
598 |
|
599 // FORM FUNCTIONS |
|
600 |
|
601 function formSubmitHandler(event) { |
|
602 var f = getTargetNode(event); |
|
603 if (!validateForm(f)) { |
|
604 stopEvent(event); |
|
605 } |
|
606 } |
|
607 |
|
608 /** |
|
609 * Validate form |
|
610 * |
|
611 * @param form form to validate |
|
612 * @type Boolean |
|
613 * @return boolean value whether validation was successful |
|
614 */ |
|
615 function validateForm(form) { |
|
616 var isValid = true; |
|
617 var labels; |
|
618 var r = []; |
|
619 var missing; |
|
620 var col = form.elements; |
|
621 elements: for (var i = 0; i < col.length; i++) { |
|
622 if (isClass(col[i], "required") && !col[i].disabled) { |
|
623 var f = col[col[i].name]; |
|
624 var valid = false; |
|
625 if (col[i].type === "checkbox" || col[i].type === "radio") { |
|
626 if (col[i].value != f[0].value) { // test only the first |
|
627 continue; |
|
628 } |
|
629 for (var j = 0; j < f.length; j++) { |
|
630 if (f[j].checked && f[j].className.indexOf("nonrequired") == -1) { |
|
631 continue elements; |
|
632 } |
|
633 } |
|
634 } else if (col[i].type === "select-one") { |
|
635 if (!isClass(col[i][col[i].selectedIndex], "nonvalue")) { |
|
636 continue; |
|
637 } |
|
638 } else { |
|
639 if (f.value !== "") { |
|
640 continue; |
|
641 } |
|
642 } |
|
643 |
|
644 if (labels === undefined) { |
|
645 labels = form.getElementsByTagName("label"); |
|
646 } |
|
647 var label; |
|
648 for (j = 0; j < labels.length; j++) { |
|
649 if (labels[j].htmlFor == col[i].name) { |
|
650 label = labels[j]; |
|
651 } |
|
652 } |
|
653 if (!valid) { |
|
654 isValid = false; |
|
655 if (missing === undefined) { |
|
656 missing = []; |
|
657 } |
|
658 missing[missing.length] = label.firstChild.data; |
|
659 addClass(label, "form_invalid"); |
|
660 } else { |
|
661 removeClass(label, "form_invalid"); |
|
662 } |
|
663 } |
|
664 } |
|
665 if (!isValid) { |
|
666 window.alert("Required field" + (missing.length < 2 ? "" : "s") + |
|
667 " " + toProseString(missing) + |
|
668 " " + (missing.length < 2 ? "has" : "have") + " no value"); |
|
669 } |
|
670 return isValid; |
|
671 } |