right-rails 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.textile +50 -0
  3. data/Rakefile +23 -0
  4. data/generators/right_rails/right_rails_generator.rb +41 -0
  5. data/generators/right_rails/templates/iframed.html.erb +10 -0
  6. data/generators/right_scaffold/right_scaffold_generator.rb +53 -0
  7. data/generators/right_scaffold/templates/controller.rb +99 -0
  8. data/generators/right_scaffold/templates/helper.rb +2 -0
  9. data/generators/right_scaffold/templates/layout.html.erb +18 -0
  10. data/generators/right_scaffold/templates/style.css +54 -0
  11. data/generators/right_scaffold/templates/view__form.html.erb +16 -0
  12. data/generators/right_scaffold/templates/view__item.html.erb +13 -0
  13. data/generators/right_scaffold/templates/view_edit.html.erb +6 -0
  14. data/generators/right_scaffold/templates/view_index.html.erb +9 -0
  15. data/generators/right_scaffold/templates/view_new.html.erb +5 -0
  16. data/generators/right_scaffold/templates/view_show.html.erb +10 -0
  17. data/init.rb +12 -0
  18. data/javascripts/right-autocompleter-src.js +303 -0
  19. data/javascripts/right-autocompleter.js +9 -0
  20. data/javascripts/right-behavior-src.js +240 -0
  21. data/javascripts/right-behavior.js +8 -0
  22. data/javascripts/right-calendar-src.js +855 -0
  23. data/javascripts/right-calendar.js +9 -0
  24. data/javascripts/right-dnd-src.js +555 -0
  25. data/javascripts/right-dnd.js +9 -0
  26. data/javascripts/right-effects-src.js +425 -0
  27. data/javascripts/right-effects.js +6 -0
  28. data/javascripts/right-events-src.js +369 -0
  29. data/javascripts/right-events.js +6 -0
  30. data/javascripts/right-json-src.js +176 -0
  31. data/javascripts/right-json.js +6 -0
  32. data/javascripts/right-lightbox-src.js +597 -0
  33. data/javascripts/right-lightbox.js +9 -0
  34. data/javascripts/right-rails-src.js +269 -0
  35. data/javascripts/right-rails.js +9 -0
  36. data/javascripts/right-rater-src.js +248 -0
  37. data/javascripts/right-rater.js +9 -0
  38. data/javascripts/right-selectable-src.js +507 -0
  39. data/javascripts/right-selectable.js +7 -0
  40. data/javascripts/right-slider-src.js +291 -0
  41. data/javascripts/right-slider.js +7 -0
  42. data/javascripts/right-sortable-src.js +221 -0
  43. data/javascripts/right-sortable.js +9 -0
  44. data/javascripts/right-src.js +4939 -0
  45. data/javascripts/right-tabs-src.js +776 -0
  46. data/javascripts/right-tabs.js +6 -0
  47. data/javascripts/right-tooltips-src.js +130 -0
  48. data/javascripts/right-tooltips.js +9 -0
  49. data/javascripts/right-ui-i18n-de.js +29 -0
  50. data/javascripts/right-ui-i18n-en-us.js +11 -0
  51. data/javascripts/right-ui-i18n-es.js +29 -0
  52. data/javascripts/right-ui-i18n-fr.js +29 -0
  53. data/javascripts/right-ui-i18n-jp.js +33 -0
  54. data/javascripts/right-ui-i18n-ru.js +29 -0
  55. data/javascripts/right-ui-i18n-uk.js +29 -0
  56. data/javascripts/right.js +10 -0
  57. data/lib/right-rails.rb +11 -0
  58. data/lib/right_rails/controller_extensions.rb +85 -0
  59. data/lib/right_rails/helpers/basic.rb +111 -0
  60. data/lib/right_rails/helpers/forms.rb +239 -0
  61. data/lib/right_rails/helpers/misc.rb +164 -0
  62. data/lib/right_rails/helpers/rails.rb +166 -0
  63. data/lib/right_rails/helpers.rb +5 -0
  64. data/lib/right_rails/java_script_generator.rb +313 -0
  65. data/lib/right_rails.rb +6 -0
  66. data/spec/lib/right_rails/controller_extensions_spec.rb +60 -0
  67. data/spec/lib/right_rails/helpers/basic_spec.rb +74 -0
  68. data/spec/lib/right_rails/helpers/forms_spec.rb +51 -0
  69. data/spec/lib/right_rails/helpers/misc_spec.rb +120 -0
  70. data/spec/lib/right_rails/helpers/rails_spec.rb +149 -0
  71. data/spec/lib/right_rails/java_script_generator_spec.rb +317 -0
  72. data/spec/spec.opts +5 -0
  73. data/spec/spec_helper.rb +15 -0
  74. metadata +128 -0
@@ -0,0 +1,4939 @@
1
+ /**
2
+ * RightJS - the right javascript framework
3
+ *
4
+ * The library released under terms of the MIT license
5
+ * Visit http://rightjs.org for more details
6
+ *
7
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St.
8
+ */
9
+
10
+ /**
11
+ * The framework description object
12
+ *
13
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
14
+ */
15
+ var RightJS = {
16
+ version: "1.4.3",
17
+ modules: ["core", "form", "cookie", "xhr", "fx"]
18
+ };
19
+
20
+ /**
21
+ * this object will contain info about the current browser
22
+ *
23
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
24
+ */
25
+ var Browser = (function(agent) {
26
+ return {
27
+ IE: !!(window.attachEvent && !window.opera),
28
+ Opera: !!window.opera,
29
+ WebKit: agent.indexOf('AppleWebKit/') > -1,
30
+ Gecko: agent.indexOf('Gecko') > -1 && agent.indexOf('KHTML') < 0,
31
+ MobileSafari: !!agent.match(/Apple.*Mobile.*Safari/),
32
+ Konqueror: agent.indexOf('Konqueror') > -1,
33
+
34
+ // marker for the browsers which don't give access to the HTMLElement unit
35
+ OLD: agent.indexOf('MSIE 6') > -1 || agent.indexOf('MSIE 7') > -1,
36
+ IE8: agent.indexOf('MSIE 8') > -1
37
+ }
38
+ })(navigator.userAgent);
39
+
40
+ /**
41
+ * There are some util methods
42
+ *
43
+ * Credits:
44
+ * Some of the functionality and names are inspired or copied from
45
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
46
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
47
+ *
48
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
49
+ */
50
+
51
+ /**
52
+ * extends the first object with the keys and values of the second one
53
+ *
54
+ * NOTE: the third optional argument tells if the existing values
55
+ * of the first object should _NOT_ get updated by the values of the second object
56
+ *
57
+ * @param Object destintation object
58
+ * @param Object source object
59
+ * @param Boolean flag if the function should not overwrite intersecting values
60
+ * @return Objecte extended destination object
61
+ */
62
+ function $ext(dest, src, dont_overwrite) {
63
+ var src = src || {};
64
+
65
+ for (var key in src)
66
+ if (!(dont_overwrite && dest[key] !== undefined))
67
+ dest[key] = src[key];
68
+
69
+ return dest;
70
+ };
71
+
72
+ /**
73
+ * tries to execute all the functions passed as arguments
74
+ *
75
+ * NOTE: will hide all the exceptions raised by the functions
76
+ *
77
+ * @param Function to execute
78
+ * ......
79
+ * @return mixed first sucessfully executed function result or undefined by default
80
+ */
81
+ function $try() {
82
+ for (var i=0; i < arguments.length; i++) {
83
+ try {
84
+ return arguments[i]();
85
+ } catch(e) {}
86
+ }
87
+ };
88
+
89
+ /**
90
+ * evals the given javascript text in the context of the current window
91
+ *
92
+ * @param String javascript
93
+ * @return void
94
+ */
95
+ function $eval(text) {
96
+ if (!isString(text) || text.blank()) return;
97
+ if (window.execScript) {
98
+ window.execScript(text);
99
+ } else {
100
+ var script = document.createElement('script');
101
+ script.setAttribute('type', 'text/javascript');
102
+ script.text = text;
103
+ document.body.appendChild(script);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * throws an exception to break iterations throw a callback
109
+ *
110
+ * @return void
111
+ * @throws Break
112
+ */
113
+ function $break() {
114
+ throw new Break();
115
+ };
116
+
117
+ /**
118
+ * generates aliases for the object properties
119
+ *
120
+ * @param Object object
121
+ * @param Object aliases hash
122
+ * @return Object the extended objects
123
+ */
124
+ function $alias(object, names) {
125
+ for (var new_name in names) {
126
+ object[new_name] = object[names[new_name]];
127
+ }
128
+ return object;
129
+ };
130
+
131
+ /**
132
+ * checks if the given value or a reference points
133
+ * to a really defined value
134
+ *
135
+ * NOTE: will return true for variables equal to null, false, 0, and so one.
136
+ *
137
+ * EXAMPLE:
138
+ *
139
+ * var smth = null;
140
+ * defined(smth); <- will return true
141
+ *
142
+ * var obj = {};
143
+ * defined(obj['smth']); <- will return false
144
+ *
145
+ * @param mixed value
146
+ * @return boolean check result
147
+ */
148
+ function defined(value) {
149
+ return value !== undefined;
150
+ };
151
+
152
+ /**
153
+ * checks if the given value is a hash-like object
154
+ *
155
+ * @param mixed value
156
+ * @return boolean check result
157
+ */
158
+ function isHash(value) {
159
+ return typeof(value) == 'object' && value !== null && value.constructor === Object;
160
+ };
161
+
162
+ // Konqueror 3 patch
163
+ if (navigator.userAgent.indexOf('Konqueror/3') != -1) {
164
+ eval(isHash.toString().replace(';', '&&!(arguments[0] instanceof HTMLElement);'));
165
+ }
166
+
167
+
168
+ /**
169
+ * checks if the given value is a function
170
+ *
171
+ * @param mixed value
172
+ * @return boolean check result
173
+ */
174
+ function isFunction(value) {
175
+ return typeof(value) == 'function';
176
+ };
177
+
178
+ /**
179
+ * checks if the given value is a string
180
+ *
181
+ * @param mixed value
182
+ * @return boolean check result
183
+ */
184
+ function isString(value) {
185
+ return typeof(value) == 'string';
186
+ };
187
+
188
+ /**
189
+ * checks if the given value is an array
190
+ *
191
+ * @param mixed value to check
192
+ * @return boolean check result
193
+ */
194
+ function isArray(value) {
195
+ return value instanceof Array;
196
+ };
197
+
198
+ /**
199
+ * checks if the given value is a number
200
+ *
201
+ * @param mixed value to check
202
+ * @return boolean check result
203
+ */
204
+ function isNumber(value) {
205
+ return typeof(value) == 'number';
206
+ };
207
+
208
+ /**
209
+ * checks if the given value is an element
210
+ *
211
+ * @param mixed value to check
212
+ * @return boolean check result
213
+ */
214
+ function isElement(value) {
215
+ return value && !!value.tagName;
216
+ };
217
+
218
+ /**
219
+ * checks if the given value is a DOM-node
220
+ *
221
+ * @param mixed value to check
222
+ * @return boolean check result
223
+ */
224
+ function isNode(value) {
225
+ return value && !!value.nodeType;
226
+ };
227
+
228
+ /**
229
+ * converts any iterables into an array
230
+ *
231
+ * @param Object iterable
232
+ * @return Array list
233
+ */
234
+ var $A = (function(slice) {
235
+ return function (it) {
236
+ try {
237
+ var a = slice.call(it);
238
+ } catch(e) {
239
+ for (var a=[], i=0, length = it.length; i < length; i++)
240
+ a[i] = it[i];
241
+ }
242
+ return a;
243
+ };
244
+ })(Array.prototype.slice);
245
+
246
+ /**
247
+ * shortcut to instance new elements
248
+ *
249
+ * @param String tag name
250
+ * @param object options
251
+ * @return Element instance
252
+ */
253
+ function $E(tag_name, options) {
254
+ return new Element(tag_name, options);
255
+ };
256
+
257
+ /**
258
+ * searches an element by id and/or extends it with the framework extentions
259
+ *
260
+ * @param String element id or Element to extend
261
+ * @return Element or null
262
+ */
263
+ function $(element) {
264
+ var element = typeof(element) == 'string' ? document.getElementById(element) : element;
265
+ return Browser.OLD ? Element.prepare(element) : element;
266
+ };
267
+
268
+ /**
269
+ * searches for elements in the document which matches the given css-rule
270
+ *
271
+ * @param String css-rule
272
+ * @return Array matching elements list
273
+ */
274
+ function $$(css_rule) {
275
+ return $A(document.querySelectorAll(css_rule));
276
+ };
277
+
278
+ /**
279
+ * shortcut, generates an array of words from a given string
280
+ *
281
+ * @param String string
282
+ * @return Array of words
283
+ */
284
+ function $w(string) {
285
+ return string.trim().split(/\s+/);
286
+ }
287
+
288
+ /**
289
+ * generates an unique id for an object
290
+ *
291
+ * @param Object object
292
+ * @return Integer uniq id
293
+ */
294
+ var $uid = (function() {
295
+ var _UID = 1;
296
+
297
+ return function(item) {
298
+ return item.uid || (item.uid = _UID++);
299
+ };
300
+ })();
301
+
302
+
303
+ /**
304
+ * The Object class extentions
305
+ *
306
+ * Credits:
307
+ * Some functionality is inspired by
308
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
309
+ *
310
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
311
+ */
312
+ $ext(Object, {
313
+ /**
314
+ * extracts the list of the attribute names of the given object
315
+ *
316
+ * @param Object object
317
+ * @return Array keys list
318
+ */
319
+ keys: function(object) {
320
+ var keys = [];
321
+ for (var key in object)
322
+ keys.push(key);
323
+ return keys;
324
+ },
325
+
326
+ /**
327
+ * extracts the list of the attribute values of the given object
328
+ *
329
+ * @param Object object
330
+ * @return Array values list
331
+ */
332
+ values: function(object) {
333
+ var values = [];
334
+ for (var key in object)
335
+ values.push(object[key]);
336
+ return values;
337
+ },
338
+
339
+ /**
340
+ * checks if the object-hash has no keys
341
+ *
342
+ * @param Object object
343
+ * @return check result
344
+ */
345
+ empty: function(object) {
346
+ for (var key in object) break;
347
+ return !key;
348
+ },
349
+
350
+ /**
351
+ * returns a copy of the object which contains
352
+ * all the same keys/values except the key-names
353
+ * passed the the method arguments
354
+ *
355
+ * @param Object object
356
+ * @param String key-name to exclude
357
+ * .....
358
+ * @return Object filtered copy
359
+ */
360
+ without: function() {
361
+ var filter = $A(arguments), object = filter.shift(), copy = {};
362
+
363
+ for (var key in object)
364
+ if (!filter.includes(key))
365
+ copy[key] = object[key];
366
+
367
+ return copy;
368
+ },
369
+
370
+ /**
371
+ * returns a copy of the object which contains all the
372
+ * key/value pairs from the specified key-names list
373
+ *
374
+ * NOTE: if some key does not exists in the original object, it will be just skipped
375
+ *
376
+ * @param Object object
377
+ * @param String key name to exclude
378
+ * .....
379
+ * @return Object filtered copy
380
+ */
381
+ only: function() {
382
+ var filter = $A(arguments), object = filter.shift(), copy = {};
383
+
384
+ for (var i=0, length = filter.length; i < length; i++) {
385
+ if (defined(object[filter[i]]))
386
+ copy[filter[i]] = object[filter[i]];
387
+ }
388
+
389
+ return copy;
390
+ },
391
+
392
+ /**
393
+ * merges the given objects and returns the result
394
+ *
395
+ * NOTE this method _DO_NOT_ change the objects, it creates a new object
396
+ * which conatins all the given ones.
397
+ * if there is some keys introspections, the last object wins.
398
+ * all non-object arguments will be omitted
399
+ *
400
+ * @param Object object
401
+ * @param Object mixing
402
+ * ......
403
+ * @return Object merged object
404
+ */
405
+ merge: function() {
406
+ var object = {};
407
+ for (var i=0, length = arguments.length; i < length; i++) {
408
+ if (isHash(arguments[i])) {
409
+ $ext(object, arguments[i]);
410
+ }
411
+ }
412
+ return object;
413
+ },
414
+
415
+ /**
416
+ * converts a hash-object into an equivalent url query string
417
+ *
418
+ * @param Object object
419
+ * @return String query
420
+ */
421
+ toQueryString: function(object) {
422
+ var tokens = [];
423
+ for (var key in object) {
424
+ tokens.push(key+'='+encodeURIComponent(object[key]))
425
+ }
426
+ return tokens.join('&');
427
+ }
428
+ });
429
+
430
+ /**
431
+ * here are the starndard Math object extends
432
+ *
433
+ * Credits:
434
+ * The idea of random mehtod is taken from
435
+ * - Ruby (http://www.ruby-lang.org) Copyright (C) Yukihiro Matsumoto
436
+ *
437
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
438
+ */
439
+ $ext(Math, {
440
+ /**
441
+ * the standard random method replacement, to make it more useful
442
+ *
443
+ * USE:
444
+ * Math.random(); // original functionality, returns a float between 0 and 1
445
+ * Math.random(10); // returns an integer between 0 and 10
446
+ * Math.random(1,4); // returns an integer between 1 and 4
447
+ *
448
+ * @param Integer minimum value if there's two arguments and maximum value if there's only one
449
+ * @param Integer maximum value
450
+ * @return Float random between 0 and 1 if there's no arguments or an integer in the given range
451
+ */
452
+ random: function(min, max) {
453
+ var rand = this._random();
454
+ if (arguments.length == 0)
455
+ return rand;
456
+
457
+ if (arguments.length == 1)
458
+ var max = min, min = 0;
459
+
460
+ return Math.floor(rand * (max-min+1)+min);
461
+ },
462
+ _random: Math.random
463
+ });
464
+
465
+ /**
466
+ * The Array class extentions
467
+ *
468
+ * Credits:
469
+ * Some of the functionality is inspired by
470
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
471
+ * - Ruby (http://www.ruby-lang.org) Copyright (C) Yukihiro Matsumoto
472
+ *
473
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
474
+ */
475
+ $ext(Array.prototype, (function(A_proto) {
476
+
477
+ // JavaScript 1.6 methods recatching up or faking
478
+ var for_each = A_proto.forEach || function(callback, scope) {
479
+ for (var i=0, length = this.length; i < length; i++)
480
+ callback.call(scope, this[i], i, this);
481
+ };
482
+
483
+ var filter = A_proto.filter || function(callback, scope) {
484
+ for (var result=[], i=0, length = this.length; i < length; i++) {
485
+ if (callback.call(scope, this[i], i, this))
486
+ result.push(this[i]);
487
+ }
488
+ return result;
489
+ };
490
+
491
+ var map = A_proto.map || function(callback, scope) {
492
+ for (var result=[], i=0, length = this.length; i < length; i++) {
493
+ result.push(callback.call(scope, this[i], i, this));
494
+ }
495
+ return result;
496
+ };
497
+
498
+ var some = A_proto.some || function(callback, scope) {
499
+ for (var i=0, length = this.length; i < length; i++) {
500
+ if (callback.call(scope, this[i], i, this))
501
+ return true;
502
+ }
503
+ return false;
504
+ };
505
+
506
+ var every = A_proto.every || function(callback, scope) {
507
+ for (var i=0, length = this.length; i < length; i++) {
508
+ if (!callback.call(scope, this[i], i, this))
509
+ return false;
510
+ }
511
+ return true;
512
+ };
513
+
514
+ var first = function(callback, scope) {
515
+ for (var i=0, length = this.length; i < length; i++) {
516
+ if (callback.call(scope, this[i], i, this))
517
+ return this[i];
518
+ }
519
+ return undefined;
520
+ };
521
+
522
+ var last = function(callback, scope) {
523
+ for (var i=this.length-1; i > -1; i--) {
524
+ if (callback.call(scope, this[i], i, this))
525
+ return this[i];
526
+ }
527
+ return undefined;
528
+ };
529
+
530
+
531
+ //
532
+ // RightJS callbacks magick preprocessing
533
+ //
534
+
535
+ // prepares a correct callback function
536
+ var guess_callback = function(args, array) {
537
+ var callback = args[0], args = A_proto.slice.call(args, 1), scope = array;
538
+
539
+ if (isString(callback)) {
540
+ var attr = callback;
541
+ if (array.length && isFunction(array[0][attr])) {
542
+ callback = function(object) { return object[attr].apply(object, args); };
543
+ } else {
544
+ callback = function(object) { return object[attr]; };
545
+ }
546
+ } else {
547
+ scope = args[0];
548
+ }
549
+
550
+ return [callback, scope];
551
+ };
552
+
553
+ // calls the given method with preprocessing the arguments
554
+ var call_method = function(func, scope, args) {
555
+ try {
556
+ return func.apply(scope, guess_callback(args, scope));
557
+ } catch(e) { if (!(e instanceof Break)) throw(e); }
558
+ };
559
+
560
+ return {
561
+ /**
562
+ * IE fix
563
+ * returns the index of the value in the array
564
+ *
565
+ * @param mixed value
566
+ * @param Integer optional offset
567
+ * @return Integer index or -1 if not found
568
+ */
569
+ indexOf: A_proto.indexOf || function(value, from) {
570
+ for (var i=(from<0) ? Math.max(0, this.length+from) : from || 0; i < this.length; i++)
571
+ if (this[i] === value)
572
+ return i;
573
+ return -1;
574
+ },
575
+
576
+ /**
577
+ * IE fix
578
+ * returns the last index of the value in the array
579
+ *
580
+ * @param mixed value
581
+ * @return Integer index or -1 if not found
582
+ */
583
+ lastIndexOf: A_proto.lastIndexOf || function(value) {
584
+ for (var i=this.length-1; i > -1; i--)
585
+ if (this[i] === value)
586
+ return i;
587
+ return -1;
588
+ },
589
+
590
+ /**
591
+ * returns the first element of the array
592
+ *
593
+ * @return mixed first element of the array
594
+ */
595
+ first: function() {
596
+ return arguments.length ? call_method(first, this, arguments) : this[0];
597
+ },
598
+
599
+ /**
600
+ * returns the last element of the array
601
+ *
602
+ * @return mixed last element of the array
603
+ */
604
+ last: function() {
605
+ return arguments.length ? call_method(last, this, arguments) : this[this.length-1];
606
+ },
607
+
608
+ /**
609
+ * returns a random item of the array
610
+ *
611
+ * @return mixed a random item
612
+ */
613
+ random: function() {
614
+ return this.length ? this[Math.random(this.length-1)] : null;
615
+ },
616
+
617
+ /**
618
+ * returns the array size
619
+ *
620
+ * @return Integer the array size
621
+ */
622
+ size: function() {
623
+ return this.length;
624
+ },
625
+
626
+ /**
627
+ * cleans the array
628
+ * @return Array this
629
+ */
630
+ clean: function() {
631
+ this.length = 0;
632
+ return this;
633
+ },
634
+
635
+ /**
636
+ * checks if the array has no elements in it
637
+ *
638
+ * @return boolean check result
639
+ */
640
+ empty: function() {
641
+ return !this.length;
642
+ },
643
+
644
+ /**
645
+ * creates a copy of the given array
646
+ *
647
+ * @return Array copy of the array
648
+ */
649
+ clone: function() {
650
+ return this.slice(0);
651
+ },
652
+
653
+ /**
654
+ * calls the given callback function in the given scope for each element of the array
655
+ *
656
+ * @param Function callback
657
+ * @param Object scope
658
+ * @return Array this
659
+ */
660
+ each: function() {
661
+ call_method(for_each, this, arguments);
662
+ return this;
663
+ },
664
+ forEach: for_each,
665
+
666
+ /**
667
+ * creates a list of the array items converted in the given callback function
668
+ *
669
+ * @param Function callback
670
+ * @param Object optional scope
671
+ * @return Array collected
672
+ */
673
+ map: function() {
674
+ return call_method(map, this, arguments);
675
+ },
676
+
677
+ /**
678
+ * creates a list of the array items which are matched in the given callback function
679
+ *
680
+ * @param Function callback
681
+ * @param Object optional scope
682
+ * @return Array filtered copy
683
+ */
684
+ filter: function() {
685
+ return call_method(filter, this, arguments);
686
+ },
687
+
688
+ /**
689
+ * checks if any of the array elements is logically true
690
+ *
691
+ * @param Function optional callback for checks
692
+ * @param Object optional scope for the callback
693
+ * @return boolean check result
694
+ */
695
+ some: function() {
696
+ return call_method(some, this, arguments.length ? arguments : [function(i) { return !!i; }]);
697
+ },
698
+
699
+ /**
700
+ * checks if all the array elements are logically true
701
+ *
702
+ * @param Function optional callback for checks
703
+ * @param Object optional scope for the callback
704
+ * @return Boolean check result
705
+ */
706
+ every: function() {
707
+ return call_method(every, this, arguments.length ? arguments : [function(i) { return !!i; }]);
708
+ },
709
+
710
+ /**
711
+ * applies the given lambda to each element in the array
712
+ *
713
+ * NOTE: changes the array by itself
714
+ *
715
+ * @param Function callback
716
+ * @param Object optional scope
717
+ * @return Array this
718
+ */
719
+ walk: function() {
720
+ this.map.apply(this, arguments).forEach(function(value, i) { this[i] = value; }, this);
721
+ return this;
722
+ },
723
+
724
+ /**
725
+ * similar to the concat function but it adds only the values which are not on the list yet
726
+ *
727
+ * @param Array to merge
728
+ * ....................
729
+ * @return Array new merged
730
+ */
731
+ merge: function() {
732
+ for (var copy = this.clone(), arg, i=0, length = arguments.length; i < length; i++) {
733
+ arg = arguments[i];
734
+ if (isArray(arg)) {
735
+ for (var j=0; j < arg.length; j++) {
736
+ if (copy.indexOf(arg[j]) == -1)
737
+ copy.push(arg[j]);
738
+ }
739
+ } else if (copy.indexOf(arg) == -1) {
740
+ copy.push(arg);
741
+ }
742
+ }
743
+ return copy;
744
+ },
745
+
746
+ /**
747
+ * flats out complex array into a single dimension array
748
+ *
749
+ * @return Array flatten copy
750
+ */
751
+ flatten: function() {
752
+ var copy = [];
753
+ this.forEach(function(value) {
754
+ if (isArray(value)) {
755
+ copy = copy.concat(value.flatten());
756
+ } else {
757
+ copy.push(value);
758
+ }
759
+ });
760
+ return copy;
761
+ },
762
+
763
+ /**
764
+ * returns a copy of the array whithout any null or undefined values
765
+ *
766
+ * @return Array filtered version
767
+ */
768
+ compact: function() {
769
+ return this.without(null, undefined);
770
+ },
771
+
772
+ /**
773
+ * returns a copy of the array which contains only the unique values
774
+ *
775
+ * @return Array filtered copy
776
+ */
777
+ uniq: function() {
778
+ return [].merge(this);
779
+ },
780
+
781
+ /**
782
+ * checks if all of the given values
783
+ * exists in the given array
784
+ *
785
+ * @param mixed value
786
+ * ....
787
+ * @return boolean check result
788
+ */
789
+ includes: function() {
790
+ for (var i=0, length = arguments.length; i < length; i++)
791
+ if (this.indexOf(arguments[i]) == -1)
792
+ return false;
793
+ return true;
794
+ },
795
+
796
+ /**
797
+ * returns a copy of the array without the items passed as the arguments
798
+ *
799
+ * @param mixed value
800
+ * ......
801
+ * @return Array filtered copy
802
+ */
803
+ without: function() {
804
+ var filter = $A(arguments);
805
+ return this.filter(function(value) {
806
+ return !filter.includes(value);
807
+ });
808
+ },
809
+
810
+ /**
811
+ * Shuffles the array items in a random order
812
+ *
813
+ * @return Array shuffled version
814
+ */
815
+ shuffle: function() {
816
+ var shuff = this.clone();
817
+
818
+ for (var j, x, i = shuff.length; i;
819
+ j = Math.random(i-1), x = shuff[--i], shuff[i] = shuff[j], shuff[j] = x);
820
+
821
+ return shuff;
822
+ },
823
+
824
+ /**
825
+ * sorts the array by running its items though a lambda or calling their attributes
826
+ *
827
+ * @param Function callback or attribute name
828
+ * @param Object scope or attribute argument
829
+ * @return Array sorted copy
830
+ */
831
+ sortBy: function() {
832
+ var pair = guess_callback(arguments, this);
833
+ return this.map(function(item, i) {
834
+ return {
835
+ item: item,
836
+ value: pair[0].call(pair[1], item, i, this)
837
+ }
838
+ }).sort(function(a, b) {
839
+ return a.value > b.value ? 1 : a.value < b.value ? -1 : 0;
840
+ }).map('item');
841
+ }
842
+ }})(Array.prototype));
843
+
844
+ $alias(Array.prototype, {
845
+ include: 'includes',
846
+ all: 'every',
847
+ any: 'some'
848
+ });
849
+
850
+ /**
851
+ * The String class extentions
852
+ *
853
+ * Credits:
854
+ * Some of the functionality inspired by
855
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
856
+ * The trim function taken from work of Steven Levithan
857
+ * - http://blog.stevenlevithan.com/archives/faster-trim-javascript
858
+ *
859
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
860
+ */
861
+ $ext(String.prototype, {
862
+ /**
863
+ * checks if the string is an empty string
864
+ *
865
+ * @return boolean check result
866
+ */
867
+ empty: function() {
868
+ return this == '';
869
+ },
870
+
871
+ /**
872
+ * checks if the string contains only white-spaces
873
+ *
874
+ * @return boolean check result
875
+ */
876
+ blank: function() {
877
+ return /^\s*$/.test(this);
878
+ },
879
+
880
+ /**
881
+ * removes trailing whitespaces
882
+ *
883
+ * @return String trimmed version
884
+ */
885
+ trim: String.prototype.trim || function() {
886
+ var str = this.replace(/^\s\s*/, ''), i = str.length;
887
+ while (/\s/.test(str.charAt(--i)));
888
+ return str.slice(0, i + 1);
889
+ },
890
+
891
+ /**
892
+ * returns a copy of the string with all the tags removed
893
+ * @return String without tags
894
+ */
895
+ stripTags: function() {
896
+ return this.replace(/<\/?[^>]+>/ig, '');
897
+ },
898
+
899
+ /**
900
+ * removes all the scripts declarations out of the string
901
+ * @param mixed option. If it equals true the scrips will be executed,
902
+ * if a function the scripts will be passed in it
903
+ * @return String without scripts
904
+ */
905
+ stripScripts: function(option) {
906
+ var scripts = '';
907
+ var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/img, function(match, source) {
908
+ scripts += source.trim() + "\n";
909
+ return '';
910
+ });
911
+
912
+ if (option === true)
913
+ $eval(scripts);
914
+ else if (isFunction(option))
915
+ option(scripts, text);
916
+ else if (isNumber(option))
917
+ $eval.bind(scripts).delay(options);
918
+
919
+ return text;
920
+ },
921
+
922
+ /**
923
+ * extracts all the scripts out of the string
924
+ *
925
+ * @return String the extracted stcripts
926
+ */
927
+ extractScripts: function() {
928
+ var scripts = '';
929
+ this.stripScripts(function(s,t) { scripts = s; });
930
+ return scripts;
931
+ },
932
+
933
+ /**
934
+ * evals all the scripts in the string
935
+ *
936
+ * @return String self (unchanged version with scripts still in their place)
937
+ */
938
+ evalScripts: function() {
939
+ $eval(this.extractScripts());
940
+ return this;
941
+ },
942
+
943
+ /**
944
+ * converts underscored or dasherized string to a camelized one
945
+ * @returns String camelized version
946
+ */
947
+ camelize: function() {
948
+ var prefix = this.match(/^(\-|_)+?/g) || ''; // <- keeps start dashes alive
949
+ return prefix + this.substr(prefix.length, this.length).replace(
950
+ /(\-|_)+?(\D)/g, function(match) {
951
+ return match.replace(/\-|_/, '').toUpperCase();
952
+ });
953
+ },
954
+
955
+ /**
956
+ * converts a camelized or dasherized string into an underscored one
957
+ * @return String underscored version
958
+ */
959
+ underscored: function() {
960
+ return this.replace(/([a-z0-9])([A-Z]+)/g, function(match, first, second) {
961
+ return first+"_"+(second.length > 1 ? second : second.toLowerCase());
962
+ }).replace(/\-/g, '_');
963
+ },
964
+
965
+ /**
966
+ * returns a capitalised version of the string
967
+ *
968
+ * @return String captialised version
969
+ */
970
+ capitalize: function() {
971
+ return this.replace(/(^|\s|\-|_)[a-z\u00e0-\u00fe\u0430-\u045f]/g, function(match) {
972
+ return match.toUpperCase();
973
+ });
974
+ },
975
+
976
+ /**
977
+ * checks if the string contains the given substring
978
+ *
979
+ * @param String string
980
+ * @return boolean check result
981
+ */
982
+ includes: function(string) {
983
+ return this.indexOf(string) != -1;
984
+ },
985
+
986
+ /**
987
+ * checks if the string starts with the given substring
988
+ *
989
+ * @param String string
990
+ * @param boolean ignore the letters case
991
+ * @return boolean check result
992
+ */
993
+ startsWith: function(string, ignorecase) {
994
+ var start_str = this.substr(0, string.length);
995
+ return ignorecase ? start_str.toLowerCase() == string.toLowerCase() :
996
+ start_str == string;
997
+ },
998
+
999
+ /**
1000
+ * checks if the string ends with the given substring
1001
+ *
1002
+ * @param String substring
1003
+ * @param boolean ignore the letters case
1004
+ * @return boolean check result
1005
+ */
1006
+ endsWith: function(string, ignorecase) {
1007
+ var end_str = this.substring(this.length - string.length);
1008
+ return ignorecase ? end_str.toLowerCase() == string.toLowerCase() :
1009
+ end_str == string;
1010
+ },
1011
+
1012
+ /**
1013
+ * converts the string to an integer value
1014
+ * @param Integer base
1015
+ * @return Integer or NaN
1016
+ */
1017
+ toInt: function(base) {
1018
+ return parseInt(this, base || 10);
1019
+ },
1020
+
1021
+ /**
1022
+ * converts the string to a float value
1023
+ * @param boolean flat if the method should not use a flexible matching
1024
+ * @return Float or NaN
1025
+ */
1026
+ toFloat: function(strict) {
1027
+ return parseFloat(strict ? this : this.replace(',', '.').replace(/(\d)-(\d)/g, '$1.$2'));
1028
+ }
1029
+
1030
+ });
1031
+
1032
+ $alias(String.prototype, {include: 'includes'});
1033
+
1034
+ /**
1035
+ * The Function class extentions
1036
+ *
1037
+ * Credits:
1038
+ * Some of the functionality inspired by
1039
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
1040
+ *
1041
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
1042
+ */
1043
+ $ext(Function.prototype, {
1044
+ /**
1045
+ * binds the function to be executed in the given scope
1046
+ *
1047
+ * @param Object scope
1048
+ * @param mixed optional curry (left) argument
1049
+ * ....
1050
+ * @return Function binded function
1051
+ */
1052
+ bind: function() {
1053
+ if (arguments.length < 2 && !defined(arguments[0])) return this;
1054
+
1055
+ var _method = this, args = $A(arguments), scope = args.shift();
1056
+ return function() {
1057
+ return _method.apply(scope, args.concat($A(arguments)));
1058
+ };
1059
+ },
1060
+
1061
+ /**
1062
+ * binds the function as an event listener to the given scope object
1063
+ *
1064
+ * @param Object scope
1065
+ * @param mixed optional curry (left) argument
1066
+ * .......
1067
+ * @return Function binded function
1068
+ */
1069
+ bindAsEventListener: function() {
1070
+ var _method = this, args = $A(arguments), scope = args.shift();
1071
+ return function(event) {
1072
+ return _method.apply(scope, [event || window.event].concat(args).concat($A(arguments)));
1073
+ };
1074
+ },
1075
+
1076
+ /**
1077
+ * allows you to put some curry in your cookery
1078
+ *
1079
+ * @param mixed value to curry
1080
+ * ....
1081
+ * @return Function carried function
1082
+ */
1083
+ curry: function() {
1084
+ return this.bind.apply(this, [this].concat($A(arguments)));
1085
+ },
1086
+
1087
+ /**
1088
+ * delays the function execution
1089
+ *
1090
+ * @param Integer delay ms
1091
+ * @param mixed value to curry
1092
+ * .....
1093
+ * @return Integer timeout marker
1094
+ */
1095
+ delay: function() {
1096
+ var args = $A(arguments), timeout = args.shift();
1097
+ var timer = new Number(window.setTimeout(this.bind.apply(this, [this].concat(args)), timeout));
1098
+
1099
+ timer['cancel'] = function() { window.clearTimeout(this); };
1100
+
1101
+ return timer;
1102
+ },
1103
+
1104
+ /**
1105
+ * creates a periodical execution of the function with the given timeout
1106
+ *
1107
+ * @param Integer delay ms
1108
+ * @param mixed value to curry
1109
+ * ...
1110
+ * @return Ineger interval marker
1111
+ */
1112
+ periodical: function() {
1113
+ var args = $A(arguments), timeout = args.shift();
1114
+ var timer = new Number(window.setInterval(this.bind.apply(this, [this].concat(args)), timeout));
1115
+
1116
+ timer['stop'] = function() { window.clearInterval(this); };
1117
+
1118
+ return timer;
1119
+ }
1120
+ });
1121
+
1122
+ /**
1123
+ * The Number class extentions
1124
+ *
1125
+ * Credits:
1126
+ * Some methods inspired by
1127
+ * - Ruby (http://www.ruby-lang.org) Copyright (C) Yukihiro Matsumoto
1128
+ *
1129
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
1130
+ */
1131
+ $ext(Number.prototype, {
1132
+ /**
1133
+ * executes the given callback the given number of times
1134
+ *
1135
+ * @param Function callback
1136
+ * @param Object optional callback execution scope
1137
+ * @return void
1138
+ */
1139
+ times: function(callback, scope) {
1140
+ for (var i=0; i < this; i++)
1141
+ callback.call(scope, i);
1142
+ return this;
1143
+ },
1144
+
1145
+ upto: function(number, callback, scope) {
1146
+ for (var i=this+0; i <= number; i++)
1147
+ callback.call(scope, i);
1148
+ return this;
1149
+ },
1150
+
1151
+ downto: function(number, callback, scope) {
1152
+ for (var i=this+0; i >= number; i--)
1153
+ callback.call(scope, i);
1154
+ return this;
1155
+ },
1156
+
1157
+ abs: function() {
1158
+ return Math.abs(this);
1159
+ },
1160
+
1161
+ round: function() {
1162
+ return Math.round(this);
1163
+ },
1164
+
1165
+ ceil: function() {
1166
+ return Math.ceil(this);
1167
+ },
1168
+
1169
+ floor: function() {
1170
+ return Math.floor(this);
1171
+ }
1172
+ });
1173
+
1174
+ /**
1175
+ * The Regexp class extentions
1176
+ *
1177
+ * Credits:
1178
+ * Inspired by
1179
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
1180
+ *
1181
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
1182
+ */
1183
+ $ext(RegExp, {
1184
+ /**
1185
+ * Escapes the string for safely use as a regular expression
1186
+ *
1187
+ * @param String raw string
1188
+ * @return String escaped string
1189
+ */
1190
+ escape: function(string) {
1191
+ return String(string).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
1192
+ }
1193
+ });
1194
+
1195
+ /**
1196
+ * The basic Class unit
1197
+ *
1198
+ * Credits:
1199
+ * The Class unit is inspired by its implementation in
1200
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
1201
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
1202
+ * - Ruby (http://www.ruby-lang.org) Copyright (C) Yukihiro Matsumoto
1203
+ *
1204
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
1205
+ */
1206
+ var Class = function() {
1207
+ var args = $A(arguments), properties = args.pop() || {}, parent = args.pop();
1208
+
1209
+ // if only the parent class has been specified
1210
+ if (arguments.length == 1 && isFunction(properties)) {
1211
+ parent = properties; properties = {};
1212
+ }
1213
+
1214
+ // basic class object definition
1215
+ var klass = function() {
1216
+ return this.initialize ? this.initialize.apply(this, arguments) : this;
1217
+ };
1218
+
1219
+ // attaching main class-level methods
1220
+ $ext(klass, Class.Methods);
1221
+
1222
+ // handling the parent class assign
1223
+ Class.Util.catchSuper(klass, parent);
1224
+ klass.prototype.constructor = klass; // <- don't put it lower
1225
+
1226
+ // handling the inlinde extends and includes
1227
+ Class.Util.catchExtends(klass, properties);
1228
+ Class.Util.catchIncludes(klass, properties);
1229
+
1230
+ klass.include(properties);
1231
+
1232
+ return klass;
1233
+ };
1234
+
1235
+ /**
1236
+ * This module contains some utils which hepls handling new classes definition
1237
+ *
1238
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
1239
+ */
1240
+ Class.Util = {
1241
+ /**
1242
+ * handles the class superclass catching up
1243
+ *
1244
+ * @param Function class
1245
+ * @param Class superclass
1246
+ * @return void
1247
+ */
1248
+ catchSuper: function(klass, parent) {
1249
+ if (parent && defined(parent.prototype)) {
1250
+ klass.parent = parent;
1251
+ var s_klass = function() {};
1252
+ s_klass.prototype = parent.prototype;
1253
+ klass.prototype = new s_klass;
1254
+ }
1255
+
1256
+ klass.ancestors = [];
1257
+ while (parent) {
1258
+ klass.ancestors.push(parent);
1259
+ parent = parent.parent;
1260
+ }
1261
+ },
1262
+
1263
+ /**
1264
+ * handles the inline extendings on class definitions
1265
+ *
1266
+ * @param Function class
1267
+ * @param Object user's properties
1268
+ * @return void
1269
+ */
1270
+ catchExtends: function(klass, properties) {
1271
+ if (properties['extend']) {
1272
+ var exts = properties['extend'];
1273
+
1274
+ klass.extend.apply(klass, isArray(exts) ? exts : [exts]);
1275
+ delete(properties['extend']);
1276
+ }
1277
+ },
1278
+
1279
+ /**
1280
+ * handles the inline includes of the class definitions
1281
+ *
1282
+ * @param Function class
1283
+ * @param Object user's properties
1284
+ * @return void
1285
+ */
1286
+ catchIncludes: function(klass, properties) {
1287
+ if (properties['include']) {
1288
+ var includes = properties['include'];
1289
+
1290
+ klass.include.apply(klass, isArray(includes) ? includes : [includes]);
1291
+ delete(properties['include']);
1292
+ }
1293
+ }
1294
+ };
1295
+
1296
+ /**
1297
+ * This module contains the methods by which the Class instances
1298
+ * will be extended. It provides basic and standard way to work
1299
+ * with the classes.
1300
+ *
1301
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
1302
+ */
1303
+ Class.Methods = {
1304
+ /**
1305
+ * this method will extend the class-level with the given objects
1306
+ *
1307
+ * NOTE: this method _WILL_OVERWRITE_ the existing itercecting entries
1308
+ *
1309
+ * NOTE: this method _WILL_NOT_OVERWRITE_ the class prototype and
1310
+ * the class 'name' and 'parent' attributes. If one of those
1311
+ * exists in one of the received modeuls, the attribute will be
1312
+ * skipped
1313
+ *
1314
+ * @param Object module to extend
1315
+ * ....
1316
+ * @return Class the klass
1317
+ */
1318
+ extend: function() {
1319
+ var filter = ['prototype', 'name', 'parent', 'extend', 'include'];
1320
+ for (var i=0; i < arguments.length; i++) {
1321
+ if (isHash(arguments[i])) {
1322
+ for (var key in arguments[i]) {
1323
+ if (!filter.includes(key)) {
1324
+ this[key] = arguments[i][key];
1325
+ }
1326
+ }
1327
+ }
1328
+ }
1329
+
1330
+ return this;
1331
+ },
1332
+
1333
+ /**
1334
+ * extends the class prototype with the given objects
1335
+ * NOTE: this method _WILL_OVERWRITE_ the existing itercecting entries
1336
+ * NOTE: this method _WILL_NOT_OVERWRITE_ the 'klass' attribute of the klass.prototype
1337
+ *
1338
+ * @param Object module to include
1339
+ * ....
1340
+ * @return Class the klass
1341
+ */
1342
+ include: function() {
1343
+ for (var i=0; i < arguments.length; i++) {
1344
+ if (isHash(arguments[i])) {
1345
+ for (var key in arguments[i]) {
1346
+ if (key != 'klass' && key != 'constructor') {
1347
+
1348
+ // handling the super methods
1349
+ var ancestor = this.ancestors.first(function(klass) { return isFunction(klass.prototype[key]); });
1350
+
1351
+ if (ancestor) {
1352
+ (function(name, method, $super) {
1353
+ this.prototype[name] = function() {
1354
+ this.$super = $super;
1355
+
1356
+ return method.apply(this, arguments);
1357
+ };
1358
+ }).call(this, key, arguments[i][key], ancestor.prototype[key]);
1359
+ } else {
1360
+ this.prototype[key] = arguments[i][key];
1361
+ }
1362
+
1363
+ }
1364
+ }
1365
+ }
1366
+ }
1367
+ return this;
1368
+ }
1369
+ };
1370
+
1371
+ /**
1372
+ * This is a simple mix-in module to be included in other classes
1373
+ *
1374
+ * Basically it privdes the <tt>setOptions</tt> method which processes
1375
+ * an instance options assigment and merging with the default options
1376
+ *
1377
+ * Credits:
1378
+ * The idea of the module is inspired by
1379
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
1380
+ *
1381
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
1382
+ */
1383
+ var Options = {
1384
+ /**
1385
+ * assigns the options by merging them with the default ones
1386
+ *
1387
+ * @param Object options
1388
+ * @return Object current instance
1389
+ */
1390
+ setOptions: function(options) {
1391
+ var names = $w('OPTIONS Options options'),
1392
+ objects = [this, this.constructor].concat(this.constructor.ancestors),
1393
+ OPTIONS = objects.map(function(object) {
1394
+ return names.map(function(name) { return object[name]; });
1395
+ }).flatten().first(function(i) { return !!i; });
1396
+
1397
+ this.options = Object.merge({}, OPTIONS, options);
1398
+
1399
+ // hooking up the observer options
1400
+ if (isFunction(this.on)) {
1401
+ var match;
1402
+ for (var key in this.options) {
1403
+ if (match = key.match(/on([A-Z][a-z]+)/)) {
1404
+ this.on(match[1].toLowerCase(), this.options[key]);
1405
+ delete(this.options[key]);
1406
+ }
1407
+ }
1408
+ }
1409
+
1410
+ return this;
1411
+ }
1412
+ };
1413
+
1414
+ /**
1415
+ * standard Observer class.
1416
+ *
1417
+ * Might be used as a usual class or as a builder over another objects
1418
+ *
1419
+ * Credits:
1420
+ * The naming principle is inspired by
1421
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
1422
+ *
1423
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
1424
+ */
1425
+ var Observer = new Class({
1426
+ include: Options,
1427
+
1428
+ /**
1429
+ * general constructor
1430
+ *
1431
+ * @param Object options
1432
+ */
1433
+ initialize: function(options) {
1434
+ this.setOptions(options);
1435
+
1436
+ // catching up the event shortucts
1437
+ var ancestor, shorts = this.EVENTS || this.constructor.EVENTS ||
1438
+ ((ancestor = this.constructor.ancestors.first('EVENTS')) ?
1439
+ ancestor.EVENTS : null);
1440
+
1441
+ Observer.createShortcuts(this, shorts);
1442
+ },
1443
+
1444
+ /**
1445
+ * starts observing an event
1446
+ *
1447
+ * USAGE:
1448
+ * observe(String event, Function callback[, arguments, ...]);
1449
+ * observe(String event, String method_name[, arguments, ...]);
1450
+ * observe(Object events_hash);
1451
+ *
1452
+ * @return Observer self
1453
+ */
1454
+ observe: function() {
1455
+ var args = $A(arguments), event = args.shift();
1456
+
1457
+ if (!event.trim) { // <- not a string
1458
+ for (var name in event) {
1459
+ this.observe.apply(this, [name].concat(
1460
+ isArray(event[name]) ? event[name] : [event[name]]
1461
+ ).concat(args));
1462
+ }
1463
+ }
1464
+
1465
+ if (!this.$listeners) this.$listeners = [];
1466
+
1467
+ var callback = args.shift();
1468
+ switch (typeof callback) {
1469
+ case "string":
1470
+ callback = this[callback];
1471
+
1472
+ case "function":
1473
+ var hash = { e: event, f: callback, a: args };
1474
+ this.$listeners.push(hash);
1475
+
1476
+ if (this.$o && this.$o.add) this.$o.add.call(this, hash);
1477
+
1478
+ break;
1479
+
1480
+ default:
1481
+ if (isArray(callback)) {
1482
+ callback.each(function(params) {
1483
+ this.observe.apply(this, [event].concat(
1484
+ isArray(params) ? params : [params]
1485
+ ).concat(args));
1486
+ }, this);
1487
+ }
1488
+ }
1489
+
1490
+ return this;
1491
+ },
1492
+
1493
+ /**
1494
+ * checks if the observer observes given event and/or callback
1495
+ *
1496
+ * USAGE:
1497
+ * observes(String event)
1498
+ * observes(Function callback)
1499
+ * observes(String event, Function callback)
1500
+ *
1501
+ * @retun Observer self
1502
+ */
1503
+ observes: function(event, callback) {
1504
+ if (this.$listeners) {
1505
+ if (!isString(event)) { callback = event; event = null; }
1506
+ if (isString(callback)) callback = this[callback];
1507
+
1508
+ return this.$listeners.some(function(i) {
1509
+ return (event && callback) ? i.e == event && i.f == callback :
1510
+ event ? i.e == event : i.f == callback;
1511
+ });
1512
+ }
1513
+
1514
+ return false;
1515
+ },
1516
+
1517
+ /**
1518
+ * stops observing an event or/and function
1519
+ *
1520
+ * USAGE:
1521
+ * stopObserving(String event)
1522
+ * stopObserving(Function callback)
1523
+ * stopObserving(String event, Function callback)
1524
+ *
1525
+ * @return Observer self
1526
+ */
1527
+ stopObserving: function(event, callback) {
1528
+ if (this.$listeners) {
1529
+ if (!isString(event)) { callback = event; event = null; }
1530
+ if (isString(callback)) callback = this[callback];
1531
+
1532
+ this.$listeners = this.$listeners.filter(function(i) {
1533
+ var result = (event && callback) ? (i.e != event || i.f != callback) :
1534
+ (event ? i.e != event : i.f != callback);
1535
+
1536
+ if (!result && this.$o && this.$o.remove) this.$o.remove.call(this, i);
1537
+
1538
+ return result;
1539
+ }, this);
1540
+ }
1541
+
1542
+ return this;
1543
+ },
1544
+
1545
+ /**
1546
+ * returns the listeners list for the event
1547
+ *
1548
+ * NOTE: if no event was specified the method will return _all_
1549
+ * event listeners for _all_ the events
1550
+ *
1551
+ * @param String event name
1552
+ * @return Array of listeners
1553
+ */
1554
+ listeners: function(event) {
1555
+ return (this.$listeners || []).filter(function(i) {
1556
+ return !event || i.e == event;
1557
+ }).map(function(i) { return i.f; }).uniq();
1558
+ },
1559
+
1560
+ /**
1561
+ * initiates the event handling
1562
+ *
1563
+ * @param String event name
1564
+ * @param mixed optional argument
1565
+ * ........
1566
+ * @return Observer self
1567
+ */
1568
+ fire: function() {
1569
+ var args = $A(arguments), event = args.shift();
1570
+
1571
+ (this.$listeners || []).each(function(i) {
1572
+ if (i.e == event) {
1573
+ (this.$o && this.$o.fire) ? this.$o.fire.call(this, event, args, i) :
1574
+ i.f.apply(this, i.a.concat(args));
1575
+ }
1576
+ }, this);
1577
+
1578
+ return this;
1579
+ },
1580
+
1581
+ extend: {
1582
+ /**
1583
+ * adds an observer functionality to any object
1584
+ *
1585
+ * @param Object object
1586
+ * @param Array optional events list to build shortcuts
1587
+ * @return Object extended object
1588
+ */
1589
+ create: function(object, events) {
1590
+ $ext(object, Object.without(this.prototype, 'initialize', 'setOptions'), true);
1591
+ return this.createShortcuts(object, events || object['EVENTS']);
1592
+ },
1593
+
1594
+ /**
1595
+ * builds shortcut methods to wire/fire events on the object
1596
+ *
1597
+ * @param Object object to extend
1598
+ * @param Array list of event names
1599
+ * @return Object extended object
1600
+ */
1601
+ createShortcuts: function(object, names) {
1602
+ (names || []).each(function(name) {
1603
+ var shortcuts = {}, method_name = name.replace(/:/g, '_').camelize();
1604
+ shortcuts[method_name] = function() {
1605
+ return this.fire.apply(this, [name].concat($A(arguments)));
1606
+ };
1607
+ shortcuts['on'+method_name.capitalize()] = function() {
1608
+ return this.on.apply(this, [name].concat($A(arguments)));
1609
+ };
1610
+ $ext(object, shortcuts, true);
1611
+ });
1612
+
1613
+ return object;
1614
+ }
1615
+ }
1616
+ });
1617
+
1618
+ $alias(Observer.prototype, { on: 'observe' });
1619
+
1620
+ /**
1621
+ * iterators in-callbacks break exception
1622
+ *
1623
+ * Copyright (C) 2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
1624
+ */
1625
+ var Break = new Class(Error, {
1626
+ message: "Manual iterator break"
1627
+ });
1628
+
1629
+ /**
1630
+ * represents some additional functionality for the Event class
1631
+ *
1632
+ * NOTE: there more additional functionality for the Event class in the rightjs-goods project
1633
+ *
1634
+ * Credits:
1635
+ * The additional method names are inspired by
1636
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
1637
+ *
1638
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
1639
+ */
1640
+ var Event = new Class(Event, {
1641
+ extend: {
1642
+ /**
1643
+ * extends a native object with additional functionality
1644
+ *
1645
+ * @param Event event
1646
+ * @return Event same event but extended
1647
+ */
1648
+ ext: function(event) {
1649
+ if (!event.stop) {
1650
+ $ext(event, this.Methods, true);
1651
+
1652
+ if (Browser.IE) {
1653
+ // faking the which button
1654
+ if (event.type == 'click' || event.type == 'dblclick') {
1655
+ event.which = 1;
1656
+ } else if (event.type == 'contextmenu') {
1657
+ event.which = 3;
1658
+ } else {
1659
+ event.which = event.button == 2 ? 3 : event.button == 4 ? 2 : 1;
1660
+ }
1661
+
1662
+ // faking the mouse position
1663
+ var scrolls = window.scrolls();
1664
+
1665
+ event.pageX = event.clientX + scrolls.x;
1666
+ event.pageY = event.clientY + scrolls.y;
1667
+
1668
+
1669
+ // faking the relatedElement
1670
+ event.relatedElement = event.type == 'mouseover' ? event.fromEvent :
1671
+ event.type == 'mouseout' ? event.toEvent : null;
1672
+
1673
+ // faking the target property
1674
+ event.target = event.srcElement;
1675
+ }
1676
+ }
1677
+
1678
+ // Safari bug fix
1679
+ if (event.target && event.target.nodeType == 3)
1680
+ event.target = event.target.parentNode;
1681
+
1682
+ return event;
1683
+ },
1684
+
1685
+ /**
1686
+ * cleans up the event name
1687
+ *
1688
+ * @param String event name
1689
+ * @return String fixed event name
1690
+ */
1691
+ cleanName: function(name) {
1692
+ name = name.toLowerCase();
1693
+ name = name.startsWith('on') ? name.slice(2) : name;
1694
+ name = name == 'rightclick' ? 'contextmenu' : name;
1695
+ return name;
1696
+ },
1697
+
1698
+ /**
1699
+ * returns a real, browser specific event name
1700
+ *
1701
+ * @param String clean unified name
1702
+ * @return String real name
1703
+ */
1704
+ realName: function(name) {
1705
+ if (Browser.Gecko && name == 'mousewheel') name = 'DOMMouseScroll';
1706
+ if (Browser.Konqueror && name == 'contextmenu') name = 'rightclick';
1707
+ return name;
1708
+ },
1709
+
1710
+ /**
1711
+ * Registers some additional event extendsions
1712
+ *
1713
+ * @param Object methods
1714
+ * @return void
1715
+ */
1716
+ addMethods: function(methods) {
1717
+ $ext(this.Methods, methods);
1718
+
1719
+ try { // extending the events prototype
1720
+ $ext(Event.parent.prototype, methods, true);
1721
+ } catch(e) {};
1722
+ },
1723
+
1724
+ // the additional methods registry
1725
+ Methods: {}
1726
+ },
1727
+
1728
+ /**
1729
+ * just initializes new custom event
1730
+ *
1731
+ * @param String event name
1732
+ * @param Object options
1733
+ * @return Event
1734
+ */
1735
+ initialize: function(name, options) {
1736
+ return new Event.Custom(Event.cleanName(name), options);
1737
+ }
1738
+ });
1739
+
1740
+ // hooking up the standard extendsions
1741
+ Event.addMethods({
1742
+ stopPropagation: function() {
1743
+ this.cancelBubble = true;
1744
+ },
1745
+
1746
+ preventDefault: function() {
1747
+ this.returnValue = false;
1748
+ },
1749
+
1750
+ stop: function() {
1751
+ this.stopPropagation();
1752
+ this.preventDefault();
1753
+ return this;
1754
+ },
1755
+
1756
+ position: function() {
1757
+ return {x: this.pageX, y: this.pageY};
1758
+ }
1759
+ });
1760
+
1761
+
1762
+
1763
+
1764
+ /**
1765
+ * custom events unit, used as a mediator for the artificial events handling in the generic observer
1766
+ *
1767
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
1768
+ */
1769
+ Event.Custom = new Class({
1770
+ /**
1771
+ * constructor
1772
+ *
1773
+ * @param String event name
1774
+ * @param Object options
1775
+ */
1776
+ initialize: function(name, options) {
1777
+ this.type = name;
1778
+ $ext(this, options || {});
1779
+ },
1780
+
1781
+ stop: function() {}
1782
+ });
1783
+
1784
+ /**
1785
+ * The DOM Element unit handling
1786
+ *
1787
+ * Credits:
1788
+ * The basic principles of the elements extending are originated from
1789
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
1790
+ *
1791
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
1792
+ */
1793
+ window.Element = new Class(window.Element, {
1794
+ /**
1795
+ * basic constructor
1796
+ *
1797
+ * @param String tag name
1798
+ * @param Object new element options
1799
+ * @return Element object
1800
+ */
1801
+ initialize: function(tag_name, options) {
1802
+ if (Browser.IE && tag_name == 'input' && options && options.checked) {
1803
+ tag_name = '<input checked="true"/>';
1804
+ }
1805
+
1806
+ var element = $(document.createElement(tag_name)), options = options || {};
1807
+
1808
+ if (options['html']) { element.innerHTML = options['html']; delete(options['html']); }
1809
+ if (options['class']) { element.className = options['class']; delete(options['class']); }
1810
+ if (options['style']) { element.setStyle(options['style']); delete(options['style']); }
1811
+ if (options['observe']) { element.observe(options['observe']); delete(options['observe']); }
1812
+
1813
+ return element.set(options);
1814
+ },
1815
+
1816
+ extend: {
1817
+ Methods: {}, // DO NOT Extend this object manually unless you need it, use Element#addMethods
1818
+
1819
+ /**
1820
+ * IE browsers manual elements extending
1821
+ *
1822
+ * @param Element
1823
+ * @return Element
1824
+ */
1825
+ prepare: function(element) {
1826
+ if (element && element.tagName && !element.set) {
1827
+ $ext(element, Element.Methods, true);
1828
+
1829
+ if (self['Form']) {
1830
+ switch(element.tagName) {
1831
+ case 'FORM':
1832
+ Form.ext(element);
1833
+ break;
1834
+
1835
+ case 'INPUT':
1836
+ case 'SELECT':
1837
+ case 'BUTTON':
1838
+ case 'TEXTAREA':
1839
+ Form.Element.ext(element);
1840
+ break;
1841
+ }
1842
+ }
1843
+ }
1844
+ return element;
1845
+ },
1846
+
1847
+ /**
1848
+ * registeres the methods on the custom element methods list
1849
+ * will add them to prototype and will generate a non extensive static mirror
1850
+ *
1851
+ * USAGE:
1852
+ * Element.addMethods({
1853
+ * foo: function(bar) {}
1854
+ * });
1855
+ *
1856
+ * $(element).foo(bar);
1857
+ * Element.foo(element, bar);
1858
+ *
1859
+ * @param Object new methods list
1860
+ * @param Boolean flag if the method should keep the existing methods alive
1861
+ * @return Element the global Element object
1862
+ */
1863
+ addMethods: function(methods, dont_overwrite) {
1864
+ $ext(this.Methods, methods, dont_overwrite);
1865
+
1866
+ try { // busting up the basic element prototypes
1867
+ $ext(HTMLElement.prototype, methods, dont_overwrite);
1868
+ } catch(e) {
1869
+ try { // IE8 native element extension
1870
+ $ext(this.parent.prototype, methods, dont_overwrite);
1871
+ } catch(e) {}
1872
+ }
1873
+
1874
+ return this;
1875
+ }
1876
+ }
1877
+ });
1878
+
1879
+ /**
1880
+ * The DOM Element unit structures handling module
1881
+ *
1882
+ * NOTE: all the methods will process and return only the Element nodes
1883
+ * all the textual nodes will be skipped
1884
+ *
1885
+ * NOTE: if a css-rule was specified then the result of the method
1886
+ * will be filtered/adjusted depends on the rule
1887
+ *
1888
+ * the css-rule might be a string or a Selector instance
1889
+ *
1890
+ * Credits:
1891
+ * The naming principle and most of the names are taken from
1892
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
1893
+ * The insertions system implementation is inspired by
1894
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
1895
+ *
1896
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
1897
+ */
1898
+ Element.addMethods({
1899
+ parent: function(css_rule) {
1900
+ return css_rule ? this.parents(css_rule).first() : $(this.parentNode);
1901
+ },
1902
+
1903
+ parents: function(css_rule) {
1904
+ return this.rCollect('parentNode', css_rule);
1905
+ },
1906
+
1907
+ subNodes: function(css_rule) {
1908
+ return this.firstChild ? (this.firstChild.tagName ? [$(this.firstChild)] : []
1909
+ ).concat(this.rCollect.call(this.firstChild, 'nextSibling', css_rule)) : [];
1910
+ },
1911
+
1912
+ siblings: function(css_rule) {
1913
+ return this.prevSiblings(css_rule).reverse().concat(this.nextSiblings(css_rule));
1914
+ },
1915
+
1916
+ nextSiblings: function(css_rule) {
1917
+ return this.rCollect('nextSibling', css_rule);
1918
+ },
1919
+
1920
+ prevSiblings: function(css_rule) {
1921
+ return this.rCollect('previousSibling', css_rule);
1922
+ },
1923
+
1924
+ next: function(css_rule) {
1925
+ return this.nextSiblings(css_rule).first();
1926
+ },
1927
+
1928
+ prev: function(css_rule) {
1929
+ return this.prevSiblings(css_rule).first();
1930
+ },
1931
+
1932
+ // those two are moved to the Selector unit definition
1933
+ // first: Element.Methods.querySelector,
1934
+ // select: Element.Methods.querySelectorAll,
1935
+
1936
+ match: function(css_rule) {
1937
+ return new Selector(css_rule).match(this);
1938
+ },
1939
+
1940
+ /**
1941
+ * removes the elemnt out of this parent node
1942
+ *
1943
+ * @return Element self
1944
+ */
1945
+ remove: function() {
1946
+ if (this.parentNode) {
1947
+ this.parentNode.removeChild(this);
1948
+ }
1949
+ return this;
1950
+ },
1951
+
1952
+ /**
1953
+ * handles the elements insertion functionality
1954
+ *
1955
+ * The content might be one of the following data
1956
+ *
1957
+ * o) an element instance
1958
+ * o) a String, which will be converted into content to insert (all the scripts will be parsed out and executed)
1959
+ * o) a list of Elements
1960
+ * o) a hash like {position: content}
1961
+ *
1962
+ * @param mixed data to insert
1963
+ * @param String position to insert top/bottom/before/after/instead
1964
+ * @return Element self
1965
+ */
1966
+ insert: function(content, position) {
1967
+ if (isHash(content)) {
1968
+ for (var position in content) {
1969
+ this.insert(content[position], position)
1970
+ }
1971
+ } else {
1972
+ var scripts = '';
1973
+ position = isString(position) ? position.toLowerCase() : 'bottom';
1974
+
1975
+ if (isString(content)) {
1976
+ content = content.stripScripts(function(s, h) { scripts = s; });
1977
+ }
1978
+
1979
+ Element.insertions[position](this, content.tagName ? content :
1980
+ Element.insertions.createFragment.call(
1981
+ (position == 'bottom' || position == 'top' || !this.parentNode) ?
1982
+ this : this.parentNode, content
1983
+ )
1984
+ );
1985
+ $eval(scripts);
1986
+ }
1987
+ return this;
1988
+ },
1989
+
1990
+ insertTo: function(element, position) {
1991
+ $(element).insert(this, position);
1992
+ return this;
1993
+ },
1994
+
1995
+ /**
1996
+ * replaces the current element by the given content
1997
+ *
1998
+ * @param mixed content (a String, an Element or a list of elements)
1999
+ * @return Element self
2000
+ */
2001
+ replace: function(content) {
2002
+ return this.insert(content, 'instead');
2003
+ },
2004
+
2005
+ /**
2006
+ * updates the content of the element by the given content
2007
+ *
2008
+ * @param mixed content (a String, an Element or a list of elements)
2009
+ * @return Element self
2010
+ */
2011
+ update: function(content) {
2012
+ if (isString(content)) {
2013
+ this.innerHTML = content.stripScripts();
2014
+ content.evalScripts();
2015
+ } else {
2016
+ this.clean().insert(content);
2017
+ }
2018
+ return this;
2019
+ },
2020
+
2021
+ /**
2022
+ * wraps the element with the given element
2023
+ *
2024
+ * @param Element wrapper
2025
+ * @return Element self
2026
+ */
2027
+ wrap: function(element) {
2028
+ if (this.parentNode) {
2029
+ this.parentNode.replaceChild(element, this);
2030
+ element.appendChild(this);
2031
+ }
2032
+ return this;
2033
+ },
2034
+
2035
+ /**
2036
+ * removes all the child nodes out of the element
2037
+ *
2038
+ * @return Element self
2039
+ */
2040
+ clean: function() {
2041
+ while (this.firstChild) {
2042
+ this.removeChild(this.firstChild);
2043
+ }
2044
+
2045
+ return this;
2046
+ },
2047
+
2048
+ /**
2049
+ * checks if the element has no child nodes
2050
+ *
2051
+ * @return boolean check result
2052
+ */
2053
+ empty: function() {
2054
+ return this.innerHTML.blank();
2055
+ },
2056
+
2057
+ /**
2058
+ * recursively collects nodes by pointer attribute name
2059
+ *
2060
+ * @param String pointer attribute name
2061
+ * @param String optional css-atom rule
2062
+ * @return Array found elements
2063
+ */
2064
+ rCollect: function(attr, css_rule) {
2065
+ var node = this, nodes = [];
2066
+
2067
+ while ((node = node[attr])) {
2068
+ if (node.tagName && (!css_rule || new Selector(css_rule).match(node))) {
2069
+ nodes.push(Browser.OLD ? Element.prepare(node) : node);
2070
+ }
2071
+ }
2072
+
2073
+ return nodes;
2074
+ }
2075
+ });
2076
+
2077
+ // list of insertions handling functions
2078
+ // NOTE: each of the methods will be called in the contects of the current element
2079
+ Element.insertions = {
2080
+ bottom: function(target, content) {
2081
+ target.appendChild(content);
2082
+ },
2083
+
2084
+ top: function(target, content) {
2085
+ target.firstChild ? target.insertBefore(content, target.firstChild) : target.appendChild(content);
2086
+ },
2087
+
2088
+ after: function(target, content) {
2089
+ if (target.parentNode) {
2090
+ target.nextSibling ? target.parentNode.insertBefore(content, target.nextSibling) : target.parentNode.appendChild(content);
2091
+ }
2092
+ },
2093
+
2094
+ before: function(target, content) {
2095
+ if (target.parentNode) {
2096
+ target.parentNode.insertBefore(content, target);
2097
+ }
2098
+ },
2099
+
2100
+ instead: function(target, content) {
2101
+ if (target.parentNode) {
2102
+ target.parentNode.replaceChild(content, target);
2103
+ }
2104
+ },
2105
+
2106
+ // converts any data into a html fragment unit
2107
+ createFragment: function(content) {
2108
+ var fragment;
2109
+
2110
+ if (isString(content)) {
2111
+ var tmp = document.createElement('div'),
2112
+ wrap = Element.insertions.wraps[this.tagName] || ['', '', 0],
2113
+ depth = wrap[2];
2114
+
2115
+ tmp.innerHTML = wrap[0] + content + wrap[1];
2116
+
2117
+ while (depth > 0) {
2118
+ tmp = tmp.firstChild;
2119
+ depth--;
2120
+ }
2121
+
2122
+ fragment = arguments.callee.call(this, tmp.childNodes);
2123
+
2124
+ } else {
2125
+ fragment = document.createDocumentFragment();
2126
+
2127
+ if (isNode(content)) {
2128
+ fragment.appendChild(content);
2129
+ } else if (content && content.length) {
2130
+ for (var i=0, length = content.length; i < length; i++) {
2131
+ // in case of NodeList unit, the elements will be removed out of the list during the appends
2132
+ // therefore if that's an array we use the 'i' variable, and if it's a collection of nodes
2133
+ // then we always hit the first element of the stack
2134
+ fragment.appendChild(content[content.length == length ? i : 0]);
2135
+ }
2136
+ }
2137
+ }
2138
+
2139
+ return fragment;
2140
+ },
2141
+
2142
+ wraps: {
2143
+ TABLE: ['<table>', '</table>', 1],
2144
+ TBODY: ['<table><tbody>', '</tbody></table>', 2],
2145
+ TR: ['<table><tbody><tr>', '</tr></tbody></table>', 3],
2146
+ TD: ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
2147
+ SELECT: ['<select>', '</select>', 1]
2148
+ }
2149
+ };
2150
+ $alias(Element.insertions.wraps, {
2151
+ THEAD: 'TBODY',
2152
+ TFOOT: 'TBODY',
2153
+ TH: 'TD'
2154
+ });
2155
+
2156
+ /**
2157
+ * this module contains the element unit styles related methods
2158
+ *
2159
+ * Credits:
2160
+ * Some of the functionality is inspired by
2161
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
2162
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
2163
+ * - Dojo (www.dojotoolkit.org) Copyright (C) The Dojo Foundation
2164
+ *
2165
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
2166
+ */
2167
+ Element.addMethods({
2168
+ /**
2169
+ * assigns styles out of the hash to the element
2170
+ *
2171
+ * NOTE: the style keys might be camelized or dasherized, both cases should work
2172
+ *
2173
+ * @param Object styles list or String style name
2174
+ * @param String style value in case of the first param a string style name
2175
+ * @return Element self
2176
+ */
2177
+ setStyle: function(hash, value) {
2178
+ if (value) { var style = {}; style[hash] = value; hash = style; }
2179
+ else if(isString(hash)) {
2180
+ var style = {};
2181
+ hash.split(';').each(function(option) {
2182
+ var els = option.split(':').map('trim');
2183
+ if (els[0] && els[1]) {
2184
+ style[els[0]] = els[1];
2185
+ }
2186
+ });
2187
+ hash = style;
2188
+ }
2189
+
2190
+ var c_key;
2191
+ for (var key in hash) {
2192
+ c_key = key.indexOf('-') != -1 ? key.camelize() : key;
2193
+
2194
+ if (key == 'opacity') {
2195
+ this.setOpacity(hash[key]);
2196
+ } else if (key == 'float') {
2197
+ c_key = Browser.IE ? 'styleFloat' : 'cssFloat';
2198
+ }
2199
+
2200
+ this.style[c_key] = hash[key];
2201
+ }
2202
+
2203
+ return this;
2204
+ },
2205
+
2206
+ /**
2207
+ * handles the opacity setting
2208
+ *
2209
+ * @param Float opacity value between 0 and 1
2210
+ * @return Element self
2211
+ */
2212
+ setOpacity: function(value) {
2213
+ var key = 'opacity';
2214
+ if (Browser.IE) {
2215
+ key = 'filter';
2216
+ value = 'alpha(opacity='+ value * 100 +')';
2217
+ }
2218
+ this.style[key] = value;
2219
+ return this;
2220
+ },
2221
+
2222
+ /**
2223
+ * returns style of the element
2224
+ *
2225
+ * NOTE: will include the CSS level definitions
2226
+ *
2227
+ * @param String style key
2228
+ * @return String style value or null if not set
2229
+ */
2230
+ getStyle: function(key) {
2231
+ return this._getStyle(this.style, key) || this._getStyle(this.computedStyles(), key);
2232
+ },
2233
+
2234
+ /**
2235
+ * returns the hash of computed styles for the element
2236
+ *
2237
+ * @return Object/CSSDefinition computed styles
2238
+ */
2239
+ computedStyles: function() {
2240
+ // old IE, IE8, W3C
2241
+ return this.currentStyle || this.runtimeStyle || this.ownerDocument.defaultView.getComputedStyle(this, null) || {};
2242
+ },
2243
+
2244
+ // cleans up the style value
2245
+ _getStyle: function(style, key) {
2246
+ var value, key = key.camelize();
2247
+
2248
+ switch (key) {
2249
+ case 'opacity':
2250
+ value = !Browser.IE ? style[key] :
2251
+ (((style['filter'] || '').match(/opacity=(\d+)/i) || ['', '100'])[1].toInt() / 100)+'';
2252
+ break;
2253
+
2254
+ case 'float':
2255
+ key = Browser.IE ? 'styleFloat' : 'cssFloat';
2256
+
2257
+ default:
2258
+ if (style[key]) {
2259
+ value = style[key];
2260
+ } else {
2261
+ var values = $w('top right bottom left').map(function(name) {
2262
+ var tokens = key.underscored().split('_'); tokens.splice(1, 0, name);
2263
+ return style[tokens.join('_').camelize()];
2264
+ }).uniq();
2265
+
2266
+ if (values.length == 1) {
2267
+ value = values[0];
2268
+ }
2269
+ }
2270
+
2271
+ // Opera returns named colors with quotes
2272
+ if (value && Browser.Opera && /color/.test(key)) {
2273
+ var match = value.match(/"(.+?)"/);
2274
+ value = match ? match[1] : value;
2275
+ }
2276
+ }
2277
+
2278
+ return value ? value : null;
2279
+ },
2280
+
2281
+ /**
2282
+ * checks if the element has the given class name
2283
+ *
2284
+ * @param String class name
2285
+ * @return boolean check result
2286
+ */
2287
+ hasClass: function(name) {
2288
+ return (' '+this.className+' ').indexOf(' '+name+' ') != -1;
2289
+ },
2290
+
2291
+ /**
2292
+ * sets the whole class-name string for the element
2293
+ *
2294
+ * @param String class-name
2295
+ * @return Element self
2296
+ */
2297
+ setClass: function(class_name) {
2298
+ this.className = class_name;
2299
+ return this;
2300
+ },
2301
+
2302
+ /**
2303
+ * adds the given class name to the element
2304
+ *
2305
+ * @param String class name
2306
+ * @return Element self
2307
+ */
2308
+ addClass: function(name) {
2309
+ if ((' '+this.className+' ').indexOf(' '+name+' ') == -1) {
2310
+ this.className += (this.className ? ' ' : '') + name;
2311
+ }
2312
+ return this;
2313
+ },
2314
+
2315
+ /**
2316
+ * removes the given class name
2317
+ *
2318
+ * @param String class name
2319
+ * @return Element self
2320
+ */
2321
+ removeClass: function(name) {
2322
+ this.className = (' '+this.className+' ').replace(' '+name+' ', ' ').trim();
2323
+ return this;
2324
+ },
2325
+
2326
+ /**
2327
+ * toggles the given class name on the element
2328
+ *
2329
+ * @param String class name
2330
+ * @return Element self
2331
+ */
2332
+ toggleClass: function(name) {
2333
+ return this[this.hasClass(name) ? 'removeClass' : 'addClass'](name);
2334
+ },
2335
+
2336
+ /**
2337
+ * adds the given class-name to the element
2338
+ * and removes it from all the element siblings
2339
+ *
2340
+ * @param String class name
2341
+ * @return Element self
2342
+ */
2343
+ radioClass: function(name) {
2344
+ this.siblings().each('removeClass', name);
2345
+ return this.addClass(name);
2346
+ }
2347
+ });
2348
+
2349
+ /**
2350
+ * Common DOM Element unit methods
2351
+ *
2352
+ * Credits:
2353
+ * Most of the naming system in the module inspired by
2354
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
2355
+ *
2356
+ * Copyright (C) 2008 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
2357
+ */
2358
+ Element.addMethods({
2359
+ /**
2360
+ * sets the element attributes
2361
+ *
2362
+ * @param String attr name or Object attributes hash
2363
+ * @param mixed attribute value
2364
+ * @return Element self
2365
+ */
2366
+ set: function(hash, value) {
2367
+ if (value) { var val = {}; val[hash] = value; hash = val; }
2368
+
2369
+ for (var key in hash)
2370
+ this[key] = hash[key];
2371
+
2372
+ return this;
2373
+ },
2374
+
2375
+ /**
2376
+ * returns the attribute value for the name
2377
+ *
2378
+ * @param String attr name
2379
+ * @return mixed value
2380
+ */
2381
+ get: function(name) {
2382
+ var value = this.getAttribute(name) || this[name];
2383
+ return value == '' ? null : value;
2384
+ },
2385
+
2386
+ /**
2387
+ * checks if the element has that attribute
2388
+ *
2389
+ * @param String attr name
2390
+ * @return Boolean check result
2391
+ */
2392
+ has: function(name) {
2393
+ return this.get(name) != null;
2394
+ },
2395
+
2396
+ /**
2397
+ * erases the given attribute of the element
2398
+ *
2399
+ * @param String attr name
2400
+ * @return Element self
2401
+ */
2402
+ erase: function(name) {
2403
+ this.removeAttribute(name);
2404
+ return this;
2405
+ },
2406
+
2407
+ /**
2408
+ * checks if the elemnt is hidden
2409
+ *
2410
+ * NOTE: will check css level computed styles too
2411
+ *
2412
+ * @return boolean check result
2413
+ */
2414
+ hidden: function() {
2415
+ return this.getStyle('display') == 'none';
2416
+ },
2417
+
2418
+ /**
2419
+ * checks if the element is visible
2420
+ *
2421
+ * @return boolean check result
2422
+ */
2423
+ visible: function() {
2424
+ return !this.hidden();
2425
+ },
2426
+
2427
+ /**
2428
+ * hides the element
2429
+ *
2430
+ * @param String optional effect name
2431
+ * @param Object the optional effect options
2432
+ * @return Element self
2433
+ */
2434
+ hide: function(effect, options) {
2435
+ this._$pd = this.getStyle('display');
2436
+ this.style.display = 'none';
2437
+ return this;
2438
+ },
2439
+
2440
+ /**
2441
+ * shows the element
2442
+ *
2443
+ * @param String optional effect name
2444
+ * @param Object the optional effect options
2445
+ * @return Element self
2446
+ */
2447
+ show: function(effect, options) {
2448
+ // setting 'block' for the divs and 'inline' for the other elements hidden on the css-level
2449
+ var value = this.tagName == 'DIV' ? 'block' : 'inline';
2450
+ this.style.display = this._$pd == 'none' ? value : this._$pd || value;
2451
+ return this;
2452
+ },
2453
+
2454
+ /**
2455
+ * toggles the visibility state of the element
2456
+ *
2457
+ * @param String optional effect name
2458
+ * @param Object the optional effect options
2459
+ * @return Element self
2460
+ */
2461
+ toggle: function(effect, options) {
2462
+ return this[this.hidden() ? 'show' : 'hide'](effect, options);
2463
+ },
2464
+
2465
+ /**
2466
+ * shows the element and hides all the sibligns
2467
+ *
2468
+ * @param String optional effect name
2469
+ * @param Object the optional effect options
2470
+ * @return Element self
2471
+ */
2472
+ radio: function(effect, options) {
2473
+ this.siblings().each('hide', effect, options);
2474
+ return this.show();
2475
+ }
2476
+ });
2477
+
2478
+ /**
2479
+ * this module contains the Element's part of functionality
2480
+ * responsible for the dimensions and positions getting/setting
2481
+ *
2482
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
2483
+ */
2484
+ Element.addMethods({
2485
+
2486
+ sizes: function() {
2487
+ return { x: this.offsetWidth, y: this.offsetHeight };
2488
+ },
2489
+
2490
+ position: function() {
2491
+ var dims = this.dimensions();
2492
+ return { x: dims.left, y: dims.top };
2493
+ },
2494
+
2495
+ scrolls: function() {
2496
+ return { x: this.scrollLeft, y: this.scrollTop };
2497
+ },
2498
+
2499
+ /**
2500
+ * returns the element dimensions hash
2501
+ *
2502
+ * @return Object dimensions (top, left, width, height, scrollLeft, scrollTop)
2503
+ */
2504
+ dimensions: function() {
2505
+ var left = 0, top = 0;
2506
+
2507
+ if (this.getBoundingClientRect) {
2508
+ var rect = this.getBoundingClientRect(), doc = this.ownerDocument.documentElement, scrolls = window.scrolls();
2509
+
2510
+ left = rect.left + scrolls.x - doc.clientLeft;
2511
+ top = rect.top + scrolls.y - doc.clientTop;
2512
+ } else {
2513
+ // Manual version
2514
+ left = this.offsetLeft;
2515
+ top = this.offsetTop;
2516
+
2517
+ if (this.getStyle('position') != 'absolute') {
2518
+ var body = this.ownerDocument.body, html = body.parentNode;
2519
+
2520
+ left += body.offsetLeft + html.offsetLeft;
2521
+ top += body.offsetTop + html.offsetTop;
2522
+ }
2523
+ }
2524
+
2525
+ return {
2526
+ top: top,
2527
+ left: left,
2528
+ width: this.sizes().x,
2529
+ height: this.sizes().y,
2530
+ scrollLeft: this.scrolls().x,
2531
+ scrollTop: this.scrolls().y
2532
+ };
2533
+ },
2534
+
2535
+ /**
2536
+ * sets the width of the element in pixels
2537
+ *
2538
+ * NOTE: will double assign the size of the element, so it match the exact
2539
+ * size including any possible borders and paddings
2540
+ *
2541
+ * @param Integer width in pixels
2542
+ * @return Element self
2543
+ */
2544
+ setWidth: function(width_px) {
2545
+ this.style.width = width_px + 'px';
2546
+ if (this.offsetWidth) this.style.width = (2 * width_px - this.offsetWidth) + 'px';
2547
+ return this;
2548
+ },
2549
+
2550
+ /**
2551
+ * sets the width of the element in pixels
2552
+ *
2553
+ * NOTE: will double assign the size of the element, so it match the exact
2554
+ * size including any possible borders and paddings
2555
+ *
2556
+ * @param Integer height in pixels
2557
+ * @return Element self
2558
+ */
2559
+ setHeight: function(height_px) {
2560
+ this.style.height = height_px + 'px';
2561
+ if (this.offsetHeight) this.style.height = (2 * height_px - this.offsetHeight) + 'px';
2562
+ return this;
2563
+ },
2564
+
2565
+ /**
2566
+ * sets the size of the element in pixels
2567
+ *
2568
+ * NOTE: will double assign the size of the element, so it match the exact
2569
+ * size including any possible borders and paddings
2570
+ *
2571
+ * @param Integer width in pixels or {x: 10, y: 20} like object
2572
+ * @param Integer height
2573
+ * @return Element self
2574
+ */
2575
+ resize: function(width, height) {
2576
+ if (isHash(width)) {
2577
+ height = width.y;
2578
+ width = width.x;
2579
+ }
2580
+
2581
+ this.setWidth(width);
2582
+ return this.setHeight(height);
2583
+ },
2584
+
2585
+ /**
2586
+ * sets the element position (against the window corner)
2587
+ *
2588
+ * @param Number left position in pixels or an object like {x: 10, y: 20}
2589
+ * @param Number top position in pixels
2590
+ * @return Element self
2591
+ */
2592
+ moveTo: function(left, top) {
2593
+ if (isHash(left)) {
2594
+ top = left.y;
2595
+ left = left.x;
2596
+ }
2597
+
2598
+ return this.setStyle({
2599
+ left: left + 'px',
2600
+ top: top + 'px'
2601
+ });
2602
+ },
2603
+
2604
+ /**
2605
+ * sets the scroll position
2606
+ *
2607
+ * @param Integer left scroll px or an object like {x: 22, y: 33}
2608
+ * @param Integer top scroll px
2609
+ * @return Element self
2610
+ */
2611
+ scrollTo: function(left, top) {
2612
+ if (isHash(left)) {
2613
+ top = left.y;
2614
+ left = left.x;
2615
+ }
2616
+
2617
+ this.scrollLeft = left;
2618
+ this.scrollTop = top;
2619
+
2620
+ return this;
2621
+ },
2622
+
2623
+ /**
2624
+ * makes the window be scrolled to the element
2625
+ *
2626
+ * @return Element self
2627
+ */
2628
+ scrollThere: function() {
2629
+ window.scrollTo(this);
2630
+ return this;
2631
+ }
2632
+ });
2633
+
2634
+ /**
2635
+ * DOM Element events handling methods
2636
+ *
2637
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
2638
+ */
2639
+ Element.addMethods((function() {
2640
+ var observer = Observer.create({},
2641
+ $w('click rightclick contextmenu mousedown mouseup mouseover mouseout mousemove keypress keydown keyup')
2642
+ );
2643
+
2644
+ observer.$o = {
2645
+ add: function(hash) {
2646
+ var callback = hash.f, args = hash.a;
2647
+ hash.e = Event.cleanName(hash.e);
2648
+ hash.n = Event.realName(hash.e);
2649
+
2650
+ hash.w = function() {
2651
+ Event.ext(arguments[0]);
2652
+ return callback.apply(this, $A(arguments).concat(args));
2653
+ };
2654
+
2655
+ if (this.addEventListener) {
2656
+ this.addEventListener(hash.n, hash.w, false);
2657
+ } else {
2658
+ hash.w = hash.w.bind(this);
2659
+ this.attachEvent('on'+ hash.n, hash.w);
2660
+ }
2661
+ },
2662
+
2663
+ remove: function(hash) {
2664
+ if (this.removeEventListener) {
2665
+ this.removeEventListener(hash.n, hash.w, false);
2666
+ } else {
2667
+ this.detachEvent('on'+ hash.n, hash.w);
2668
+ }
2669
+ },
2670
+
2671
+ fire: function(name, args, hash) {
2672
+ var event = new Event(name, args.shift());
2673
+ hash.f.apply(this, [event].concat(hash.a).concat(args));
2674
+ }
2675
+ };
2676
+
2677
+ // a simple events terminator method to be hooked like
2678
+ // this.onClick('stopEvent');
2679
+ observer.stopEvent = function(e) { e.stop(); };
2680
+
2681
+ $ext(window, observer);
2682
+ $ext(document, observer);
2683
+
2684
+ return observer;
2685
+ })());
2686
+
2687
+
2688
+ /**
2689
+ * The DOM elements selection handling class
2690
+ *
2691
+ * Credits:
2692
+ * The naming principles of the unit are inspired by
2693
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
2694
+ *
2695
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
2696
+ */
2697
+
2698
+ // checking, monkeying and hooking the native css-selectors interface
2699
+ // IE8 W3C
2700
+ [document, (Element.parent || self['HTMLElement'] || {}.constructor).prototype].each(function(object, i) {
2701
+ var old_selector = object.querySelector;
2702
+ var old_selector_all = object.querySelectorAll;
2703
+
2704
+ // the native selectors checking/monkeying
2705
+ var selectors = {};
2706
+ if (!old_selector) selectors.querySelector = function(css_rule) {
2707
+ return new Selector(css_rule).first(this);
2708
+ };
2709
+ if (!old_selector_all) selectors.querySelectorAll = function(css_rule) {
2710
+ return new Selector(css_rule).select(this);
2711
+ };
2712
+
2713
+ // RightJS version of the selectors
2714
+ selectors.first = old_selector ? i ? function(css_rule) {
2715
+ return this.querySelector(this.tagName + ' ' + (css_rule || '*'));
2716
+ } : function(css_rule) {
2717
+ return this.querySelector(css_rule || '*');
2718
+ } : selectors.querySelector;
2719
+
2720
+ selectors.select = old_selector_all ? i ? function(css_rule) {
2721
+ return $A(this.querySelectorAll(this.tagName + ' ' + (css_rule || '*')));
2722
+ } : function(css_rule) {
2723
+ return $A(this.querySelectorAll(css_rule || '*'));
2724
+ } : selectors.querySelectorAll;
2725
+
2726
+ return i ? Element.addMethods(selectors) : $ext(object, selectors);
2727
+ });
2728
+
2729
+
2730
+ var Selector = new Class({
2731
+ extend: {
2732
+ cache: {}
2733
+ },
2734
+
2735
+ /**
2736
+ * constructor
2737
+ *
2738
+ * @param String css rule definition
2739
+ * @return void
2740
+ */
2741
+ initialize: function(css_rule) {
2742
+ var cached = isString(css_rule) ? Selector.cache[css_rule] : css_rule;
2743
+ if (cached) return cached;
2744
+ Selector.cache[css_rule] = this;
2745
+
2746
+ this.cssRule = css_rule || '*';
2747
+
2748
+ var strategy = 'Manual';
2749
+ if (this.cssRule.includes(',')) {
2750
+ strategy = 'Multiple';
2751
+ }
2752
+
2753
+ this.strategy = new Selector[strategy](this.cssRule);
2754
+ },
2755
+
2756
+ /**
2757
+ * selects the first matching element which is a sub node of the given element
2758
+ * and matches the selector's css-rule
2759
+ *
2760
+ * @param Element element
2761
+ * @return Element matching element or null if nothing found
2762
+ */
2763
+ first: Browser.OLD ? function(element) {
2764
+ var element = this.strategy.first(element);
2765
+ return element ? $(element) : null;
2766
+ } : function(element) {
2767
+ return this.strategy.first(element);
2768
+ },
2769
+
2770
+ /**
2771
+ * select all the subnodes of the element which are matching the rule
2772
+ *
2773
+ * @param Element element
2774
+ * @return Array list of found nodes
2775
+ */
2776
+ select: Browser.OLD ? function(element) {
2777
+ return this.strategy.select(element).map(Element.prepare);
2778
+ } : function(element) {
2779
+ return this.strategy.select(element);
2780
+ },
2781
+
2782
+ /**
2783
+ * checks if the element matches the rule
2784
+ *
2785
+ * @param Element element
2786
+ * @return Boolean check result
2787
+ */
2788
+ match: function(element) {
2789
+ return this.strategy.match(element);
2790
+ }
2791
+ });
2792
+
2793
+
2794
+ /**
2795
+ * this class represent a simple css-definition atom unit
2796
+ *
2797
+ * the main purpose is to organize the simpliest case of css-rule match for the manual matcher.
2798
+ *
2799
+ * Credits:
2800
+ * Some functionality and principles are inspired by css-selectors in
2801
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
2802
+ *
2803
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
2804
+ */
2805
+ Selector.Atom = new Class({
2806
+ id: null,
2807
+ tag: '*',
2808
+ classes: [],
2809
+ pseudo: null,
2810
+ pseudoValue: null,
2811
+ attrs: {},
2812
+
2813
+ rel: ' ', // relations with the previous atom
2814
+
2815
+ ID_RE: /#([\w\-_]+)/,
2816
+ TAG_RE: /^[\w\*]+/,
2817
+ CLASS_RE: /\.([\w\-\._]+)/,
2818
+ PSEUDO_RE: /:([\w\-]+)(\((.+?)\))*$/,
2819
+ ATTRS_RE: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/,
2820
+
2821
+ /**
2822
+ * constructor
2823
+ *
2824
+ * @param String css-definition
2825
+ * @param String relation with the previous atom
2826
+ * @return void
2827
+ */
2828
+ initialize: function(css_rule, rel) {
2829
+ css_rule = css_rule.trim();
2830
+ this.rel = rel || ' ';
2831
+ this.hasNonTagMatcher = !/^[a-z\*]+$/.test(css_rule);
2832
+
2833
+ // NOTE! dont change the order of the atom parsing, there might be collisions
2834
+ this.attrs = {};
2835
+ while((m = css_rule.match(this.ATTRS_RE))) {
2836
+ this.attrs[m[1]] = { op: m[2], value: m[5] || m[6] };
2837
+ css_rule = css_rule.replace(m[0], '');
2838
+ }
2839
+
2840
+ if ((m = css_rule.match(this.PSEUDO_RE))) {
2841
+ this.pseudo = m[1];
2842
+ this.pseudoValue = m[3] == '' ? null : m[3];
2843
+ css_rule = css_rule.replace(m[0], '');
2844
+ } else {
2845
+ this.pseudo = null;
2846
+ this.pseudoValue = null;
2847
+ }
2848
+
2849
+ this.id = (css_rule.match(this.ID_RE) || [1, null])[1];
2850
+ this.tag = (css_rule.match(this.TAG_RE) || '*').toString().toUpperCase();
2851
+ this.classes = (css_rule.match(this.CLASS_RE) || [1, ''])[1].split('.').without('');
2852
+
2853
+ this.buildMatch();
2854
+ },
2855
+
2856
+ /**
2857
+ * cecks if the node matches the atom
2858
+ *
2859
+ * @param Element element
2860
+ * @return Boolean check result
2861
+ */
2862
+ match: null, // this method is dinamically generated depend on the situation
2863
+
2864
+ // protected
2865
+
2866
+ // building the match method for the particular case
2867
+ buildMatch: function() {
2868
+ var matchers = [];
2869
+
2870
+ if (this.id) matchers.push('matchId');
2871
+ if (this.tag != '*') matchers.push('matchTag');
2872
+ if (this.classes.length) matchers.push('matchClass');
2873
+ if (!Object.empty(this.attrs)) matchers.push('matchAttrs');
2874
+ if (this.pseudo) matchers.push('matchPseudo');
2875
+
2876
+ if (matchers.length == 1) {
2877
+ this.match = this[matchers[0]];
2878
+ } else if (matchers.length) {
2879
+ var length = matchers.length;
2880
+ this.match = function(element) {
2881
+ for (var i=0; i < length; i++)
2882
+ if (!this[matchers[i]](element))
2883
+ return false;
2884
+ return true;
2885
+ }
2886
+ } else {
2887
+ this.match = function() { return true; }
2888
+ }
2889
+ },
2890
+
2891
+ matchId: function(element) {
2892
+ return element.id == this.id;
2893
+ },
2894
+
2895
+ matchTag: function(element) {
2896
+ return element.tagName == this.tag;
2897
+ },
2898
+
2899
+ matchClass: function(element) {
2900
+ if (element.className) {
2901
+ var names = element.className.split(' ');
2902
+ if (names.length == 1) {
2903
+ return this.classes.indexOf(names[0]) != -1;
2904
+ } else {
2905
+ for (var i=0, length = this.classes.length; i < length; i++)
2906
+ if (names.indexOf(this.classes[i]) == -1)
2907
+ return false;
2908
+
2909
+ return true;
2910
+ }
2911
+ }
2912
+ return false;
2913
+ },
2914
+
2915
+ matchAttrs: function(element) {
2916
+ var matches = true;
2917
+ for (var key in this.attrs) {
2918
+ matches &= this.matchAttr(element, key, this.attrs[key]['op'], this.attrs[key]['value']);
2919
+ }
2920
+ return matches;
2921
+ },
2922
+
2923
+ matchAttr: function(element, name, operator, value) {
2924
+ var attr = element.getAttribute(name) || '';
2925
+ switch(operator) {
2926
+ case '=': return attr == value;
2927
+ case '*=': return attr.includes(value);
2928
+ case '^=': return attr.startsWith(value);
2929
+ case '$=': return attr.endsWith(value);
2930
+ case '~=': return attr.split(' ').includes(value);
2931
+ case '|=': return attr.split('-').includes(value);
2932
+ default: return attr != '';
2933
+ }
2934
+ return false;
2935
+ },
2936
+
2937
+ matchPseudo: function(element) {
2938
+ return this.pseudoMatchers[this.pseudo].call(element, this.pseudoValue, this.pseudoMatchers);
2939
+ },
2940
+
2941
+ /**
2942
+ * W3C pseudo matchers
2943
+ *
2944
+ * NOTE: methods of the module will be called in a context of an element
2945
+ */
2946
+ pseudoMatchers: {
2947
+ checked: function() {
2948
+ return this.checked;
2949
+ },
2950
+
2951
+ disabled: function() {
2952
+ return this.disabled;
2953
+ },
2954
+
2955
+ empty: function() {
2956
+ return !(this.innerText || this.innerHTML || this.textContent || '').length;
2957
+ },
2958
+
2959
+ 'first-child': function(tag_name) {
2960
+ var node = this;
2961
+ while ((node = node.previousSibling)) {
2962
+ if (node.tagName && (!tag_name || node.tagName == tag_name)) {
2963
+ return false;
2964
+ }
2965
+ }
2966
+ return true;
2967
+ },
2968
+
2969
+ 'first-of-type': function() {
2970
+ return arguments[1]['first-child'].call(this, this.tagName);
2971
+ },
2972
+
2973
+ 'last-child': function(tag_name) {
2974
+ var node = this;
2975
+ while ((node = node.nextSibling)) {
2976
+ if (node.tagName && (!tag_name || node.tagName == tag_name)) {
2977
+ return false;
2978
+ }
2979
+ }
2980
+ return true;
2981
+ },
2982
+
2983
+ 'last-of-type': function() {
2984
+ return arguments[1]['last-child'].call(this, this.tagName);
2985
+ },
2986
+
2987
+ 'only-child': function(tag_name, matchers) {
2988
+ return matchers['first-child'].call(this, tag_name)
2989
+ && matchers['last-child'].call(this, tag_name);
2990
+ },
2991
+
2992
+ 'only-of-type': function() {
2993
+ return arguments[1]['only-child'].call(this, this.tagName, arguments[1]);
2994
+ },
2995
+
2996
+ 'nth-child': function(number, matchers, tag_name) {
2997
+ if (!matchers.hasParent(this)) return false;
2998
+ number = number.toLowerCase();
2999
+
3000
+ if (number == 'n') return true;
3001
+
3002
+ if (number.includes('n')) {
3003
+ // parsing out the matching expression
3004
+ var a = b = 0;
3005
+ if (m = number.match(/^([+-]?\d*)?n([+-]?\d*)?$/)) {
3006
+ a = m[1] == '-' ? -1 : parseInt(m[1], 10) || 1;
3007
+ b = parseInt(m[2], 10) || 0;
3008
+ }
3009
+
3010
+ // getting the element index
3011
+ var index = 1, node = this;
3012
+ while ((node = node.previousSibling)) {
3013
+ if (node.tagName && (!tag_name || node.tagName == tag_name)) index++;
3014
+ }
3015
+
3016
+ return (index - b) % a == 0 && (index - b) / a >= 0;
3017
+
3018
+ } else {
3019
+ return matchers['index'].call(this, number.toInt() - 1, matchers, tag_name);
3020
+ }
3021
+ },
3022
+
3023
+ 'nth-of-type': function(number) {
3024
+ return arguments[1]['nth-child'].call(this, number, arguments[1], this.tagName);
3025
+ },
3026
+
3027
+ // protected
3028
+ index: function(number, matchers, tag_name) {
3029
+ number = isString(number) ? number.toInt() : number;
3030
+ var node = this, count = 0;
3031
+ while ((node = node.previousSibling)) {
3032
+ if (node.tagName && (!tag_name || node.tagName == tag_name) && ++count > number) return false;
3033
+ }
3034
+ return count == number;
3035
+ },
3036
+
3037
+ // checking if the element has a parent node
3038
+ // the '-----fake' parent is a temporary context for the element
3039
+ // just of the matching process
3040
+ hasParent: function(element) {
3041
+ return element.parentNode && element.parentNode.id != '-----fake';
3042
+ }
3043
+ }
3044
+ });
3045
+
3046
+ /**
3047
+ * represents a manual (virtual) selector strategy
3048
+ *
3049
+ * Credits:
3050
+ * Some principles were inspired by
3051
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
3052
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
3053
+ *
3054
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
3055
+ */
3056
+ Selector.Manual = new Class({
3057
+ ATOMS_SPLIT_RE: /(\s*([~>+ ])\s*)(?![^\s\)\]]*(\)|\]))/,
3058
+
3059
+ /**
3060
+ * constructor
3061
+ *
3062
+ * @param String css-rule
3063
+ */
3064
+ initialize: function(css_rule) {
3065
+ var css_rule = css_rule.trim();
3066
+ this.cssRule = css_rule;
3067
+
3068
+ this.atoms = [];
3069
+
3070
+ var relation = null, match = null;
3071
+
3072
+ while (match = css_rule.match(this.ATOMS_SPLIT_RE)) {
3073
+ separator_pos = css_rule.indexOf(match[0]);
3074
+ this.atoms.push(new Selector.Atom(css_rule.substring(0, separator_pos), relation));
3075
+
3076
+ relation = match[2]; // <- puts the current relation to the next atom
3077
+
3078
+ // chopping off the first atom of the rule
3079
+ css_rule = css_rule.substr(separator_pos+(match[1].length==1 ? 1 : match[1].length-1)).trim();
3080
+ }
3081
+ this.atoms.push(new Selector.Atom(css_rule, relation));
3082
+ },
3083
+
3084
+ /**
3085
+ * searches for the first matching subnode
3086
+ *
3087
+ * @param Element base node
3088
+ * @return Element matching element or null if nothing found
3089
+ */
3090
+ first: function(node) {
3091
+ return this.select(node).first();
3092
+ },
3093
+
3094
+ /**
3095
+ * selects all the matching subnodes
3096
+ *
3097
+ * @param Element base node
3098
+ * @return Array found nodes
3099
+ */
3100
+ select: function(node) {
3101
+ var founds, atom, index, sub_founds;
3102
+
3103
+ for (var i=0, i_length = this.atoms.length; i < i_length; i++) {
3104
+ atom = this.atoms[i];
3105
+ if (i == 0) {
3106
+ founds = this.find[atom.rel](node, atom);
3107
+
3108
+ } else {
3109
+ if (i > 1) founds = this.uniq(founds);
3110
+
3111
+ for (var j=0; j < founds.length; j++) {
3112
+ sub_founds = this.find[atom.rel](founds[j], atom);
3113
+
3114
+ sub_founds.unshift(1); // <- nuke the parent node out of the list
3115
+ sub_founds.unshift(j); // <- position to insert the subresult
3116
+
3117
+ founds.splice.apply(founds, sub_founds);
3118
+
3119
+ j += sub_founds.length - 3;
3120
+ }
3121
+ }
3122
+ }
3123
+
3124
+ return this.atoms.length > 1 ? this.uniq(founds) : founds;
3125
+ },
3126
+
3127
+ /**
3128
+ * checks if the node matches the rule
3129
+ *
3130
+ * @param Element node to check
3131
+ * @return boolean check result
3132
+ */
3133
+ match: function(element) {
3134
+ // if there's more than one atom, we match the element in a context
3135
+ if (!this.atoms || this.atoms.length > 1) {
3136
+ if (element.parentNode) {
3137
+ // searching for the top parent node
3138
+ // NOTE: don't use the Element.parents in here to avoid annecessary elements extending
3139
+ var p = element, parent;
3140
+ while ((p = p.parentNode)) parent = p;
3141
+ } else {
3142
+ // putting the element in a temporary context so we could test it
3143
+ var parent = document.createElement('div'), parent_is_fake = true;
3144
+ parent.id = '-----fake'; // <- this id is used in the manual 'match' method,
3145
+ // to determine if the element originally had no parent node
3146
+ parent.appendChild(element);
3147
+ }
3148
+
3149
+ var match = this.select(parent).includes(element);
3150
+ if (parent_is_fake) parent.removeChild(element);
3151
+ } else {
3152
+ // if there's just one atom, we simple match against it.
3153
+ var match = this.atoms[0].match(element);
3154
+ }
3155
+
3156
+ return match;
3157
+ },
3158
+
3159
+ // protected
3160
+ uniq: function(elements) {
3161
+ var uniq = [], uids = [], uid;
3162
+ for (var i=0, length = elements.length; i < length; i++) {
3163
+ uid = $uid(elements[i]);
3164
+ if (!uids[uid]) {
3165
+ uniq.push(elements[i]);
3166
+ uids[uid] = true;
3167
+ }
3168
+ }
3169
+
3170
+ return uniq;
3171
+ },
3172
+
3173
+ find: {
3174
+ /**
3175
+ * search for any descendant nodes
3176
+ */
3177
+ ' ': function(element, atom) {
3178
+ var founds = $A(element.getElementsByTagName(atom.tag));
3179
+ if (atom.hasNonTagMatcher) {
3180
+ var matching = [];
3181
+ for (var i=0, length = founds.length; i < length; i++) {
3182
+ if (atom.match(founds[i]))
3183
+ matching.push(founds[i]);
3184
+ }
3185
+ return matching;
3186
+ }
3187
+ return founds;
3188
+ },
3189
+
3190
+ /**
3191
+ * search for immidate descendant nodes
3192
+ */
3193
+ '>': function(element, atom) {
3194
+ var node = element.firstChild, matched = [];
3195
+ while (node) {
3196
+ if (atom.match(node)) {
3197
+ matched.push(node);
3198
+ }
3199
+ node = node.nextSibling;
3200
+ }
3201
+ return matched;
3202
+ },
3203
+
3204
+ /**
3205
+ * search for immiate sibling nodes
3206
+ */
3207
+ '+': function(element, atom) {
3208
+ while ((element = element.nextSibling)) {
3209
+ if (element.tagName) {
3210
+ return atom.match(element) ? [element] : [];
3211
+ }
3212
+ }
3213
+ return [];
3214
+ },
3215
+
3216
+ /**
3217
+ * search for late sibling nodes
3218
+ */
3219
+ '~': function(element, atom) {
3220
+ var founds = [];
3221
+ while ((element = element.nextSibling)) {
3222
+ if (atom.match(element))
3223
+ founds.push(element);
3224
+ }
3225
+ return founds;
3226
+ }
3227
+ }
3228
+
3229
+ });
3230
+
3231
+ /**
3232
+ * represents a complex, multi ruled select strategy
3233
+ *
3234
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
3235
+ */
3236
+ Selector.Multiple = new Class({
3237
+
3238
+ /**
3239
+ * constructor
3240
+ *
3241
+ * @param String css-rule
3242
+ */
3243
+ initialize: function(css_rule) {
3244
+ this.cssRule = css_rule;
3245
+ this.selectors = css_rule.split(',').map(function(rule) {
3246
+ return rule.blank() ? null : new Selector.Manual(rule);
3247
+ }).compact();
3248
+ },
3249
+
3250
+ /**
3251
+ * searches for the first matching subnode
3252
+ *
3253
+ * @param Element base node
3254
+ * @return Element matching element or null if nothing found
3255
+ */
3256
+ first: function(node) {
3257
+ return this.selectors.map('first', node).first(function(i) { return !!i;});
3258
+ },
3259
+
3260
+ /**
3261
+ * selects all the matching subnodes
3262
+ *
3263
+ * @param Element base node
3264
+ * @return Array found nodes
3265
+ */
3266
+ select: function(node) {
3267
+ return this.selectors.map('select', node, null).flatten().uniq();
3268
+ },
3269
+
3270
+ /**
3271
+ * checks if the node matches the rule
3272
+ *
3273
+ * @param Element node to check
3274
+ * @return boolean check result
3275
+ */
3276
+ match: function(node) {
3277
+ return this.selectors.some('match', node) || !this.selectors.length;
3278
+ }
3279
+ });
3280
+
3281
+
3282
+ /**
3283
+ * the window object extensions
3284
+ *
3285
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
3286
+ */
3287
+ $ext(self, (function(win) {
3288
+ var old_scroll = win.scrollTo;
3289
+
3290
+ return {
3291
+ /**
3292
+ * returns the inner-sizes of the window
3293
+ *
3294
+ * @return Object x: d+, y: d+
3295
+ */
3296
+ sizes: function() {
3297
+ return this.innerWidth ? {x: this.innerWidth, y: this.innerHeight} :
3298
+ {x: document.documentElement.clientWidth, y: document.documentElement.clientHeight};
3299
+ },
3300
+
3301
+ /**
3302
+ * returns the scrolls for the window
3303
+ *
3304
+ * @return Object x: d+, y: d+
3305
+ */
3306
+ scrolls: function() {
3307
+ return (this.pageXOffset || this.pageYOffset) ? {x: this.pageXOffset, y: this.pageYOffset} :
3308
+ (this.document.body.scrollLeft || this.document.body.scrollTop) ?
3309
+ {x: this.document.body.scrollLeft, y: this.document.body.scrollTop} :
3310
+ {x: this.document.documentElement.scrollLeft, y: this.document.documentElement.scrollTop};
3311
+ },
3312
+
3313
+ /**
3314
+ * overloading the native scrollTo method to support hashes and element references
3315
+ *
3316
+ * @param mixed number left position, a hash position, element or a string element id
3317
+ * @param number top position
3318
+ * @return window self
3319
+ */
3320
+ scrollTo: function(left, top) {
3321
+ if(isElement(left) || (isString(left) && $(left))) {
3322
+ left = $(left).position();
3323
+ }
3324
+
3325
+ if (isHash(left)) {
3326
+ top = left.y;
3327
+ left = left.x;
3328
+ }
3329
+
3330
+ old_scroll(left, top);
3331
+
3332
+ return this;
3333
+ }
3334
+ };
3335
+
3336
+ })(window));
3337
+
3338
+ /**
3339
+ * The dom-ready event handling code
3340
+ *
3341
+ * Credits:
3342
+ * The basic principles of the module are originated from
3343
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
3344
+ *
3345
+ * Copyright (C) 2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
3346
+ */
3347
+ [window, document].each(function(object) {
3348
+ Observer.createShortcuts(object, ['ready']);
3349
+ var ready = object.ready.bind(object);
3350
+
3351
+ if (Browser.IE) {
3352
+ var tmp = $E('div');
3353
+ (function() {
3354
+ var loaded = false;
3355
+ try {
3356
+ document.body.appendChild(tmp);
3357
+ tmp.remove();
3358
+ loaded = true;
3359
+ } catch(e) { arguments.callee.delay(50);}
3360
+ if (loaded) ready();
3361
+ })();
3362
+ } else if (document['readyState'] !== undefined) {
3363
+ (function() {
3364
+ $w('loaded complete').includes(document.readyState) ? ready() : arguments.callee.delay(50);
3365
+ })();
3366
+ } else {
3367
+ document.addEventListener('DOMContentLoaded', ready, false);
3368
+ }
3369
+
3370
+ });
3371
+
3372
+ /**
3373
+ * The form unit class and extensions
3374
+ *
3375
+ * Credits:
3376
+ * The basic principles of the module are inspired by
3377
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
3378
+ *
3379
+ * Copyright (C) 2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
3380
+ */
3381
+ var Form = new Class(Element, {
3382
+ /**
3383
+ * generic forms creation constructor
3384
+ *
3385
+ * @param Object form options
3386
+ */
3387
+ initialize: function(options) {
3388
+ var options = options || {}, remote = options['remote'],
3389
+ form = this.$super('form', Object.without(options, 'remote'));
3390
+
3391
+ if (remote) form.remotize();
3392
+
3393
+ return form;
3394
+ },
3395
+
3396
+ extend: {
3397
+ /**
3398
+ * IE browsers manual elements extending
3399
+ *
3400
+ * @param Element form
3401
+ * @return Form element
3402
+ */
3403
+ ext: function(element) {
3404
+ return $ext(element, this.Methods);
3405
+ },
3406
+
3407
+ Methods: {},
3408
+
3409
+ /**
3410
+ * Extends the form functionality
3411
+ *
3412
+ * @param Object methods hash
3413
+ * @return void
3414
+ */
3415
+ addMethods: function(methods, dont_overwrite) {
3416
+ $ext(Form.Methods, methods, dont_overwrite);
3417
+
3418
+ try { // trying to extend the form element prototype
3419
+ $ext(HTMLFormElement.prototype, methods, dont_overwrite);
3420
+ } catch(e) {}
3421
+ }
3422
+ }
3423
+ });
3424
+
3425
+ Form.addMethods({
3426
+ /**
3427
+ * returns the form elements as an array of extended units
3428
+ *
3429
+ * @return Array of elements
3430
+ */
3431
+ getElements: function() {
3432
+ return this.select('input,select,textarea,button');
3433
+ },
3434
+
3435
+ /**
3436
+ * returns the list of all the input elements on the form
3437
+ *
3438
+ * @return Array of elements
3439
+ */
3440
+ inputs: function() {
3441
+ return this.getElements().filter(function(input) {
3442
+ return !['submit', 'button', 'reset', 'image', null].includes(input.type);
3443
+ });
3444
+ },
3445
+
3446
+ /**
3447
+ * focuses on the first input element on the form
3448
+ *
3449
+ * @return Form this
3450
+ */
3451
+ focus: function() {
3452
+ var first = this.inputs().first(function(input) { return input.type != 'hidden'; });
3453
+ if (first) first.focus();
3454
+ return this.fire('focus');
3455
+ },
3456
+
3457
+ /**
3458
+ * removes focus out of all the form elements
3459
+ *
3460
+ * @return Form this
3461
+ */
3462
+ blur: function() {
3463
+ this.getElements().each('blur');
3464
+ return this.fire('blur');
3465
+ },
3466
+
3467
+ /**
3468
+ * disables all the elements on the form
3469
+ *
3470
+ * @return Form this
3471
+ */
3472
+ disable: function() {
3473
+ this.getElements().each('disable');
3474
+ return this.fire('disable');
3475
+ },
3476
+
3477
+ /**
3478
+ * enables all the elements on the form
3479
+ *
3480
+ * @return Form this
3481
+ */
3482
+ enable: function() {
3483
+ this.getElements().each('enable');
3484
+ return this.fire('enable');
3485
+ },
3486
+
3487
+ /**
3488
+ * returns the list of the form values
3489
+ *
3490
+ * @return Object values
3491
+ */
3492
+ values: function() {
3493
+ var values = {};
3494
+
3495
+ this.inputs().each(function(input) {
3496
+ if (!input.disabled && input.name && (!['checkbox', 'radio'].includes(input.type) || input.checked))
3497
+ values[input.name] = input.getValue();
3498
+ });
3499
+
3500
+ return values;
3501
+ },
3502
+
3503
+ /**
3504
+ * returns the key/values organized ready to be sent via a get request
3505
+ *
3506
+ * @return String serialized values
3507
+ */
3508
+ serialize: function() {
3509
+ return Object.toQueryString(this.values());
3510
+ }
3511
+ });
3512
+
3513
+ // creating the shortcuts
3514
+ Form.addMethods(Observer.createShortcuts({}, $w('submit reset focus')), true);
3515
+
3516
+
3517
+
3518
+ /**
3519
+ * there is the form-elements additional methods container
3520
+ *
3521
+ * Credits:
3522
+ * The basic ideas are taken from
3523
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
3524
+ *
3525
+ * Copyright (C) 2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
3526
+ */
3527
+ (function() {
3528
+ // trying to get the input element classes list
3529
+ try { var input_classes = [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement];
3530
+ } catch(e) { var input_classes = []; }
3531
+
3532
+ Form.Element = {
3533
+ /**
3534
+ * IE browsers manual elements extending
3535
+ *
3536
+ * @param Element element
3537
+ * @return Element extended element
3538
+ */
3539
+ ext: function(element) {
3540
+ // highjack the native methods to be able to call them froum our wrappers
3541
+ element._blur = element.blur;
3542
+ element._focus = element.focus;
3543
+ element._select = element.select;
3544
+
3545
+ return $ext(element, this.Methods);
3546
+ },
3547
+
3548
+ // the methods container
3549
+ Methods: {},
3550
+
3551
+ /**
3552
+ * Extends the Form.Element methods
3553
+ *
3554
+ * @param Object methods list
3555
+ * @return void
3556
+ */
3557
+ addMethods: function(methods, dont_overwrite) {
3558
+ $ext(this.Methods, methods, dont_overwrite);
3559
+
3560
+ // extending the input element prototypes
3561
+ input_classes.each(function(klass) {
3562
+ $ext(klass.prototype, methods, dont_overwrite);
3563
+ });
3564
+ }
3565
+ };
3566
+
3567
+ // creating the blur, focus and select methods aliases
3568
+ input_classes.each(function(klass) {
3569
+ $alias(klass.prototype, {
3570
+ _blur: 'blur',
3571
+ _focus: 'focus',
3572
+ _select: 'select'
3573
+ });
3574
+ });
3575
+ })();
3576
+
3577
+ Form.Element.addMethods({
3578
+ /**
3579
+ * uniform access to the element values
3580
+ *
3581
+ * @return String element value
3582
+ */
3583
+ getValue: function() {
3584
+ if (this.type == 'select-multiple') {
3585
+ return $A(this.getElementsByTagName('option')).map(function(option) {
3586
+ return option.selected ? option.value : null;
3587
+ }).compact();
3588
+ } else {
3589
+ return this.value;
3590
+ }
3591
+ },
3592
+
3593
+ /**
3594
+ * uniform accesss to set the element value
3595
+ *
3596
+ * @param String value
3597
+ * @return Element this
3598
+ */
3599
+ setValue: function(value) {
3600
+ if (this.type == 'select-multiple') {
3601
+ value = (isArray(value) ? value : [value]).map(String);
3602
+ $A(this.getElementsByTagName('option')).each(function(option) {
3603
+ option.selected = value.includes(option.value);
3604
+ });
3605
+ } else {
3606
+ this.value = value;
3607
+ }
3608
+ return this;
3609
+ },
3610
+
3611
+ /**
3612
+ * makes the element disabled
3613
+ *
3614
+ * @return Element this
3615
+ */
3616
+ disable: function() {
3617
+ this.disabled = true;
3618
+ this.fire('disable');
3619
+ return this;
3620
+ },
3621
+
3622
+ /**
3623
+ * makes the element enabled
3624
+ *
3625
+ * @return Element this
3626
+ */
3627
+ enable: function() {
3628
+ this.disabled = false;
3629
+ this.fire('enable');
3630
+ return this;
3631
+ },
3632
+
3633
+ /**
3634
+ * focuses on the element
3635
+ *
3636
+ * @return Element this
3637
+ */
3638
+ focus: function() {
3639
+ Browser.OLD ? this._focus() : this._focus.call(this);
3640
+ this.focused = true;
3641
+ this.fire('focus');
3642
+ return this;
3643
+ },
3644
+
3645
+ /**
3646
+ * focuses on the element and selects its content
3647
+ *
3648
+ * @return Element this
3649
+ */
3650
+ select: function() {
3651
+ this.focus();
3652
+ Browser.OLD ? this._select() : this._select.call(this);
3653
+ return this;
3654
+ },
3655
+
3656
+ /**
3657
+ * looses the element focus
3658
+ *
3659
+ * @return Element this
3660
+ */
3661
+ blur: function() {
3662
+ Browser.OLD ? this._blur() : this._blur.call(this);
3663
+ this.focused = false;
3664
+ this.fire('blur');
3665
+ return this;
3666
+ }
3667
+ });
3668
+
3669
+ // creating the common event shortcuts
3670
+ Form.Element.addMethods(Observer.createShortcuts({}, $w('disable enable focus blur change')), true);
3671
+
3672
+
3673
+ /**
3674
+ * this module handles the work with cookies
3675
+ *
3676
+ * Credits:
3677
+ * Most things in the unit are take from
3678
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
3679
+ *
3680
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
3681
+ */
3682
+ var Cookie = new Class({
3683
+ include: Options,
3684
+
3685
+ extend: {
3686
+ // sets the cookie
3687
+ set: function(name, value, options) {
3688
+ return new this(name, options).set(value);
3689
+ },
3690
+ // gets the cookie
3691
+ get: function(name) {
3692
+ return new this(name).get();
3693
+ },
3694
+ // deletes the cookie
3695
+ remove: function(name) {
3696
+ return new this(name).remove();
3697
+ },
3698
+
3699
+ // checks if the cookies are enabled
3700
+ enabled: function() {
3701
+ document.cookie = "__t=1";
3702
+ return document.cookie.indexOf("__t=1")!=-1;
3703
+ },
3704
+
3705
+ // some basic options
3706
+ Options: {
3707
+ secure: false,
3708
+ document: document
3709
+ }
3710
+ },
3711
+
3712
+ /**
3713
+ * constructor
3714
+ * @param String cookie name
3715
+ * @param Object options
3716
+ * @return void
3717
+ */
3718
+ initialize: function(name, options) {
3719
+ this.name = name;
3720
+ this.setOptions(options);
3721
+ },
3722
+
3723
+ /**
3724
+ * sets the cookie with the name
3725
+ *
3726
+ * @param mixed value
3727
+ * @return Cookie this
3728
+ */
3729
+ set: function(value) {
3730
+ var value = encodeURIComponent(value);
3731
+ if (this.options.domain) value += '; domain=' + this.options.domain;
3732
+ if (this.options.path) value += '; path=' + this.options.path;
3733
+ if (this.options.duration){
3734
+ var date = new Date();
3735
+ date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
3736
+ value += '; expires=' + date.toGMTString();
3737
+ }
3738
+ if (this.options.secure) value += '; secure';
3739
+ this.options.document.cookie = this.name + '=' + value;
3740
+ return this;
3741
+ },
3742
+
3743
+ /**
3744
+ * searches for a cookie with the name
3745
+ *
3746
+ * @return mixed saved value or null if nothing found
3747
+ */
3748
+ get: function() {
3749
+ var value = this.options.document.cookie.match('(?:^|;)\\s*' + RegExp.escape(this.name) + '=([^;]*)');
3750
+ return (value) ? decodeURIComponent(value[1]) : null;
3751
+ },
3752
+
3753
+ /**
3754
+ * removes the cookie
3755
+ *
3756
+ * @return Cookie this
3757
+ */
3758
+ remove: function() {
3759
+ this.options.duration = -1;
3760
+ return this.set('');
3761
+ }
3762
+ });
3763
+
3764
+ /**
3765
+ * XMLHttpRequest wrapper
3766
+ *
3767
+ * Credits:
3768
+ * Some of the functionality inspired by
3769
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
3770
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
3771
+ * - jQuery (http://jquery.com) Copyright (C) John Resig
3772
+ *
3773
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
3774
+ */
3775
+ var Xhr = new Class(Observer, {
3776
+ extend: {
3777
+ // supported events list
3778
+ EVENTS: $w('success failure complete request cancel create'),
3779
+
3780
+ // default options
3781
+ Options: {
3782
+ headers: {
3783
+ 'X-Requested-With': 'XMLHttpRequest',
3784
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
3785
+ },
3786
+ method: 'post',
3787
+ encoding: 'utf-8',
3788
+ async: true,
3789
+ evalScripts: false,
3790
+ evalResponse: false,
3791
+ evalJSON: true,
3792
+ secureJSON: true,
3793
+ urlEncoded: true,
3794
+ spinner: null,
3795
+ spinnerFx: 'fade',
3796
+ params: null
3797
+ },
3798
+
3799
+ /**
3800
+ * Shortcut to initiate and send an XHR in a single call
3801
+ *
3802
+ * @param String url
3803
+ * @param Object options
3804
+ * @return Xhr request
3805
+ */
3806
+ load: function(url, options) {
3807
+ return new this(url, Object.merge({method: 'get'}, options)).send();
3808
+ }
3809
+ },
3810
+
3811
+ /**
3812
+ * basic constructor
3813
+ *
3814
+ * @param String url
3815
+ * @param Object options
3816
+ */
3817
+ initialize: function(url, options) {
3818
+ this.initCallbacks(); // system level callbacks should be initialized before the user callbacks
3819
+
3820
+ this.url = url;
3821
+ this.$super(options);
3822
+
3823
+ // copying some options to the instance level attributes
3824
+ for (var key in Xhr.Options)
3825
+ this[key] = this.options[key];
3826
+
3827
+ this.initSpinner();
3828
+ },
3829
+
3830
+ /**
3831
+ * sets a header
3832
+ *
3833
+ * @param String header name
3834
+ * @param String header value
3835
+ * @return Xhr self
3836
+ */
3837
+ setHeader: function(name, value) {
3838
+ this.headers[name] = value;
3839
+ return this;
3840
+ },
3841
+
3842
+ /**
3843
+ * tries to get a response header
3844
+ *
3845
+ * @return mixed String header value or undefined
3846
+ */
3847
+ getHeader: function(name) {
3848
+ try {
3849
+ return this.xhr.getResponseHeader(name);
3850
+ } catch(e) {}
3851
+ },
3852
+
3853
+ /**
3854
+ * checks if the request was successful
3855
+ *
3856
+ * @return boolean check result
3857
+ */
3858
+ successful: function() {
3859
+ return (this.status >= 200) && (this.status < 300);
3860
+ },
3861
+
3862
+ /**
3863
+ * performs the actual request sending
3864
+ *
3865
+ * @param Object options
3866
+ * @return Xhr self
3867
+ */
3868
+ send: function(params) {
3869
+ var add_params = {}, url = this.url;
3870
+
3871
+ var method = this.method.toUpperCase();
3872
+ if (['PUT', 'DELETE'].includes(method)) {
3873
+ add_params['_method'] = method.toLowerCase();
3874
+ method = 'POST';
3875
+ }
3876
+
3877
+ var data = this.prepareData(this.params, this.prepareParams(params), add_params);
3878
+
3879
+ if (this.urlEncoded && method == 'POST' && !this.headers['Content-type']) {
3880
+ this.setHeader('Content-type', 'application/x-www-form-urlencoded; charset='+this.encoding);
3881
+ }
3882
+
3883
+ if (method == 'GET') {
3884
+ url += (url.includes('?') ? '&' : '?') + data;
3885
+ data = null;
3886
+ }
3887
+
3888
+ this.xhr = this.createXhr();
3889
+ this.fire('create');
3890
+
3891
+ this.xhr.open(method, url, this.async);
3892
+
3893
+ this.xhr.onreadystatechange = this.stateChanged.bind(this);
3894
+
3895
+ for (var key in this.headers) {
3896
+ this.xhr.setRequestHeader(key, this.headers[key]);
3897
+ }
3898
+
3899
+ this.xhr.send(data);
3900
+ this.fire('request');
3901
+
3902
+ if (!this.async) this.stateChanged();
3903
+
3904
+ return this;
3905
+ },
3906
+
3907
+ /**
3908
+ * elements automaticall update method, creates an Xhr request
3909
+ * and updates the element innerHTML value onSuccess.
3910
+ *
3911
+ * @param Element element
3912
+ * @param Object optional request params
3913
+ * @return Xhr self
3914
+ */
3915
+ update: function(element, params) {
3916
+ return this.onSuccess(function(r) { element.update(r.text); }).send(params);
3917
+ },
3918
+
3919
+ /**
3920
+ * stops the request processing
3921
+ *
3922
+ * @return Xhr self
3923
+ */
3924
+ cancel: function() {
3925
+ if (!this.xhr || this.xhr.canceled) return this;
3926
+
3927
+ this.xhr.abort();
3928
+ this.xhr.onreadystatechange = function() {};
3929
+ this.xhr.canceled = true;
3930
+
3931
+ return this.fire('cancel');
3932
+ },
3933
+
3934
+ // protected
3935
+ // wrapping the original method to send references to the xhr objects
3936
+ fire: function(name) {
3937
+ return this.$super(name, this, this.xhr);
3938
+ },
3939
+
3940
+ // creates new request instance
3941
+ createXhr: function() {
3942
+ if (this.form && this.form.getElements().map('type').includes('file')) {
3943
+ return new Xhr.IFramed(this.form);
3944
+ } else try {
3945
+ return new XMLHttpRequest();
3946
+ } catch(e) {
3947
+ return new ActiveXObject('MSXML2.XMLHTTP');
3948
+ }
3949
+ },
3950
+
3951
+ // prepares user sending params
3952
+ prepareParams: function(params) {
3953
+ if (params && params.tagName == 'FORM') {
3954
+ this.form = params;
3955
+ params = params.values();
3956
+ }
3957
+ return params;
3958
+ },
3959
+
3960
+ // converts all the params into a url params string
3961
+ prepareData: function() {
3962
+ return $A(arguments).map(function(param) {
3963
+ if (!isString(param)) {
3964
+ param = Object.toQueryString(param);
3965
+ }
3966
+ return param.blank() ? null : param;
3967
+ }).compact().join('&');
3968
+ },
3969
+
3970
+ // handles the state change
3971
+ stateChanged: function() {
3972
+ if (this.xhr.readyState != 4 || this.xhr.canceled) return;
3973
+
3974
+ try { this.status = this.xhr.status;
3975
+ } catch(e) { this.status = 0; }
3976
+
3977
+ this.text = this.responseText = this.xhr.responseText;
3978
+ this.xml = this.responseXML = this.xhr.responseXML;
3979
+
3980
+ this.fire('complete').fire(this.successful() ? 'success' : 'failure');
3981
+ },
3982
+
3983
+ // called on success
3984
+ tryScripts: function(response) {
3985
+ if (this.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) {
3986
+ $eval(this.text);
3987
+ } else if ((/json/).test(this.getHeader('Content-type')) && this.evalJSON) {
3988
+ this.json = this.responseJSON = this.sanitizedJSON();
3989
+ } else if (this.evalScripts) {
3990
+ this.text.evalScripts();
3991
+ }
3992
+ },
3993
+
3994
+ // sanitizes the json-response texts
3995
+ sanitizedJSON: function() {
3996
+ // checking the JSON response formatting
3997
+ if (!(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(this.text.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) {
3998
+ if (this.secureJSON) {
3999
+ throw "JSON parse error: "+this.text;
4000
+ } else {
4001
+ return null;
4002
+ }
4003
+ }
4004
+
4005
+ return eval("("+this.text+")");
4006
+ },
4007
+
4008
+ // initializes the request callbacks
4009
+ initCallbacks: function() {
4010
+ // creating an automatical spinner handling
4011
+ this.on('create', 'showSpinner').on('complete', 'hideSpinner').on('cancel', 'hideSpinner');
4012
+
4013
+ // response scripts evaluation, should be before the global xhr callbacks
4014
+ this.on('success', 'tryScripts');
4015
+
4016
+ // wiring the global xhr callbacks
4017
+ Xhr.EVENTS.each(function(name) {
4018
+ this.on(name, function() { Xhr.fire(name, this, this.xhr); });
4019
+ }, this);
4020
+ },
4021
+
4022
+ // inits the spinner
4023
+ initSpinner: function() {
4024
+ if (this.spinner)
4025
+ this.spinner = $(this.spinner);
4026
+
4027
+ if (Xhr.Options.spinner && this.spinner === $(Xhr.Options.spinner))
4028
+ this.spinner = null;
4029
+ },
4030
+
4031
+ showSpinner: function() { if (this.spinner) this.spinner.show(this.spinnerFx, {duration: 100}); },
4032
+ hideSpinner: function() { if (this.spinner) this.spinner.hide(this.spinnerFx, {duration: 100}); }
4033
+ });
4034
+
4035
+ // creating the class level observer
4036
+ Observer.create(Xhr);
4037
+
4038
+ // attaching the common spinner handling
4039
+ $ext(Xhr, {
4040
+ counter: 0,
4041
+ showSpinner: function() {
4042
+ if (this.Options.spinner) $(this.Options.spinner).show(this.Options.spinnerFx, {duration: 100});
4043
+ },
4044
+ hideSpinner: function() {
4045
+ if (this.Options.spinner) $(this.Options.spinner).hide(this.Options.spinnerFx, {duration: 100});
4046
+ }
4047
+ });
4048
+
4049
+ Xhr.on('create', function() {
4050
+ this.counter++;
4051
+ this.showSpinner();
4052
+ }).on('complete', function() {
4053
+ this.counter--;
4054
+ if (this.counter < 1) this.hideSpinner();
4055
+ }).on('cancel', function() {
4056
+ this.counter--;
4057
+ if (this.counter < 1) this.hideSpinner();
4058
+ });
4059
+
4060
+
4061
+ /**
4062
+ * Here are the Form unit Xhr extensions
4063
+ *
4064
+ * Credits:
4065
+ * Some of the functionality inspired by
4066
+ * - Prototype (http://prototypejs.org) Copyright (C) Sam Stephenson
4067
+ * - jQuery (http://jquery.com) Copyright (C) John Resig
4068
+ *
4069
+ * Copyright (C) 2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
4070
+ */
4071
+ Form.addMethods({
4072
+ /**
4073
+ * sends the form via xhr request
4074
+ *
4075
+ * @params Options xhr request options
4076
+ * @return Form this
4077
+ */
4078
+ send: function(options) {
4079
+ options = options || {};
4080
+ options['method'] = options['method'] || this.method || 'post';
4081
+
4082
+ new Xhr(this.get('action') || document.location.href, options
4083
+ ).onRequest(this.disable.bind(this)
4084
+ ).onComplete(this.enable.bind(this)).send(this);
4085
+
4086
+ return this;
4087
+ },
4088
+
4089
+ /**
4090
+ * makes the form be remote by default
4091
+ *
4092
+ * @params Object default options
4093
+ * @return Form this
4094
+ */
4095
+ remotize: function(options) {
4096
+ this.onsubmit = function() {
4097
+ this.send.bind(this, Object.merge({spinner: this.first('.spinner')}, options)).delay(20);
4098
+ return false;
4099
+ };
4100
+
4101
+ this.remote = true;
4102
+ return this;
4103
+ },
4104
+
4105
+ /**
4106
+ * removes the remote call hook
4107
+ *
4108
+ * NOTE: will nuke onsubmit attribute
4109
+ *
4110
+ * @return Form this
4111
+ */
4112
+ unremotize: function() {
4113
+ this.onsubmit = function() {};
4114
+ this.remote = false;
4115
+ return this;
4116
+ }
4117
+ });
4118
+
4119
+ /**
4120
+ * this module contains the Element unit XHR related extensions
4121
+ *
4122
+ * Credits:
4123
+ * - jQuery (http://jquery.com) Copyright (C) John Resig
4124
+ *
4125
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
4126
+ */
4127
+ Element.addMethods({
4128
+ /**
4129
+ * performs an Xhr request to the given url
4130
+ * and updates the element internals with the responseText
4131
+ *
4132
+ * @param String url address
4133
+ * @param Object xhr options
4134
+ * @return Element this
4135
+ */
4136
+ load: function(url, options) {
4137
+ new Xhr(url, Object.merge({method: 'get'}, options)).update(this);
4138
+ return this;
4139
+ }
4140
+ });
4141
+
4142
+ /**
4143
+ * This unit presents a fake drop in replacement for the XmlHTTPRequest unit
4144
+ * but works with an iframe targeting in the background
4145
+ *
4146
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
4147
+ */
4148
+ Xhr.IFramed = new Class({
4149
+ /**
4150
+ * constructor
4151
+ *
4152
+ * @param Form form which will be submitted via the frame
4153
+ * @return void
4154
+ */
4155
+ initialize: function(form) {
4156
+ this.form = form;
4157
+
4158
+ var id = 'xhr_frame_'+Math.random().toString().split('.').last();
4159
+ $E('div').insertTo(document.body).update('<iframe name="'+id+'" id="'+id+'" width="0" height="0" frameborder="0" src="about:blank"></iframe>');
4160
+
4161
+ this.iframe = $(id);
4162
+ this.iframe.on('load', this.onLoad.bind(this));
4163
+ },
4164
+
4165
+ send: function() {
4166
+ // stubbing the onsubmit method so it allowed us to submit the form
4167
+ var old_onsubmit = this.form.onsubmit,
4168
+ old_target = this.form.target;
4169
+
4170
+ this.form.onsubmit = function() {};
4171
+ this.form.target = this.iframe.id;
4172
+
4173
+ this.form.submit();
4174
+
4175
+ this.form.onsubmit = old_onsubmit;
4176
+ this.form.target = old_target;
4177
+ },
4178
+
4179
+ onLoad: function() {
4180
+ this.status = 200;
4181
+ this.readyState = 4;
4182
+
4183
+ var doc = window[this.iframe.id].document.documentElement;
4184
+ this.responseText = doc ? doc.innerHTML : null;
4185
+
4186
+ this.onreadystatechange();
4187
+ },
4188
+
4189
+ // dummy API methods
4190
+ open: function() {},
4191
+ abort: function() {},
4192
+ setRequestHeader: function() {},
4193
+ onreadystatechange: function() {}
4194
+ });
4195
+
4196
+ /**
4197
+ * Basic visual effects class
4198
+ *
4199
+ * Credits:
4200
+ * The basic principles, structures and naming system are inspired by
4201
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
4202
+ *
4203
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
4204
+ */
4205
+ var Fx = new Class(Observer, {
4206
+ extend: {
4207
+ EVENTS: $w('start finish cancel'),
4208
+
4209
+ // named durations
4210
+ Durations: {
4211
+ 'short': 200,
4212
+ 'normal': 400,
4213
+ 'long': 800
4214
+ },
4215
+
4216
+ // default options
4217
+ Options: {
4218
+ fps: Browser.IE ? 40 : 60,
4219
+ duration: 'normal',
4220
+ transition: 'Sin',
4221
+ queue: true
4222
+ },
4223
+
4224
+ // list of basic transitions
4225
+ Transitions: {
4226
+ Sin: function(i) {
4227
+ return -(Math.cos(Math.PI * i) - 1) / 2;
4228
+ },
4229
+
4230
+ Cos: function(i) {
4231
+ return Math.asin((i-0.5) * 2)/Math.PI + 0.5;
4232
+ },
4233
+
4234
+ Exp: function(i) {
4235
+ return Math.pow(2, 8 * (i - 1));
4236
+ },
4237
+
4238
+ Log: function(i) {
4239
+ return 1 - Math.pow(2, - 8 * i);
4240
+ },
4241
+
4242
+ Lin: function(i) {
4243
+ return i;
4244
+ }
4245
+ }
4246
+ },
4247
+
4248
+ /**
4249
+ * Basic constructor
4250
+ *
4251
+ * @param Object options
4252
+ */
4253
+ initialize: function(element, options) {
4254
+ this.$super(options);
4255
+ this.element = $(element);
4256
+ },
4257
+
4258
+ /**
4259
+ * starts the transition
4260
+ *
4261
+ * @return Fx this
4262
+ */
4263
+ start: function() {
4264
+ if (this.queue(arguments)) return this;
4265
+ this.prepare.apply(this, arguments);
4266
+
4267
+ this.transition = Fx.Transitions[this.options.transition] || this.options.transition;
4268
+ var duration = Fx.Durations[this.options.duration] || this.options.duration;
4269
+
4270
+ this.steps = (duration / 1000 * this.options.fps).ceil();
4271
+ this.number = 1;
4272
+
4273
+ return this.fire('start', this).startTimer();
4274
+ },
4275
+
4276
+ /**
4277
+ * finishes the transition
4278
+ *
4279
+ * @return Fx this
4280
+ */
4281
+ finish: function() {
4282
+ return this.stopTimer().fire('finish').next();
4283
+ },
4284
+
4285
+ /**
4286
+ * interrupts the transition
4287
+ *
4288
+ * @return Fx this
4289
+ */
4290
+ cancel: function() {
4291
+ return this.stopTimer().fire('cancel').next();
4292
+ },
4293
+
4294
+ /**
4295
+ * pauses the transition
4296
+ *
4297
+ * @return Fx this
4298
+ */
4299
+ pause: function() {
4300
+ return this.stopTimer();
4301
+ },
4302
+
4303
+ /**
4304
+ * resumes a paused transition
4305
+ *
4306
+ * @return Fx this
4307
+ */
4308
+ resume: function() {
4309
+ return this.startTimer();
4310
+ },
4311
+
4312
+ // protected
4313
+ // dummy method, should be implemented in a subclass
4314
+ prepare: function() {},
4315
+
4316
+ // dummy method, should implement the actual things happenning
4317
+ render: function(value) {},
4318
+
4319
+ // the periodically called method
4320
+ // NOTE: called outside of the instance scope!
4321
+ step: function($this) {
4322
+ if ($this.steps >= $this.number) {
4323
+ $this.render($this.transition($this.number / $this.steps));
4324
+
4325
+ $this.number ++;
4326
+ } else {
4327
+ $this.finish();
4328
+ }
4329
+ },
4330
+
4331
+ // calculates the current value
4332
+ calc: function(start, end, delata) {
4333
+ return start + (end - start) * delta;
4334
+ },
4335
+
4336
+ startTimer: function() {
4337
+ this.timer = this.step.periodical((1000 / this.options.fps).round(), this);
4338
+ return this;
4339
+ },
4340
+
4341
+ stopTimer: function() {
4342
+ if (this.timer) {
4343
+ this.timer.stop();
4344
+ }
4345
+ return this;
4346
+ },
4347
+
4348
+ // handles effects queing
4349
+ // should return false if there's no queue and true if there is a queue
4350
+ queue: function(args) {
4351
+ if (!this.element) return false;
4352
+ if (this.$chained) {
4353
+ delete(this['$chained']);
4354
+ return false;
4355
+ }
4356
+
4357
+ var uid = $uid(this.element), chain;
4358
+ if (!Fx.$chains) Fx.$chains = {};
4359
+ if (!Fx.$chains[uid]) Fx.$chains[uid] = [];
4360
+ chain = Fx.$chains[uid];
4361
+
4362
+ if (this.options.queue)
4363
+ chain.push([args, this]);
4364
+
4365
+ this.next = function() {
4366
+ var next = chain.shift(); next = chain[0];
4367
+ if (next) {
4368
+ next[1].$chained = true;
4369
+ next[1].start.apply(next[1], next[0]);
4370
+ }
4371
+ return this;
4372
+ };
4373
+
4374
+ return chain[0][1] !== this && this.options.queue;
4375
+ },
4376
+
4377
+ next: function() {
4378
+ return this;
4379
+ }
4380
+
4381
+
4382
+ });
4383
+
4384
+ /**
4385
+ * Here are the Array class extensions depend on the fx library
4386
+ *
4387
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
4388
+ */
4389
+ $ext(Array.prototype, {
4390
+ /**
4391
+ * converts the array into a string rgb(R,G,B) definition
4392
+ *
4393
+ * @return String rgb(DDD, DDD, DDD) value
4394
+ */
4395
+ toRgb: function() {
4396
+ return 'rgb('+this.map(Math.round)+')';
4397
+ }
4398
+ });
4399
+
4400
+ /**
4401
+ * There are the String unit extensions for the effects library
4402
+ *
4403
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-il>
4404
+ */
4405
+ String.COLORS = {
4406
+ maroon: '#800000',
4407
+ red: '#ff0000',
4408
+ orange: '#ffA500',
4409
+ yellow: '#ffff00',
4410
+ olive: '#808000',
4411
+ purple: '#800080',
4412
+ fuchsia: '#ff00ff',
4413
+ white: '#ffffff',
4414
+ lime: '#00ff00',
4415
+ green: '#008000',
4416
+ navy: '#000080',
4417
+ blue: '#0000ff',
4418
+ aqua: '#00ffff',
4419
+ teal: '#008080',
4420
+ black: '#000000',
4421
+ silver: '#c0c0c0',
4422
+ gray: '#808080',
4423
+ brown: '#a52a2a'
4424
+ };
4425
+
4426
+ $ext(String.prototype, {
4427
+ /**
4428
+ * converts a #XXX or rgb(X, X, X) sring into standard #XXXXXX color string
4429
+ *
4430
+ * @return String hex color
4431
+ */
4432
+ toHex: function() {
4433
+ var match = this.match(/^#(\w)(\w)(\w)$/);
4434
+
4435
+ if (match) {
4436
+ match = "#"+ match[1]+match[1]+match[2]+match[2]+match[3]+match[3];
4437
+ } else if (match = this.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)) {
4438
+ match = "#"+ match.slice(1).map(function(bit) {
4439
+ bit = (bit-0).toString(16);
4440
+ return bit.length == 1 ? '0'+bit : bit;
4441
+ }).join('');
4442
+ } else {
4443
+ match = String.COLORS[this] || this;
4444
+ }
4445
+
4446
+ return match;
4447
+ },
4448
+
4449
+ /**
4450
+ * converts a hex string into an rgb array
4451
+ *
4452
+ * @param boolean flag if need an array
4453
+ * @return String rgb(R,G,B) or Array [R,G,B]
4454
+ */
4455
+ toRgb: function(array) {
4456
+ var match = (this.toHex()||'').match(/#([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})/i);
4457
+
4458
+ if (match) {
4459
+ match = match.slice(1).map('toInt', 16);
4460
+ match = array ? match : match.toRgb();
4461
+ }
4462
+
4463
+ return match;
4464
+ }
4465
+ });
4466
+
4467
+ /**
4468
+ * This class provides the basic effect for styles manipulation
4469
+ *
4470
+ * Credits:
4471
+ * The idea is inspired by the Morph effect from
4472
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
4473
+ *
4474
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
4475
+ */
4476
+ Fx.Morph = new Class(Fx, {
4477
+
4478
+ // protected
4479
+
4480
+ /**
4481
+ * starts the effect
4482
+ *
4483
+ * @param mixed an Object with an end style or a string with the end class-name(s)
4484
+ * @return Fx this
4485
+ */
4486
+ prepare: function(style) {
4487
+ this.endStyle = this._findStyle(style);
4488
+ this.startStyle = this._getStyle(this.element, Object.keys(this.endStyle));
4489
+
4490
+ this._cleanStyles();
4491
+
4492
+ return this.$super();
4493
+ },
4494
+
4495
+ render: function(delta) {
4496
+ var value, start, end;
4497
+
4498
+ for (var key in this.endStyle) {
4499
+ start = this.startStyle[key];
4500
+ end = this.endStyle[key];
4501
+
4502
+ if (typeof(start) == 'number') {
4503
+ // handling floats like opacity
4504
+ value = start + (end - start) * delta;
4505
+
4506
+ } else if(start.length == 2) {
4507
+ // handling usual sizes with dimensions
4508
+ value = (start[0] + (end[0] - start[0]) * delta) + end[1];
4509
+
4510
+ } else if(start.length == 3) {
4511
+ // calculating colors
4512
+ value = end.map(function(value, i) {
4513
+ return start[i] + (value - start[i]) * delta;
4514
+ }).toRgb();
4515
+ }
4516
+
4517
+ if (key == 'opacity') {
4518
+ this.element.setOpacity(value);
4519
+ } else {
4520
+ this.element.style[key] = value;
4521
+ }
4522
+ }
4523
+ },
4524
+
4525
+ // private
4526
+
4527
+ // finds the style definition by a css-selector string
4528
+ _findStyle: function(style) {
4529
+ // a dummy node to calculate the end styles
4530
+ var element = this._dummy().setStyle(style);
4531
+
4532
+ // grabbing the computed styles
4533
+ var element_styles = element.computedStyles();
4534
+ var this_element_styles = this.element.computedStyles();
4535
+
4536
+ // grabbing the element style
4537
+ var end_style = this._getStyle(element, Object.keys(style), element_styles);
4538
+
4539
+ // assigning the border style if the end style has a border
4540
+ var border_style = element_styles.borderTopStyle, element_border_style = this_element_styles.borderTopStyle;
4541
+ if (border_style != element_border_style) {
4542
+ if (element_border_style == 'none') {
4543
+ this.element.style.borderWidth = '0px';
4544
+ }
4545
+ this.element.style.borderStyle = border_style;
4546
+ if (this._transp(this_element_styles.borderTopColor)) {
4547
+ this.element.style.borderColor = this_element_styles.color;
4548
+ }
4549
+ }
4550
+
4551
+ element.remove();
4552
+
4553
+ return end_style;
4554
+ },
4555
+
4556
+ // creates a dummy element to work with
4557
+ _dummy: function() {
4558
+ // a container for the styles extraction element
4559
+ var container = Fx.Morph.$c = (Fx.Morph.$c || $E('div', {style: "visibility:hidden;float:left;height:0;width:0"}));
4560
+ if (this.element.parentNode) this.element.parentNode.insertBefore(container, this.element);
4561
+
4562
+ return $(this.element.cloneNode(false)).insertTo(container);
4563
+ },
4564
+
4565
+ // grabs computed styles with the given keys out of the element
4566
+ _getStyle: function(element, keys, styles) {
4567
+ var style = {}, styles = styles || element.computedStyles(), name;
4568
+ if (isString(keys)) { name = keys, keys = [keys]; }
4569
+
4570
+ for (var i=0; i < keys.length; i++) {
4571
+ var key = keys[i].camelize();
4572
+
4573
+ // keys preprocessing
4574
+ if (key == 'background') key = 'backgroundColor';
4575
+ else if (key == 'border') {
4576
+ key = 'borderWidth';
4577
+ keys.splice(i+1, 0, 'borderColor'); // inserting the border color as the next unit
4578
+ }
4579
+
4580
+ // getting the actual style
4581
+ style[key] = element._getStyle(styles, key);
4582
+
4583
+ // Opera returns named colors as quoted strings
4584
+ if (Browser.Opera && /color/i.test(key)) style[key] = style[key].replace(/'|"/g, '');
4585
+
4586
+ // getting the real color if it's a transparent
4587
+ if (this._transp(style[key])) style[key] = this._getBGColor(element);
4588
+
4589
+ // getting the real width and height if they not set or set as 'auto'
4590
+ if (!style[key] || style[key] == 'auto') {
4591
+ style[key] = key == 'width' ? element.offsetWidth + 'px' :
4592
+ key == 'height' ? element.offsetHeight + 'px' : '';
4593
+ }
4594
+ }
4595
+
4596
+ return name ? style[name] : style;
4597
+ },
4598
+
4599
+ // looking for the visible background color of the element
4600
+ _getBGColor: function(element) {
4601
+ return [element].concat(element.parents()).map(function(node) {
4602
+ var bg = node.getStyle('backgroundColor');
4603
+ return (bg && !this._transp(bg)) ? bg : null;
4604
+ }, this).compact().first() || 'rgb(255,255,255)';
4605
+ },
4606
+
4607
+ // prepares the style values to be processed correctly
4608
+ _cleanStyles: function() {
4609
+ var end = this.endStyle, start = this.startStyle;
4610
+
4611
+ // filling up missing styles
4612
+ for (var key in end) {
4613
+ if (start[key] === '' && /^[\d\.\-]+[a-z]+$/.test(end[key])) {
4614
+ start[key] = '0px';
4615
+ }
4616
+ }
4617
+
4618
+ [end, start].each(this._cleanStyle, this);
4619
+
4620
+ // removing duplications between start and end styles
4621
+ for (var key in end) {
4622
+ if (!defined(start[key]) || (end[key] instanceof Array ? end[key].join() === start[key].join() : end[key] === start[key])) {
4623
+ delete(end[key]);
4624
+ delete(start[key]);
4625
+ }
4626
+ }
4627
+ },
4628
+
4629
+ // cleans up a style object
4630
+ _cleanStyle: function(style) {
4631
+ var match;
4632
+ for (var key in style) {
4633
+ style[key] = String(style[key]);
4634
+
4635
+ if (/color/i.test(key)) {
4636
+ // preparing the colors
4637
+ style[key] = style[key].toRgb(true);
4638
+ if (!style[key]) delete(style[key]);
4639
+ } else if (/^[\d\.]+$/.test(style[key])) {
4640
+ // preparing numberic values
4641
+ style[key] = style[key].toFloat();
4642
+ } else if (match = style[key].match(/^([\d\.\-]+)([a-z]+)$/i)) {
4643
+ // preparing values with dimensions
4644
+ style[key] = [match[1].toFloat(), match[2]];
4645
+
4646
+ } else {
4647
+ delete(style[key]);
4648
+ }
4649
+ }
4650
+ },
4651
+
4652
+ // checks if the color is transparent
4653
+ _transp: function(color) {
4654
+ return color == 'transparent' || color == 'rgba(0, 0, 0, 0)';
4655
+ }
4656
+ });
4657
+
4658
+ /**
4659
+ * the elements hightlighting effect
4660
+ *
4661
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
4662
+ */
4663
+ Fx.Highlight = new Class(Fx.Morph, {
4664
+ extend: {
4665
+ Options: Object.merge(Fx.Options, {
4666
+ color: '#FF8',
4667
+ transition: 'Exp'
4668
+ })
4669
+ },
4670
+
4671
+ // protected
4672
+
4673
+ /**
4674
+ * starts the transition
4675
+ *
4676
+ * @param String the hightlight color
4677
+ * @param String optional fallback color
4678
+ * @return self
4679
+ */
4680
+ prepare: function(start, end) {
4681
+ var end_color = end || this.element.getStyle('backgroundColor');
4682
+
4683
+ if (this._transp(end_color)) {
4684
+ this.onFinish(function() { this.element.style.backgroundColor = 'transparent'; });
4685
+ end_color = this._getBGColor(this.element);
4686
+ }
4687
+
4688
+ this.element.style.backgroundColor = (start || this.options.color);
4689
+
4690
+ return this.$super({backgroundColor: end_color});
4691
+ }
4692
+ });
4693
+
4694
+ /**
4695
+ * this is a superclass for the bidirectional effects
4696
+ *
4697
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
4698
+ */
4699
+ Fx.Twin = new Class(Fx.Morph, {
4700
+
4701
+ /**
4702
+ * hides the element if it meant to be switched off
4703
+ *
4704
+ * @return Fx self
4705
+ */
4706
+ finish: function() {
4707
+ if (this.how == 'out')
4708
+ this.element.hide();
4709
+
4710
+ return this.$super();
4711
+ },
4712
+
4713
+ // protected
4714
+
4715
+ /**
4716
+ * assigns the direction of the effect in or out
4717
+ *
4718
+ * @param String 'in', 'out' or 'toggle', 'toggle' by default
4719
+ */
4720
+ setHow: function(how) {
4721
+ this.how = how || 'toggle';
4722
+
4723
+ if (this.how == 'toggle')
4724
+ this.how = this.element.visible() ? 'out' : 'in';
4725
+ }
4726
+
4727
+ });
4728
+
4729
+ /**
4730
+ * the slide effects wrapper
4731
+ *
4732
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
4733
+ */
4734
+ Fx.Slide = new Class(Fx.Twin, {
4735
+ extend: {
4736
+ Options: Object.merge(Fx.Options, {
4737
+ direction: 'top'
4738
+ })
4739
+ },
4740
+
4741
+ // protected
4742
+ prepare: function(how) {
4743
+ this.setHow(how);
4744
+
4745
+ this.element.show();
4746
+ this.sizes = this.element.sizes();
4747
+
4748
+ this.styles = {};
4749
+ $w('overflow height width marginTop marginLeft').each(function(key) {
4750
+ this.styles[key] = this.element.style[key];
4751
+ }, this);
4752
+
4753
+ this.element.style.overflow = 'hidden';
4754
+ this.onFinish('_getBack').onCancel('_getBack');
4755
+
4756
+ return this.$super(this._endStyle(this.options.direction));
4757
+ },
4758
+
4759
+ _getBack: function() {
4760
+ this.element.setStyle(this.styles);
4761
+ },
4762
+
4763
+ // calculates the final style
4764
+ _endStyle: function(direction) {
4765
+ var style = {}, sizes = this.sizes,
4766
+ margin_left = (this.styles.marginLeft || '0').toFloat(),
4767
+ margin_top = (this.styles.marginTop || '0').toFloat();
4768
+
4769
+ if (this.how == 'out') {
4770
+ style[['top', 'bottom'].includes(direction) ? 'height' : 'width'] = '0px';
4771
+
4772
+ if (direction == 'right') {
4773
+ style.marginLeft = margin_left + sizes.x+'px';
4774
+ } else if (direction == 'bottom') {
4775
+ style.marginTop = margin_top + sizes.y +'px';
4776
+ }
4777
+
4778
+ } else if (this.how == 'in') {
4779
+ var element_style = this.element.style;
4780
+
4781
+ if (['top', 'bottom'].includes(direction)) {
4782
+ style.height = sizes.y + 'px';
4783
+ element_style.height = '0px';
4784
+ } else {
4785
+ style.width = sizes.x + 'px';
4786
+ element_style.width = '0px';
4787
+ }
4788
+
4789
+ if (direction == 'right') {
4790
+ style.marginLeft = margin_left + 'px';
4791
+ element_style.marginLeft = margin_left + sizes.x + 'px';
4792
+ } else if (direction == 'bottom') {
4793
+ style.marginTop = margin_top + 'px';
4794
+ element_style.marginTop = margin_top + sizes.y + 'px';
4795
+ }
4796
+ }
4797
+
4798
+ return style;
4799
+ }
4800
+
4801
+ });
4802
+
4803
+ /**
4804
+ * The opacity effects wrapper
4805
+ *
4806
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
4807
+ */
4808
+ Fx.Fade = new Class(Fx.Twin, {
4809
+ prepare: function(how) {
4810
+ this.setHow(how);
4811
+
4812
+ if (this.how == 'in')
4813
+ this.element.setOpacity(0).show();
4814
+
4815
+ return this.$super({opacity: typeof(how) == 'number' ? how : this.how == 'in' ? 1 : 0});
4816
+ }
4817
+ });
4818
+
4819
+ /**
4820
+ * This block contains additional Element shortcuts for effects easy handling
4821
+ *
4822
+ * Credits:
4823
+ * Some ideas are inspired by
4824
+ * - MooTools (http://mootools.net) Copyright (C) Valerio Proietti
4825
+ *
4826
+ * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
4827
+ */
4828
+ Element.addMethods((function(methods) {
4829
+ var old_hide = methods.hide,
4830
+ old_show = methods.show,
4831
+ old_resize = methods.resize;
4832
+
4833
+ return {
4834
+
4835
+ /**
4836
+ * hides the element with given visual effect
4837
+ *
4838
+ * @param String fx name
4839
+ * @param Object fx options
4840
+ */
4841
+ hide: function(fx, options) {
4842
+ return fx ? this.fx(fx, ['out', options], old_hide) : old_hide.call(this);
4843
+ },
4844
+
4845
+ /**
4846
+ * shows the element with the given visual effect
4847
+ *
4848
+ * @param String fx name
4849
+ * @param Object fx options
4850
+ */
4851
+ show: function(fx, options) {
4852
+ return fx ? this.fx(fx, ['in', options], old_show) : old_show.call(this);
4853
+ },
4854
+
4855
+ /**
4856
+ * resizes the element using the Morph visual effect
4857
+ *
4858
+ * @param Integer width
4859
+ * @param Integer height
4860
+ * @param Object options
4861
+ */
4862
+ resize: function(width, height, options) {
4863
+ if (isHash(width)) {
4864
+ height = width.y;
4865
+ width = width.x;
4866
+ }
4867
+ if (options) {
4868
+ var style = {};
4869
+ if (isNumber(height)) style.height = height+'px';
4870
+ if (isNumber(width)) style.width = width +'px';
4871
+
4872
+ if (!isHash(options)) options = {duration: options};
4873
+
4874
+ return this.fx('morph', [style, options]);
4875
+ } else {
4876
+ return old_resize.call(this, width, height);
4877
+ }
4878
+ },
4879
+
4880
+ /**
4881
+ * runs the Fx.Morth effect to the given style
4882
+ *
4883
+ * @param Object style or a String class names
4884
+ * @param Object optional effect options
4885
+ * @return Element self
4886
+ */
4887
+ morph: function(style, options) {
4888
+ return this.fx('morph', [style, options || {}]); // <- don't replace with arguments
4889
+ },
4890
+
4891
+ /**
4892
+ * highlights the element
4893
+ *
4894
+ * @param String start color
4895
+ * @param String optional end color
4896
+ * @param Object effect options
4897
+ * @return Element self
4898
+ */
4899
+ highlight: function() {
4900
+ return this.fx('highlight', arguments);
4901
+ },
4902
+
4903
+ /**
4904
+ * runs the Fx.Fade effect on the element
4905
+ *
4906
+ * @param mixed fade direction 'in' 'out' or a float number
4907
+ * @return Element self
4908
+ */
4909
+ fade: function() {
4910
+ return this.fx('fade', arguments);
4911
+ },
4912
+
4913
+ /**
4914
+ * runs the Fx.Slide effect on the element
4915
+ *
4916
+ * @param String 'in' or 'out'
4917
+ * @param Object effect options
4918
+ * @return Element self
4919
+ */
4920
+ slide: function() {
4921
+ return this.fx('slide', arguments);
4922
+ },
4923
+
4924
+ // protected
4925
+
4926
+ // runs an Fx on the element
4927
+ fx: function(name, args, on_finish) {
4928
+ var args = $A(args).compact(), options = {};
4929
+ if (isHash(args.last())) { options = args.pop(); }
4930
+
4931
+ var fx = new Fx[name.capitalize()](this, options);
4932
+ if (on_finish) fx.onFinish(on_finish.bind(this));
4933
+ fx.start.apply(fx, args);
4934
+
4935
+ return this;
4936
+ }
4937
+
4938
+ }})(Element.Methods));
4939
+