Joomla! PHP Cross Reference Web Portals

Source: /media/system/js/mootools-more-uncompressed.js - 13373 lines - 348861 bytes - Summary - Text - Print

   1  // MooTools: the javascript framework.
   2  // Load this file's selection again by visiting: http://mootools.net/more/065f2f092ece4e3b32bb5214464cf926 
   3  // Or build this file again with packager using: packager build More/More More/Events.Pseudos More/Class.Refactor More/Class.Binds More/Class.Occlude More/Chain.Wait More/Array.Extras More/Date More/Date.Extras More/Number.Format More/Object.Extras More/String.Extras More/String.QueryString More/URI More/URI.Relative More/Hash More/Hash.Extras More/Element.Forms More/Elements.From More/Element.Event.Pseudos More/Element.Event.Pseudos.Keys More/Element.Measure More/Element.Pin More/Element.Position More/Element.Shortcuts More/Form.Request More/Form.Request.Append More/Form.Validator More/Form.Validator.Inline More/Form.Validator.Extras More/OverText More/Fx.Elements More/Fx.Accordion More/Fx.Move More/Fx.Reveal More/Fx.Scroll More/Fx.Slide More/Fx.SmoothScroll More/Fx.Sort More/Drag More/Drag.Move More/Slider More/Sortables More/Request.JSONP More/Request.Queue More/Request.Periodical More/Assets More/Color More/Group More/Hash.Cookie More/IframeShim More/Table More/HtmlTable More/HtmlTable.Zebra More/HtmlTable.Sort More/HtmlTable.Select More/Keyboard More/Keyboard.Extras More/Mask More/Scroller More/Tips More/Spinner More/Locale More/Locale.Set.From More/Locale.en-US.Date More/Locale.en-US.Form.Validator More/Locale.en-US.Number More/Locale.ar.Date More/Locale.ar.Form.Validator More/Locale.ca-CA.Date More/Locale.ca-CA.Form.Validator More/Locale.cs-CZ.Date More/Locale.cs-CZ.Form.Validator More/Locale.da-DK.Date More/Locale.da-DK.Form.Validator More/Locale.de-CH.Date More/Locale.de-CH.Form.Validator More/Locale.de-DE.Date More/Locale.de-DE.Form.Validator More/Locale.de-DE.Number More/Locale.en-GB.Date More/Locale.es-AR.Date More/Locale.es-AR.Form.Validator More/Locale.es-ES.Date More/Locale.es-ES.Form.Validator More/Locale.et-EE.Date More/Locale.et-EE.Form.Validator More/Locale.EU.Number More/Locale.fa.Date More/Locale.fa.Form.Validator More/Locale.fi-FI.Date More/Locale.fi-FI.Form.Validator More/Locale.fi-FI.Number More/Locale.fr-FR.Date More/Locale.fr-FR.Form.Validator More/Locale.fr-FR.Number More/Locale.he-IL.Date More/Locale.he-IL.Form.Validator More/Locale.he-IL.Number More/Locale.hu-HU.Date More/Locale.hu-HU.Form.Validator More/Locale.it-IT.Date More/Locale.it-IT.Form.Validator More/Locale.ja-JP.Date More/Locale.ja-JP.Form.Validator More/Locale.ja-JP.Number More/Locale.nl-NL.Date More/Locale.nl-NL.Form.Validator More/Locale.nl-NL.Number More/Locale.no-NO.Date More/Locale.no-NO.Form.Validator More/Locale.pl-PL.Date More/Locale.pl-PL.Form.Validator More/Locale.pt-BR.Date More/Locale.pt-BR.Form.Validator More/Locale.pt-PT.Date More/Locale.pt-PT.Form.Validator More/Locale.ru-RU-unicode.Date More/Locale.ru-RU-unicode.Form.Validator More/Locale.si-SI.Date More/Locale.si-SI.Form.Validator More/Locale.sv-SE.Date More/Locale.sv-SE.Form.Validator More/Locale.uk-UA.Date More/Locale.uk-UA.Form.Validator More/Locale.zh-CH.Date More/Locale.zh-CH.Form.Validator
   4  /*
   5  ---
   6  
   7  script: More.js
   8  
   9  name: More
  10  
  11  description: MooTools More
  12  
  13  license: MIT-style license
  14  
  15  authors:
  16    - Guillermo Rauch
  17    - Thomas Aylott
  18    - Scott Kyle
  19    - Arian Stolwijk
  20    - Tim Wienk
  21    - Christoph Pojer
  22    - Aaron Newton
  23    - Jacob Thornton
  24  
  25  requires:
  26    - Core/MooTools
  27  
  28  provides: [MooTools.More]
  29  
  30  ...
  31  */
  32  
  33  MooTools.More = {
  34      'version': '1.4.0.1',
  35      'build': 'a4244edf2aa97ac8a196fc96082dd35af1abab87'
  36  };
  37  
  38  
  39  /*
  40  ---
  41  
  42  name: Events.Pseudos
  43  
  44  description: Adds the functionality to add pseudo events
  45  
  46  license: MIT-style license
  47  
  48  authors:
  49    - Arian Stolwijk
  50  
  51  requires: [Core/Class.Extras, Core/Slick.Parser, More/MooTools.More]
  52  
  53  provides: [Events.Pseudos]
  54  
  55  ...
  56  */
  57  
  58  (function(){
  59  
  60  Events.Pseudos = function(pseudos, addEvent, removeEvent){
  61  
  62      var storeKey = '_monitorEvents:';
  63  
  64      var storageOf = function(object){
  65          return {
  66              store: object.store ? function(key, value){
  67                  object.store(storeKey + key, value);
  68              } : function(key, value){
  69                  (object._monitorEvents || (object._monitorEvents = {}))[key] = value;
  70              },
  71              retrieve: object.retrieve ? function(key, dflt){
  72                  return object.retrieve(storeKey + key, dflt);
  73              } : function(key, dflt){
  74                  if (!object._monitorEvents) return dflt;
  75                  return object._monitorEvents[key] || dflt;
  76              }
  77          };
  78      };
  79  
  80      var splitType = function(type){
  81          if (type.indexOf(':') == -1 || !pseudos) return null;
  82  
  83          var parsed = Slick.parse(type).expressions[0][0],
  84              parsedPseudos = parsed.pseudos,
  85              l = parsedPseudos.length,
  86              splits = [];
  87  
  88          while (l--){
  89              var pseudo = parsedPseudos[l].key,
  90                  listener = pseudos[pseudo];
  91              if (listener != null) splits.push({
  92                  event: parsed.tag,
  93                  value: parsedPseudos[l].value,
  94                  pseudo: pseudo,
  95                  original: type,
  96                  listener: listener
  97              });
  98          }
  99          return splits.length ? splits : null;
 100      };
 101  
 102      return {
 103  
 104          addEvent: function(type, fn, internal){
 105              var split = splitType(type);
 106              if (!split) return addEvent.call(this, type, fn, internal);
 107  
 108              var storage = storageOf(this),
 109                  events = storage.retrieve(type, []),
 110                  eventType = split[0].event,
 111                  args = Array.slice(arguments, 2),
 112                  stack = fn,
 113                  self = this;
 114  
 115              split.each(function(item){
 116                  var listener = item.listener,
 117                      stackFn = stack;
 118                  if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')';
 119                  else stack = function(){
 120                      listener.call(self, item, stackFn, arguments, stack);
 121                  };
 122              });
 123  
 124              events.include({type: eventType, event: fn, monitor: stack});
 125              storage.store(type, events);
 126  
 127              if (type != eventType) addEvent.apply(this, [type, fn].concat(args));
 128              return addEvent.apply(this, [eventType, stack].concat(args));
 129          },
 130  
 131          removeEvent: function(type, fn){
 132              var split = splitType(type);
 133              if (!split) return removeEvent.call(this, type, fn);
 134  
 135              var storage = storageOf(this),
 136                  events = storage.retrieve(type);
 137              if (!events) return this;
 138  
 139              var args = Array.slice(arguments, 2);
 140  
 141              removeEvent.apply(this, [type, fn].concat(args));
 142              events.each(function(monitor, i){
 143                  if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args));
 144                  delete events[i];
 145              }, this);
 146  
 147              storage.store(type, events);
 148              return this;
 149          }
 150  
 151      };
 152  
 153  };
 154  
 155  var pseudos = {
 156  
 157      once: function(split, fn, args, monitor){
 158          fn.apply(this, args);
 159          this.removeEvent(split.event, monitor)
 160              .removeEvent(split.original, fn);
 161      },
 162  
 163      throttle: function(split, fn, args){
 164          if (!fn._throttled){
 165              fn.apply(this, args);
 166              fn._throttled = setTimeout(function(){
 167                  fn._throttled = false;
 168              }, split.value || 250);
 169          }
 170      },
 171  
 172      pause: function(split, fn, args){
 173          clearTimeout(fn._pause);
 174          fn._pause = fn.delay(split.value || 250, this, args);
 175      }
 176  
 177  };
 178  
 179  Events.definePseudo = function(key, listener){
 180      pseudos[key] = listener;
 181      return this;
 182  };
 183  
 184  Events.lookupPseudo = function(key){
 185      return pseudos[key];
 186  };
 187  
 188  var proto = Events.prototype;
 189  Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
 190  
 191  ['Request', 'Fx'].each(function(klass){
 192      if (this[klass]) this[klass].implement(Events.prototype);
 193  });
 194  
 195  })();
 196  
 197  
 198  /*
 199  ---
 200  
 201  script: Class.Refactor.js
 202  
 203  name: Class.Refactor
 204  
 205  description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
 206  
 207  license: MIT-style license
 208  
 209  authors:
 210    - Aaron Newton
 211  
 212  requires:
 213    - Core/Class
 214    - /MooTools.More
 215  
 216  # Some modules declare themselves dependent on Class.Refactor
 217  provides: [Class.refactor, Class.Refactor]
 218  
 219  ...
 220  */
 221  
 222  Class.refactor = function(original, refactors){
 223  
 224      Object.each(refactors, function(item, name){
 225          var origin = original.prototype[name];
 226          origin = (origin && origin.$origin) || origin || function(){};
 227          original.implement(name, (typeof item == 'function') ? function(){
 228              var old = this.previous;
 229              this.previous = origin;
 230              var value = item.apply(this, arguments);
 231              this.previous = old;
 232              return value;
 233          } : item);
 234      });
 235  
 236      return original;
 237  
 238  };
 239  
 240  
 241  /*
 242  ---
 243  
 244  script: Class.Binds.js
 245  
 246  name: Class.Binds
 247  
 248  description: Automagically binds specified methods in a class to the instance of the class.
 249  
 250  license: MIT-style license
 251  
 252  authors:
 253    - Aaron Newton
 254  
 255  requires:
 256    - Core/Class
 257    - /MooTools.More
 258  
 259  provides: [Class.Binds]
 260  
 261  ...
 262  */
 263  
 264  Class.Mutators.Binds = function(binds){
 265      if (!this.prototype.initialize) this.implement('initialize', function(){});
 266      return Array.from(binds).concat(this.prototype.Binds || []);
 267  };
 268  
 269  Class.Mutators.initialize = function(initialize){
 270      return function(){
 271          Array.from(this.Binds).each(function(name){
 272              var original = this[name];
 273              if (original) this[name] = original.bind(this);
 274          }, this);
 275          return initialize.apply(this, arguments);
 276      };
 277  };
 278  
 279  
 280  /*
 281  ---
 282  
 283  script: Class.Occlude.js
 284  
 285  name: Class.Occlude
 286  
 287  description: Prevents a class from being applied to a DOM element twice.
 288  
 289  license: MIT-style license.
 290  
 291  authors:
 292    - Aaron Newton
 293  
 294  requires:
 295    - Core/Class
 296    - Core/Element
 297    - /MooTools.More
 298  
 299  provides: [Class.Occlude]
 300  
 301  ...
 302  */
 303  
 304  Class.Occlude = new Class({
 305  
 306      occlude: function(property, element){
 307          element = document.id(element || this.element);
 308          var instance = element.retrieve(property || this.property);
 309          if (instance && !this.occluded)
 310              return (this.occluded = instance);
 311  
 312          this.occluded = false;
 313          element.store(property || this.property, this);
 314          return this.occluded;
 315      }
 316  
 317  });
 318  
 319  
 320  /*
 321  ---
 322  
 323  script: Chain.Wait.js
 324  
 325  name: Chain.Wait
 326  
 327  description: value, Adds a method to inject pauses between chained events.
 328  
 329  license: MIT-style license.
 330  
 331  authors:
 332    - Aaron Newton
 333  
 334  requires:
 335    - Core/Chain
 336    - Core/Element
 337    - Core/Fx
 338    - /MooTools.More
 339  
 340  provides: [Chain.Wait]
 341  
 342  ...
 343  */
 344  
 345  (function(){
 346  
 347      var wait = {
 348          wait: function(duration){
 349              return this.chain(function(){
 350                  this.callChain.delay(duration == null ? 500 : duration, this);
 351                  return this;
 352              }.bind(this));
 353          }
 354      };
 355  
 356      Chain.implement(wait);
 357  
 358      if (this.Fx) Fx.implement(wait);
 359  
 360      if (this.Element && Element.implement && this.Fx){
 361          Element.implement({
 362  
 363              chains: function(effects){
 364                  Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){
 365                      effect = this.get(effect);
 366                      if (!effect) return;
 367                      effect.setOptions({
 368                          link:'chain'
 369                      });
 370                  }, this);
 371                  return this;
 372              },
 373  
 374              pauseFx: function(duration, effect){
 375                  this.chains(effect).get(effect || 'tween').wait(duration);
 376                  return this;
 377              }
 378  
 379          });
 380      }
 381  
 382  })();
 383  
 384  
 385  /*
 386  ---
 387  
 388  script: Array.Extras.js
 389  
 390  name: Array.Extras
 391  
 392  description: Extends the Array native object to include useful methods to work with arrays.
 393  
 394  license: MIT-style license
 395  
 396  authors:
 397    - Christoph Pojer
 398    - Sebastian Markbåge
 399  
 400  requires:
 401    - Core/Array
 402    - MooTools.More
 403  
 404  provides: [Array.Extras]
 405  
 406  ...
 407  */
 408  
 409  (function(nil){
 410  
 411  Array.implement({
 412  
 413      min: function(){
 414          return Math.min.apply(null, this);
 415      },
 416  
 417      max: function(){
 418          return Math.max.apply(null, this);
 419      },
 420  
 421      average: function(){
 422          return this.length ? this.sum() / this.length : 0;
 423      },
 424  
 425      sum: function(){
 426          var result = 0, l = this.length;
 427          if (l){
 428              while (l--) result += this[l];
 429          }
 430          return result;
 431      },
 432  
 433      unique: function(){
 434          return [].combine(this);
 435      },
 436  
 437      shuffle: function(){
 438          for (var i = this.length; i && --i;){
 439              var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
 440              this[i] = this[r];
 441              this[r] = temp;
 442          }
 443          return this;
 444      },
 445  
 446      reduce: function(fn, value){
 447          for (var i = 0, l = this.length; i < l; i++){
 448              if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
 449          }
 450          return value;
 451      },
 452  
 453      reduceRight: function(fn, value){
 454          var i = this.length;
 455          while (i--){
 456              if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this);
 457          }
 458          return value;
 459      }
 460  
 461  });
 462  
 463  })();
 464  
 465  
 466  /*
 467  ---
 468  
 469  script: Object.Extras.js
 470  
 471  name: Object.Extras
 472  
 473  description: Extra Object generics, like getFromPath which allows a path notation to child elements.
 474  
 475  license: MIT-style license
 476  
 477  authors:
 478    - Aaron Newton
 479  
 480  requires:
 481    - Core/Object
 482    - /MooTools.More
 483  
 484  provides: [Object.Extras]
 485  
 486  ...
 487  */
 488  
 489  (function(){
 490  
 491  var defined = function(value){
 492      return value != null;
 493  };
 494  
 495  var hasOwnProperty = Object.prototype.hasOwnProperty;
 496  
 497  Object.extend({
 498  
 499      getFromPath: function(source, parts){
 500          if (typeof parts == 'string') parts = parts.split('.');
 501          for (var i = 0, l = parts.length; i < l; i++){
 502              if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]];
 503              else return null;
 504          }
 505          return source;
 506      },
 507  
 508      cleanValues: function(object, method){
 509          method = method || defined;
 510          for (var key in object) if (!method(object[key])){
 511              delete object[key];
 512          }
 513          return object;
 514      },
 515  
 516      erase: function(object, key){
 517          if (hasOwnProperty.call(object, key)) delete object[key];
 518          return object;
 519      },
 520  
 521      run: function(object){
 522          var args = Array.slice(arguments, 1);
 523          for (var key in object) if (object[key].apply){
 524              object[key].apply(object, args);
 525          }
 526          return object;
 527      }
 528  
 529  });
 530  
 531  })();
 532  
 533  
 534  /*
 535  ---
 536  
 537  script: Locale.js
 538  
 539  name: Locale
 540  
 541  description: Provides methods for localization.
 542  
 543  license: MIT-style license
 544  
 545  authors:
 546    - Aaron Newton
 547    - Arian Stolwijk
 548  
 549  requires:
 550    - Core/Events
 551    - /Object.Extras
 552    - /MooTools.More
 553  
 554  provides: [Locale, Lang]
 555  
 556  ...
 557  */
 558  
 559  (function(){
 560  
 561  var current = null,
 562      locales = {},
 563      inherits = {};
 564  
 565  var getSet = function(set){
 566      if (instanceOf(set, Locale.Set)) return set;
 567      else return locales[set];
 568  };
 569  
 570  var Locale = this.Locale = {
 571  
 572      define: function(locale, set, key, value){
 573          var name;
 574          if (instanceOf(locale, Locale.Set)){
 575              name = locale.name;
 576              if (name) locales[name] = locale;
 577          } else {
 578              name = locale;
 579              if (!locales[name]) locales[name] = new Locale.Set(name);
 580              locale = locales[name];
 581          }
 582  
 583          if (set) locale.define(set, key, value);
 584  
 585          
 586  
 587          if (!current) current = locale;
 588  
 589          return locale;
 590      },
 591  
 592      use: function(locale){
 593          locale = getSet(locale);
 594  
 595          if (locale){
 596              current = locale;
 597  
 598              this.fireEvent('change', locale);
 599  
 600              
 601          }
 602  
 603          return this;
 604      },
 605  
 606      getCurrent: function(){
 607          return current;
 608      },
 609  
 610      get: function(key, args){
 611          return (current) ? current.get(key, args) : '';
 612      },
 613  
 614      inherit: function(locale, inherits, set){
 615          locale = getSet(locale);
 616  
 617          if (locale) locale.inherit(inherits, set);
 618          return this;
 619      },
 620  
 621      list: function(){
 622          return Object.keys(locales);
 623      }
 624  
 625  };
 626  
 627  Object.append(Locale, new Events);
 628  
 629  Locale.Set = new Class({
 630  
 631      sets: {},
 632  
 633      inherits: {
 634          locales: [],
 635          sets: {}
 636      },
 637  
 638      initialize: function(name){
 639          this.name = name || '';
 640      },
 641  
 642      define: function(set, key, value){
 643          var defineData = this.sets[set];
 644          if (!defineData) defineData = {};
 645  
 646          if (key){
 647              if (typeOf(key) == 'object') defineData = Object.merge(defineData, key);
 648              else defineData[key] = value;
 649          }
 650          this.sets[set] = defineData;
 651  
 652          return this;
 653      },
 654  
 655      get: function(key, args, _base){
 656          var value = Object.getFromPath(this.sets, key);
 657          if (value != null){
 658              var type = typeOf(value);
 659              if (type == 'function') value = value.apply(null, Array.from(args));
 660              else if (type == 'object') value = Object.clone(value);
 661              return value;
 662          }
 663  
 664          // get value of inherited locales
 665          var index = key.indexOf('.'),
 666              set = index < 0 ? key : key.substr(0, index),
 667              names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US');
 668          if (!_base) _base = [];
 669  
 670          for (var i = 0, l = names.length; i < l; i++){
 671              if (_base.contains(names[i])) continue;
 672              _base.include(names[i]);
 673  
 674              var locale = locales[names[i]];
 675              if (!locale) continue;
 676  
 677              value = locale.get(key, args, _base);
 678              if (value != null) return value;
 679          }
 680  
 681          return '';
 682      },
 683  
 684      inherit: function(names, set){
 685          names = Array.from(names);
 686  
 687          if (set && !this.inherits.sets[set]) this.inherits.sets[set] = [];
 688  
 689          var l = names.length;
 690          while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]);
 691  
 692          return this;
 693      }
 694  
 695  });
 696  
 697  
 698  
 699  })();
 700  
 701  
 702  /*
 703  ---
 704  
 705  name: Locale.en-US.Date
 706  
 707  description: Date messages for US English.
 708  
 709  license: MIT-style license
 710  
 711  authors:
 712    - Aaron Newton
 713  
 714  requires:
 715    - /Locale
 716  
 717  provides: [Locale.en-US.Date]
 718  
 719  ...
 720  */
 721  
 722  Locale.define('en-US', 'Date', {
 723  
 724      months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
 725      months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
 726      days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
 727      days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
 728  
 729      // Culture's date order: MM/DD/YYYY
 730      dateOrder: ['month', 'date', 'year'],
 731      shortDate: '%m/%d/%Y',
 732      shortTime: '%I:%M%p',
 733      AM: 'AM',
 734      PM: 'PM',
 735      firstDayOfWeek: 0,
 736  
 737      // Date.Extras
 738      ordinal: function(dayOfMonth){
 739          // 1st, 2nd, 3rd, etc.
 740          return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
 741      },
 742  
 743      lessThanMinuteAgo: 'less than a minute ago',
 744      minuteAgo: 'about a minute ago',
 745      minutesAgo: '{delta} minutes ago',
 746      hourAgo: 'about an hour ago',
 747      hoursAgo: 'about {delta} hours ago',
 748      dayAgo: '1 day ago',
 749      daysAgo: '{delta} days ago',
 750      weekAgo: '1 week ago',
 751      weeksAgo: '{delta} weeks ago',
 752      monthAgo: '1 month ago',
 753      monthsAgo: '{delta} months ago',
 754      yearAgo: '1 year ago',
 755      yearsAgo: '{delta} years ago',
 756  
 757      lessThanMinuteUntil: 'less than a minute from now',
 758      minuteUntil: 'about a minute from now',
 759      minutesUntil: '{delta} minutes from now',
 760      hourUntil: 'about an hour from now',
 761      hoursUntil: 'about {delta} hours from now',
 762      dayUntil: '1 day from now',
 763      daysUntil: '{delta} days from now',
 764      weekUntil: '1 week from now',
 765      weeksUntil: '{delta} weeks from now',
 766      monthUntil: '1 month from now',
 767      monthsUntil: '{delta} months from now',
 768      yearUntil: '1 year from now',
 769      yearsUntil: '{delta} years from now'
 770  
 771  });
 772  
 773  
 774  /*
 775  ---
 776  
 777  script: Date.js
 778  
 779  name: Date
 780  
 781  description: Extends the Date native object to include methods useful in managing dates.
 782  
 783  license: MIT-style license
 784  
 785  authors:
 786    - Aaron Newton
 787    - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
 788    - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
 789    - Scott Kyle - scott [at] appden.com; http://appden.com
 790  
 791  requires:
 792    - Core/Array
 793    - Core/String
 794    - Core/Number
 795    - MooTools.More
 796    - Locale
 797    - Locale.en-US.Date
 798  
 799  provides: [Date]
 800  
 801  ...
 802  */
 803  
 804  (function(){
 805  
 806  var Date = this.Date;
 807  
 808  var DateMethods = Date.Methods = {
 809      ms: 'Milliseconds',
 810      year: 'FullYear',
 811      min: 'Minutes',
 812      mo: 'Month',
 813      sec: 'Seconds',
 814      hr: 'Hours'
 815  };
 816  
 817  ['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
 818      'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
 819      'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){
 820      Date.Methods[method.toLowerCase()] = method;
 821  });
 822  
 823  var pad = function(n, digits, string){
 824      if (digits == 1) return n;
 825      return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n;
 826  };
 827  
 828  Date.implement({
 829  
 830      set: function(prop, value){
 831          prop = prop.toLowerCase();
 832          var method = DateMethods[prop] && 'set' + DateMethods[prop];
 833          if (method && this[method]) this[method](value);
 834          return this;
 835      }.overloadSetter(),
 836  
 837      get: function(prop){
 838          prop = prop.toLowerCase();
 839          var method = DateMethods[prop] && 'get' + DateMethods[prop];
 840          if (method && this[method]) return this[method]();
 841          return null;
 842      }.overloadGetter(),
 843  
 844      clone: function(){
 845          return new Date(this.get('time'));
 846      },
 847  
 848      increment: function(interval, times){
 849          interval = interval || 'day';
 850          times = times != null ? times : 1;
 851  
 852          switch (interval){
 853              case 'year':
 854                  return this.increment('month', times * 12);
 855              case 'month':
 856                  var d = this.get('date');
 857                  this.set('date', 1).set('mo', this.get('mo') + times);
 858                  return this.set('date', d.min(this.get('lastdayofmonth')));
 859              case 'week':
 860                  return this.increment('day', times * 7);
 861              case 'day':
 862                  return this.set('date', this.get('date') + times);
 863          }
 864  
 865          if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
 866  
 867          return this.set('time', this.get('time') + times * Date.units[interval]());
 868      },
 869  
 870      decrement: function(interval, times){
 871          return this.increment(interval, -1 * (times != null ? times : 1));
 872      },
 873  
 874      isLeapYear: function(){
 875          return Date.isLeapYear(this.get('year'));
 876      },
 877  
 878      clearTime: function(){
 879          return this.set({hr: 0, min: 0, sec: 0, ms: 0});
 880      },
 881  
 882      diff: function(date, resolution){
 883          if (typeOf(date) == 'string') date = Date.parse(date);
 884  
 885          return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month
 886      },
 887  
 888      getLastDayOfMonth: function(){
 889          return Date.daysInMonth(this.get('mo'), this.get('year'));
 890      },
 891  
 892      getDayOfYear: function(){
 893          return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1)
 894              - Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
 895      },
 896  
 897      setDay: function(day, firstDayOfWeek){
 898          if (firstDayOfWeek == null){
 899              firstDayOfWeek = Date.getMsg('firstDayOfWeek');
 900              if (firstDayOfWeek === '') firstDayOfWeek = 1;
 901          }
 902  
 903          day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7;
 904          var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7;
 905  
 906          return this.increment('day', day - currentDay);
 907      },
 908  
 909      getWeek: function(firstDayOfWeek){
 910          if (firstDayOfWeek == null){
 911              firstDayOfWeek = Date.getMsg('firstDayOfWeek');
 912              if (firstDayOfWeek === '') firstDayOfWeek = 1;
 913          }
 914  
 915          var date = this,
 916              dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7,
 917              dividend = 0,
 918              firstDayOfYear;
 919  
 920          if (firstDayOfWeek == 1){
 921              // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week)
 922              var month = date.get('month'),
 923                  startOfWeek = date.get('date') - dayOfWeek;
 924  
 925              if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year
 926  
 927              if (month == 0 && startOfWeek < -2){
 928                  // Use a date from last year to determine the week
 929                  date = new Date(date).decrement('day', dayOfWeek);
 930                  dayOfWeek = 0;
 931              }
 932  
 933              firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7;
 934              if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1
 935          } else {
 936              // In other cultures the first week of the year is always week 1 and the last week always 53 or 54.
 937              // Days in the same week can have a different weeknumber if the week spreads across two years.
 938              firstDayOfYear = new Date(date.get('year'), 0, 1).get('day');
 939          }
 940  
 941          dividend += date.get('dayofyear');
 942          dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week
 943          dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week
 944  
 945          return (dividend / 7);
 946      },
 947  
 948      getOrdinal: function(day){
 949          return Date.getMsg('ordinal', day || this.get('date'));
 950      },
 951  
 952      getTimezone: function(){
 953          return this.toString()
 954              .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
 955              .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
 956      },
 957  
 958      getGMTOffset: function(){
 959          var off = this.get('timezoneOffset');
 960          return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
 961      },
 962  
 963      setAMPM: function(ampm){
 964          ampm = ampm.toUpperCase();
 965          var hr = this.get('hr');
 966          if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
 967          else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
 968          return this;
 969      },
 970  
 971      getAMPM: function(){
 972          return (this.get('hr') < 12) ? 'AM' : 'PM';
 973      },
 974  
 975      parse: function(str){
 976          this.set('time', Date.parse(str));
 977          return this;
 978      },
 979  
 980      isValid: function(date){
 981          if (!date) date = this;
 982          return typeOf(date) == 'date' && !isNaN(date.valueOf());
 983      },
 984  
 985      format: function(format){
 986          if (!this.isValid()) return 'invalid date';
 987  
 988          if (!format) format = '%x %X';
 989          if (typeof format == 'string') format = formats[format.toLowerCase()] || format;
 990          if (typeof format == 'function') return format(this);
 991  
 992          var d = this;
 993          return format.replace(/%([a-z%])/gi,
 994              function($0, $1){
 995                  switch ($1){
 996                      case 'a': return Date.getMsg('days_abbr')[d.get('day')];
 997                      case 'A': return Date.getMsg('days')[d.get('day')];
 998                      case 'b': return Date.getMsg('months_abbr')[d.get('month')];
 999                      case 'B': return Date.getMsg('months')[d.get('month')];
1000                      case 'c': return d.format('%a %b %d %H:%M:%S %Y');
1001                      case 'd': return pad(d.get('date'), 2);
1002                      case 'e': return pad(d.get('date'), 2, ' ');
1003                      case 'H': return pad(d.get('hr'), 2);
1004                      case 'I': return pad((d.get('hr') % 12) || 12, 2);
1005                      case 'j': return pad(d.get('dayofyear'), 3);
1006                      case 'k': return pad(d.get('hr'), 2, ' ');
1007                      case 'l': return pad((d.get('hr') % 12) || 12, 2, ' ');
1008                      case 'L': return pad(d.get('ms'), 3);
1009                      case 'm': return pad((d.get('mo') + 1), 2);
1010                      case 'M': return pad(d.get('min'), 2);
1011                      case 'o': return d.get('ordinal');
1012                      case 'p': return Date.getMsg(d.get('ampm'));
1013                      case 's': return Math.round(d / 1000);
1014                      case 'S': return pad(d.get('seconds'), 2);
1015                      case 'T': return d.format('%H:%M:%S');
1016                      case 'U': return pad(d.get('week'), 2);
1017                      case 'w': return d.get('day');
1018                      case 'x': return d.format(Date.getMsg('shortDate'));
1019                      case 'X': return d.format(Date.getMsg('shortTime'));
1020                      case 'y': return d.get('year').toString().substr(2);
1021                      case 'Y': return d.get('year');
1022                      case 'z': return d.get('GMTOffset');
1023                      case 'Z': return d.get('Timezone');
1024                  }
1025                  return $1;
1026              }
1027          );
1028      },
1029  
1030      toISOString: function(){
1031          return this.format('iso8601');
1032      }
1033  
1034  }).alias({
1035      toJSON: 'toISOString',
1036      compare: 'diff',
1037      strftime: 'format'
1038  });
1039  
1040  // The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized
1041  var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
1042      rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
1043  
1044  var formats = {
1045      db: '%Y-%m-%d %H:%M:%S',
1046      compact: '%Y%m%dT%H%M%S',
1047      'short': '%d %b %H:%M',
1048      'long': '%B %d, %Y %H:%M',
1049      rfc822: function(date){
1050          return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z');
1051      },
1052      rfc2822: function(date){
1053          return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z');
1054      },
1055      iso8601: function(date){
1056          return (
1057              date.getUTCFullYear() + '-' +
1058              pad(date.getUTCMonth() + 1, 2) + '-' +
1059              pad(date.getUTCDate(), 2) + 'T' +
1060              pad(date.getUTCHours(), 2) + ':' +
1061              pad(date.getUTCMinutes(), 2) + ':' +
1062              pad(date.getUTCSeconds(), 2) + '.' +
1063              pad(date.getUTCMilliseconds(), 3) + 'Z'
1064          );
1065      }
1066  };
1067  
1068  var parsePatterns = [],
1069      nativeParse = Date.parse;
1070  
1071  var parseWord = function(type, word, num){
1072      var ret = -1,
1073          translated = Date.getMsg(type + 's');
1074      switch (typeOf(word)){
1075          case 'object':
1076              ret = translated[word.get(type)];
1077              break;
1078          case 'number':
1079              ret = translated[word];
1080              if (!ret) throw new Error('Invalid ' + type + ' index: ' + word);
1081              break;
1082          case 'string':
1083              var match = translated.filter(function(name){
1084                  return this.test(name);
1085              }, new RegExp('^' + word, 'i'));
1086              if (!match.length) throw new Error('Invalid ' + type + ' string');
1087              if (match.length > 1) throw new Error('Ambiguous ' + type);
1088              ret = match[0];
1089      }
1090  
1091      return (num) ? translated.indexOf(ret) : ret;
1092  };
1093  
1094  var startCentury = 1900,
1095      startYear = 70;
1096  
1097  Date.extend({
1098  
1099      getMsg: function(key, args){
1100          return Locale.get('Date.' + key, args);
1101      },
1102  
1103      units: {
1104          ms: Function.from(1),
1105          second: Function.from(1000),
1106          minute: Function.from(60000),
1107          hour: Function.from(3600000),
1108          day: Function.from(86400000),
1109          week: Function.from(608400000),
1110          month: function(month, year){
1111              var d = new Date;
1112              return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000;
1113          },
1114          year: function(year){
1115              year = year || new Date().get('year');
1116              return Date.isLeapYear(year) ? 31622400000 : 31536000000;
1117          }
1118      },
1119  
1120      daysInMonth: function(month, year){
1121          return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
1122      },
1123  
1124      isLeapYear: function(year){
1125          return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
1126      },
1127  
1128      parse: function(from){
1129          var t = typeOf(from);
1130          if (t == 'number') return new Date(from);
1131          if (t != 'string') return from;
1132          from = from.clean();
1133          if (!from.length) return null;
1134  
1135          var parsed;
1136          parsePatterns.some(function(pattern){
1137              var bits = pattern.re.exec(from);
1138              return (bits) ? (parsed = pattern.handler(bits)) : false;
1139          });
1140  
1141          if (!(parsed && parsed.isValid())){
1142              parsed = new Date(nativeParse(from));
1143              if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt());
1144          }
1145          return parsed;
1146      },
1147  
1148      parseDay: function(day, num){
1149          return parseWord('day', day, num);
1150      },
1151  
1152      parseMonth: function(month, num){
1153          return parseWord('month', month, num);
1154      },
1155  
1156      parseUTC: function(value){
1157          var localDate = new Date(value);
1158          var utcSeconds = Date.UTC(
1159              localDate.get('year'),
1160              localDate.get('mo'),
1161              localDate.get('date'),
1162              localDate.get('hr'),
1163              localDate.get('min'),
1164              localDate.get('sec'),
1165              localDate.get('ms')
1166          );
1167          return new Date(utcSeconds);
1168      },
1169  
1170      orderIndex: function(unit){
1171          return Date.getMsg('dateOrder').indexOf(unit) + 1;
1172      },
1173  
1174      defineFormat: function(name, format){
1175          formats[name] = format;
1176          return this;
1177      },
1178  
1179      
1180  
1181      defineParser: function(pattern){
1182          parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
1183          return this;
1184      },
1185  
1186      defineParsers: function(){
1187          Array.flatten(arguments).each(Date.defineParser);
1188          return this;
1189      },
1190  
1191      define2DigitYearStart: function(year){
1192          startYear = year % 100;
1193          startCentury = year - startYear;
1194          return this;
1195      }
1196  
1197  }).extend({
1198      defineFormats: Date.defineFormat.overloadSetter()
1199  });
1200  
1201  var regexOf = function(type){
1202      return new RegExp('(?:' + Date.getMsg(type).map(function(name){
1203          return name.substr(0, 3);
1204      }).join('|') + ')[a-z]*');
1205  };
1206  
1207  var replacers = function(key){
1208      switch (key){
1209          case 'T':
1210              return '%H:%M:%S';
1211          case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
1212              return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?';
1213          case 'X':
1214              return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?';
1215      }
1216      return null;
1217  };
1218  
1219  var keys = {
1220      d: /[0-2]?[0-9]|3[01]/,
1221      H: /[01]?[0-9]|2[0-3]/,
1222      I: /0?[1-9]|1[0-2]/,
1223      M: /[0-5]?\d/,
1224      s: /\d+/,
1225      o: /[a-z]*/,
1226      p: /[ap]\.?m\.?/,
1227      y: /\d{2}|\d{4}/,
1228      Y: /\d{4}/,
1229      z: /Z|[+-]\d{2}(?::?\d{2})?/
1230  };
1231  
1232  keys.m = keys.I;
1233  keys.S = keys.M;
1234  
1235  var currentLanguage;
1236  
1237  var recompile = function(language){
1238      currentLanguage = language;
1239  
1240      keys.a = keys.A = regexOf('days');
1241      keys.b = keys.B = regexOf('months');
1242  
1243      parsePatterns.each(function(pattern, i){
1244          if (pattern.format) parsePatterns[i] = build(pattern.format);
1245      });
1246  };
1247  
1248  var build = function(format){
1249      if (!currentLanguage) return {format: format};
1250  
1251      var parsed = [];
1252      var re = (format.source || format) // allow format to be regex
1253       .replace(/%([a-z])/gi,
1254          function($0, $1){
1255              return replacers($1) || $0;
1256          }
1257      ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
1258       .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
1259       .replace(/%([a-z%])/gi,
1260          function($0, $1){
1261              var p = keys[$1];
1262              if (!p) return $1;
1263              parsed.push($1);
1264              return '(' + p.source + ')';
1265          }
1266      ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words
1267  
1268      return {
1269          format: format,
1270          re: new RegExp('^' + re + '$', 'i'),
1271          handler: function(bits){
1272              bits = bits.slice(1).associate(parsed);
1273              var date = new Date().clearTime(),
1274                  year = bits.y || bits.Y;
1275  
1276              if (year != null) handle.call(date, 'y', year); // need to start in the right year
1277              if ('d' in bits) handle.call(date, 'd', 1);
1278              if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1);
1279  
1280              for (var key in bits) handle.call(date, key, bits[key]);
1281              return date;
1282          }
1283      };
1284  };
1285  
1286  var handle = function(key, value){
1287      if (!value) return this;
1288  
1289      switch (key){
1290          case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
1291          case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
1292          case 'd': return this.set('date', value);
1293          case 'H': case 'I': return this.set('hr', value);
1294          case 'm': return this.set('mo', value - 1);
1295          case 'M': return this.set('min', value);
1296          case 'p': return this.set('ampm', value.replace(/\./g, ''));
1297          case 'S': return this.set('sec', value);
1298          case 's': return this.set('ms', ('0.' + value) * 1000);
1299          case 'w': return this.set('day', value);
1300          case 'Y': return this.set('year', value);
1301          case 'y':
1302              value = +value;
1303              if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
1304              return this.set('year', value);
1305          case 'z':
1306              if (value == 'Z') value = '+00';
1307              var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
1308              offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
1309              return this.set('time', this - offset * 60000);
1310      }
1311  
1312      return this;
1313  };
1314  
1315  Date.defineParsers(
1316      '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
1317      '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
1318      '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
1319      '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
1320      '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
1321      '%Y %b( %d%o( %X)?)?', // Same as above with year coming first
1322      '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009"
1323      '%T', // %H:%M:%S
1324      '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05"
1325  );
1326  
1327  Locale.addEvent('change', function(language){
1328      if (Locale.get('Date')) recompile(language);
1329  }).fireEvent('change', Locale.getCurrent());
1330  
1331  })();
1332  
1333  
1334  /*
1335  ---
1336  
1337  script: Date.Extras.js
1338  
1339  name: Date.Extras
1340  
1341  description: Extends the Date native object to include extra methods (on top of those in Date.js).
1342  
1343  license: MIT-style license
1344  
1345  authors:
1346    - Aaron Newton
1347    - Scott Kyle
1348  
1349  requires:
1350    - /Date
1351  
1352  provides: [Date.Extras]
1353  
1354  ...
1355  */
1356  
1357  Date.implement({
1358  
1359      timeDiffInWords: function(to){
1360          return Date.distanceOfTimeInWords(this, to || new Date);
1361      },
1362  
1363      timeDiff: function(to, separator){
1364          if (to == null) to = new Date;
1365          var delta = ((to - this) / 1000).floor().abs();
1366  
1367          var vals = [],
1368              durations = [60, 60, 24, 365, 0],
1369              names = ['s', 'm', 'h', 'd', 'y'],
1370              value, duration;
1371  
1372          for (var item = 0; item < durations.length; item++){
1373              if (item && !delta) break;
1374              value = delta;
1375              if ((duration = durations[item])){
1376                  value = (delta % duration);
1377                  delta = (delta / duration).floor();
1378              }
1379              vals.unshift(value + (names[item] || ''));
1380          }
1381  
1382          return vals.join(separator || ':');
1383      }
1384  
1385  }).extend({
1386  
1387      distanceOfTimeInWords: function(from, to){
1388          return Date.getTimePhrase(((to - from) / 1000).toInt());
1389      },
1390  
1391      getTimePhrase: function(delta){
1392          var suffix = (delta < 0) ? 'Until' : 'Ago';
1393          if (delta < 0) delta *= -1;
1394  
1395          var units = {
1396              minute: 60,
1397              hour: 60,
1398              day: 24,
1399              week: 7,
1400              month: 52 / 12,
1401              year: 12,
1402              eon: Infinity
1403          };
1404  
1405          var msg = 'lessThanMinute';
1406  
1407          for (var unit in units){
1408              var interval = units[unit];
1409              if (delta < 1.5 * interval){
1410                  if (delta > 0.75 * interval) msg = unit;
1411                  break;
1412              }
1413              delta /= interval;
1414              msg = unit + 's';
1415          }
1416  
1417          delta = delta.round();
1418          return Date.getMsg(msg + suffix, delta).substitute({delta: delta});
1419      }
1420  
1421  }).defineParsers(
1422  
1423      {
1424          // "today", "tomorrow", "yesterday"
1425          re: /^(?:tod|tom|yes)/i,
1426          handler: function(bits){
1427              var d = new Date().clearTime();
1428              switch (bits[0]){
1429                  case 'tom': return d.increment();
1430                  case 'yes': return d.decrement();
1431                  default: return d;
1432              }
1433          }
1434      },
1435  
1436      {
1437          // "next Wednesday", "last Thursday"
1438          re: /^(next|last) ([a-z]+)$/i,
1439          handler: function(bits){
1440              var d = new Date().clearTime();
1441              var day = d.getDay();
1442              var newDay = Date.parseDay(bits[2], true);
1443              var addDays = newDay - day;
1444              if (newDay <= day) addDays += 7;
1445              if (bits[1] == 'last') addDays -= 7;
1446              return d.set('date', d.getDate() + addDays);
1447          }
1448      }
1449  
1450  ).alias('timeAgoInWords', 'timeDiffInWords');
1451  
1452  
1453  /*
1454  ---
1455  
1456  name: Locale.en-US.Number
1457  
1458  description: Number messages for US English.
1459  
1460  license: MIT-style license
1461  
1462  authors:
1463    - Arian Stolwijk
1464  
1465  requires:
1466    - /Locale
1467  
1468  provides: [Locale.en-US.Number]
1469  
1470  ...
1471  */
1472  
1473  Locale.define('en-US', 'Number', {
1474  
1475      decimal: '.',
1476      group: ',',
1477  
1478  /*     Commented properties are the defaults for Number.format
1479      decimals: 0,
1480      precision: 0,
1481      scientific: null,
1482  
1483      prefix: null,
1484      suffic: null,
1485  
1486      // Negative/Currency/percentage will mixin Number
1487      negative: {
1488          prefix: '-'
1489      },*/
1490  
1491      currency: {
1492  //        decimals: 2,
1493          prefix: '$ '
1494      }/*,
1495  
1496      percentage: {
1497          decimals: 2,
1498          suffix: '%'
1499      }*/
1500  
1501  });
1502  
1503  
1504  
1505  
1506  /*
1507  ---
1508  name: Number.Format
1509  description: Extends the Number Type object to include a number formatting method.
1510  license: MIT-style license
1511  authors: [Arian Stolwijk]
1512  requires: [Core/Number, Locale.en-US.Number]
1513  # Number.Extras is for compatibility
1514  provides: [Number.Format, Number.Extras]
1515  ...
1516  */
1517  
1518  
1519  Number.implement({
1520  
1521      format: function(options){
1522          // Thanks dojo and YUI for some inspiration
1523          var value = this;
1524          options = options ? Object.clone(options) : {};
1525          var getOption = function(key){
1526              if (options[key] != null) return options[key];
1527              return Locale.get('Number.' + key);
1528          };
1529  
1530          var negative = value < 0,
1531              decimal = getOption('decimal'),
1532              precision = getOption('precision'),
1533              group = getOption('group'),
1534              decimals = getOption('decimals');
1535  
1536          if (negative){
1537              var negativeLocale = getOption('negative') || {};
1538              if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-';
1539              ['prefix', 'suffix'].each(function(key){
1540                  if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key];
1541              });
1542  
1543              value = -value;
1544          }
1545  
1546          var prefix = getOption('prefix'),
1547              suffix = getOption('suffix');
1548  
1549          if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals);
1550          if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision);
1551  
1552          value += '';
1553          var index;
1554          if (getOption('scientific') === false && value.indexOf('e') > -1){
1555              var match = value.split('e'),
1556                  zeros = +match[1];
1557              value = match[0].replace('.', '');
1558  
1559              if (zeros < 0){
1560                  zeros = -zeros - 1;
1561                  index = match[0].indexOf('.');
1562                  if (index > -1) zeros -= index - 1;
1563                  while (zeros--) value = '0' + value;
1564                  value = '0.' + value;
1565              } else {
1566                  index = match[0].lastIndexOf('.');
1567                  if (index > -1) zeros -= match[0].length - index - 1;
1568                  while (zeros--) value += '0';
1569              }
1570          }
1571  
1572          if (decimal != '.') value = value.replace('.', decimal);
1573  
1574          if (group){
1575              index = value.lastIndexOf(decimal);
1576              index = (index > -1) ? index : value.length;
1577              var newOutput = value.substring(index),
1578                  i = index;
1579  
1580              while (i--){
1581                  if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput;
1582                  newOutput = value.charAt(i) + newOutput;
1583              }
1584  
1585              value = newOutput;
1586          }
1587  
1588          if (prefix) value = prefix + value;
1589          if (suffix) value += suffix;
1590  
1591          return value;
1592      },
1593  
1594      formatCurrency: function(decimals){
1595          var locale = Locale.get('Number.currency') || {};
1596          if (locale.scientific == null) locale.scientific = false;
1597          locale.decimals = decimals != null ? decimals
1598              : (locale.decimals == null ? 2 : locale.decimals);
1599  
1600          return this.format(locale);
1601      },
1602  
1603      formatPercentage: function(decimals){
1604          var locale = Locale.get('Number.percentage') || {};
1605          if (locale.suffix == null) locale.suffix = '%';
1606          locale.decimals = decimals != null ? decimals
1607              : (locale.decimals == null ? 2 : locale.decimals);
1608  
1609          return this.format(locale);
1610      }
1611  
1612  });
1613  
1614  
1615  /*
1616  ---
1617  
1618  script: String.Extras.js
1619  
1620  name: String.Extras
1621  
1622  description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
1623  
1624  license: MIT-style license
1625  
1626  authors:
1627    - Aaron Newton
1628    - Guillermo Rauch
1629    - Christopher Pitt
1630  
1631  requires:
1632    - Core/String
1633    - Core/Array
1634    - MooTools.More
1635  
1636  provides: [String.Extras]
1637  
1638  ...
1639  */
1640  
1641  (function(){
1642  
1643  var special = {
1644      'a': /[àáâãäåăą]/g,
1645      'A': /[ÀÁÂÃÄÅĂĄ]/g,
1646      'c': /[ćčç]/g,
1647      'C': /[ĆČÇ]/g,
1648      'd': /[ďđ]/g,
1649      'D': /[ĎÐ]/g,
1650      'e': /[èéêëěę]/g,
1651      'E': /[ÈÉÊËĚĘ]/g,
1652      'g': /[ğ]/g,
1653      'G': /[Ğ]/g,
1654      'i': /[ìíîï]/g,
1655      'I': /[ÌÍÎÏ]/g,
1656      'l': /[ĺľł]/g,
1657      'L': /[ĹĽŁ]/g,
1658      'n': /[ñňń]/g,
1659      'N': /[ÑŇŃ]/g,
1660      'o': /[òóôõöøő]/g,
1661      'O': /[ÒÓÔÕÖØ]/g,
1662      'r': /[řŕ]/g,
1663      'R': /[ŘŔ]/g,
1664      's': /[ššş]/g,
1665      'S': /[ŠŞŚ]/g,
1666      't': /[ťţ]/g,
1667      'T': /[ŤŢ]/g,
1668      'ue': /[ü]/g,
1669      'UE': /[Ü]/g,
1670      'u': /[ùúûůµ]/g,
1671      'U': /[ÙÚÛŮ]/g,
1672      'y': /[ÿý]/g,
1673      'Y': /[ŸÝ]/g,
1674      'z': /[žźż]/g,
1675      'Z': /[ŽŹŻ]/g,
1676      'th': /[þ]/g,
1677      'TH': /[Þ]/g,
1678      'dh': /[ð]/g,
1679      'DH': /[Ð]/g,
1680      'ss': /[ß]/g,
1681      'oe': /[œ]/g,
1682      'OE': /[Œ]/g,
1683      'ae': /[æ]/g,
1684      'AE': /[Æ]/g
1685  },
1686  
1687  tidy = {
1688      ' ': /[\xa0\u2002\u2003\u2009]/g,
1689      '*': /[\xb7]/g,
1690      '\'': /[\u2018\u2019]/g,
1691      '"': /[\u201c\u201d]/g,
1692      '...': /[\u2026]/g,
1693      '-': /[\u2013]/g,
1694  //    '--': /[\u2014]/g,
1695      '&raquo;': /[\uFFFD]/g
1696  };
1697  
1698  var walk = function(string, replacements){
1699      var result = string, key;
1700      for (key in replacements) result = result.replace(replacements[key], key);
1701      return result;
1702  };
1703  
1704  var getRegexForTag = function(tag, contents){
1705      tag = tag || '';
1706      var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>",
1707          reg = new RegExp(regstr, "gi");
1708      return reg;
1709  };
1710  
1711  String.implement({
1712  
1713      standardize: function(){
1714          return walk(this, special);
1715      },
1716  
1717      repeat: function(times){
1718          return new Array(times + 1).join(this);
1719      },
1720  
1721      pad: function(length, str, direction){
1722          if (this.length >= length) return this;
1723  
1724          var pad = (str == null ? ' ' : '' + str)
1725              .repeat(length - this.length)
1726              .substr(0, length - this.length);
1727  
1728          if (!direction || direction == 'right') return this + pad;
1729          if (direction == 'left') return pad + this;
1730  
1731          return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
1732      },
1733  
1734      getTags: function(tag, contents){
1735          return this.match(getRegexForTag(tag, contents)) || [];
1736      },
1737  
1738      stripTags: function(tag, contents){
1739          return this.replace(getRegexForTag(tag, contents), '');
1740      },
1741  
1742      tidy: function(){
1743          return walk(this, tidy);
1744      },
1745  
1746      truncate: function(max, trail, atChar){
1747          var string = this;
1748          if (trail == null && arguments.length == 1) trail = '…';
1749          if (string.length > max){
1750              string = string.substring(0, max);
1751              if (atChar){
1752                  var index = string.lastIndexOf(atChar);
1753                  if (index != -1) string = string.substr(0, index);
1754              }
1755              if (trail) string += trail;
1756          }
1757          return string;
1758      }
1759  
1760  });
1761  
1762  })();
1763  
1764  
1765  /*
1766  ---
1767  
1768  script: String.QueryString.js
1769  
1770  name: String.QueryString
1771  
1772  description: Methods for dealing with URI query strings.
1773  
1774  license: MIT-style license
1775  
1776  authors:
1777    - Sebastian Markbåge
1778    - Aaron Newton
1779    - Lennart Pilon
1780    - Valerio Proietti
1781  
1782  requires:
1783    - Core/Array
1784    - Core/String
1785    - /MooTools.More
1786  
1787  provides: [String.QueryString]
1788  
1789  ...
1790  */
1791  
1792  String.implement({
1793  
1794      parseQueryString: function(decodeKeys, decodeValues){
1795          if (decodeKeys == null) decodeKeys = true;
1796          if (decodeValues == null) decodeValues = true;
1797  
1798          var vars = this.split(/[&;]/),
1799              object = {};
1800          if (!vars.length) return object;
1801  
1802          vars.each(function(val){
1803              var index = val.indexOf('=') + 1,
1804                  value = index ? val.substr(index) : '',
1805                  keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val],
1806                  obj = object;
1807              if (!keys) return;
1808              if (decodeValues) value = decodeURIComponent(value);
1809              keys.each(function(key, i){
1810                  if (decodeKeys) key = decodeURIComponent(key);
1811                  var current = obj[key];
1812  
1813                  if (i < keys.length - 1) obj = obj[key] = current || {};
1814                  else if (typeOf(current) == 'array') current.push(value);
1815                  else obj[key] = current != null ? [current, value] : value;
1816              });
1817          });
1818  
1819          return object;
1820      },
1821  
1822      cleanQueryString: function(method){
1823          return this.split('&').filter(function(val){
1824              var index = val.indexOf('='),
1825                  key = index < 0 ? '' : val.substr(0, index),
1826                  value = val.substr(index + 1);
1827  
1828              return method ? method.call(null, key, value) : (value || value === 0);
1829          }).join('&');
1830      }
1831  
1832  });
1833  
1834  
1835  /*
1836  ---
1837  
1838  script: URI.js
1839  
1840  name: URI
1841  
1842  description: Provides methods useful in managing the window location and uris.
1843  
1844  license: MIT-style license
1845  
1846  authors:
1847    - Sebastian Markbåge
1848    - Aaron Newton
1849  
1850  requires:
1851    - Core/Object
1852    - Core/Class
1853    - Core/Class.Extras
1854    - Core/Element
1855    - /String.QueryString
1856  
1857  provides: [URI]
1858  
1859  ...
1860  */
1861  
1862  (function(){
1863  
1864  var toString = function(){
1865      return this.get('value');
1866  };
1867  
1868  var URI = this.URI = new Class({
1869  
1870      Implements: Options,
1871  
1872      options: {
1873          /*base: false*/
1874      },
1875  
1876      regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
1877      parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
1878      schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
1879  
1880      initialize: function(uri, options){
1881          this.setOptions(options);
1882          var base = this.options.base || URI.base;
1883          if (!uri) uri = base;
1884  
1885          if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed);
1886          else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
1887      },
1888  
1889      parse: function(value, base){
1890          var bits = value.match(this.regex);
1891          if (!bits) return false;
1892          bits.shift();
1893          return this.merge(bits.associate(this.parts), base);
1894      },
1895  
1896      merge: function(bits, base){
1897          if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
1898          if (base){
1899              this.parts.every(function(part){
1900                  if (bits[part]) return false;
1901                  bits[part] = base[part] || '';
1902                  return true;
1903              });
1904          }
1905          bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
1906          bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
1907          return bits;
1908      },
1909  
1910      parseDirectory: function(directory, baseDirectory){
1911          directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
1912          if (!directory.test(URI.regs.directoryDot)) return directory;
1913          var result = [];
1914          directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
1915              if (dir == '..' && result.length > 0) result.pop();
1916              else if (dir != '.') result.push(dir);
1917          });
1918          return result.join('/') + '/';
1919      },
1920  
1921      combine: function(bits){
1922          return bits.value || bits.scheme + '://' +
1923              (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
1924              (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
1925              (bits.directory || '/') + (bits.file || '') +
1926              (bits.query ? '?' + bits.query : '') +
1927              (bits.fragment ? '#' + bits.fragment : '');
1928      },
1929  
1930      set: function(part, value, base){
1931          if (part == 'value'){
1932              var scheme = value.match(URI.regs.scheme);
1933              if (scheme) scheme = scheme[1];
1934              if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value };
1935              else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
1936          } else if (part == 'data'){
1937              this.setData(value);
1938          } else {
1939              this.parsed[part] = value;
1940          }
1941          return this;
1942      },
1943  
1944      get: function(part, base){
1945          switch (part){
1946              case 'value': return this.combine(this.parsed, base ? base.parsed : false);
1947              case 'data' : return this.getData();
1948          }
1949          return this.parsed[part] || '';
1950      },
1951  
1952      go: function(){
1953          document.location.href = this.toString();
1954      },
1955  
1956      toURI: function(){
1957          return this;
1958      },
1959  
1960      getData: function(key, part){
1961          var qs = this.get(part || 'query');
1962          if (!(qs || qs === 0)) return key ? null : {};
1963          var obj = qs.parseQueryString();
1964          return key ? obj[key] : obj;
1965      },
1966  
1967      setData: function(values, merge, part){
1968          if (typeof values == 'string'){
1969              var data = this.getData();
1970              data[arguments[0]] = arguments[1];
1971              values = data;
1972          } else if (merge){
1973              values = Object.merge(this.getData(), values);
1974          }
1975          return this.set(part || 'query', Object.toQueryString(values));
1976      },
1977  
1978      clearData: function(part){
1979          return this.set(part || 'query', '');
1980      },
1981  
1982      toString: toString,
1983      valueOf: toString
1984  
1985  });
1986  
1987  URI.regs = {
1988      endSlash: /\/$/,
1989      scheme: /^(\w+):/,
1990      directoryDot: /\.\/|\.$/
1991  };
1992  
1993  URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location});
1994  
1995  String.implement({
1996  
1997      toURI: function(options){
1998          return new URI(this, options);
1999      }
2000  
2001  });
2002  
2003  })();
2004  
2005  
2006  /*
2007  ---
2008  
2009  script: URI.Relative.js
2010  
2011  name: URI.Relative
2012  
2013  description: Extends the URI class to add methods for computing relative and absolute urls.
2014  
2015  license: MIT-style license
2016  
2017  authors:
2018    - Sebastian Markbåge
2019  
2020  
2021  requires:
2022    - /Class.refactor
2023    - /URI
2024  
2025  provides: [URI.Relative]
2026  
2027  ...
2028  */
2029  
2030  URI = Class.refactor(URI, {
2031  
2032      combine: function(bits, base){
2033          if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
2034              return this.previous.apply(this, arguments);
2035          var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
2036  
2037          if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
2038  
2039          var baseDir = base.directory.split('/'),
2040              relDir = bits.directory.split('/'),
2041              path = '',
2042              offset;
2043  
2044          var i = 0;
2045          for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
2046          for (i = 0; i < baseDir.length - offset - 1; i++) path += '../';
2047          for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
2048  
2049          return (path || (bits.file ? '' : './')) + end;
2050      },
2051  
2052      toAbsolute: function(base){
2053          base = new URI(base);
2054          if (base) base.set('directory', '').set('file', '');
2055          return this.toRelative(base);
2056      },
2057  
2058      toRelative: function(base){
2059          return this.get('value', new URI(base));
2060      }
2061  
2062  });
2063  
2064  
2065  /*
2066  ---
2067  
2068  name: Hash
2069  
2070  description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
2071  
2072  license: MIT-style license.
2073  
2074  requires:
2075    - Core/Object
2076    - /MooTools.More
2077  
2078  provides: [Hash]
2079  
2080  ...
2081  */
2082  
2083  (function(){
2084  
2085  if (this.Hash) return;
2086  
2087  var Hash = this.Hash = new Type('Hash', function(object){
2088      if (typeOf(object) == 'hash') object = Object.clone(object.getClean());
2089      for (var key in object) this[key] = object[key];
2090      return this;
2091  });
2092  
2093  this.$H = function(object){
2094      return new Hash(object);
2095  };
2096  
2097  Hash.implement({
2098  
2099      forEach: function(fn, bind){
2100          Object.forEach(this, fn, bind);
2101      },
2102  
2103      getClean: function(){
2104          var clean = {};
2105          for (var key in this){
2106              if (this.hasOwnProperty(key)) clean[key] = this[key];
2107          }
2108          return clean;
2109      },
2110  
2111      getLength: function(){
2112          var length = 0;
2113          for (var key in this){
2114              if (this.hasOwnProperty(key)) length++;
2115          }
2116          return length;
2117      }
2118  
2119  });
2120  
2121  Hash.alias('each', 'forEach');
2122  
2123  Hash.implement({
2124  
2125      has: Object.prototype.hasOwnProperty,
2126  
2127      keyOf: function(value){
2128          return Object.keyOf(this, value);
2129      },
2130  
2131      hasValue: function(value){
2132          return Object.contains(this, value);
2133      },
2134  
2135      extend: function(properties){
2136          Hash.each(properties || {}, function(value, key){
2137              Hash.set(this, key, value);
2138          }, this);
2139          return this;
2140      },
2141  
2142      combine: function(properties){
2143          Hash.each(properties || {}, function(value, key){
2144              Hash.include(this, key, value);
2145          }, this);
2146          return this;
2147      },
2148  
2149      erase: function(key){
2150          if (this.hasOwnProperty(key)) delete this[key];
2151          return this;
2152      },
2153  
2154      get: function(key){
2155          return (this.hasOwnProperty(key)) ? this[key] : null;
2156      },
2157  
2158      set: function(key, value){
2159          if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
2160          return this;
2161      },
2162  
2163      empty: function(){
2164          Hash.each(this, function(value, key){
2165              delete this[key];
2166          }, this);
2167          return this;
2168      },
2169  
2170      include: function(key, value){
2171          if (this[key] == undefined) this[key] = value;
2172          return this;
2173      },
2174  
2175      map: function(fn, bind){
2176          return new Hash(Object.map(this, fn, bind));
2177      },
2178  
2179      filter: function(fn, bind){
2180          return new Hash(Object.filter(this, fn, bind));
2181      },
2182  
2183      every: function(fn, bind){
2184          return Object.every(this, fn, bind);
2185      },
2186  
2187      some: function(fn, bind){
2188          return Object.some(this, fn, bind);
2189      },
2190  
2191      getKeys: function(){
2192          return Object.keys(this);
2193      },
2194  
2195      getValues: function(){
2196          return Object.values(this);
2197      },
2198  
2199      toQueryString: function(base){
2200          return Object.toQueryString(this, base);
2201      }
2202  
2203  });
2204  
2205  Hash.alias({indexOf: 'keyOf', contains: 'hasValue'});
2206  
2207  
2208  })();
2209  
2210  
2211  
2212  /*
2213  ---
2214  
2215  script: Hash.Extras.js
2216  
2217  name: Hash.Extras
2218  
2219  description: Extends the Hash Type to include getFromPath which allows a path notation to child elements.
2220  
2221  license: MIT-style license
2222  
2223  authors:
2224    - Aaron Newton
2225  
2226  requires:
2227    - /Hash
2228    - /Object.Extras
2229  
2230  provides: [Hash.Extras]
2231  
2232  ...
2233  */
2234  
2235  Hash.implement({
2236  
2237      getFromPath: function(notation){
2238          return Object.getFromPath(this, notation);
2239      },
2240  
2241      cleanValues: function(method){
2242          return new Hash(Object.cleanValues(this, method));
2243      },
2244  
2245      run: function(){
2246          Object.run(arguments);
2247      }
2248  
2249  });
2250  
2251  
2252  /*
2253  ---
2254  
2255  script: Element.Forms.js
2256  
2257  name: Element.Forms
2258  
2259  description: Extends the Element native object to include methods useful in managing inputs.
2260  
2261  license: MIT-style license
2262  
2263  authors:
2264    - Aaron Newton
2265  
2266  requires:
2267    - Core/Element
2268    - /String.Extras
2269    - /MooTools.More
2270  
2271  provides: [Element.Forms]
2272  
2273  ...
2274  */
2275  
2276  Element.implement({
2277  
2278      tidy: function(){
2279          this.set('value', this.get('value').tidy());
2280      },
2281  
2282      getTextInRange: function(start, end){
2283          return this.get('value').substring(start, end);
2284      },
2285  
2286      getSelectedText: function(){
2287          if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
2288          return document.selection.createRange().text;
2289      },
2290  
2291      getSelectedRange: function(){
2292          if (this.selectionStart != null){
2293              return {
2294                  start: this.selectionStart,
2295                  end: this.selectionEnd
2296              };
2297          }
2298  
2299          var pos = {
2300              start: 0,
2301              end: 0
2302          };
2303          var range = this.getDocument().selection.createRange();
2304          if (!range || range.parentElement() != this) return pos;
2305          var duplicate = range.duplicate();
2306  
2307          if (this.type == 'text'){
2308              pos.start = 0 - duplicate.moveStart('character', -100000);
2309              pos.end = pos.start + range.text.length;
2310          } else {
2311              var value = this.get('value');
2312              var offset = value.length;
2313              duplicate.moveToElementText(this);
2314              duplicate.setEndPoint('StartToEnd', range);
2315              if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
2316              pos.end = offset - duplicate.text.length;
2317              duplicate.setEndPoint('StartToStart', range);
2318              pos.start = offset - duplicate.text.length;
2319          }
2320          return pos;
2321      },
2322  
2323      getSelectionStart: function(){
2324          return this.getSelectedRange().start;
2325      },
2326  
2327      getSelectionEnd: function(){
2328          return this.getSelectedRange().end;
2329      },
2330  
2331      setCaretPosition: function(pos){
2332          if (pos == 'end') pos = this.get('value').length;
2333          this.selectRange(pos, pos);
2334          return this;
2335      },
2336  
2337      getCaretPosition: function(){
2338          return this.getSelectedRange().start;
2339      },
2340  
2341      selectRange: function(start, end){
2342          if (this.setSelectionRange){
2343              this.focus();
2344              this.setSelectionRange(start, end);
2345          } else {
2346              var value = this.get('value');
2347              var diff = value.substr(start, end - start).replace(/\r/g, '').length;
2348              start = value.substr(0, start).replace(/\r/g, '').length;
2349              var range = this.createTextRange();
2350              range.collapse(true);
2351              range.moveEnd('character', start + diff);
2352              range.moveStart('character', start);
2353              range.select();
2354          }
2355          return this;
2356      },
2357  
2358      insertAtCursor: function(value, select){
2359          var pos = this.getSelectedRange();
2360          var text = this.get('value');
2361          this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
2362          if (select !== false) this.selectRange(pos.start, pos.start + value.length);
2363          else this.setCaretPosition(pos.start + value.length);
2364          return this;
2365      },
2366  
2367      insertAroundCursor: function(options, select){
2368          options = Object.append({
2369              before: '',
2370              defaultMiddle: '',
2371              after: ''
2372          }, options);
2373  
2374          var value = this.getSelectedText() || options.defaultMiddle;
2375          var pos = this.getSelectedRange();
2376          var text = this.get('value');
2377  
2378          if (pos.start == pos.end){
2379              this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
2380              this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
2381          } else {
2382              var current = text.substring(pos.start, pos.end);
2383              this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
2384              var selStart = pos.start + options.before.length;
2385              if (select !== false) this.selectRange(selStart, selStart + current.length);
2386              else this.setCaretPosition(selStart + text.length);
2387          }
2388          return this;
2389      }
2390  
2391  });
2392  
2393  
2394  /*
2395  ---
2396  
2397  script: Elements.From.js
2398  
2399  name: Elements.From
2400  
2401  description: Returns a collection of elements from a string of html.
2402  
2403  license: MIT-style license
2404  
2405  authors:
2406    - Aaron Newton
2407  
2408  requires:
2409    - Core/String
2410    - Core/Element
2411    - /MooTools.More
2412  
2413  provides: [Elements.from, Elements.From]
2414  
2415  ...
2416  */
2417  
2418  Elements.from = function(text, excludeScripts){
2419      if (excludeScripts || excludeScripts == null) text = text.stripScripts();
2420  
2421      var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
2422  
2423      if (match){
2424          container = new Element('table');
2425          var tag = match[1].toLowerCase();
2426          if (['td', 'th', 'tr'].contains(tag)){
2427              container = new Element('tbody').inject(container);
2428              if (tag != 'tr') container = new Element('tr').inject(container);
2429          }
2430      }
2431  
2432      return (container || new Element('div')).set('html', text).getChildren();
2433  };
2434  
2435  
2436  /*
2437  ---
2438  
2439  name: Element.Event.Pseudos
2440  
2441  description: Adds the functionality to add pseudo events for Elements
2442  
2443  license: MIT-style license
2444  
2445  authors:
2446    - Arian Stolwijk
2447  
2448  requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos]
2449  
2450  provides: [Element.Event.Pseudos, Element.Delegation]
2451  
2452  ...
2453  */
2454  
2455  (function(){
2456  
2457  var pseudos = {relay: false},
2458      copyFromEvents = ['once', 'throttle', 'pause'],
2459      count = copyFromEvents.length;
2460  
2461  while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]);
2462  
2463  DOMEvent.definePseudo = function(key, listener){
2464      pseudos[key] = listener;
2465      return this;
2466  };
2467  
2468  var proto = Element.prototype;
2469  [Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent));
2470  
2471  })();
2472  
2473  
2474  /*
2475  ---
2476  
2477  name: Element.Event.Pseudos.Keys
2478  
2479  description: Adds functionality fire events if certain keycombinations are pressed
2480  
2481  license: MIT-style license
2482  
2483  authors:
2484    - Arian Stolwijk
2485  
2486  requires: [Element.Event.Pseudos]
2487  
2488  provides: [Element.Event.Pseudos.Keys]
2489  
2490  ...
2491  */
2492  
2493  (function(){
2494  
2495  var keysStoreKey = '$moo:keys-pressed',
2496      keysKeyupStoreKey = '$moo:keys-keyup';
2497  
2498  
2499  DOMEvent.definePseudo('keys', function(split, fn, args){
2500  
2501      var event = args[0],
2502          keys = [],
2503          pressed = this.retrieve(keysStoreKey, []);
2504  
2505      keys.append(split.value.replace('++', function(){
2506          keys.push('+'); // shift++ and shift+++a
2507          return '';
2508      }).split('+'));
2509  
2510      pressed.include(event.key);
2511  
2512      if (keys.every(function(key){
2513          return pressed.contains(key);
2514      })) fn.apply(this, args);
2515  
2516      this.store(keysStoreKey, pressed);
2517  
2518      if (!this.retrieve(keysKeyupStoreKey)){
2519          var keyup = function(event){
2520              (function(){
2521                  pressed = this.retrieve(keysStoreKey, []).erase(event.key);
2522                  this.store(keysStoreKey, pressed);
2523              }).delay(0, this); // Fix for IE
2524          };
2525          this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup);
2526      }
2527  
2528  });
2529  
2530  DOMEvent.defineKeys({
2531      '16': 'shift',
2532      '17': 'control',
2533      '18': 'alt',
2534      '20': 'capslock',
2535      '33': 'pageup',
2536      '34': 'pagedown',
2537      '35': 'end',
2538      '36': 'home',
2539      '144': 'numlock',
2540      '145': 'scrolllock',
2541      '186': ';',
2542      '187': '=',
2543      '188': ',',
2544      '190': '.',
2545      '191': '/',
2546      '192': '`',
2547      '219': '[',
2548      '220': '\\',
2549      '221': ']',
2550      '222': "'",
2551      '107': '+'
2552  }).defineKey(Browser.firefox ? 109 : 189, '-');
2553  
2554  })();
2555  
2556  
2557  /*
2558  ---
2559  
2560  script: Element.Measure.js
2561  
2562  name: Element.Measure
2563  
2564  description: Extends the Element native object to include methods useful in measuring dimensions.
2565  
2566  credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
2567  
2568  license: MIT-style license
2569  
2570  authors:
2571    - Aaron Newton
2572  
2573  requires:
2574    - Core/Element.Style
2575    - Core/Element.Dimensions
2576    - /MooTools.More
2577  
2578  provides: [Element.Measure]
2579  
2580  ...
2581  */
2582  
2583  (function(){
2584  
2585  var getStylesList = function(styles, planes){
2586      var list = [];
2587      Object.each(planes, function(directions){
2588          Object.each(directions, function(edge){
2589              styles.each(function(style){
2590                  list.push(style + '-' + edge + (style == 'border' ? '-width' : ''));
2591              });
2592          });
2593      });
2594      return list;
2595  };
2596  
2597  var calculateEdgeSize = function(edge, styles){
2598      var total = 0;
2599      Object.each(styles, function(value, style){
2600          if (style.test(edge)) total = total + value.toInt();
2601      });
2602      return total;
2603  };
2604  
2605  var isVisible = function(el){
2606      return !!(!el || el.offsetHeight || el.offsetWidth);
2607  };
2608  
2609  
2610  Element.implement({
2611  
2612      measure: function(fn){
2613          if (isVisible(this)) return fn.call(this);
2614          var parent = this.getParent(),
2615              toMeasure = [];
2616          while (!isVisible(parent) && parent != document.body){
2617              toMeasure.push(parent.expose());
2618              parent = parent.getParent();
2619          }
2620          var restore = this.expose(),
2621              result = fn.call(this);
2622          restore();
2623          toMeasure.each(function(restore){
2624              restore();
2625          });
2626          return result;
2627      },
2628  
2629      expose: function(){
2630          if (this.getStyle('display') != 'none') return function(){};
2631          var before = this.style.cssText;
2632          this.setStyles({
2633              display: 'block',
2634              position: 'absolute',
2635              visibility: 'hidden'
2636          });
2637          return function(){
2638              this.style.cssText = before;
2639          }.bind(this);
2640      },
2641  
2642      getDimensions: function(options){
2643          options = Object.merge({computeSize: false}, options);
2644          var dim = {x: 0, y: 0};
2645  
2646          var getSize = function(el, options){
2647              return (options.computeSize) ? el.getComputedSize(options) : el.getSize();
2648          };
2649  
2650          var parent = this.getParent('body');
2651  
2652          if (parent && this.getStyle('display') == 'none'){
2653              dim = this.measure(function(){
2654                  return getSize(this, options);
2655              });
2656          } else if (parent){
2657              try { //safari sometimes crashes here, so catch it
2658                  dim = getSize(this, options);
2659              }catch(e){}
2660          }
2661  
2662          return Object.append(dim, (dim.x || dim.x === 0) ? {
2663                  width: dim.x,
2664                  height: dim.y
2665              } : {
2666                  x: dim.width,
2667                  y: dim.height
2668              }
2669          );
2670      },
2671  
2672      getComputedSize: function(options){
2673          
2674  
2675          options = Object.merge({
2676              styles: ['padding','border'],
2677              planes: {
2678                  height: ['top','bottom'],
2679                  width: ['left','right']
2680              },
2681              mode: 'both'
2682          }, options);
2683  
2684          var styles = {},
2685              size = {width: 0, height: 0},
2686              dimensions;
2687  
2688          if (options.mode == 'vertical'){
2689              delete size.width;
2690              delete options.planes.width;
2691          } else if (options.mode == 'horizontal'){
2692              delete size.height;
2693              delete options.planes.height;
2694          }
2695  
2696          getStylesList(options.styles, options.planes).each(function(style){
2697              styles[style] = this.getStyle(style).toInt();
2698          }, this);
2699  
2700          Object.each(options.planes, function(edges, plane){
2701  
2702              var capitalized = plane.capitalize(),
2703                  style = this.getStyle(plane);
2704  
2705              if (style == 'auto' && !dimensions) dimensions = this.getDimensions();
2706  
2707              style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt();
2708              size['total' + capitalized] = style;
2709  
2710              edges.each(function(edge){
2711                  var edgesize = calculateEdgeSize(edge, styles);
2712                  size['computed' + edge.capitalize()] = edgesize;
2713                  size['total' + capitalized] += edgesize;
2714              });
2715  
2716          }, this);
2717  
2718          return Object.append(size, styles);
2719      }
2720  
2721  });
2722  
2723  })();
2724  
2725  
2726  /*
2727  ---
2728  
2729  script: Element.Pin.js
2730  
2731  name: Element.Pin
2732  
2733  description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
2734  
2735  license: MIT-style license
2736  
2737  authors:
2738    - Aaron Newton
2739  
2740  requires:
2741    - Core/Element.Event
2742    - Core/Element.Dimensions
2743    - Core/Element.Style
2744    - /MooTools.More
2745  
2746  provides: [Element.Pin]
2747  
2748  ...
2749  */
2750  
2751  (function(){
2752      var supportsPositionFixed = false,
2753          supportTested = false;
2754  
2755      var testPositionFixed = function(){
2756          var test = new Element('div').setStyles({
2757              position: 'fixed',
2758              top: 0,
2759              right: 0
2760          }).inject(document.body);
2761          supportsPositionFixed = (test.offsetTop === 0);
2762          test.dispose();
2763          supportTested = true;
2764      };
2765  
2766      Element.implement({
2767  
2768          pin: function(enable, forceScroll){
2769              if (!supportTested) testPositionFixed();
2770              if (this.getStyle('display') == 'none') return this;
2771  
2772              var pinnedPosition,
2773                  scroll = window.getScroll(),
2774                  parent,
2775                  scrollFixer;
2776  
2777              if (enable !== false){
2778                  pinnedPosition = this.getPosition(supportsPositionFixed ? document.body : this.getOffsetParent());
2779                  if (!this.retrieve('pin:_pinned')){
2780                      var currentPosition = {
2781                          top: pinnedPosition.y - scroll.y,
2782                          left: pinnedPosition.x - scroll.x
2783                      };
2784  
2785                      if (supportsPositionFixed && !forceScroll){
2786                          this.setStyle('position', 'fixed').setStyles(currentPosition);
2787                      } else {
2788  
2789                          parent = this.getOffsetParent();
2790                          var position = this.getPosition(parent),
2791                              styles = this.getStyles('left', 'top');
2792  
2793                          if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position);
2794                          if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute');
2795  
2796                          position = {
2797                              x: styles.left.toInt() - scroll.x,
2798                              y: styles.top.toInt() - scroll.y
2799                          };
2800  
2801                          scrollFixer = function(){
2802                              if (!this.retrieve('pin:_pinned')) return;
2803                              var scroll = window.getScroll();
2804                              this.setStyles({
2805                                  left: position.x + scroll.x,
2806                                  top: position.y + scroll.y
2807                              });
2808                          }.bind(this);
2809  
2810                          this.store('pin:_scrollFixer', scrollFixer);
2811                          window.addEvent('scroll', scrollFixer);
2812                      }
2813                      this.store('pin:_pinned', true);
2814                  }
2815  
2816              } else {
2817                  if (!this.retrieve('pin:_pinned')) return this;
2818  
2819                  parent = this.getParent();
2820                  var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
2821  
2822                  pinnedPosition = this.getPosition(offsetParent);
2823  
2824                  this.store('pin:_pinned', false);
2825                  scrollFixer = this.retrieve('pin:_scrollFixer');
2826                  if (!scrollFixer){
2827                      this.setStyles({
2828                          position: 'absolute',
2829                          top: pinnedPosition.y + scroll.y,
2830                          left: pinnedPosition.x + scroll.x
2831                      });
2832                  } else {
2833                      this.store('pin:_scrollFixer', null);
2834                      window.removeEvent('scroll', scrollFixer);
2835                  }
2836                  this.removeClass('isPinned');
2837              }
2838              return this;
2839          },
2840  
2841          unpin: function(){
2842              return this.pin(false);
2843          },
2844  
2845          togglePin: function(){
2846              return this.pin(!this.retrieve('pin:_pinned'));
2847          }
2848  
2849      });
2850  
2851  
2852  
2853  })();
2854  
2855  
2856  /*
2857  ---
2858  
2859  script: Element.Position.js
2860  
2861  name: Element.Position
2862  
2863  description: Extends the Element native object to include methods useful positioning elements relative to others.
2864  
2865  license: MIT-style license
2866  
2867  authors:
2868    - Aaron Newton
2869    - Jacob Thornton
2870  
2871  requires:
2872    - Core/Options
2873    - Core/Element.Dimensions
2874    - Element.Measure
2875  
2876  provides: [Element.Position]
2877  
2878  ...
2879  */
2880  
2881  (function(original){
2882  
2883  var local = Element.Position = {
2884  
2885      options: {/*
2886          edge: false,
2887          returnPos: false,
2888          minimum: {x: 0, y: 0},
2889          maximum: {x: 0, y: 0},
2890          relFixedPosition: false,
2891          ignoreMargins: false,
2892          ignoreScroll: false,
2893          allowNegative: false,*/
2894          relativeTo: document.body,
2895          position: {
2896              x: 'center', //left, center, right
2897              y: 'center' //top, center, bottom
2898          },
2899          offset: {x: 0, y: 0}
2900      },
2901  
2902      getOptions: function(element, options){
2903          options = Object.merge({}, local.options, options);
2904          local.setPositionOption(options);
2905          local.setEdgeOption(options);
2906          local.setOffsetOption(element, options);
2907          local.setDimensionsOption(element, options);
2908          return options;
2909      },
2910  
2911      setPositionOption: function(options){
2912          options.position = local.getCoordinateFromValue(options.position);
2913      },
2914  
2915      setEdgeOption: function(options){
2916          var edgeOption = local.getCoordinateFromValue(options.edge);
2917          options.edge = edgeOption ? edgeOption :
2918              (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} :
2919              {x: 'left', y: 'top'};
2920      },
2921  
2922      setOffsetOption: function(element, options){
2923          var parentOffset = {x: 0, y: 0},
2924              offsetParent = element.measure(function(){
2925                  return document.id(this.getOffsetParent());
2926              }),
2927              parentScroll = offsetParent.getScroll();
2928  
2929          if (!offsetParent || offsetParent == element.getDocument().body) return;
2930          parentOffset = offsetParent.measure(function(){
2931              var position = this.getPosition();
2932              if (this.getStyle('position') == 'fixed'){
2933                  var scroll = window.getScroll();
2934                  position.x += scroll.x;
2935                  position.y += scroll.y;
2936              }
2937              return position;
2938          });
2939  
2940          options.offset = {
2941              parentPositioned: offsetParent != document.id(options.relativeTo),
2942              x: options.offset.x - parentOffset.x + parentScroll.x,
2943              y: options.offset.y - parentOffset.y + parentScroll.y
2944          };
2945      },
2946  
2947      setDimensionsOption: function(element, options){
2948          options.dimensions = element.getDimensions({
2949              computeSize: true,
2950              styles: ['padding', 'border', 'margin']
2951          });
2952      },
2953  
2954      getPosition: function(element, options){
2955          var position = {};
2956          options = local.getOptions(element, options);
2957          var relativeTo = document.id(options.relativeTo) || document.body;
2958  
2959          local.setPositionCoordinates(options, position, relativeTo);
2960          if (options.edge) local.toEdge(position, options);
2961  
2962          var offset = options.offset;
2963          position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt();
2964          position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt();
2965  
2966          local.toMinMax(position, options);
2967  
2968          if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position);
2969          if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position);
2970          if (options.ignoreMargins) local.toIgnoreMargins(position, options);
2971  
2972          position.left = Math.ceil(position.left);
2973          position.top = Math.ceil(position.top);
2974          delete position.x;
2975          delete position.y;
2976  
2977          return position;
2978      },
2979  
2980      setPositionCoordinates: function(options, position, relativeTo){
2981          var offsetY = options.offset.y,
2982              offsetX = options.offset.x,
2983              calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(),
2984              top = calc.y,
2985              left = calc.x,
2986              winSize = window.getSize();
2987  
2988          switch(options.position.x){
2989              case 'left': position.x = left + offsetX; break;
2990              case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break;
2991              default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break;
2992          }
2993  
2994          switch(options.position.y){
2995              case 'top': position.y = top + offsetY; break;
2996              case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break;
2997              default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break;
2998          }
2999      },
3000  
3001      toMinMax: function(position, options){
3002          var xy = {left: 'x', top: 'y'}, value;
3003          ['minimum', 'maximum'].each(function(minmax){
3004              ['left', 'top'].each(function(lr){
3005                  value = options[minmax] ? options[minmax][xy[lr]] : null;
3006                  if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value;
3007              });
3008          });
3009      },
3010  
3011      toRelFixedPosition: function(relativeTo, position){
3012          var winScroll = window.getScroll();
3013          position.top += winScroll.y;
3014          position.left += winScroll.x;
3015      },
3016  
3017      toIgnoreScroll: function(relativeTo, position){
3018          var relScroll = relativeTo.getScroll();
3019          position.top -= relScroll.y;
3020          position.left -= relScroll.x;
3021      },
3022  
3023      toIgnoreMargins: function(position, options){
3024          position.left += options.edge.x == 'right'
3025              ? options.dimensions['margin-right']
3026              : (options.edge.x != 'center'
3027                  ? -options.dimensions['margin-left']
3028                  : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2));
3029  
3030          position.top += options.edge.y == 'bottom'
3031              ? options.dimensions['margin-bottom']
3032              : (options.edge.y != 'center'
3033                  ? -options.dimensions['margin-top']
3034                  : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2));
3035      },
3036  
3037      toEdge: function(position, options){
3038          var edgeOffset = {},
3039              dimensions = options.dimensions,
3040              edge = options.edge;
3041  
3042          switch(edge.x){
3043              case 'left': edgeOffset.x = 0; break;
3044              case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break;
3045              // center
3046              default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break;
3047          }
3048  
3049          switch(edge.y){
3050              case 'top': edgeOffset.y = 0; break;
3051              case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break;
3052              // center
3053              default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break;
3054          }
3055  
3056          position.x += edgeOffset.x;
3057          position.y += edgeOffset.y;
3058      },
3059  
3060      getCoordinateFromValue: function(option){
3061          if (typeOf(option) != 'string') return option;
3062          option = option.toLowerCase();
3063  
3064          return {
3065              x: option.test('left') ? 'left'
3066                  : (option.test('right') ? 'right' : 'center'),
3067              y: option.test(/upper|top/) ? 'top'
3068                  : (option.test('bottom') ? 'bottom' : 'center')
3069          };
3070      }
3071  
3072  };
3073  
3074  Element.implement({
3075  
3076      position: function(options){
3077          if (options && (options.x != null || options.y != null)){
3078              return (original ? original.apply(this, arguments) : this);
3079          }
3080          var position = this.setStyle('position', 'absolute').calculatePosition(options);
3081          return (options && options.returnPos) ? position : this.setStyles(position);
3082      },
3083  
3084      calculatePosition: function(options){
3085          return local.getPosition(this, options);
3086      }
3087  
3088  });
3089  
3090  })(Element.prototype.position);
3091  
3092  
3093  /*
3094  ---
3095  
3096  script: Element.Shortcuts.js
3097  
3098  name: Element.Shortcuts
3099  
3100  description: Extends the Element native object to include some shortcut methods.
3101  
3102  license: MIT-style license
3103  
3104  authors:
3105    - Aaron Newton
3106  
3107  requires:
3108    - Core/Element.Style
3109    - /MooTools.More
3110  
3111  provides: [Element.Shortcuts]
3112  
3113  ...
3114  */
3115  
3116  Element.implement({
3117  
3118      isDisplayed: function(){
3119          return this.getStyle('display') != 'none';
3120      },
3121  
3122      isVisible: function(){
3123          var w = this.offsetWidth,
3124              h = this.offsetHeight;
3125          return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none';
3126      },
3127  
3128      toggle: function(){
3129          return this[this.isDisplayed() ? 'hide' : 'show']();
3130      },
3131  
3132      hide: function(){
3133          var d;
3134          try {
3135              //IE fails here if the element is not in the dom
3136              d = this.getStyle('display');
3137          } catch(e){}
3138          if (d == 'none') return this;
3139          return this.store('element:_originalDisplay', d || '').setStyle('display', 'none');
3140      },
3141  
3142      show: function(display){
3143          if (!display && this.isDisplayed()) return this;
3144          display = display || this.retrieve('element:_originalDisplay') || 'block';
3145          return this.setStyle('display', (display == 'none') ? 'block' : display);
3146      },
3147  
3148      swapClass: function(remove, add){
3149          return this.removeClass(remove).addClass(add);
3150      }
3151  
3152  });
3153  
3154  Document.implement({
3155  
3156      clearSelection: function(){
3157          if (window.getSelection){
3158              var selection = window.getSelection();
3159              if (selection && selection.removeAllRanges) selection.removeAllRanges();
3160          } else if (document.selection && document.selection.empty){
3161              try {
3162                  //IE fails here if selected element is not in dom
3163                  document.selection.empty();
3164              } catch(e){}
3165          }
3166      }
3167  
3168  });
3169  
3170  
3171  /*
3172  ---
3173  
3174  script: IframeShim.js
3175  
3176  name: IframeShim
3177  
3178  description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
3179  
3180  license: MIT-style license
3181  
3182  authors:
3183    - Aaron Newton
3184  
3185  requires:
3186    - Core/Element.Event
3187    - Core/Element.Style
3188    - Core/Options
3189    - Core/Events
3190    - /Element.Position
3191    - /Class.Occlude
3192  
3193  provides: [IframeShim]
3194  
3195  ...
3196  */
3197  
3198  var IframeShim = new Class({
3199  
3200      Implements: [Options, Events, Class.Occlude],
3201  
3202      options: {
3203          className: 'iframeShim',
3204          src: 'javascript:false;document.write("");',
3205          display: false,
3206          zIndex: null,
3207          margin: 0,
3208          offset: {x: 0, y: 0},
3209          browsers: (Browser.ie6 || (Browser.firefox && Browser.version < 3 && Browser.Platform.mac))
3210      },
3211  
3212      property: 'IframeShim',
3213  
3214      initialize: function(element, options){
3215          this.element = document.id(element);
3216          if (this.occlude()) return this.occluded;
3217          this.setOptions(options);
3218          this.makeShim();
3219          return this;
3220      },
3221  
3222      makeShim: function(){
3223          if (this.options.browsers){
3224              var zIndex = this.element.getStyle('zIndex').toInt();
3225  
3226              if (!zIndex){
3227                  zIndex = 1;
3228                  var pos = this.element.getStyle('position');
3229                  if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
3230                  this.element.setStyle('zIndex', zIndex);
3231              }
3232              zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
3233              if (zIndex < 0) zIndex = 1;
3234              this.shim = new Element('iframe', {
3235                  src: this.options.src,
3236                  scrolling: 'no',
3237                  frameborder: 0,
3238                  styles: {
3239                      zIndex: zIndex,
3240                      position: 'absolute',
3241                      border: 'none',
3242                      filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
3243                  },
3244                  'class': this.options.className
3245              }).store('IframeShim', this);
3246              var inject = (function(){
3247                  this.shim.inject(this.element, 'after');
3248                  this[this.options.display ? 'show' : 'hide']();
3249                  this.fireEvent('inject');
3250              }).bind(this);
3251              if (!IframeShim.ready) window.addEvent('load', inject);
3252              else inject();
3253          } else {
3254              this.position = this.hide = this.show = this.dispose = Function.from(this);
3255          }
3256      },
3257  
3258      position: function(){
3259          if (!IframeShim.ready || !this.shim) return this;
3260          var size = this.element.measure(function(){
3261              return this.getSize();
3262          });
3263          if (this.options.margin != undefined){
3264              size.x = size.x - (this.options.margin * 2);
3265              size.y = size.y - (this.options.margin * 2);
3266              this.options.offset.x += this.options.margin;
3267              this.options.offset.y += this.options.margin;
3268          }
3269          this.shim.set({width: size.x, height: size.y}).position({
3270              relativeTo: this.element,
3271              offset: this.options.offset
3272          });
3273          return this;
3274      },
3275  
3276      hide: function(){
3277          if (this.shim) this.shim.setStyle('display', 'none');
3278          return this;
3279      },
3280  
3281      show: function(){
3282          if (this.shim) this.shim.setStyle('display', 'block');
3283          return this.position();
3284      },
3285  
3286      dispose: function(){
3287          if (this.shim) this.shim.dispose();
3288          return this;
3289      },
3290  
3291      destroy: function(){
3292          if (this.shim) this.shim.destroy();
3293          return this;
3294      }
3295  
3296  });
3297  
3298  window.addEvent('load', function(){
3299      IframeShim.ready = true;
3300  });
3301  
3302  
3303  /*
3304  ---
3305  
3306  script: Mask.js
3307  
3308  name: Mask
3309  
3310  description: Creates a mask element to cover another.
3311  
3312  license: MIT-style license
3313  
3314  authors:
3315    - Aaron Newton
3316  
3317  requires:
3318    - Core/Options
3319    - Core/Events
3320    - Core/Element.Event
3321    - /Class.Binds
3322    - /Element.Position
3323    - /IframeShim
3324  
3325  provides: [Mask]
3326  
3327  ...
3328  */
3329  
3330  var Mask = new Class({
3331  
3332      Implements: [Options, Events],
3333  
3334      Binds: ['position'],
3335  
3336      options: {/*
3337          onShow: function(){},
3338          onHide: function(){},
3339          onDestroy: function(){},
3340          onClick: function(event){},
3341          inject: {
3342              where: 'after',
3343              target: null,
3344          },
3345          hideOnClick: false,
3346          id: null,
3347          destroyOnHide: false,*/
3348          style: {},
3349          'class': 'mask',
3350          maskMargins: false,
3351          useIframeShim: true,
3352          iframeShimOptions: {}
3353      },
3354  
3355      initialize: function(target, options){
3356          this.target = document.id(target) || document.id(document.body);
3357          this.target.store('mask', this);
3358          this.setOptions(options);
3359          this.render();
3360          this.inject();
3361      },
3362  
3363      render: function(){
3364          this.element = new Element('div', {
3365              'class': this.options['class'],
3366              id: this.options.id || 'mask-' + String.uniqueID(),
3367              styles: Object.merge({}, this.options.style, {
3368                  display: 'none'
3369              }),
3370              events: {
3371                  click: function(event){
3372                      this.fireEvent('click', event);
3373                      if (this.options.hideOnClick) this.hide();
3374                  }.bind(this)
3375              }
3376          });
3377  
3378          this.hidden = true;
3379      },
3380  
3381      toElement: function(){
3382          return this.element;
3383      },
3384  
3385      inject: function(target, where){
3386          where = where || (this.options.inject ? this.options.inject.where : '') || this.target == document.body ? 'inside' : 'after';
3387          target = target || (this.options.inject && this.options.inject.target) || this.target;
3388  
3389          this.element.inject(target, where);
3390  
3391          if (this.options.useIframeShim){
3392              this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
3393  
3394              this.addEvents({
3395                  show: this.shim.show.bind(this.shim),
3396                  hide: this.shim.hide.bind(this.shim),
3397                  destroy: this.shim.destroy.bind(this.shim)
3398              });
3399          }
3400      },
3401  
3402      position: function(){
3403          this.resize(this.options.width, this.options.height);
3404  
3405          this.element.position({
3406              relativeTo: this.target,
3407              position: 'topLeft',
3408              ignoreMargins: !this.options.maskMargins,
3409              ignoreScroll: this.target == document.body
3410          });
3411  
3412          return this;
3413      },
3414  
3415      resize: function(x, y){
3416          var opt = {
3417              styles: ['padding', 'border']
3418          };
3419          if (this.options.maskMargins) opt.styles.push('margin');
3420  
3421          var dim = this.target.getComputedSize(opt);
3422          if (this.target == document.body){
3423              this.element.setStyles({width: 0, height: 0});
3424              var win = window.getScrollSize();
3425              if (dim.totalHeight < win.y) dim.totalHeight = win.y;
3426              if (dim.totalWidth < win.x) dim.totalWidth = win.x;
3427          }
3428          this.element.setStyles({
3429              width: Array.pick([x, dim.totalWidth, dim.x]),
3430              height: Array.pick([y, dim.totalHeight, dim.y])
3431          });
3432  
3433          return this;
3434      },
3435  
3436      show: function(){
3437          if (!this.hidden) return this;
3438  
3439          window.addEvent('resize', this.position);
3440          this.position();
3441          this.showMask.apply(this, arguments);
3442  
3443          return this;
3444      },
3445  
3446      showMask: function(){
3447          this.element.setStyle('display', 'block');
3448          this.hidden = false;
3449          this.fireEvent('show');
3450      },
3451  
3452      hide: function(){
3453          if (this.hidden) return this;
3454  
3455          window.removeEvent('resize', this.position);
3456          this.hideMask.apply(this, arguments);
3457          if (this.options.destroyOnHide) return this.destroy();
3458  
3459          return this;
3460      },
3461  
3462      hideMask: function(){
3463          this.element.setStyle('display', 'none');
3464          this.hidden = true;
3465          this.fireEvent('hide');
3466      },
3467  
3468      toggle: function(){
3469          this[this.hidden ? 'show' : 'hide']();
3470      },
3471  
3472      destroy: function(){
3473          this.hide();
3474          this.element.destroy();
3475          this.fireEvent('destroy');
3476          this.target.eliminate('mask');
3477      }
3478  
3479  });
3480  
3481  Element.Properties.mask = {
3482  
3483      set: function(options){
3484          var mask = this.retrieve('mask');
3485          if (mask) mask.destroy();
3486          return this.eliminate('mask').store('mask:options', options);
3487      },
3488  
3489      get: function(){
3490          var mask = this.retrieve('mask');
3491          if (!mask){
3492              mask = new Mask(this, this.retrieve('mask:options'));
3493              this.store('mask', mask);
3494          }
3495          return mask;
3496      }
3497  
3498  };
3499  
3500  Element.implement({
3501  
3502      mask: function(options){
3503          if (options) this.set('mask', options);
3504          this.get('mask').show();
3505          return this;
3506      },
3507  
3508      unmask: function(){
3509          this.get('mask').hide();
3510          return this;
3511      }
3512  
3513  });
3514  
3515  
3516  /*
3517  ---
3518  
3519  script: Spinner.js
3520  
3521  name: Spinner
3522  
3523  description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
3524  
3525  license: MIT-style license
3526  
3527  authors:
3528    - Aaron Newton
3529  
3530  requires:
3531    - Core/Fx.Tween
3532    - Core/Request
3533    - /Class.refactor
3534    - /Mask
3535  
3536  provides: [Spinner]
3537  
3538  ...
3539  */
3540  
3541  var Spinner = new Class({
3542  
3543      Extends: Mask,
3544  
3545      Implements: Chain,
3546  
3547      options: {/*
3548          message: false,*/
3549          'class': 'spinner',
3550          containerPosition: {},
3551          content: {
3552              'class': 'spinner-content'
3553          },
3554          messageContainer: {
3555              'class': 'spinner-msg'
3556          },
3557          img: {
3558              'class': 'spinner-img'
3559          },
3560          fxOptions: {
3561              link: 'chain'
3562          }
3563      },
3564  
3565      initialize: function(target, options){
3566          this.target = document.id(target) || document.id(document.body);
3567          this.target.store('spinner', this);
3568          this.setOptions(options);
3569          this.render();
3570          this.inject();
3571  
3572          // Add this to events for when noFx is true; parent methods handle hide/show.
3573          var deactivate = function(){ this.active = false; }.bind(this);
3574          this.addEvents({
3575              hide: deactivate,
3576              show: deactivate
3577          });
3578      },
3579  
3580      render: function(){
3581          this.parent();
3582  
3583          this.element.set('id', this.options.id || 'spinner-' + String.uniqueID());
3584  
3585          this.content = document.id(this.options.content) || new Element('div', this.options.content);
3586          this.content.inject(this.element);
3587  
3588          if (this.options.message){
3589              this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
3590              this.msg.inject(this.content);
3591          }
3592  
3593          if (this.options.img){
3594              this.img = document.id(this.options.img) || new Element('div', this.options.img);
3595              this.img.inject(this.content);
3596          }
3597  
3598          this.element.set('tween', this.options.fxOptions);
3599      },
3600  
3601      show: function(noFx){
3602          if (this.active) return this.chain(this.show.bind(this));
3603          if (!this.hidden){
3604              this.callChain.delay(20, this);
3605              return this;
3606          }
3607  
3608          this.active = true;
3609  
3610          return this.parent(noFx);
3611      },
3612  
3613      showMask: function(noFx){
3614          var pos = function(){
3615              this.content.position(Object.merge({
3616                  relativeTo: this.element
3617              }, this.options.containerPosition));
3618          }.bind(this);
3619  
3620          if (noFx){
3621              this.parent();
3622              pos();
3623          } else {
3624              if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat();
3625              this.element.setStyles({
3626                  display: 'block',
3627                  opacity: 0
3628              }).tween('opacity', this.options.style.opacity);
3629              pos();
3630              this.hidden = false;
3631              this.fireEvent('show');
3632              this.callChain();
3633          }
3634      },
3635  
3636      hide: function(noFx){
3637          if (this.active) return this.chain(this.hide.bind(this));
3638          if (this.hidden){
3639              this.callChain.delay(20, this);
3640              return this;
3641          }
3642          this.active = true;
3643          return this.parent(noFx);
3644      },
3645  
3646      hideMask: function(noFx){
3647          if (noFx) return this.parent();
3648          this.element.tween('opacity', 0).get('tween').chain(function(){
3649              this.element.setStyle('display', 'none');
3650              this.hidden = true;
3651              this.fireEvent('hide');
3652              this.callChain();
3653          }.bind(this));
3654      },
3655  
3656      destroy: function(){
3657          this.content.destroy();
3658          this.parent();
3659          this.target.eliminate('spinner');
3660      }
3661  
3662  });
3663  
3664  Request = Class.refactor(Request, {
3665  
3666      options: {
3667          useSpinner: false,
3668          spinnerOptions: {},
3669          spinnerTarget: false
3670      },
3671  
3672      initialize: function(options){
3673          this._send = this.send;
3674          this.send = function(options){
3675              var spinner = this.getSpinner();
3676              if (spinner) spinner.chain(this._send.pass(options, this)).show();
3677              else this._send(options);
3678              return this;
3679          };
3680          this.previous(options);
3681      },
3682  
3683      getSpinner: function(){
3684          if (!this.spinner){
3685              var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
3686              if (this.options.useSpinner && update){
3687                  update.set('spinner', this.options.spinnerOptions);
3688                  var spinner = this.spinner = update.get('spinner');
3689                  ['complete', 'exception', 'cancel'].each(function(event){
3690                      this.addEvent(event, spinner.hide.bind(spinner));
3691                  }, this);
3692              }
3693          }
3694          return this.spinner;
3695      }
3696  
3697  });
3698  
3699  Element.Properties.spinner = {
3700  
3701      set: function(options){
3702          var spinner = this.retrieve('spinner');
3703          if (spinner) spinner.destroy();
3704          return this.eliminate('spinner').store('spinner:options', options);
3705      },
3706  
3707      get: function(){
3708          var spinner = this.retrieve('spinner');
3709          if (!spinner){
3710              spinner = new Spinner(this, this.retrieve('spinner:options'));
3711              this.store('spinner', spinner);
3712          }
3713          return spinner;
3714      }
3715  
3716  };
3717  
3718  Element.implement({
3719  
3720      spin: function(options){
3721          if (options) this.set('spinner', options);
3722          this.get('spinner').show();
3723          return this;
3724      },
3725  
3726      unspin: function(){
3727          this.get('spinner').hide();
3728          return this;
3729      }
3730  
3731  });
3732  
3733  
3734  /*
3735  ---
3736  
3737  script: Form.Request.js
3738  
3739  name: Form.Request
3740  
3741  description: Handles the basic functionality of submitting a form and updating a dom element with the result.
3742  
3743  license: MIT-style license
3744  
3745  authors:
3746    - Aaron Newton
3747  
3748  requires:
3749    - Core/Request.HTML
3750    - /Class.Binds
3751    - /Class.Occlude
3752    - /Spinner
3753    - /String.QueryString
3754    - /Element.Delegation
3755  
3756  provides: [Form.Request]
3757  
3758  ...
3759  */
3760  
3761  if (!window.Form) window.Form = {};
3762  
3763  (function(){
3764  
3765      Form.Request = new Class({
3766  
3767          Binds: ['onSubmit', 'onFormValidate'],
3768  
3769          Implements: [Options, Events, Class.Occlude],
3770  
3771          options: {/*
3772              onFailure: function(){},
3773              onSuccess: function(){}, // aliased to onComplete,
3774              onSend: function(){}*/
3775              requestOptions: {
3776                  evalScripts: true,
3777                  useSpinner: true,
3778                  emulation: false,
3779                  link: 'ignore'
3780              },
3781              sendButtonClicked: true,
3782              extraData: {},
3783              resetForm: true
3784          },
3785  
3786          property: 'form.request',
3787  
3788          initialize: function(form, target, options){
3789              this.element = document.id(form);
3790              if (this.occlude()) return this.occluded;
3791              this.setOptions(options)
3792                  .setTarget(target)
3793                  .attach();
3794          },
3795  
3796          setTarget: function(target){
3797              this.target = document.id(target);
3798              if (!this.request){
3799                  this.makeRequest();
3800              } else {
3801                  this.request.setOptions({
3802                      update: this.target
3803                  });
3804              }
3805              return this;
3806          },
3807  
3808          toElement: function(){
3809              return this.element;
3810          },
3811  
3812          makeRequest: function(){
3813              var self = this;
3814              this.request = new Request.HTML(Object.merge({
3815                      update: this.target,
3816                      emulation: false,
3817                      spinnerTarget: this.element,
3818                      method: this.element.get('method') || 'post'
3819              }, this.options.requestOptions)).addEvents({
3820                  success: function(tree, elements, html, javascript){
3821                      ['complete', 'success'].each(function(evt){
3822                          self.fireEvent(evt, [self.target, tree, elements, html, javascript]);
3823                      });
3824                  },
3825                  failure: function(){
3826                      self.fireEvent('complete', arguments).fireEvent('failure', arguments);
3827                  },
3828                  exception: function(){
3829                      self.fireEvent('failure', arguments);
3830                  }
3831              });
3832              return this.attachReset();
3833          },
3834  
3835          attachReset: function(){
3836              if (!this.options.resetForm) return this;
3837              this.request.addEvent('success', function(){
3838                  Function.attempt(function(){
3839                      this.element.reset();
3840                  }.bind(this));
3841                  if (window.OverText) OverText.update();
3842              }.bind(this));
3843              return this;
3844          },
3845  
3846          attach: function(attach){
3847              var method = (attach != false) ? 'addEvent' : 'removeEvent';
3848              this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this));
3849  
3850              var fv = this.element.retrieve('validator');
3851              if (fv) fv[method]('onFormValidate', this.onFormValidate);
3852              else this.element[method]('submit', this.onSubmit);
3853  
3854              return this;
3855          },
3856  
3857          detach: function(){
3858              return this.attach(false);
3859          },
3860  
3861          //public method
3862          enable: function(){
3863              return this.attach();
3864          },
3865  
3866          //public method
3867          disable: function(){
3868              return this.detach();
3869          },
3870  
3871          onFormValidate: function(valid, form, event){
3872              //if there's no event, then this wasn't a submit event
3873              if (!event) return;
3874              var fv = this.element.retrieve('validator');
3875              if (valid || (fv && !fv.options.stopOnFailure)){
3876                  event.stop();
3877                  this.send();
3878              }
3879          },
3880  
3881          onSubmit: function(event){
3882              var fv = this.element.retrieve('validator');
3883              if (fv){
3884                  //form validator was created after Form.Request
3885                  this.element.removeEvent('submit', this.onSubmit);
3886                  fv.addEvent('onFormValidate', this.onFormValidate);
3887                  this.element.validate();
3888                  return;
3889              }
3890              if (event) event.stop();
3891              this.send();
3892          },
3893  
3894          saveClickedButton: function(event, target){
3895              var targetName = target.get('name');
3896              if (!targetName || !this.options.sendButtonClicked) return;
3897              this.options.extraData[targetName] = target.get('value') || true;
3898              this.clickedCleaner = function(){
3899                  delete this.options.extraData[targetName];
3900                  this.clickedCleaner = function(){};
3901              }.bind(this);
3902          },
3903  
3904          clickedCleaner: function(){},
3905  
3906          send: function(){
3907              var str = this.element.toQueryString().trim(),
3908                  data = Object.toQueryString(this.options.extraData);
3909  
3910              if (str) str += "&" + data;
3911              else str = data;
3912  
3913              this.fireEvent('send', [this.element, str.parseQueryString()]);
3914              this.request.send({
3915                  data: str,
3916                  url: this.options.requestOptions.url || this.element.get('action')
3917              });
3918              this.clickedCleaner();
3919              return this;
3920          }
3921  
3922      });
3923  
3924      Element.implement('formUpdate', function(update, options){
3925          var fq = this.retrieve('form.request');
3926          if (!fq){
3927              fq = new Form.Request(this, update, options);
3928          } else {
3929              if (update) fq.setTarget(update);
3930              if (options) fq.setOptions(options).makeRequest();
3931          }
3932          fq.send();
3933          return this;
3934      });
3935  
3936  })();
3937  
3938  
3939  /*
3940  ---
3941  
3942  script: Fx.Reveal.js
3943  
3944  name: Fx.Reveal
3945  
3946  description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
3947  
3948  license: MIT-style license
3949  
3950  authors:
3951    - Aaron Newton
3952  
3953  requires:
3954    - Core/Fx.Morph
3955    - /Element.Shortcuts
3956    - /Element.Measure
3957  
3958  provides: [Fx.Reveal]
3959  
3960  ...
3961  */
3962  
3963  (function(){
3964  
3965  
3966  var hideTheseOf = function(object){
3967      var hideThese = object.options.hideInputs;
3968      if (window.OverText){
3969          var otClasses = [null];
3970          OverText.each(function(ot){
3971              otClasses.include('.' + ot.options.labelClass);
3972          });
3973          if (otClasses) hideThese += otClasses.join(', ');
3974      }
3975      return (hideThese) ? object.element.getElements(hideThese) : null;
3976  };
3977  
3978  
3979  Fx.Reveal = new Class({
3980  
3981      Extends: Fx.Morph,
3982  
3983      options: {/*
3984          onShow: function(thisElement){},
3985          onHide: function(thisElement){},
3986          onComplete: function(thisElement){},
3987          heightOverride: null,
3988          widthOverride: null,*/
3989          link: 'cancel',
3990          styles: ['padding', 'border', 'margin'],
3991          transitionOpacity: !Browser.ie6,
3992          mode: 'vertical',
3993          display: function(){
3994              return this.element.get('tag') != 'tr' ? 'block' : 'table-row';
3995          },
3996          opacity: 1,
3997          hideInputs: Browser.ie ? 'select, input, textarea, object, embed' : null
3998      },
3999  
4000      dissolve: function(){
4001          if (!this.hiding && !this.showing){
4002              if (this.element.getStyle('display') != 'none'){
4003                  this.hiding = true;
4004                  this.showing = false;
4005                  this.hidden = true;
4006                  this.cssText = this.element.style.cssText;
4007  
4008                  var startStyles = this.element.getComputedSize({
4009                      styles: this.options.styles,
4010                      mode: this.options.mode
4011                  });
4012                  if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity;
4013  
4014                  var zero = {};
4015                  Object.each(startStyles, function(style, name){
4016                      zero[name] = [style, 0];
4017                  });
4018  
4019                  this.element.setStyles({
4020                      display: Function.from(this.options.display).call(this),
4021                      overflow: 'hidden'
4022                  });
4023  
4024                  var hideThese = hideTheseOf(this);
4025                  if (hideThese) hideThese.setStyle('visibility', 'hidden');
4026  
4027                  this.$chain.unshift(function(){
4028                      if (this.hidden){
4029                          this.hiding = false;
4030                          this.element.style.cssText = this.cssText;
4031                          this.element.setStyle('display', 'none');
4032                          if (hideThese) hideThese.setStyle('visibility', 'visible');
4033                      }
4034                      this.fireEvent('hide', this.element);
4035                      this.callChain();
4036                  }.bind(this));
4037  
4038                  this.start(zero);
4039              } else {
4040                  this.callChain.delay(10, this);
4041                  this.fireEvent('complete', this.element);
4042                  this.fireEvent('hide', this.element);
4043              }
4044          } else if (this.options.link == 'chain'){
4045              this.chain(this.dissolve.bind(this));
4046          } else if (this.options.link == 'cancel' && !this.hiding){
4047              this.cancel();
4048              this.dissolve();
4049          }
4050          return this;
4051      },
4052  
4053      reveal: function(){
4054          if (!this.showing && !this.hiding){
4055              if (this.element.getStyle('display') == 'none'){
4056                  this.hiding = false;
4057                  this.showing = true;
4058                  this.hidden = false;
4059                  this.cssText = this.element.style.cssText;
4060  
4061                  var startStyles;
4062                  this.element.measure(function(){
4063                      startStyles = this.element.getComputedSize({
4064                          styles: this.options.styles,
4065                          mode: this.options.mode
4066                      });
4067                  }.bind(this));
4068                  if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt();
4069                  if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt();
4070                  if (this.options.transitionOpacity){
4071                      this.element.setStyle('opacity', 0);
4072                      startStyles.opacity = this.options.opacity;
4073                  }
4074  
4075                  var zero = {
4076                      height: 0,
4077                      display: Function.from(this.options.display).call(this)
4078                  };
4079                  Object.each(startStyles, function(style, name){
4080                      zero[name] = 0;
4081                  });
4082                  zero.overflow = 'hidden';
4083  
4084                  this.element.setStyles(zero);
4085  
4086                  var hideThese = hideTheseOf(this);
4087                  if (hideThese) hideThese.setStyle('visibility', 'hidden');
4088  
4089                  this.$chain.unshift(function(){
4090                      this.element.style.cssText = this.cssText;
4091                      this.element.setStyle('display', Function.from(this.options.display).call(this));
4092                      if (!this.hidden) this.showing = false;
4093                      if (hideThese) hideThese.setStyle('visibility', 'visible');
4094                      this.callChain();
4095                      this.fireEvent('show', this.element);
4096                  }.bind(this));
4097  
4098                  this.start(startStyles);
4099              } else {
4100                  this.callChain();
4101                  this.fireEvent('complete', this.element);
4102                  this.fireEvent('show', this.element);
4103              }
4104          } else if (this.options.link == 'chain'){
4105              this.chain(this.reveal.bind(this));
4106          } else if (this.options.link == 'cancel' && !this.showing){
4107              this.cancel();
4108              this.reveal();
4109          }
4110          return this;
4111      },
4112  
4113      toggle: function(){
4114          if (this.element.getStyle('display') == 'none'){
4115              this.reveal();
4116          } else {
4117              this.dissolve();
4118          }
4119          return this;
4120      },
4121  
4122      cancel: function(){
4123          this.parent.apply(this, arguments);
4124          if (this.cssText != null) this.element.style.cssText = this.cssText;
4125          this.hiding = false;
4126          this.showing = false;
4127          return this;
4128      }
4129  
4130  });
4131  
4132  Element.Properties.reveal = {
4133  
4134      set: function(options){
4135          this.get('reveal').cancel().setOptions(options);
4136          return this;
4137      },
4138  
4139      get: function(){
4140          var reveal = this.retrieve('reveal');
4141          if (!reveal){
4142              reveal = new Fx.Reveal(this);
4143              this.store('reveal', reveal);
4144          }
4145          return reveal;
4146      }
4147  
4148  };
4149  
4150  Element.Properties.dissolve = Element.Properties.reveal;
4151  
4152  Element.implement({
4153  
4154      reveal: function(options){
4155          this.get('reveal').setOptions(options).reveal();
4156          return this;
4157      },
4158  
4159      dissolve: function(options){
4160          this.get('reveal').setOptions(options).dissolve();
4161          return this;
4162      },
4163  
4164      nix: function(options){
4165          var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject});
4166          this.get('reveal').setOptions(options).dissolve().chain(function(){
4167              this[params.destroy ? 'destroy' : 'dispose']();
4168          }.bind(this));
4169          return this;
4170      },
4171  
4172      wink: function(){
4173          var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject});
4174          var reveal = this.get('reveal').setOptions(params.options);
4175          reveal.reveal().chain(function(){
4176              (function(){
4177                  reveal.dissolve();
4178              }).delay(params.duration || 2000);
4179          });
4180      }
4181  
4182  });
4183  
4184  })();
4185  
4186  
4187  /*
4188  ---
4189  
4190  script: Form.Request.Append.js
4191  
4192  name: Form.Request.Append
4193  
4194  description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
4195  
4196  license: MIT-style license
4197  
4198  authors:
4199    - Aaron Newton
4200  
4201  requires:
4202    - /Form.Request
4203    - /Fx.Reveal
4204    - /Elements.from
4205  
4206  provides: [Form.Request.Append]
4207  
4208  ...
4209  */
4210  
4211  Form.Request.Append = new Class({
4212  
4213      Extends: Form.Request,
4214  
4215      options: {
4216          //onBeforeEffect: function(){},
4217          useReveal: true,
4218          revealOptions: {},
4219          inject: 'bottom'
4220      },
4221  
4222      makeRequest: function(){
4223          this.request = new Request.HTML(Object.merge({
4224                  url: this.element.get('action'),
4225                  method: this.element.get('method') || 'post',
4226                  spinnerTarget: this.element
4227              }, this.options.requestOptions, {
4228                  evalScripts: false
4229              })
4230          ).addEvents({
4231              success: function(tree, elements, html, javascript){
4232                  var container;
4233                  var kids = Elements.from(html);
4234                  if (kids.length == 1){
4235                      container = kids[0];
4236                  } else {
4237                       container = new Element('div', {
4238                          styles: {
4239                              display: 'none'
4240                          }
4241                      }).adopt(kids);
4242                  }
4243                  container.inject(this.target, this.options.inject);
4244                  if (this.options.requestOptions.evalScripts) Browser.exec(javascript);
4245                  this.fireEvent('beforeEffect', container);
4246                  var finish = function(){
4247                      this.fireEvent('success', [container, this.target, tree, elements, html, javascript]);
4248                  }.bind(this);
4249                  if (this.options.useReveal){
4250                      container.set('reveal', this.options.revealOptions).get('reveal').chain(finish);
4251                      container.reveal();
4252                  } else {
4253                      finish();
4254                  }
4255              }.bind(this),
4256              failure: function(xhr){
4257                  this.fireEvent('failure', xhr);
4258              }.bind(this)
4259          });
4260          this.attachReset();
4261      }
4262  
4263  });
4264  
4265  
4266  /*
4267  ---
4268  
4269  name: Locale.en-US.Form.Validator
4270  
4271  description: Form Validator messages for English.
4272  
4273  license: MIT-style license
4274  
4275  authors:
4276    - Aaron Newton
4277  
4278  requires:
4279    - /Locale
4280  
4281  provides: [Locale.en-US.Form.Validator]
4282  
4283  ...
4284  */
4285  
4286  Locale.define('en-US', 'FormValidator', {
4287  
4288      required: 'This field is required.',
4289      length: 'Please enter {length} characters (you entered {elLength} characters)',
4290      minLength: 'Please enter at least {minLength} characters (you entered {length} characters).',
4291      maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).',
4292      integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
4293      numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
4294      digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
4295      alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.',
4296      alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.',
4297      dateSuchAs: 'Please enter a valid date such as {date}',
4298      dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
4299      email: 'Please enter a valid email address. For example "fred@domain.com".',
4300      url: 'Please enter a valid URL such as http://www.example.com.',
4301      currencyDollar: 'Please enter a valid $ amount. For example $100.00 .',
4302      oneRequired: 'Please enter something for at least one of these inputs.',
4303      errorPrefix: 'Error: ',
4304      warningPrefix: 'Warning: ',
4305  
4306      // Form.Validator.Extras
4307      noSpace: 'There can be no spaces in this input.',
4308      reqChkByNode: 'No items are selected.',
4309      requiredChk: 'This field is required.',
4310      reqChkByName: 'Please select a {label}.',
4311      match: 'This field needs to match the {matchName} field',
4312      startDate: 'the start date',
4313      endDate: 'the end date',
4314      currendDate: 'the current date',
4315      afterDate: 'The date should be the same or after {label}.',
4316      beforeDate: 'The date should be the same or before {label}.',
4317      startMonth: 'Please select a start month',
4318      sameMonth: 'These two dates must be in the same month - you must change one or the other.',
4319      creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
4320  
4321  });
4322  
4323  
4324  /*
4325  ---
4326  
4327  script: Form.Validator.js
4328  
4329  name: Form.Validator
4330  
4331  description: A css-class based form validation system.
4332  
4333  license: MIT-style license
4334  
4335  authors:
4336    - Aaron Newton
4337  
4338  requires:
4339    - Core/Options
4340    - Core/Events
4341    - Core/Slick.Finder
4342    - Core/Element.Event
4343    - Core/Element.Style
4344    - Core/JSON
4345    - /Locale
4346    - /Class.Binds
4347    - /Date
4348    - /Element.Forms
4349    - /Locale.en-US.Form.Validator
4350    - /Element.Shortcuts
4351  
4352  provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
4353  
4354  ...
4355  */
4356  if (!window.Form) window.Form = {};
4357  
4358  var InputValidator = this.InputValidator = new Class({
4359  
4360      Implements: [Options],
4361  
4362      options: {
4363          errorMsg: 'Validation failed.',
4364          test: Function.from(true)
4365      },
4366  
4367      initialize: function(className, options){
4368          this.setOptions(options);
4369          this.className = className;
4370      },
4371  
4372      test: function(field, props){
4373          field = document.id(field);
4374          return (field) ? this.options.test(field, props || this.getProps(field)) : false;
4375      },
4376  
4377      getError: function(field, props){
4378          field = document.id(field);
4379          var err = this.options.errorMsg;
4380          if (typeOf(err) == 'function') err = err(field, props || this.getProps(field));
4381          return err;
4382      },
4383  
4384      getProps: function(field){
4385          field = document.id(field);
4386          return (field) ? field.get('validatorProps') : {};
4387      }
4388  
4389  });
4390  
4391  Element.Properties.validators = {
4392  
4393      get: function(){
4394          return (this.get('data-validators') || this.className).clean().split(' ');
4395      }
4396  
4397  };
4398  
4399  Element.Properties.validatorProps = {
4400  
4401      set: function(props){
4402          return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props);
4403      },
4404  
4405      get: function(props){
4406          if (props) this.set(props);
4407          if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps');
4408          if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){
4409              try {
4410                  this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties')));
4411              }catch(e){
4412                  return {};
4413              }
4414          } else {
4415              var vals = this.get('validators').filter(function(cls){
4416                  return cls.test(':');
4417              });
4418              if (!vals.length){
4419                  this.store('$moo:validatorProps', {});
4420              } else {
4421                  props = {};
4422                  vals.each(function(cls){
4423                      var split = cls.split(':');
4424                      if (split[1]){
4425                          try {
4426                              props[split[0]] = JSON.decode(split[1]);
4427                          } catch(e){}
4428                      }
4429                  });
4430                  this.store('$moo:validatorProps', props);
4431              }
4432          }
4433          return this.retrieve('$moo:validatorProps');
4434      }
4435  
4436  };
4437  
4438  Form.Validator = new Class({
4439  
4440      Implements: [Options, Events],
4441  
4442      Binds: ['onSubmit'],
4443  
4444      options: {/*
4445          onFormValidate: function(isValid, form, event){},
4446          onElementValidate: function(isValid, field, className, warn){},
4447          onElementPass: function(field){},
4448          onElementFail: function(field, validatorsFailed){}, */
4449          fieldSelectors: 'input, select, textarea',
4450          ignoreHidden: true,
4451          ignoreDisabled: true,
4452          useTitles: false,
4453          evaluateOnSubmit: true,
4454          evaluateFieldsOnBlur: true,
4455          evaluateFieldsOnChange: true,
4456          serial: true,
4457          stopOnFailure: true,
4458          warningPrefix: function(){
4459              return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
4460          },
4461          errorPrefix: function(){
4462              return Form.Validator.getMsg('errorPrefix') || 'Error: ';
4463          }
4464      },
4465  
4466      initialize: function(form, options){
4467          this.setOptions(options);
4468          this.element = document.id(form);
4469          this.element.store('validator', this);
4470          this.warningPrefix = Function.from(this.options.warningPrefix)();
4471          this.errorPrefix = Function.from(this.options.errorPrefix)();
4472          if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
4473          if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
4474      },
4475  
4476      toElement: function(){
4477          return this.element;
4478      },
4479  
4480      getFields: function(){
4481          return (this.fields = this.element.getElements(this.options.fieldSelectors));
4482      },
4483  
4484      watchFields: function(fields){
4485          fields.each(function(el){
4486              if (this.options.evaluateFieldsOnBlur)
4487                  el.addEvent('blur', this.validationMonitor.pass([el, false], this));
4488              if (this.options.evaluateFieldsOnChange)
4489                  el.addEvent('change', this.validationMonitor.pass([el, true], this));
4490          }, this);
4491      },
4492  
4493      validationMonitor: function(){
4494          clearTimeout(this.timer);
4495          this.timer = this.validateField.delay(50, this, arguments);
4496      },
4497  
4498      onSubmit: function(event){
4499          if (this.validate(event)) this.reset();
4500      },
4501  
4502      reset: function(){
4503          this.getFields().each(this.resetField, this);
4504          return this;
4505      },
4506  
4507      validate: function(event){
4508          var result = this.getFields().map(function(field){
4509              return this.validateField(field, true);
4510          }, this).every(function(v){
4511              return v;
4512          });
4513          this.fireEvent('formValidate', [result, this.element, event]);
4514          if (this.options.stopOnFailure && !result && event) event.preventDefault();
4515          return result;
4516      },
4517  
4518      validateField: function(field, force){
4519          if (this.paused) return true;
4520          field = document.id(field);
4521          var passed = !field.hasClass('validation-failed');
4522          var failed, warned;
4523          if (this.options.serial && !force){
4524              failed = this.element.getElement('.validation-failed');
4525              warned = this.element.getElement('.warning');
4526          }
4527          if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
4528              var validationTypes = field.get('validators');
4529              var validators = validationTypes.some(function(cn){
4530                  return this.getValidator(cn);
4531              }, this);
4532              var validatorsFailed = [];
4533              validationTypes.each(function(className){
4534                  if (className && !this.test(className, field)) validatorsFailed.include(className);
4535              }, this);
4536              passed = validatorsFailed.length === 0;
4537              if (validators && !this.hasValidator(field, 'warnOnly')){
4538                  if (passed){
4539                      field.addClass('validation-passed').removeClass('validation-failed');
4540                      this.fireEvent('elementPass', [field]);
4541                  } else {
4542                      field.addClass('validation-failed').removeClass('validation-passed');
4543                      this.fireEvent('elementFail', [field, validatorsFailed]);
4544                  }
4545              }
4546              if (!warned){
4547                  var warnings = validationTypes.some(function(cn){
4548                      if (cn.test('^warn'))
4549                          return this.getValidator(cn.replace(/^warn-/,''));
4550                      else return null;
4551                  }, this);
4552                  field.removeClass('warning');
4553                  var warnResult = validationTypes.map(function(cn){
4554                      if (cn.test('^warn'))
4555                          return this.test(cn.replace(/^warn-/,''), field, true);
4556                      else return null;
4557                  }, this);
4558              }
4559          }
4560          return passed;
4561      },
4562  
4563      test: function(className, field, warn){
4564          field = document.id(field);
4565          if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
4566          var validator = this.getValidator(className);
4567          if (warn != null) warn = false;
4568          if (this.hasValidator(field, 'warnOnly')) warn = true;
4569          var isValid = this.hasValidator(field, 'ignoreValidation') || (validator ? validator.test(field) : true);
4570          if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
4571          if (warn) return true;
4572          return isValid;
4573      },
4574  
4575      hasValidator: function(field, value){
4576          return field.get('validators').contains(value);
4577      },
4578  
4579      resetField: function(field){
4580          field = document.id(field);
4581          if (field){
4582              field.get('validators').each(function(className){
4583                  if (className.test('^warn-')) className = className.replace(/^warn-/, '');
4584                  field.removeClass('validation-failed');
4585                  field.removeClass('warning');
4586                  field.removeClass('validation-passed');
4587              }, this);
4588          }
4589          return this;
4590      },
4591  
4592      stop: function(){
4593          this.paused = true;
4594          return this;
4595      },
4596  
4597      start: function(){
4598          this.paused = false;
4599          return this;
4600      },
4601  
4602      ignoreField: function(field, warn){
4603          field = document.id(field);
4604          if (field){
4605              this.enforceField(field);
4606              if (warn) field.addClass('warnOnly');
4607              else field.addClass('ignoreValidation');
4608          }
4609          return this;
4610      },
4611  
4612      enforceField: function(field){
4613          field = document.id(field);
4614          if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
4615          return this;
4616      }
4617  
4618  });
4619  
4620  Form.Validator.getMsg = function(key){
4621      return Locale.get('FormValidator.' + key);
4622  };
4623  
4624  Form.Validator.adders = {
4625  
4626      validators:{},
4627  
4628      add : function(className, options){
4629          this.validators[className] = new InputValidator(className, options);
4630          //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
4631          //extend these validators into it
4632          //this allows validators to be global and/or per instance
4633          if (!this.initialize){
4634              this.implement({
4635                  validators: this.validators
4636              });
4637          }
4638      },
4639  
4640      addAllThese : function(validators){
4641          Array.from(validators).each(function(validator){
4642              this.add(validator[0], validator[1]);
4643          }, this);
4644      },
4645  
4646      getValidator: function(className){
4647          return this.validators[className.split(':')[0]];
4648      }
4649  
4650  };
4651  
4652  Object.append(Form.Validator, Form.Validator.adders);
4653  
4654  Form.Validator.implement(Form.Validator.adders);
4655  
4656  Form.Validator.add('IsEmpty', {
4657  
4658      errorMsg: false,
4659      test: function(element){
4660          if (element.type == 'select-one' || element.type == 'select')
4661              return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
4662          else
4663              return ((element.get('value') == null) || (element.get('value').length == 0));
4664      }
4665  
4666  });
4667  
4668  Form.Validator.addAllThese([
4669  
4670      ['required', {
4671          errorMsg: function(){
4672              return Form.Validator.getMsg('required');
4673          },
4674          test: function(element){
4675              return !Form.Validator.getValidator('IsEmpty').test(element);
4676          }
4677      }],
4678  
4679      ['length', {
4680          errorMsg: function(element, props){
4681              if (typeOf(props.length) != 'null')
4682                  return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length});
4683              else return '';
4684          },
4685          test: function(element, props){
4686              if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0);
4687              else return true;
4688          }
4689      }],    
4690  
4691      ['minLength', {
4692          errorMsg: function(element, props){
4693              if (typeOf(props.minLength) != 'null')
4694                  return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length});
4695              else return '';
4696          },
4697          test: function(element, props){
4698              if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0));
4699              else return true;
4700          }
4701      }],
4702  
4703      ['maxLength', {
4704          errorMsg: function(element, props){
4705              //props is {maxLength:10}
4706              if (typeOf(props.maxLength) != 'null')
4707                  return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length});
4708              else return '';
4709          },
4710          test: function(element, props){
4711              return element.get('value').length <= (props.maxLength || 10000);
4712          }
4713      }],
4714  
4715      ['validate-integer', {
4716          errorMsg: Form.Validator.getMsg.pass('integer'),
4717          test: function(element){
4718              return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
4719          }
4720      }],
4721  
4722      ['validate-numeric', {
4723          errorMsg: Form.Validator.getMsg.pass('numeric'),
4724          test: function(element){
4725              return Form.Validator.getValidator('IsEmpty').test(element) ||
4726                  (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
4727          }
4728      }],
4729  
4730      ['validate-digits', {
4731          errorMsg: Form.Validator.getMsg.pass('digits'),
4732          test: function(element){
4733              return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
4734          }
4735      }],
4736  
4737      ['validate-alpha', {
4738          errorMsg: Form.Validator.getMsg.pass('alpha'),
4739          test: function(element){
4740              return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value'));
4741          }
4742      }],
4743  
4744      ['validate-alphanum', {
4745          errorMsg: Form.Validator.getMsg.pass('alphanum'),
4746          test: function(element){
4747              return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
4748          }
4749      }],
4750  
4751      ['validate-date', {
4752          errorMsg: function(element, props){
4753              if (Date.parse){
4754                  var format = props.dateFormat || '%x';
4755                  return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
4756              } else {
4757                  return Form.Validator.getMsg('dateInFormatMDY');
4758              }
4759          },
4760          test: function(element, props){
4761              if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
4762              var dateLocale = Locale.getCurrent().sets.Date,
4763                  dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr].flatten().join('|'), 'i'),
4764                  value = element.get('value'),
4765                  wordsInValue = value.match(/[a-z]+/gi);
4766  
4767                  if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false;
4768  
4769                  var date = Date.parse(value),
4770                      format = props.dateFormat || '%x',
4771                      formatted = date.format(format);
4772  
4773                  if (formatted != 'invalid date') element.set('value', formatted);
4774                  return date.isValid();
4775          }
4776      }],
4777  
4778      ['validate-email', {
4779          errorMsg: Form.Validator.getMsg.pass('email'),
4780          test: function(element){
4781              /*
4782              var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]",
4783                  local = '(?:' + chars + '\\.?){0,63}' + chars,
4784  
4785                  label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?',
4786                  hostname = '(?:' + label + '\\.)*' + label;
4787  
4788                  octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
4789                  ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]',
4790  
4791                  domain = '(?:' + hostname + '|' + ipv4 + ')';
4792  
4793              var regex = new RegExp('^' + local + '@' + domain + '$', 'i');
4794              */
4795              return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
4796          }
4797      }],
4798  
4799      ['validate-url', {
4800          errorMsg: Form.Validator.getMsg.pass('url'),
4801          test: function(element){
4802              return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
4803          }
4804      }],
4805  
4806      ['validate-currency-dollar', {
4807          errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
4808          test: function(element){
4809              return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
4810          }
4811      }],
4812  
4813      ['validate-one-required', {
4814          errorMsg: Form.Validator.getMsg.pass('oneRequired'),
4815          test: function(element, props){
4816              var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']);
4817              return p.getElements('input').some(function(el){
4818                  if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
4819                  return el.get('value');
4820              });
4821          }
4822      }]
4823  
4824  ]);
4825  
4826  Element.Properties.validator = {
4827  
4828      set: function(options){
4829          this.get('validator').setOptions(options);
4830      },
4831  
4832      get: function(){
4833          var validator = this.retrieve('validator');
4834          if (!validator){
4835              validator = new Form.Validator(this);
4836              this.store('validator', validator);
4837          }
4838          return validator;
4839      }
4840  
4841  };
4842  
4843  Element.implement({
4844  
4845      validate: function(options){
4846          if (options) this.set('validator', options);
4847          return this.get('validator').validate();
4848      }
4849  
4850  });
4851  
4852  
4853  
4854  
4855  
4856  
4857  
4858  /*
4859  ---
4860  
4861  script: Form.Validator.Inline.js
4862  
4863  name: Form.Validator.Inline
4864  
4865  description: Extends Form.Validator to add inline messages.
4866  
4867  license: MIT-style license
4868  
4869  authors:
4870    - Aaron Newton
4871  
4872  requires:
4873    - /Form.Validator
4874  
4875  provides: [Form.Validator.Inline]
4876  
4877  ...
4878  */
4879  
4880  Form.Validator.Inline = new Class({
4881  
4882      Extends: Form.Validator,
4883  
4884      options: {
4885          showError: function(errorElement){
4886              if (errorElement.reveal) errorElement.reveal();
4887              else errorElement.setStyle('display', 'block');
4888          },
4889          hideError: function(errorElement){
4890              if (errorElement.dissolve) errorElement.dissolve();
4891              else errorElement.setStyle('display', 'none');
4892          },
4893          scrollToErrorsOnSubmit: true,
4894          scrollToErrorsOnBlur: false,
4895          scrollToErrorsOnChange: false,
4896          scrollFxOptions: {
4897              transition: 'quad:out',
4898              offset: {
4899                  y: -20
4900              }
4901          }
4902      },
4903  
4904      initialize: function(form, options){
4905          this.parent(form, options);
4906          this.addEvent('onElementValidate', function(isValid, field, className, warn){
4907              var validator = this.getValidator(className);
4908              if (!isValid && validator.getError(field)){
4909                  if (warn) field.addClass('warning');
4910                  var advice = this.makeAdvice(className, field, validator.getError(field), warn);
4911                  this.insertAdvice(advice, field);
4912                  this.showAdvice(className, field);
4913              } else {
4914                  this.hideAdvice(className, field);
4915              }
4916          });
4917      },
4918  
4919      makeAdvice: function(className, field, error, warn){
4920          var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix;
4921              errorMsg += (this.options.useTitles) ? field.title || error:error;
4922          var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
4923          var advice = this.getAdvice(className, field);
4924          if (advice){
4925              advice = advice.set('html', errorMsg);
4926          } else {
4927              advice = new Element('div', {
4928                  html: errorMsg,
4929                  styles: { display: 'none' },
4930                  id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field)
4931              }).addClass(cssClass);
4932          }
4933          field.store('$moo:advice-' + className, advice);
4934          return advice;
4935      },
4936  
4937      getFieldId : function(field){
4938          return field.id ? field.id : field.id = 'input_' + field.name;
4939      },
4940  
4941      showAdvice: function(className, field){
4942          var advice = this.getAdvice(className, field);
4943          if (
4944              advice &&
4945              !field.retrieve('$moo:' + this.getPropName(className)) &&
4946              (
4947                  advice.getStyle('display') == 'none' ||
4948                  advice.getStyle('visiblity') == 'hidden' ||
4949                  advice.getStyle('opacity') == 0
4950              )
4951          ){
4952              field.store('$moo:' + this.getPropName(className), true);
4953              this.options.showError(advice);
4954              this.fireEvent('showAdvice', [field, advice, className]);
4955          }
4956      },
4957  
4958      hideAdvice: function(className, field){
4959          var advice = this.getAdvice(className, field);
4960          if (advice && field.retrieve('$moo:' + this.getPropName(className))){
4961              field.store('$moo:' + this.getPropName(className), false);
4962              this.options.hideError(advice);
4963              this.fireEvent('hideAdvice', [field, advice, className]);
4964          }
4965      },
4966  
4967      getPropName: function(className){
4968          return 'advice' + className;
4969      },
4970  
4971      resetField: function(field){
4972          field = document.id(field);
4973          if (!field) return this;
4974          this.parent(field);
4975          field.get('validators').each(function(className){
4976              this.hideAdvice(className, field);
4977          }, this);
4978          return this;
4979      },
4980  
4981      getAllAdviceMessages: function(field, force){
4982          var advice = [];
4983          if (field.hasClass('ignoreValidation') && !force) return advice;
4984          var validators = field.get('validators').some(function(cn){
4985              var warner = cn.test('^warn-') || field.hasClass('warnOnly');
4986              if (warner) cn = cn.replace(/^warn-/, '');
4987              var validator = this.getValidator(cn);
4988              if (!validator) return;
4989              advice.push({
4990                  message: validator.getError(field),
4991                  warnOnly: warner,
4992                  passed: validator.test(),
4993                  validator: validator
4994              });
4995          }, this);
4996          return advice;
4997      },
4998  
4999      getAdvice: function(className, field){
5000          return field.retrieve('$moo:advice-' + className);
5001      },
5002  
5003      insertAdvice: function(advice, field){
5004          //Check for error position prop
5005          var props = field.get('validatorProps');
5006          //Build advice
5007          if (!props.msgPos || !document.id(props.msgPos)){
5008              if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
5009              else advice.inject(document.id(field), 'after');
5010          } else {
5011              document.id(props.msgPos).grab(advice);
5012          }
5013      },
5014  
5015      validateField: function(field, force, scroll){
5016          var result = this.parent(field, force);
5017          if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){
5018              var failed = document.id(this).getElement('.validation-failed');
5019              var par = document.id(this).getParent();
5020              while (par != document.body && par.getScrollSize().y == par.getSize().y){
5021                  par = par.getParent();
5022              }
5023              var fx = par.retrieve('$moo:fvScroller');
5024              if (!fx && window.Fx && Fx.Scroll){
5025                  fx = new Fx.Scroll(par, this.options.scrollFxOptions);
5026                  par.store('$moo:fvScroller', fx);
5027              }
5028              if (failed){
5029                  if (fx) fx.toElement(failed);
5030                  else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
5031              }
5032          }
5033          return result;
5034      },
5035  
5036      watchFields: function(fields){
5037          fields.each(function(el){
5038          if (this.options.evaluateFieldsOnBlur){
5039              el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this));
5040          }
5041          if (this.options.evaluateFieldsOnChange){
5042                  el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this));
5043              }
5044          }, this);
5045      }
5046  
5047  });
5048  
5049  
5050  /*
5051  ---
5052  
5053  script: Form.Validator.Extras.js
5054  
5055  name: Form.Validator.Extras
5056  
5057  description: Additional validators for the Form.Validator class.
5058  
5059  license: MIT-style license
5060  
5061  authors:
5062    - Aaron Newton
5063  
5064  requires:
5065    - /Form.Validator
5066  
5067  provides: [Form.Validator.Extras]
5068  
5069  ...
5070  */
5071  Form.Validator.addAllThese([
5072  
5073      ['validate-enforce-oncheck', {
5074          test: function(element, props){
5075              var fv = element.getParent('form').retrieve('validator');
5076              if (!fv) return true;
5077              (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
5078                  if (element.checked){
5079                      fv.enforceField(item);
5080                  } else {
5081                      fv.ignoreField(item);
5082                      fv.resetField(item);
5083                  }
5084              });
5085              return true;
5086          }
5087      }],
5088  
5089      ['validate-ignore-oncheck', {
5090          test: function(element, props){
5091              var fv = element.getParent('form').retrieve('validator');
5092              if (!fv) return true;
5093              (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
5094                  if (element.checked){
5095                      fv.ignoreField(item);
5096                      fv.resetField(item);
5097                  } else {
5098                      fv.enforceField(item);
5099                  }
5100              });
5101              return true;
5102          }
5103      }],
5104  
5105      ['validate-nospace', {
5106          errorMsg: function(){
5107              return Form.Validator.getMsg('noSpace');
5108          },
5109          test: function(element, props){
5110              return !element.get('value').test(/\s/);
5111          }
5112      }],
5113  
5114      ['validate-toggle-oncheck', {
5115          test: function(element, props){
5116              var fv = element.getParent('form').retrieve('validator');
5117              if (!fv) return true;
5118              var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
5119              if (!element.checked){
5120                  eleArr.each(function(item){
5121                      fv.ignoreField(item);
5122                      fv.resetField(item);
5123                  });
5124              } else {
5125                  eleArr.each(function(item){
5126                      fv.enforceField(item);
5127                  });
5128              }
5129              return true;
5130          }
5131      }],
5132  
5133      ['validate-reqchk-bynode', {
5134          errorMsg: function(){
5135              return Form.Validator.getMsg('reqChkByNode');
5136          },
5137          test: function(element, props){
5138              return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
5139                  return item.checked;
5140              });
5141          }
5142      }],
5143  
5144      ['validate-required-check', {
5145          errorMsg: function(element, props){
5146              return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
5147          },
5148          test: function(element, props){
5149              return !!element.checked;
5150          }
5151      }],
5152  
5153      ['validate-reqchk-byname', {
5154          errorMsg: function(element, props){
5155              return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
5156          },
5157          test: function(element, props){
5158              var grpName = props.groupName || element.get('name');
5159              var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
5160                  return item.checked;
5161              });
5162              var fv = element.getParent('form').retrieve('validator');
5163              if (oneCheckedItem && fv) fv.resetField(element);
5164              return oneCheckedItem;
5165          }
5166      }],
5167  
5168      ['validate-match', {
5169          errorMsg: function(element, props){
5170              return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
5171          },
5172          test: function(element, props){
5173              var eleVal = element.get('value');
5174              var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
5175              return eleVal && matchVal ? eleVal == matchVal : true;
5176          }
5177      }],
5178  
5179      ['validate-after-date', {
5180          errorMsg: function(element, props){
5181              return Form.Validator.getMsg('afterDate').substitute({
5182                  label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
5183              });
5184          },
5185          test: function(element, props){
5186              var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
5187              var end = Date.parse(element.get('value'));
5188              return end && start ? end >= start : true;
5189          }
5190      }],
5191  
5192      ['validate-before-date', {
5193          errorMsg: function(element, props){
5194              return Form.Validator.getMsg('beforeDate').substitute({
5195                  label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
5196              });
5197          },
5198          test: function(element, props){
5199              var start = Date.parse(element.get('value'));
5200              var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
5201              return end && start ? end >= start : true;
5202          }
5203      }],
5204  
5205      ['validate-custom-required', {
5206          errorMsg: function(){
5207              return Form.Validator.getMsg('required');
5208          },
5209          test: function(element, props){
5210              return element.get('value') != props.emptyValue;
5211          }
5212      }],
5213  
5214      ['validate-same-month', {
5215          errorMsg: function(element, props){
5216              var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
5217              var eleVal = element.get('value');
5218              if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
5219          },
5220          test: function(element, props){
5221              var d1 = Date.parse(element.get('value'));
5222              var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
5223              return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
5224          }
5225      }],
5226  
5227  
5228      ['validate-cc-num', {
5229          errorMsg: function(element){
5230              var ccNum = element.get('value').replace(/[^0-9]/g, '');
5231              return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
5232          },
5233          test: function(element){
5234              // required is a different test
5235              if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
5236  
5237              // Clean number value
5238              var ccNum = element.get('value');
5239              ccNum = ccNum.replace(/[^0-9]/g, '');
5240  
5241              var valid_type = false;
5242  
5243              if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
5244              else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
5245              else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
5246              else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';
5247  
5248              if (valid_type){
5249                  var sum = 0;
5250                  var cur = 0;
5251  
5252                  for (var i=ccNum.length-1; i>=0; --i){
5253                      cur = ccNum.charAt(i).toInt();
5254                      if (cur == 0) continue;
5255  
5256                      if ((ccNum.length-i) % 2 == 0) cur += cur;
5257                      if (cur > 9){
5258                          cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt();
5259                      }
5260  
5261                      sum += cur;
5262                  }
5263                  if ((sum % 10) == 0) return true;
5264              }
5265  
5266              var chunks = '';
5267              while (ccNum != ''){
5268                  chunks += ' ' + ccNum.substr(0,4);
5269                  ccNum = ccNum.substr(4);
5270              }
5271  
5272              element.getParent('form').retrieve('validator').ignoreField(element);
5273              element.set('value', chunks.clean());
5274              element.getParent('form').retrieve('validator').enforceField(element);
5275              return false;
5276          }
5277      }]
5278  
5279  
5280  ]);
5281  
5282  
5283  /*
5284  ---
5285  
5286  script: OverText.js
5287  
5288  name: OverText
5289  
5290  description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
5291  
5292  license: MIT-style license
5293  
5294  authors:
5295    - Aaron Newton
5296  
5297  requires:
5298    - Core/Options
5299    - Core/Events
5300    - Core/Element.Event
5301    - Class.Binds
5302    - Class.Occlude
5303    - Element.Position
5304    - Element.Shortcuts
5305  
5306  provides: [OverText]
5307  
5308  ...
5309  */
5310  
5311  var OverText = new Class({
5312  
5313      Implements: [Options, Events, Class.Occlude],
5314  
5315      Binds: ['reposition', 'assert', 'focus', 'hide'],
5316  
5317      options: {/*
5318          textOverride: null,
5319          onFocus: function(){},
5320          onTextHide: function(textEl, inputEl){},
5321          onTextShow: function(textEl, inputEl){}, */
5322          element: 'label',
5323          labelClass: 'overTxtLabel',
5324          positionOptions: {
5325              position: 'upperLeft',
5326              edge: 'upperLeft',
5327              offset: {
5328                  x: 4,
5329                  y: 2
5330              }
5331          },
5332          poll: false,
5333          pollInterval: 250,
5334          wrap: false
5335      },
5336  
5337      property: 'OverText',
5338  
5339      initialize: function(element, options){
5340          element = this.element = document.id(element);
5341  
5342          if (this.occlude()) return this.occluded;
5343          this.setOptions(options);
5344  
5345          this.attach(element);
5346          OverText.instances.push(this);
5347  
5348          if (this.options.poll) this.poll();
5349      },
5350  
5351      toElement: function(){
5352          return this.element;
5353      },
5354  
5355      attach: function(){
5356          var element = this.element,
5357              options = this.options,
5358              value = options.textOverride || element.get('alt') || element.get('title');
5359  
5360          if (!value) return this;
5361  
5362          var text = this.text = new Element(options.element, {
5363              'class': options.labelClass,
5364              styles: {
5365                  lineHeight: 'normal',
5366                  position: 'absolute',
5367                  cursor: 'text'
5368              },
5369              html: value,
5370              events: {
5371                  click: this.hide.pass(options.element == 'label', this)
5372              }
5373          }).inject(element, 'after');
5374  
5375          if (options.element == 'label'){
5376              if (!element.get('id')) element.set('id', 'input_' + String.uniqueID());
5377              text.set('for', element.get('id'));
5378          }
5379  
5380          if (options.wrap){
5381              this.textHolder = new Element('div.overTxtWrapper', {
5382                  styles: {
5383                      lineHeight: 'normal',
5384                      position: 'relative'
5385                  }
5386              }).grab(text).inject(element, 'before');
5387          }
5388  
5389          return this.enable();
5390      },
5391  
5392      destroy: function(){
5393          this.element.eliminate(this.property); // Class.Occlude storage
5394          this.disable();
5395          if (this.text) this.text.destroy();
5396          if (this.textHolder) this.textHolder.destroy();
5397          return this;
5398      },
5399  
5400      disable: function(){
5401          this.element.removeEvents({
5402              focus: this.focus,
5403              blur: this.assert,
5404              change: this.assert
5405          });
5406          window.removeEvent('resize', this.reposition);
5407          this.hide(true, true);
5408          return this;
5409      },
5410  
5411      enable: function(){
5412          this.element.addEvents({
5413              focus: this.focus,
5414              blur: this.assert,
5415              change: this.assert
5416          });
5417          window.addEvent('resize', this.reposition);
5418          this.reposition();
5419          return this;
5420      },
5421  
5422      wrap: function(){
5423          if (this.options.element == 'label'){
5424              if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID());
5425              this.text.set('for', this.element.get('id'));
5426          }
5427      },
5428  
5429      startPolling: function(){
5430          this.pollingPaused = false;
5431          return this.poll();
5432      },
5433  
5434      poll: function(stop){
5435          //start immediately
5436          //pause on focus
5437          //resumeon blur
5438          if (this.poller && !stop) return this;
5439          if (stop){
5440              clearInterval(this.poller);
5441          } else {
5442              this.poller = (function(){
5443                  if (!this.pollingPaused) this.assert(true);
5444              }).periodical(this.options.pollInterval, this);
5445          }
5446  
5447          return this;
5448      },
5449  
5450      stopPolling: function(){
5451          this.pollingPaused = true;
5452          return this.poll(true);
5453      },
5454  
5455      focus: function(){
5456          if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this;
5457          return this.hide();
5458      },
5459  
5460      hide: function(suppressFocus, force){
5461          if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
5462              this.text.hide();
5463              this.fireEvent('textHide', [this.text, this.element]);
5464              this.pollingPaused = true;
5465              if (!suppressFocus){
5466                  try {
5467                      this.element.fireEvent('focus');
5468                      this.element.focus();
5469                  } catch(e){} //IE barfs if you call focus on hidden elements
5470              }
5471          }
5472          return this;
5473      },
5474  
5475      show: function(){
5476          if (this.text && !this.text.isDisplayed()){
5477              this.text.show();
5478              this.reposition();
5479              this.fireEvent('textShow', [this.text, this.element]);
5480              this.pollingPaused = false;
5481          }
5482          return this;
5483      },
5484  
5485      test: function(){
5486          return !this.element.get('value');
5487      },
5488  
5489      assert: function(suppressFocus){
5490          return this[this.test() ? 'show' : 'hide'](suppressFocus);
5491      },
5492  
5493      reposition: function(){
5494          this.assert(true);
5495          if (!this.element.isVisible()) return this.stopPolling().hide();
5496          if (this.text && this.test()){
5497              this.text.position(Object.merge(this.options.positionOptions, {
5498                  relativeTo: this.element
5499              }));
5500          }
5501          return this;
5502      }
5503  
5504  });
5505  
5506  OverText.instances = [];
5507  
5508  Object.append(OverText, {
5509  
5510      each: function(fn){
5511          return OverText.instances.each(function(ot, i){
5512              if (ot.element && ot.text) fn.call(OverText, ot, i);
5513          });
5514      },
5515  
5516      update: function(){
5517  
5518          return OverText.each(function(ot){
5519              return ot.reposition();
5520          });
5521  
5522      },
5523  
5524      hideAll: function(){
5525  
5526          return OverText.each(function(ot){
5527              return ot.hide(true, true);
5528          });
5529  
5530      },
5531  
5532      showAll: function(){
5533          return OverText.each(function(ot){
5534              return ot.show();
5535          });
5536      }
5537  
5538  });
5539  
5540  
5541  
5542  /*
5543  ---
5544  
5545  script: Fx.Elements.js
5546  
5547  name: Fx.Elements
5548  
5549  description: Effect to change any number of CSS properties of any number of Elements.
5550  
5551  license: MIT-style license
5552  
5553  authors:
5554    - Valerio Proietti
5555  
5556  requires:
5557    - Core/Fx.CSS
5558    - /MooTools.More
5559  
5560  provides: [Fx.Elements]
5561  
5562  ...
5563  */
5564  
5565  Fx.Elements = new Class({
5566  
5567      Extends: Fx.CSS,
5568  
5569      initialize: function(elements, options){
5570          this.elements = this.subject = $$(elements);
5571          this.parent(options);
5572      },
5573  
5574      compute: function(from, to, delta){
5575          var now = {};
5576  
5577          for (var i in from){
5578              var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
5579              for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
5580          }
5581  
5582          return now;
5583      },
5584  
5585      set: function(now){
5586          for (var i in now){
5587              if (!this.elements[i]) continue;
5588  
5589              var iNow = now[i];
5590              for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
5591          }
5592  
5593          return this;
5594      },
5595  
5596      start: function(obj){
5597          if (!this.check(obj)) return this;
5598          var from = {}, to = {};
5599  
5600          for (var i in obj){
5601              if (!this.elements[i]) continue;
5602  
5603              var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
5604  
5605              for (var p in iProps){
5606                  var parsed = this.prepare(this.elements[i], p, iProps[p]);
5607                  iFrom[p] = parsed.from;
5608                  iTo[p] = parsed.to;
5609              }
5610          }
5611  
5612          return this.parent(from, to);
5613      }
5614  
5615  });
5616  
5617  
5618  /*
5619  ---
5620  
5621  script: Fx.Accordion.js
5622  
5623  name: Fx.Accordion
5624  
5625  description: An Fx.Elements extension which allows you to easily create accordion type controls.
5626  
5627  license: MIT-style license
5628  
5629  authors:
5630    - Valerio Proietti
5631  
5632  requires:
5633    - Core/Element.Event
5634    - /Fx.Elements
5635  
5636  provides: [Fx.Accordion]
5637  
5638  ...
5639  */
5640  
5641  Fx.Accordion = new Class({
5642  
5643      Extends: Fx.Elements,
5644  
5645      options: {/*
5646          onActive: function(toggler, section){},
5647          onBackground: function(toggler, section){},*/
5648          fixedHeight: false,
5649          fixedWidth: false,
5650          display: 0,
5651          show: false,
5652          height: true,
5653          width: false,
5654          opacity: true,
5655          alwaysHide: false,
5656          trigger: 'click',
5657          initialDisplayFx: true,
5658          resetHeight: true
5659      },
5660  
5661      initialize: function(){
5662          var defined = function(obj){
5663              return obj != null;
5664          };
5665  
5666          var params = Array.link(arguments, {
5667              'container': Type.isElement, //deprecated
5668              'options': Type.isObject,
5669              'togglers': defined,
5670              'elements': defined
5671          });
5672          this.parent(params.elements, params.options);
5673  
5674          var options = this.options,
5675              togglers = this.togglers = $$(params.togglers);
5676  
5677          this.previous = -1;
5678          this.internalChain = new Chain();
5679  
5680          if (options.alwaysHide) this.options.link = 'chain';
5681  
5682          if (options.show || this.options.show === 0){
5683              options.display = false;
5684              this.previous = options.show;
5685          }
5686  
5687          if (options.start){
5688              options.display = false;
5689              options.show = false;
5690          }
5691  
5692          var effects = this.effects = {};
5693  
5694          if (options.opacity) effects.opacity = 'fullOpacity';
5695          if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth';
5696          if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight';
5697  
5698          for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]);
5699  
5700          this.elements.each(function(el, i){
5701              if (options.show === i){
5702                  this.fireEvent('active', [togglers[i], el]);
5703              } else {
5704                  for (var fx in effects) el.setStyle(fx, 0);
5705              }
5706          }, this);
5707  
5708          if (options.display || options.display === 0 || options.initialDisplayFx === false){
5709              this.display(options.display, options.initialDisplayFx);
5710          }
5711  
5712          if (options.fixedHeight !== false) options.resetHeight = false;
5713          this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
5714      },
5715  
5716      addSection: function(toggler, element){
5717          toggler = document.id(toggler);
5718          element = document.id(element);
5719          this.togglers.include(toggler);
5720          this.elements.include(element);
5721  
5722          var togglers = this.togglers,
5723              options = this.options,
5724              test = togglers.contains(toggler),
5725              idx = togglers.indexOf(toggler),
5726              displayer = this.display.pass(idx, this);
5727  
5728          toggler.store('accordion:display', displayer)
5729              .addEvent(options.trigger, displayer);
5730  
5731          if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
5732          if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
5733  
5734          element.fullOpacity = 1;
5735          if (options.fixedWidth) element.fullWidth = options.fixedWidth;
5736          if (options.fixedHeight) element.fullHeight = options.fixedHeight;
5737          element.setStyle('overflow', 'hidden');
5738  
5739          if (!test) for (var fx in this.effects){
5740              element.setStyle(fx, 0);
5741          }
5742          return this;
5743      },
5744  
5745      removeSection: function(toggler, displayIndex){
5746          var togglers = this.togglers,
5747              idx = togglers.indexOf(toggler),
5748              element = this.elements[idx];
5749  
5750          var remover = function(){
5751              togglers.erase(toggler);
5752              this.elements.erase(element);
5753              this.detach(toggler);
5754          }.bind(this);
5755  
5756          if (this.now == idx || displayIndex != null){
5757              this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover);
5758          } else {
5759              remover();
5760          }
5761          return this;
5762      },
5763  
5764      detach: function(toggler){
5765          var remove = function(toggler){
5766              toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
5767          }.bind(this);
5768  
5769          if (!toggler) this.togglers.each(remove);
5770          else remove(toggler);
5771          return this;
5772      },
5773  
5774      display: function(index, useFx){
5775          if (!this.check(index, useFx)) return this;
5776  
5777          var obj = {},
5778              elements = this.elements,
5779              options = this.options,
5780              effects = this.effects;
5781  
5782          if (useFx == null) useFx = true;
5783          if (typeOf(index) == 'element') index = elements.indexOf(index);
5784          if (index == this.previous && !options.alwaysHide) return this;
5785  
5786          if (options.resetHeight){
5787              var prev = elements[this.previous];
5788              if (prev && !this.selfHidden){
5789                  for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]);
5790              }
5791          }
5792  
5793          if ((this.timer && options.link == 'chain') || (index === this.previous && !options.alwaysHide)) return this;
5794  
5795          this.previous = index;
5796          this.selfHidden = false;
5797  
5798          elements.each(function(el, i){
5799              obj[i] = {};
5800              var hide;
5801              if (i != index){
5802                  hide = true;
5803              } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){
5804                  hide = true;
5805                  this.selfHidden = true;
5806              }
5807              this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
5808              for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]];
5809              if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto';
5810          }, this);
5811  
5812          this.internalChain.clearChain();
5813          this.internalChain.chain(function(){
5814              if (options.resetHeight && !this.selfHidden){
5815                  var el = elements[index];
5816                  if (el) el.setStyle('height', 'auto');
5817              }
5818          }.bind(this));
5819  
5820          return useFx ? this.start(obj) : this.set(obj).internalChain.callChain();
5821      }
5822  
5823  });
5824  
5825  
5826  
5827  
5828  /*
5829  ---
5830  
5831  script: Fx.Move.js
5832  
5833  name: Fx.Move
5834  
5835  description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
5836  
5837  license: MIT-style license
5838  
5839  authors:
5840    - Aaron Newton
5841  
5842  requires:
5843    - Core/Fx.Morph
5844    - /Element.Position
5845  
5846  provides: [Fx.Move]
5847  
5848  ...
5849  */
5850  
5851  Fx.Move = new Class({
5852  
5853      Extends: Fx.Morph,
5854  
5855      options: {
5856          relativeTo: document.body,
5857          position: 'center',
5858          edge: false,
5859          offset: {x: 0, y: 0}
5860      },
5861  
5862      start: function(destination){
5863          var element = this.element,
5864              topLeft = element.getStyles('top', 'left');
5865          if (topLeft.top == 'auto' || topLeft.left == 'auto'){
5866              element.setPosition(element.getPosition(element.getOffsetParent()));
5867          }
5868          return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true})));
5869      }
5870  
5871  });
5872  
5873  Element.Properties.move = {
5874  
5875      set: function(options){
5876          this.get('move').cancel().setOptions(options);
5877          return this;
5878      },
5879  
5880      get: function(){
5881          var move = this.retrieve('move');
5882          if (!move){
5883              move = new Fx.Move(this, {link: 'cancel'});
5884              this.store('move', move);
5885          }
5886          return move;
5887      }
5888  
5889  };
5890  
5891  Element.implement({
5892  
5893      move: function(options){
5894          this.get('move').start(options);
5895          return this;
5896      }
5897  
5898  });
5899  
5900  
5901  /*
5902  ---
5903  
5904  script: Fx.Scroll.js
5905  
5906  name: Fx.Scroll
5907  
5908  description: Effect to smoothly scroll any element, including the window.
5909  
5910  license: MIT-style license
5911  
5912  authors:
5913    - Valerio Proietti
5914  
5915  requires:
5916    - Core/Fx
5917    - Core/Element.Event
5918    - Core/Element.Dimensions
5919    - /MooTools.More
5920  
5921  provides: [Fx.Scroll]
5922  
5923  ...
5924  */
5925  
5926  (function(){
5927  
5928  Fx.Scroll = new Class({
5929  
5930      Extends: Fx,
5931  
5932      options: {
5933          offset: {x: 0, y: 0},
5934          wheelStops: true
5935      },
5936  
5937      initialize: function(element, options){
5938          this.element = this.subject = document.id(element);
5939          this.parent(options);
5940  
5941          if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
5942  
5943          if (this.options.wheelStops){
5944              var stopper = this.element,
5945                  cancel = this.cancel.pass(false, this);
5946              this.addEvent('start', function(){
5947                  stopper.addEvent('mousewheel', cancel);
5948              }, true);
5949              this.addEvent('complete', function(){
5950                  stopper.removeEvent('mousewheel', cancel);
5951              }, true);
5952          }
5953      },
5954  
5955      set: function(){
5956          var now = Array.flatten(arguments);
5957          if (Browser.firefox) now = [Math.round(now[0]), Math.round(now[1])]; // not needed anymore in newer firefox versions
5958          this.element.scrollTo(now[0], now[1]);
5959          return this;
5960      },
5961  
5962      compute: function(from, to, delta){
5963          return [0, 1].map(function(i){
5964              return Fx.compute(from[i], to[i], delta);
5965          });
5966      },
5967  
5968      start: function(x, y){
5969          if (!this.check(x, y)) return this;
5970          var scroll = this.element.getScroll();
5971          return this.parent([scroll.x, scroll.y], [x, y]);
5972      },
5973  
5974      calculateScroll: function(x, y){
5975          var element = this.element,
5976              scrollSize = element.getScrollSize(),
5977              scroll = element.getScroll(),
5978              size = element.getSize(),
5979              offset = this.options.offset,
5980              values = {x: x, y: y};
5981  
5982          for (var z in values){
5983              if (!values[z] && values[z] !== 0) values[z] = scroll[z];
5984              if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z];
5985              values[z] += offset[z];
5986          }
5987  
5988          return [values.x, values.y];
5989      },
5990  
5991      toTop: function(){
5992          return this.start.apply(this, this.calculateScroll(false, 0));
5993      },
5994  
5995      toLeft: function(){
5996          return this.start.apply(this, this.calculateScroll(0, false));
5997      },
5998  
5999      toRight: function(){
6000          return this.start.apply(this, this.calculateScroll('right', false));
6001      },
6002  
6003      toBottom: function(){
6004          return this.start.apply(this, this.calculateScroll(false, 'bottom'));
6005      },
6006  
6007      toElement: function(el, axes){
6008          axes = axes ? Array.from(axes) : ['x', 'y'];
6009          var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll();
6010          var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){
6011              return axes.contains(axis) ? value + scroll[axis] : false;
6012          });
6013          return this.start.apply(this, this.calculateScroll(position.x, position.y));
6014      },
6015  
6016      toElementEdge: function(el, axes, offset){
6017          axes = axes ? Array.from(axes) : ['x', 'y'];
6018          el = document.id(el);
6019          var to = {},
6020              position = el.getPosition(this.element),
6021              size = el.getSize(),
6022              scroll = this.element.getScroll(),
6023              containerSize = this.element.getSize(),
6024              edge = {
6025                  x: position.x + size.x,
6026                  y: position.y + size.y
6027              };
6028  
6029          ['x', 'y'].each(function(axis){
6030              if (axes.contains(axis)){
6031                  if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
6032                  if (position[axis] < scroll[axis]) to[axis] = position[axis];
6033              }
6034              if (to[axis] == null) to[axis] = scroll[axis];
6035              if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
6036          }, this);
6037  
6038          if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
6039          return this;
6040      },
6041  
6042      toElementCenter: function(el, axes, offset){
6043          axes = axes ? Array.from(axes) : ['x', 'y'];
6044          el = document.id(el);
6045          var to = {},
6046              position = el.getPosition(this.element),
6047              size = el.getSize(),
6048              scroll = this.element.getScroll(),
6049              containerSize = this.element.getSize();
6050  
6051          ['x', 'y'].each(function(axis){
6052              if (axes.contains(axis)){
6053                  to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2;
6054              }
6055              if (to[axis] == null) to[axis] = scroll[axis];
6056              if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
6057          }, this);
6058  
6059          if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
6060          return this;
6061      }
6062  
6063  });
6064  
6065  
6066  
6067  function isBody(element){
6068      return (/^(?:body|html)$/i).test(element.tagName);
6069  }
6070  
6071  })();
6072  
6073  
6074  /*
6075  ---
6076  
6077  script: Fx.Slide.js
6078  
6079  name: Fx.Slide
6080  
6081  description: Effect to slide an element in and out of view.
6082  
6083  license: MIT-style license
6084  
6085  authors:
6086    - Valerio Proietti
6087  
6088  requires:
6089    - Core/Fx
6090    - Core/Element.Style
6091    - /MooTools.More
6092  
6093  provides: [Fx.Slide]
6094  
6095  ...
6096  */
6097  
6098  Fx.Slide = new Class({
6099  
6100      Extends: Fx,
6101  
6102      options: {
6103          mode: 'vertical',
6104          wrapper: false,
6105          hideOverflow: true,
6106          resetHeight: false
6107      },
6108  
6109      initialize: function(element, options){
6110          element = this.element = this.subject = document.id(element);
6111          this.parent(options);
6112          options = this.options;
6113  
6114          var wrapper = element.retrieve('wrapper'),
6115              styles = element.getStyles('margin', 'position', 'overflow');
6116  
6117          if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'});
6118          if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles);
6119  
6120          if (!wrapper) wrapper = new Element('div', {
6121              styles: styles
6122          }).wraps(element);
6123  
6124          element.store('wrapper', wrapper).setStyle('margin', 0);
6125          if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden');
6126  
6127          this.now = [];
6128          this.open = true;
6129          this.wrapper = wrapper;
6130  
6131          this.addEvent('complete', function(){
6132              this.open = (wrapper['offset' + this.layout.capitalize()] != 0);
6133              if (this.open && this.options.resetHeight) wrapper.setStyle('height', '');
6134          }, true);
6135      },
6136  
6137      vertical: function(){
6138          this.margin = 'margin-top';
6139          this.layout = 'height';
6140          this.offset = this.element.offsetHeight;
6141      },
6142  
6143      horizontal: function(){
6144          this.margin = 'margin-left';
6145          this.layout = 'width';
6146          this.offset = this.element.offsetWidth;
6147      },
6148  
6149      set: function(now){
6150          this.element.setStyle(this.margin, now[0]);
6151          this.wrapper.setStyle(this.layout, now[1]);
6152          return this;
6153      },
6154  
6155      compute: function(from, to, delta){
6156          return [0, 1].map(function(i){
6157              return Fx.compute(from[i], to[i], delta);
6158          });
6159      },
6160  
6161      start: function(how, mode){
6162          if (!this.check(how, mode)) return this;
6163          this[mode || this.options.mode]();
6164  
6165          var margin = this.element.getStyle(this.margin).toInt(),
6166              layout = this.wrapper.getStyle(this.layout).toInt(),
6167              caseIn = [[margin, layout], [0, this.offset]],
6168              caseOut = [[margin, layout], [-this.offset, 0]],
6169              start;
6170  
6171          switch (how){
6172              case 'in': start = caseIn; break;
6173              case 'out': start = caseOut; break;
6174              case 'toggle': start = (layout == 0) ? caseIn : caseOut;
6175          }
6176          return this.parent(start[0], start[1]);
6177      },
6178  
6179      slideIn: function(mode){
6180          return this.start('in', mode);
6181      },
6182  
6183      slideOut: function(mode){
6184          return this.start('out', mode);
6185      },
6186  
6187      hide: function(mode){
6188          this[mode || this.options.mode]();
6189          this.open = false;
6190          return this.set([-this.offset, 0]);
6191      },
6192  
6193      show: function(mode){
6194          this[mode || this.options.mode]();
6195          this.open = true;
6196          return this.set([0, this.offset]);
6197      },
6198  
6199      toggle: function(mode){
6200          return this.start('toggle', mode);
6201      }
6202  
6203  });
6204  
6205  Element.Properties.slide = {
6206  
6207      set: function(options){
6208          this.get('slide').cancel().setOptions(options);
6209          return this;
6210      },
6211  
6212      get: function(){
6213          var slide = this.retrieve('slide');
6214          if (!slide){
6215              slide = new Fx.Slide(this, {link: 'cancel'});
6216              this.store('slide', slide);
6217          }
6218          return slide;
6219      }
6220  
6221  };
6222  
6223  Element.implement({
6224  
6225      slide: function(how, mode){
6226          how = how || 'toggle';
6227          var slide = this.get('slide'), toggle;
6228          switch (how){
6229              case 'hide': slide.hide(mode); break;
6230              case 'show': slide.show(mode); break;
6231              case 'toggle':
6232                  var flag = this.retrieve('slide:flag', slide.open);
6233                  slide[flag ? 'slideOut' : 'slideIn'](mode);
6234                  this.store('slide:flag', !flag);
6235                  toggle = true;
6236              break;
6237              default: slide.start(how, mode);
6238          }
6239          if (!toggle) this.eliminate('slide:flag');
6240          return this;
6241      }
6242  
6243  });
6244  
6245  
6246  /*
6247  ---
6248  
6249  script: Fx.SmoothScroll.js
6250  
6251  name: Fx.SmoothScroll
6252  
6253  description: Class for creating a smooth scrolling effect to all internal links on the page.
6254  
6255  license: MIT-style license
6256  
6257  authors:
6258    - Valerio Proietti
6259  
6260  requires:
6261    - Core/Slick.Finder
6262    - /Fx.Scroll
6263  
6264  provides: [Fx.SmoothScroll]
6265  
6266  ...
6267  */
6268  
6269  Fx.SmoothScroll = new Class({
6270  
6271      Extends: Fx.Scroll,
6272  
6273      options: {
6274          axes: ['x', 'y']
6275      },
6276  
6277      initialize: function(options, context){
6278          context = context || document;
6279          this.doc = context.getDocument();
6280          this.parent(this.doc, options);
6281  
6282          var win = context.getWindow(),
6283              location = win.location.href.match(/^[^#]*/)[0] + '#',
6284              links = $$(this.options.links || this.doc.links);
6285  
6286          links.each(function(link){
6287              if (link.href.indexOf(location) != 0) return;
6288              var anchor = link.href.substr(location.length);
6289              if (anchor) this.useLink(link, anchor);
6290          }, this);
6291  
6292          this.addEvent('complete', function(){
6293              win.location.hash = this.anchor;
6294              this.element.scrollTo(this.to[0], this.to[1]);
6295          }, true);
6296      },
6297  
6298      useLink: function(link, anchor){
6299  
6300          link.addEvent('click', function(event){
6301              var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
6302              if (!el) return;
6303  
6304              event.preventDefault();
6305              this.toElement(el, this.options.axes).chain(function(){
6306                  this.fireEvent('scrolledTo', [link, el]);
6307              }.bind(this));
6308  
6309              this.anchor = anchor;
6310  
6311          }.bind(this));
6312  
6313          return this;
6314      }
6315  });
6316  
6317  
6318  /*
6319  ---
6320  
6321  script: Fx.Sort.js
6322  
6323  name: Fx.Sort
6324  
6325  description: Defines Fx.Sort, a class that reorders lists with a transition.
6326  
6327  license: MIT-style license
6328  
6329  authors:
6330    - Aaron Newton
6331  
6332  requires:
6333    - Core/Element.Dimensions
6334    - /Fx.Elements
6335    - /Element.Measure
6336  
6337  provides: [Fx.Sort]
6338  
6339  ...
6340  */
6341  
6342  Fx.Sort = new Class({
6343  
6344      Extends: Fx.Elements,
6345  
6346      options: {
6347          mode: 'vertical'
6348      },
6349  
6350      initialize: function(elements, options){
6351          this.parent(elements, options);
6352          this.elements.each(function(el){
6353              if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
6354          });
6355          this.setDefaultOrder();
6356      },
6357  
6358      setDefaultOrder: function(){
6359          this.currentOrder = this.elements.map(function(el, index){
6360              return index;
6361          });
6362      },
6363  
6364      sort: function(){
6365          if (!this.check(arguments)) return this;
6366          var newOrder = Array.flatten(arguments);
6367  
6368          var top = 0,
6369              left = 0,
6370              next = {},
6371              zero = {},
6372              vert = this.options.mode == 'vertical';
6373  
6374          var current = this.elements.map(function(el, index){
6375              var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
6376              var val;
6377              if (vert){
6378                  val = {
6379                      top: top,
6380                      margin: size['margin-top'],
6381                      height: size.totalHeight
6382                  };
6383                  top += val.height - size['margin-top'];
6384              } else {
6385                  val = {
6386                      left: left,
6387                      margin: size['margin-left'],
6388                      width: size.totalWidth
6389                  };
6390                  left += val.width;
6391              }
6392              var plane = vert ? 'top' : 'left';
6393              zero[index] = {};
6394              var start = el.getStyle(plane).toInt();
6395              zero[index][plane] = start || 0;
6396              return val;
6397          }, this);
6398  
6399          this.set(zero);
6400          newOrder = newOrder.map(function(i){ return i.toInt(); });
6401          if (newOrder.length != this.elements.length){
6402              this.currentOrder.each(function(index){
6403                  if (!newOrder.contains(index)) newOrder.push(index);
6404              });
6405              if (newOrder.length > this.elements.length)
6406                  newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
6407          }
6408          var margin = 0;
6409          top = left = 0;
6410          newOrder.each(function(item){
6411              var newPos = {};
6412              if (vert){
6413                  newPos.top = top - current[item].top - margin;
6414                  top += current[item].height;
6415              } else {
6416                  newPos.left = left - current[item].left;
6417                  left += current[item].width;
6418              }
6419              margin = margin + current[item].margin;
6420              next[item]=newPos;
6421          }, this);
6422          var mapped = {};
6423          Array.clone(newOrder).sort().each(function(index){
6424              mapped[index] = next[index];
6425          });
6426          this.start(mapped);
6427          this.currentOrder = newOrder;
6428  
6429          return this;
6430      },
6431  
6432      rearrangeDOM: function(newOrder){
6433          newOrder = newOrder || this.currentOrder;
6434          var parent = this.elements[0].getParent();
6435          var rearranged = [];
6436          this.elements.setStyle('opacity', 0);
6437          //move each element and store the new default order
6438          newOrder.each(function(index){
6439              rearranged.push(this.elements[index].inject(parent).setStyles({
6440                  top: 0,
6441                  left: 0
6442              }));
6443          }, this);
6444          this.elements.setStyle('opacity', 1);
6445          this.elements = $$(rearranged);
6446          this.setDefaultOrder();
6447          return this;
6448      },
6449  
6450      getDefaultOrder: function(){
6451          return this.elements.map(function(el, index){
6452              return index;
6453          });
6454      },
6455  
6456      getCurrentOrder: function(){
6457          return this.currentOrder;
6458      },
6459  
6460      forward: function(){
6461          return this.sort(this.getDefaultOrder());
6462      },
6463  
6464      backward: function(){
6465          return this.sort(this.getDefaultOrder().reverse());
6466      },
6467  
6468      reverse: function(){
6469          return this.sort(this.currentOrder.reverse());
6470      },
6471  
6472      sortByElements: function(elements){
6473          return this.sort(elements.map(function(el){
6474              return this.elements.indexOf(el);
6475          }, this));
6476      },
6477  
6478      swap: function(one, two){
6479          if (typeOf(one) == 'element') one = this.elements.indexOf(one);
6480          if (typeOf(two) == 'element') two = this.elements.indexOf(two);
6481  
6482          var newOrder = Array.clone(this.currentOrder);
6483          newOrder[this.currentOrder.indexOf(one)] = two;
6484          newOrder[this.currentOrder.indexOf(two)] = one;
6485  
6486          return this.sort(newOrder);
6487      }
6488  
6489  });
6490  
6491  
6492  /*
6493  ---
6494  
6495  script: Drag.js
6496  
6497  name: Drag
6498  
6499  description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
6500  
6501  license: MIT-style license
6502  
6503  authors:
6504    - Valerio Proietti
6505    - Tom Occhinno
6506    - Jan Kassens
6507  
6508  requires:
6509    - Core/Events
6510    - Core/Options
6511    - Core/Element.Event
6512    - Core/Element.Style
6513    - Core/Element.Dimensions
6514    - /MooTools.More
6515  
6516  provides: [Drag]
6517  ...
6518  
6519  */
6520  
6521  var Drag = new Class({
6522  
6523      Implements: [Events, Options],
6524  
6525      options: {/*
6526          onBeforeStart: function(thisElement){},
6527          onStart: function(thisElement, event){},
6528          onSnap: function(thisElement){},
6529          onDrag: function(thisElement, event){},
6530          onCancel: function(thisElement){},
6531          onComplete: function(thisElement, event){},*/
6532          snap: 6,
6533          unit: 'px',
6534          grid: false,
6535          style: true,
6536          limit: false,
6537          handle: false,
6538          invert: false,
6539          preventDefault: false,
6540          stopPropagation: false,
6541          modifiers: {x: 'left', y: 'top'}
6542      },
6543  
6544      initialize: function(){
6545          var params = Array.link(arguments, {
6546              'options': Type.isObject,
6547              'element': function(obj){
6548                  return obj != null;
6549              }
6550          });
6551  
6552          this.element = document.id(params.element);
6553          this.document = this.element.getDocument();
6554          this.setOptions(params.options || {});
6555          var htype = typeOf(this.options.handle);
6556          this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
6557          this.mouse = {'now': {}, 'pos': {}};
6558          this.value = {'start': {}, 'now': {}};
6559  
6560          this.selection = (Browser.ie) ? 'selectstart' : 'mousedown';
6561  
6562  
6563          if (Browser.ie && !Drag.ondragstartFixed){
6564              document.ondragstart = Function.from(false);
6565              Drag.ondragstartFixed = true;
6566          }
6567  
6568          this.bound = {
6569              start: this.start.bind(this),
6570              check: this.check.bind(this),
6571              drag: this.drag.bind(this),
6572              stop: this.stop.bind(this),
6573              cancel: this.cancel.bind(this),
6574              eventStop: Function.from(false)
6575          };
6576          this.attach();
6577      },
6578  
6579      attach: function(){
6580          this.handles.addEvent('mousedown', this.bound.start);
6581          return this;
6582      },
6583  
6584      detach: function(){
6585          this.handles.removeEvent('mousedown', this.bound.start);
6586          return this;
6587      },
6588  
6589      start: function(event){
6590          var options = this.options;
6591  
6592          if (event.rightClick) return;
6593  
6594          if (options.preventDefault) event.preventDefault();
6595          if (options.stopPropagation) event.stopPropagation();
6596          this.mouse.start = event.page;
6597  
6598          this.fireEvent('beforeStart', this.element);
6599  
6600          var limit = options.limit;
6601          this.limit = {x: [], y: []};
6602  
6603          var z, coordinates;
6604          for (z in options.modifiers){
6605              if (!options.modifiers[z]) continue;
6606  
6607              var style = this.element.getStyle(options.modifiers[z]);
6608  
6609              // Some browsers (IE and Opera) don't always return pixels.
6610              if (style && !style.match(/px$/)){
6611                  if (!coordinates) coordinates = this.element.getCoordinates(this.element.getOffsetParent());
6612                  style = coordinates[options.modifiers[z]];
6613              }
6614  
6615              if (options.style) this.value.now[z] = (style || 0).toInt();
6616              else this.value.now[z] = this.element[options.modifiers[z]];
6617  
6618              if (options.invert) this.value.now[z] *= -1;
6619  
6620              this.mouse.pos[z] = event.page[z] - this.value.now[z];
6621  
6622              if (limit && limit[z]){
6623                  var i = 2;
6624                  while (i--){
6625                      var limitZI = limit[z][i];
6626                      if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI;
6627                  }
6628              }
6629          }
6630  
6631          if (typeOf(this.options.grid) == 'number') this.options.grid = {
6632              x: this.options.grid,
6633              y: this.options.grid
6634          };
6635  
6636          var events = {
6637              mousemove: this.bound.check,
6638              mouseup: this.bound.cancel
6639          };
6640          events[this.selection] = this.bound.eventStop;
6641          this.document.addEvents(events);
6642      },
6643  
6644      check: function(event){
6645          if (this.options.preventDefault) event.preventDefault();
6646          var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
6647          if (distance > this.options.snap){
6648              this.cancel();
6649              this.document.addEvents({
6650                  mousemove: this.bound.drag,
6651                  mouseup: this.bound.stop
6652              });
6653              this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
6654          }
6655      },
6656  
6657      drag: function(event){
6658          var options = this.options;
6659  
6660          if (options.preventDefault) event.preventDefault();
6661          this.mouse.now = event.page;
6662  
6663          for (var z in options.modifiers){
6664              if (!options.modifiers[z]) continue;
6665              this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
6666  
6667              if (options.invert) this.value.now[z] *= -1;
6668  
6669              if (options.limit && this.limit[z]){
6670                  if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){
6671                      this.value.now[z] = this.limit[z][1];
6672                  } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){
6673                      this.value.now[z] = this.limit[z][0];
6674                  }
6675              }
6676  
6677              if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]);
6678  
6679              if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit);
6680              else this.element[options.modifiers[z]] = this.value.now[z];
6681          }
6682  
6683          this.fireEvent('drag', [this.element, event]);
6684      },
6685  
6686      cancel: function(event){
6687          this.document.removeEvents({
6688              mousemove: this.bound.check,
6689              mouseup: this.bound.cancel
6690          });
6691          if (event){
6692              this.document.removeEvent(this.selection, this.bound.eventStop);
6693              this.fireEvent('cancel', this.element);
6694          }
6695      },
6696  
6697      stop: function(event){
6698          var events = {
6699              mousemove: this.bound.drag,
6700              mouseup: this.bound.stop
6701          };
6702          events[this.selection] = this.bound.eventStop;
6703          this.document.removeEvents(events);
6704          if (event) this.fireEvent('complete', [this.element, event]);
6705      }
6706  
6707  });
6708  
6709  Element.implement({
6710  
6711      makeResizable: function(options){
6712          var drag = new Drag(this, Object.merge({
6713              modifiers: {
6714                  x: 'width',
6715                  y: 'height'
6716              }
6717          }, options));
6718  
6719          this.store('resizer', drag);
6720          return drag.addEvent('drag', function(){
6721              this.fireEvent('resize', drag);
6722          }.bind(this));
6723      }
6724  
6725  });
6726  
6727  
6728  /*
6729  ---
6730  
6731  script: Drag.Move.js
6732  
6733  name: Drag.Move
6734  
6735  description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
6736  
6737  license: MIT-style license
6738  
6739  authors:
6740    - Valerio Proietti
6741    - Tom Occhinno
6742    - Jan Kassens
6743    - Aaron Newton
6744    - Scott Kyle
6745  
6746  requires:
6747    - Core/Element.Dimensions
6748    - /Drag
6749  
6750  provides: [Drag.Move]
6751  
6752  ...
6753  */
6754  
6755  Drag.Move = new Class({
6756  
6757      Extends: Drag,
6758  
6759      options: {/*
6760          onEnter: function(thisElement, overed){},
6761          onLeave: function(thisElement, overed){},
6762          onDrop: function(thisElement, overed, event){},*/
6763          droppables: [],
6764          container: false,
6765          precalculate: false,
6766          includeMargins: true,
6767          checkDroppables: true
6768      },
6769  
6770      initialize: function(element, options){
6771          this.parent(element, options);
6772          element = this.element;
6773  
6774          this.droppables = $$(this.options.droppables);
6775          this.container = document.id(this.options.container);
6776  
6777          if (this.container && typeOf(this.container) != 'element')
6778              this.container = document.id(this.container.getDocument().body);
6779  
6780          if (this.options.style){
6781              if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){
6782                  var parent = element.getOffsetParent(),
6783                      styles = element.getStyles('left', 'top');
6784                  if (parent && (styles.left == 'auto' || styles.top == 'auto')){
6785                      element.setPosition(element.getPosition(parent));
6786                  }
6787              }
6788  
6789              if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute');
6790          }
6791  
6792          this.addEvent('start', this.checkDroppables, true);
6793          this.overed = null;
6794      },
6795  
6796      start: function(event){
6797          if (this.container) this.options.limit = this.calculateLimit();
6798  
6799          if (this.options.precalculate){
6800              this.positions = this.droppables.map(function(el){
6801                  return el.getCoordinates();
6802              });
6803          }
6804  
6805          this.parent(event);
6806      },
6807  
6808      calculateLimit: function(){
6809          var element = this.element,
6810              container = this.container,
6811  
6812              offsetParent = document.id(element.getOffsetParent()) || document.body,
6813              containerCoordinates = container.getCoordinates(offsetParent),
6814              elementMargin = {},
6815              elementBorder = {},
6816              containerMargin = {},
6817              containerBorder = {},
6818              offsetParentPadding = {};
6819  
6820          ['top', 'right', 'bottom', 'left'].each(function(pad){
6821              elementMargin[pad] = element.getStyle('margin-' + pad).toInt();
6822              elementBorder[pad] = element.getStyle('border-' + pad).toInt();
6823              containerMargin[pad] = container.getStyle('margin-' + pad).toInt();
6824              containerBorder[pad] = container.getStyle('border-' + pad).toInt();
6825              offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
6826          }, this);
6827  
6828          var width = element.offsetWidth + elementMargin.left + elementMargin.right,
6829              height = element.offsetHeight + elementMargin.top + elementMargin.bottom,
6830              left = 0,
6831              top = 0,
6832              right = containerCoordinates.right - containerBorder.right - width,
6833              bottom = containerCoordinates.bottom - containerBorder.bottom - height;
6834  
6835          if (this.options.includeMargins){
6836              left += elementMargin.left;
6837              top += elementMargin.top;
6838          } else {
6839              right += elementMargin.right;
6840              bottom += elementMargin.bottom;
6841          }
6842  
6843          if (element.getStyle('position') == 'relative'){
6844              var coords = element.getCoordinates(offsetParent);
6845              coords.left -= element.getStyle('left').toInt();
6846              coords.top -= element.getStyle('top').toInt();
6847  
6848              left -= coords.left;
6849              top -= coords.top;
6850              if (container.getStyle('position') != 'relative'){
6851                  left += containerBorder.left;
6852                  top += containerBorder.top;
6853              }
6854              right += elementMargin.left - coords.left;
6855              bottom += elementMargin.top - coords.top;
6856  
6857              if (container != offsetParent){
6858                  left += containerMargin.left + offsetParentPadding.left;
6859                  top += ((Browser.ie6 || Browser.ie7) ? 0 : containerMargin.top) + offsetParentPadding.top;
6860              }
6861          } else {
6862              left -= elementMargin.left;
6863              top -= elementMargin.top;
6864              if (container != offsetParent){
6865                  left += containerCoordinates.left + containerBorder.left;
6866                  top += containerCoordinates.top + containerBorder.top;
6867              }
6868          }
6869  
6870          return {
6871              x: [left, right],
6872              y: [top, bottom]
6873          };
6874      },
6875  
6876      getDroppableCoordinates: function(element){
6877          var position = element.getCoordinates();
6878          if (element.getStyle('position') == 'fixed'){
6879              var scroll = window.getScroll();
6880              position.left += scroll.x;
6881              position.right += scroll.x;
6882              position.top += scroll.y;
6883              position.bottom += scroll.y;
6884          }
6885          return position;
6886      },
6887  
6888      checkDroppables: function(){
6889          var overed = this.droppables.filter(function(el, i){
6890              el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el);
6891              var now = this.mouse.now;
6892              return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
6893          }, this).getLast();
6894  
6895          if (this.overed != overed){
6896              if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
6897              if (overed) this.fireEvent('enter', [this.element, overed]);
6898              this.overed = overed;
6899          }
6900      },
6901  
6902      drag: function(event){
6903          this.parent(event);
6904          if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
6905      },
6906  
6907      stop: function(event){
6908          this.checkDroppables();
6909          this.fireEvent('drop', [this.element, this.overed, event]);
6910          this.overed = null;
6911          return this.parent(event);
6912      }
6913  
6914  });
6915  
6916  Element.implement({
6917  
6918      makeDraggable: function(options){
6919          var drag = new Drag.Move(this, options);
6920          this.store('dragger', drag);
6921          return drag;
6922      }
6923  
6924  });
6925  
6926  
6927  /*
6928  ---
6929  
6930  script: Slider.js
6931  
6932  name: Slider
6933  
6934  description: Class for creating horizontal and vertical slider controls.
6935  
6936  license: MIT-style license
6937  
6938  authors:
6939    - Valerio Proietti
6940  
6941  requires:
6942    - Core/Element.Dimensions
6943    - /Class.Binds
6944    - /Drag
6945    - /Element.Measure
6946  
6947  provides: [Slider]
6948  
6949  ...
6950  */
6951  
6952  var Slider = new Class({
6953  
6954      Implements: [Events, Options],
6955  
6956      Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
6957  
6958      options: {/*
6959          onTick: function(intPosition){},
6960          onChange: function(intStep){},
6961          onComplete: function(strStep){},*/
6962          onTick: function(position){
6963              this.setKnobPosition(position);
6964          },
6965          initialStep: 0,
6966          snap: false,
6967          offset: 0,
6968          range: false,
6969          wheel: false,
6970          steps: 100,
6971          mode: 'horizontal'
6972      },
6973  
6974      initialize: function(element, knob, options){
6975          this.setOptions(options);
6976          options = this.options;
6977          this.element = document.id(element);
6978          knob = this.knob = document.id(knob);
6979          this.previousChange = this.previousEnd = this.step = -1;
6980  
6981          var limit = {},
6982              modifiers = {x: false, y: false};
6983  
6984          switch (options.mode){
6985              case 'vertical':
6986                  this.axis = 'y';
6987                  this.property = 'top';
6988                  this.offset = 'offsetHeight';
6989                  break;
6990              case 'horizontal':
6991                  this.axis = 'x';
6992                  this.property = 'left';
6993                  this.offset = 'offsetWidth';
6994          }
6995  
6996          this.setSliderDimensions();
6997          this.setRange(options.range);
6998  
6999          if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative');
7000          knob.setStyle(this.property, -options.offset);
7001          modifiers[this.axis] = this.property;
7002          limit[this.axis] = [-options.offset, this.full - options.offset];
7003  
7004          var dragOptions = {
7005              snap: 0,
7006              limit: limit,
7007              modifiers: modifiers,
7008              onDrag: this.draggedKnob,
7009              onStart: this.draggedKnob,
7010              onBeforeStart: (function(){
7011                  this.isDragging = true;
7012              }).bind(this),
7013              onCancel: function(){
7014                  this.isDragging = false;
7015              }.bind(this),
7016              onComplete: function(){
7017                  this.isDragging = false;
7018                  this.draggedKnob();
7019                  this.end();
7020              }.bind(this)
7021          };
7022          if (options.snap) this.setSnap(dragOptions);
7023  
7024          this.drag = new Drag(knob, dragOptions);
7025          this.attach();
7026          if (options.initialStep != null) this.set(options.initialStep);
7027      },
7028  
7029      attach: function(){
7030          this.element.addEvent('mousedown', this.clickedElement);
7031          if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
7032          this.drag.attach();
7033          return this;
7034      },
7035  
7036      detach: function(){
7037          this.element.removeEvent('mousedown', this.clickedElement)
7038              .removeEvent('mousewheel', this.scrolledElement);
7039          this.drag.detach();
7040          return this;
7041      },
7042  
7043      autosize: function(){
7044          this.setSliderDimensions()
7045              .setKnobPosition(this.toPosition(this.step));
7046          this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset];
7047          if (this.options.snap) this.setSnap();
7048          return this;
7049      },
7050  
7051      setSnap: function(options){
7052          if (!options) options = this.drag.options;
7053          options.grid = Math.ceil(this.stepWidth);
7054          options.limit[this.axis][1] = this.full;
7055          return this;
7056      },
7057  
7058      setKnobPosition: function(position){
7059          if (this.options.snap) position = this.toPosition(this.step);
7060          this.knob.setStyle(this.property, position);
7061          return this;
7062      },
7063  
7064      setSliderDimensions: function(){
7065          this.full = this.element.measure(function(){
7066              this.half = this.knob[this.offset] / 2;
7067              return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2);
7068          }.bind(this));
7069          return this;
7070      },
7071  
7072      set: function(step){
7073          if (!((this.range > 0) ^ (step < this.min))) step = this.min;
7074          if (!((this.range > 0) ^ (step > this.max))) step = this.max;
7075  
7076          this.step = Math.round(step);
7077          return this.checkStep()
7078              .fireEvent('tick', this.toPosition(this.step))
7079              .end();
7080      },
7081  
7082      setRange: function(range, pos){
7083          this.min = Array.pick([range[0], 0]);
7084          this.max = Array.pick([range[1], this.options.steps]);
7085          this.range = this.max - this.min;
7086          this.steps = this.options.steps || this.full;
7087          this.stepSize = Math.abs(this.range) / this.steps;
7088          this.stepWidth = this.stepSize * this.full / Math.abs(this.range);
7089          if (range) this.set(Array.pick([pos, this.step]).floor(this.min).max(this.max));
7090          return this;
7091      },
7092  
7093      clickedElement: function(event){
7094          if (this.isDragging || event.target == this.knob) return;
7095  
7096          var dir = this.range < 0 ? -1 : 1,
7097              position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
7098  
7099          position = position.limit(-this.options.offset, this.full - this.options.offset);
7100  
7101          this.step = Math.round(this.min + dir * this.toStep(position));
7102  
7103          this.checkStep()
7104              .fireEvent('tick', position)
7105              .end();
7106      },
7107  
7108      scrolledElement: function(event){
7109          var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
7110          this.set(this.step + (mode ? -1 : 1) * this.stepSize);
7111          event.stop();
7112      },
7113  
7114      draggedKnob: function(){
7115          var dir = this.range < 0 ? -1 : 1,
7116              position = this.drag.value.now[this.axis];
7117  
7118          position = position.limit(-this.options.offset, this.full -this.options.offset);
7119  
7120          this.step = Math.round(this.min + dir * this.toStep(position));
7121          this.checkStep();
7122      },
7123  
7124      checkStep: function(){
7125          var step = this.step;
7126          if (this.previousChange != step){
7127              this.previousChange = step;
7128              this.fireEvent('change', step);
7129          }
7130          return this;
7131      },
7132  
7133      end: function(){
7134          var step = this.step;
7135          if (this.previousEnd !== step){
7136              this.previousEnd = step;
7137              this.fireEvent('complete', step + '');
7138          }
7139          return this;
7140      },
7141  
7142      toStep: function(position){
7143          var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
7144          return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
7145      },
7146  
7147      toPosition: function(step){
7148          return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
7149      }
7150  
7151  });
7152  
7153  
7154  /*
7155  ---
7156  
7157  script: Sortables.js
7158  
7159  name: Sortables
7160  
7161  description: Class for creating a drag and drop sorting interface for lists of items.
7162  
7163  license: MIT-style license
7164  
7165  authors:
7166    - Tom Occhino
7167  
7168  requires:
7169    - Core/Fx.Morph
7170    - /Drag.Move
7171  
7172  provides: [Sortables]
7173  
7174  ...
7175  */
7176  
7177  var Sortables = new Class({
7178  
7179      Implements: [Events, Options],
7180  
7181      options: {/*
7182          onSort: function(element, clone){},
7183          onStart: function(element, clone){},
7184          onComplete: function(element){},*/
7185          opacity: 1,
7186          clone: false,
7187          revert: false,
7188          handle: false,
7189          dragOptions: {}
7190      },
7191  
7192      initialize: function(lists, options){
7193          this.setOptions(options);
7194  
7195          this.elements = [];
7196          this.lists = [];
7197          this.idle = true;
7198  
7199          this.addLists($$(document.id(lists) || lists));
7200  
7201          if (!this.options.clone) this.options.revert = false;
7202          if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({
7203              duration: 250,
7204              link: 'cancel'
7205          }, this.options.revert));
7206      },
7207  
7208      attach: function(){
7209          this.addLists(this.lists);
7210          return this;
7211      },
7212  
7213      detach: function(){
7214          this.lists = this.removeLists(this.lists);
7215          return this;
7216      },
7217  
7218      addItems: function(){
7219          Array.flatten(arguments).each(function(element){
7220              this.elements.push(element);
7221              var start = element.retrieve('sortables:start', function(event){
7222                  this.start.call(this, event, element);
7223              }.bind(this));
7224              (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
7225          }, this);
7226          return this;
7227      },
7228  
7229      addLists: function(){
7230          Array.flatten(arguments).each(function(list){
7231              this.lists.include(list);
7232              this.addItems(list.getChildren());
7233          }, this);
7234          return this;
7235      },
7236  
7237      removeItems: function(){
7238          return $$(Array.flatten(arguments).map(function(element){
7239              this.elements.erase(element);
7240              var start = element.retrieve('sortables:start');
7241              (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
7242  
7243              return element;
7244          }, this));
7245      },
7246  
7247      removeLists: function(){
7248          return $$(Array.flatten(arguments).map(function(list){
7249              this.lists.erase(list);
7250              this.removeItems(list.getChildren());
7251  
7252              return list;
7253          }, this));
7254      },
7255  
7256      getClone: function(event, element){
7257          if (!this.options.clone) return new Element(element.tagName).inject(document.body);
7258          if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
7259          var clone = element.clone(true).setStyles({
7260              margin: 0,
7261              position: 'absolute',
7262              visibility: 'hidden',
7263              width: element.getStyle('width')
7264          }).addEvent('mousedown', function(event){
7265              element.fireEvent('mousedown', event);
7266          });
7267          //prevent the duplicated radio inputs from unchecking the real one
7268          if (clone.get('html').test('radio')){
7269              clone.getElements('input[type=radio]').each(function(input, i){
7270                  input.set('name', 'clone_' + i);
7271                  if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
7272              });
7273          }
7274  
7275          return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
7276      },
7277  
7278      getDroppables: function(){
7279          var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
7280          if (!this.options.constrain) droppables.append(this.lists).erase(this.list);
7281          return droppables;
7282      },
7283  
7284      insert: function(dragging, element){
7285          var where = 'inside';
7286          if (this.lists.contains(element)){
7287              this.list = element;
7288              this.drag.droppables = this.getDroppables();
7289          } else {
7290              where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
7291          }
7292          this.element.inject(element, where);
7293          this.fireEvent('sort', [this.element, this.clone]);
7294      },
7295  
7296      start: function(event, element){
7297          if (
7298              !this.idle ||
7299              event.rightClick ||
7300              ['button', 'input', 'a', 'textarea'].contains(event.target.get('tag'))
7301          ) return;
7302  
7303          this.idle = false;
7304          this.element = element;
7305          this.opacity = element.getStyle('opacity');
7306          this.list = element.getParent();
7307          this.clone = this.getClone(event, element);
7308  
7309          this.drag = new Drag.Move(this.clone, Object.merge({
7310              
7311              droppables: this.getDroppables()
7312          }, this.options.dragOptions)).addEvents({
7313              onSnap: function(){
7314                  event.stop();
7315                  this.clone.setStyle('visibility', 'visible');
7316                  this.element.setStyle('opacity', this.options.opacity || 0);
7317                  this.fireEvent('start', [this.element, this.clone]);
7318              }.bind(this),
7319              onEnter: this.insert.bind(this),
7320              onCancel: this.end.bind(this),
7321              onComplete: this.end.bind(this)
7322          });
7323  
7324          this.clone.inject(this.element, 'before');
7325          this.drag.start(event);
7326      },
7327  
7328      end: function(){
7329          this.drag.detach();
7330          this.element.setStyle('opacity', this.opacity);
7331          if (this.effect){
7332              var dim = this.element.getStyles('width', 'height'),
7333                  clone = this.clone,
7334                  pos = clone.computePosition(this.element.getPosition(this.clone.getOffsetParent()));
7335  
7336              var destroy = function(){
7337                  this.removeEvent('cancel', destroy);
7338                  clone.destroy();
7339              };
7340  
7341              this.effect.element = clone;
7342              this.effect.start({
7343                  top: pos.top,
7344                  left: pos.left,
7345                  width: dim.width,
7346                  height: dim.height,
7347                  opacity: 0.25
7348              }).addEvent('cancel', destroy).chain(destroy);
7349          } else {
7350              this.clone.destroy();
7351          }
7352          this.reset();
7353      },
7354  
7355      reset: function(){
7356          this.idle = true;
7357          this.fireEvent('complete', this.element);
7358      },
7359  
7360      serialize: function(){
7361          var params = Array.link(arguments, {
7362              modifier: Type.isFunction,
7363              index: function(obj){
7364                  return obj != null;
7365              }
7366          });
7367          var serial = this.lists.map(function(list){
7368              return list.getChildren().map(params.modifier || function(element){
7369                  return element.get('id');
7370              }, this);
7371          }, this);
7372  
7373          var index = params.index;
7374          if (this.lists.length == 1) index = 0;
7375          return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
7376      }
7377  
7378  });
7379  
7380  
7381  /*
7382  ---
7383  
7384  script: Request.JSONP.js
7385  
7386  name: Request.JSONP
7387  
7388  description: Defines Request.JSONP, a class for cross domain javascript via script injection.
7389  
7390  license: MIT-style license
7391  
7392  authors:
7393    - Aaron Newton
7394    - Guillermo Rauch
7395    - Arian Stolwijk
7396  
7397  requires:
7398    - Core/Element
7399    - Core/Request
7400    - MooTools.More
7401  
7402  provides: [Request.JSONP]
7403  
7404  ...
7405  */
7406  
7407  Request.JSONP = new Class({
7408  
7409      Implements: [Chain, Events, Options],
7410  
7411      options: {/*
7412          onRequest: function(src, scriptElement){},
7413          onComplete: function(data){},
7414          onSuccess: function(data){},
7415          onCancel: function(){},
7416          onTimeout: function(){},
7417          onError: function(){}, */
7418          onRequest: function(src){
7419              if (this.options.log && window.console && console.log){
7420                  console.log('JSONP retrieving script with url:' + src);
7421              }
7422          },
7423          onError: function(src){
7424              if (this.options.log && window.console && console.warn){
7425                  console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
7426              }
7427          },
7428          url: '',
7429          callbackKey: 'callback',
7430          injectScript: document.head,
7431          data: '',
7432          link: 'ignore',
7433          timeout: 0,
7434          log: false
7435      },
7436  
7437      initialize: function(options){
7438          this.setOptions(options);
7439      },
7440  
7441      send: function(options){
7442          if (!Request.prototype.check.call(this, options)) return this;
7443          this.running = true;
7444  
7445          var type = typeOf(options);
7446          if (type == 'string' || type == 'element') options = {data: options};
7447          options = Object.merge(this.options, options || {});
7448  
7449          var data = options.data;
7450          switch (typeOf(data)){
7451              case 'element': data = document.id(data).toQueryString(); break;
7452              case 'object': case 'hash': data = Object.toQueryString(data);
7453          }
7454  
7455          var index = this.index = Request.JSONP.counter++;
7456  
7457          var src = options.url +
7458              (options.url.test('\\?') ? '&' :'?') +
7459              (options.callbackKey) +
7460              '=Request.JSONP.request_map.request_'+ index +
7461              (data ? '&' + data : '');
7462  
7463          if (src.length > 2083) this.fireEvent('error', src);
7464  
7465          Request.JSONP.request_map['request_' + index] = function(){
7466              this.success(arguments, index);
7467          }.bind(this);
7468  
7469          var script = this.getScript(src).inject(options.injectScript);
7470          this.fireEvent('request', [src, script]);
7471  
7472          if (options.timeout) this.timeout.delay(options.timeout, this);
7473  
7474          return this;
7475      },
7476  
7477      getScript: function(src){
7478          if (!this.script) this.script = new Element('script', {
7479              type: 'text/javascript',
7480              async: true,
7481              src: src
7482          });
7483          return this.script;
7484      },
7485  
7486      success: function(args, index){
7487          if (!this.running) return;
7488          this.clear()
7489              .fireEvent('complete', args).fireEvent('success', args)
7490              .callChain();
7491      },
7492  
7493      cancel: function(){
7494          if (this.running) this.clear().fireEvent('cancel');
7495          return this;
7496      },
7497  
7498      isRunning: function(){
7499          return !!this.running;
7500      },
7501  
7502      clear: function(){
7503          this.running = false;
7504          if (this.script){
7505              this.script.destroy();
7506              this.script = null;
7507          }
7508          return this;
7509      },
7510  
7511      timeout: function(){
7512          if (this.running){
7513              this.running = false;
7514              this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel();
7515          }
7516          return this;
7517      }
7518  
7519  });
7520  
7521  Request.JSONP.counter = 0;
7522  Request.JSONP.request_map = {};
7523  
7524  
7525  /*
7526  ---
7527  
7528  script: Request.Queue.js
7529  
7530  name: Request.Queue
7531  
7532  description: Controls several instances of Request and its variants to run only one request at a time.
7533  
7534  license: MIT-style license
7535  
7536  authors:
7537    - Aaron Newton
7538  
7539  requires:
7540    - Core/Element
7541    - Core/Request
7542    - /Class.Binds
7543  
7544  provides: [Request.Queue]
7545  
7546  ...
7547  */
7548  
7549  Request.Queue = new Class({
7550  
7551      Implements: [Options, Events],
7552  
7553      Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
7554  
7555      options: {/*
7556          onRequest: function(argsPassedToOnRequest){},
7557          onSuccess: function(argsPassedToOnSuccess){},
7558          onComplete: function(argsPassedToOnComplete){},
7559          onCancel: function(argsPassedToOnCancel){},
7560          onException: function(argsPassedToOnException){},
7561          onFailure: function(argsPassedToOnFailure){},
7562          onEnd: function(){},
7563          */
7564          stopOnFailure: true,
7565          autoAdvance: true,
7566          concurrent: 1,
7567          requests: {}
7568      },
7569  
7570      initialize: function(options){
7571          var requests;
7572          if (options){
7573              requests = options.requests;
7574              delete options.requests;
7575          }
7576          this.setOptions(options);
7577          this.requests = {};
7578          this.queue = [];
7579          this.reqBinders = {};
7580  
7581          if (requests) this.addRequests(requests);
7582      },
7583  
7584      addRequest: function(name, request){
7585          this.requests[name] = request;
7586          this.attach(name, request);
7587          return this;
7588      },
7589  
7590      addRequests: function(obj){
7591          Object.each(obj, function(req, name){
7592              this.addRequest(name, req);
7593          }, this);
7594          return this;
7595      },
7596  
7597      getName: function(req){
7598          return Object.keyOf(this.requests, req);
7599      },
7600  
7601      attach: function(name, req){
7602          if (req._groupSend) return this;
7603          ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
7604              if (!this.reqBinders[name]) this.reqBinders[name] = {};
7605              this.reqBinders[name][evt] = function(){
7606                  this['on' + evt.capitalize()].apply(this, [name, req].append(arguments));
7607              }.bind(this);
7608              req.addEvent(evt, this.reqBinders[name][evt]);
7609          }, this);
7610          req._groupSend = req.send;
7611          req.send = function(options){
7612              this.send(name, options);
7613              return req;
7614          }.bind(this);
7615          return this;
7616      },
7617  
7618      removeRequest: function(req){
7619          var name = typeOf(req) == 'object' ? this.getName(req) : req;
7620          if (!name && typeOf(name) != 'string') return this;
7621          req = this.requests[name];
7622          if (!req) return this;
7623          ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
7624              req.removeEvent(evt, this.reqBinders[name][evt]);
7625          }, this);
7626          req.send = req._groupSend;
7627          delete req._groupSend;
7628          return this;
7629      },
7630  
7631      getRunning: function(){
7632          return Object.filter(this.requests, function(r){
7633              return r.running;
7634          });
7635      },
7636  
7637      isRunning: function(){
7638          return !!(Object.keys(this.getRunning()).length);
7639      },
7640  
7641      send: function(name, options){
7642          var q = function(){
7643              this.requests[name]._groupSend(options);
7644              this.queue.erase(q);
7645          }.bind(this);
7646  
7647          q.name = name;
7648          if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
7649          else q();
7650          return this;
7651      },
7652  
7653      hasNext: function(name){
7654          return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
7655      },
7656  
7657      resume: function(){
7658          this.error = false;
7659          (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this);
7660          return this;
7661      },
7662  
7663      runNext: function(name){
7664          if (!this.queue.length) return this;
7665          if (!name){
7666              this.queue[0]();
7667          } else {
7668              var found;
7669              this.queue.each(function(q){
7670                  if (!found && q.name == name){
7671                      found = true;
7672                      q();
7673                  }
7674              });
7675          }
7676          return this;
7677      },
7678  
7679      runAll: function(){
7680          this.queue.each(function(q){
7681              q();
7682          });
7683          return this;
7684      },
7685  
7686      clear: function(name){
7687          if (!name){
7688              this.queue.empty();
7689          } else {
7690              this.queue = this.queue.map(function(q){
7691                  if (q.name != name) return q;
7692                  else return false;
7693              }).filter(function(q){
7694                  return q;
7695              });
7696          }
7697          return this;
7698      },
7699  
7700      cancel: function(name){
7701          this.requests[name].cancel();
7702          return this;
7703      },
7704  
7705      onRequest: function(){
7706          this.fireEvent('request', arguments);
7707      },
7708  
7709      onComplete: function(){
7710          this.fireEvent('complete', arguments);
7711          if (!this.queue.length) this.fireEvent('end');
7712      },
7713  
7714      onCancel: function(){
7715          if (this.options.autoAdvance && !this.error) this.runNext();
7716          this.fireEvent('cancel', arguments);
7717      },
7718  
7719      onSuccess: function(){
7720          if (this.options.autoAdvance && !this.error) this.runNext();
7721          this.fireEvent('success', arguments);
7722      },
7723  
7724      onFailure: function(){
7725          this.error = true;
7726          if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
7727          this.fireEvent('failure', arguments);
7728      },
7729  
7730      onException: function(){
7731          this.error = true;
7732          if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
7733          this.fireEvent('exception', arguments);
7734      }
7735  
7736  });
7737  
7738  
7739  /*
7740  ---
7741  
7742  script: Request.Periodical.js
7743  
7744  name: Request.Periodical
7745  
7746  description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
7747  
7748  license: MIT-style license
7749  
7750  authors:
7751    - Christoph Pojer
7752  
7753  requires:
7754    - Core/Request
7755    - /MooTools.More
7756  
7757  provides: [Request.Periodical]
7758  
7759  ...
7760  */
7761  
7762  Request.implement({
7763  
7764      options: {
7765          initialDelay: 5000,
7766          delay: 5000,
7767          limit: 60000
7768      },
7769  
7770      startTimer: function(data){
7771          var fn = function(){
7772              if (!this.running) this.send({data: data});
7773          };
7774          this.lastDelay = this.options.initialDelay;
7775          this.timer = fn.delay(this.lastDelay, this);
7776          this.completeCheck = function(response){
7777              clearTimeout(this.timer);
7778              this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
7779              this.timer = fn.delay(this.lastDelay, this);
7780          };
7781          return this.addEvent('complete', this.completeCheck);
7782      },
7783  
7784      stopTimer: function(){
7785          clearTimeout(this.timer);
7786          return this.removeEvent('complete', this.completeCheck);
7787      }
7788  
7789  });
7790  
7791  
7792  /*
7793  ---
7794  
7795  script: Assets.js
7796  
7797  name: Assets
7798  
7799  description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
7800  
7801  license: MIT-style license
7802  
7803  authors:
7804    - Valerio Proietti
7805  
7806  requires:
7807    - Core/Element.Event
7808    - /MooTools.More
7809  
7810  provides: [Assets]
7811  
7812  ...
7813  */
7814  
7815  var Asset = {
7816  
7817      javascript: function(source, properties){
7818          if (!properties) properties = {};
7819  
7820          var script = new Element('script', {src: source, type: 'text/javascript'}),
7821              doc = properties.document || document,
7822              load = properties.onload || properties.onLoad;
7823  
7824          delete properties.onload;
7825          delete properties.onLoad;
7826          delete properties.document;
7827  
7828          if (load){
7829              if (typeof script.onreadystatechange != 'undefined'){
7830                  script.addEvent('readystatechange', function(){
7831                      if (['loaded', 'complete'].contains(this.readyState)) load.call(this);
7832                  });
7833              } else {
7834                  script.addEvent('load', load);
7835              }
7836          }
7837  
7838          return script.set(properties).inject(doc.head);
7839      },
7840  
7841      css: function(source, properties){
7842          if (!properties) properties = {};
7843  
7844          var link = new Element('link', {
7845              rel: 'stylesheet',
7846              media: 'screen',
7847              type: 'text/css',
7848              href: source
7849          });
7850  
7851          var load = properties.onload || properties.onLoad,
7852              doc = properties.document || document;
7853  
7854          delete properties.onload;
7855          delete properties.onLoad;
7856          delete properties.document;
7857  
7858          if (load) link.addEvent('load', load);
7859          return link.set(properties).inject(doc.head);
7860      },
7861  
7862      image: function(source, properties){
7863          if (!properties) properties = {};
7864  
7865          var image = new Image(),
7866              element = document.id(image) || new Element('img');
7867  
7868          ['load', 'abort', 'error'].each(function(name){
7869              var type = 'on' + name,
7870                  cap = 'on' + name.capitalize(),
7871                  event = properties[type] || properties[cap] || function(){};
7872  
7873              delete properties[cap];
7874              delete properties[type];
7875  
7876              image[type] = function(){
7877                  if (!image) return;
7878                  if (!element.parentNode){
7879                      element.width = image.width;
7880                      element.height = image.height;
7881                  }
7882                  image = image.onload = image.onabort = image.onerror = null;
7883                  event.delay(1, element, element);
7884                  element.fireEvent(name, element, 1);
7885              };
7886          });
7887  
7888          image.src = element.src = source;
7889          if (image && image.complete) image.onload.delay(1);
7890          return element.set(properties);
7891      },
7892  
7893      images: function(sources, options){
7894          sources = Array.from(sources);
7895  
7896          var fn = function(){},
7897              counter = 0;
7898  
7899          options = Object.merge({
7900              onComplete: fn,
7901              onProgress: fn,
7902              onError: fn,
7903              properties: {}
7904          }, options);
7905  
7906          return new Elements(sources.map(function(source, index){
7907              return Asset.image(source, Object.append(options.properties, {
7908                  onload: function(){
7909                      counter++;
7910                      options.onProgress.call(this, counter, index, source);
7911                      if (counter == sources.length) options.onComplete();
7912                  },
7913                  onerror: function(){
7914                      counter++;
7915                      options.onError.call(this, counter, index, source);
7916                      if (counter == sources.length) options.onComplete();
7917                  }
7918              }));
7919          }));
7920      }
7921  
7922  };
7923  
7924  
7925  /*
7926  ---
7927  
7928  script: Color.js
7929  
7930  name: Color
7931  
7932  description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
7933  
7934  license: MIT-style license
7935  
7936  authors:
7937    - Valerio Proietti
7938  
7939  requires:
7940    - Core/Array
7941    - Core/String
7942    - Core/Number
7943    - Core/Hash
7944    - Core/Function
7945    - MooTools.More
7946  
7947  provides: [Color]
7948  
7949  ...
7950  */
7951  
7952  (function(){
7953  
7954  var Color = this.Color = new Type('Color', function(color, type){
7955      if (arguments.length >= 3){
7956          type = 'rgb'; color = Array.slice(arguments, 0, 3);
7957      } else if (typeof color == 'string'){
7958          if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
7959          else if (color.match(/hsb/)) color = color.hsbToRgb();
7960          else color = color.hexToRgb(true);
7961      }
7962      type = type || 'rgb';
7963      switch (type){
7964          case 'hsb':
7965              var old = color;
7966              color = color.hsbToRgb();
7967              color.hsb = old;
7968          break;
7969          case 'hex': color = color.hexToRgb(true); break;
7970      }
7971      color.rgb = color.slice(0, 3);
7972      color.hsb = color.hsb || color.rgbToHsb();
7973      color.hex = color.rgbToHex();
7974      return Object.append(color, this);
7975  });
7976  
7977  Color.implement({
7978  
7979      mix: function(){
7980          var colors = Array.slice(arguments);
7981          var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50;
7982          var rgb = this.slice();
7983          colors.each(function(color){
7984              color = new Color(color);
7985              for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
7986          });
7987          return new Color(rgb, 'rgb');
7988      },
7989  
7990      invert: function(){
7991          return new Color(this.map(function(value){
7992              return 255 - value;
7993          }));
7994      },
7995  
7996      setHue: function(value){
7997          return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
7998      },
7999  
8000      setSaturation: function(percent){
8001          return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
8002      },
8003  
8004      setBrightness: function(percent){
8005          return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
8006      }
8007  
8008  });
8009  
8010  this.$RGB = function(r, g, b){
8011      return new Color([r, g, b], 'rgb');
8012  };
8013  
8014  this.$HSB = function(h, s, b){
8015      return new Color([h, s, b], 'hsb');
8016  };
8017  
8018  this.$HEX = function(hex){
8019      return new Color(hex, 'hex');
8020  };
8021  
8022  Array.implement({
8023  
8024      rgbToHsb: function(){
8025          var red = this[0],
8026                  green = this[1],
8027                  blue = this[2],
8028                  hue = 0;
8029          var max = Math.max(red, green, blue),
8030                  min = Math.min(red, green, blue);
8031          var delta = max - min;
8032          var brightness = max / 255,
8033                  saturation = (max != 0) ? delta / max : 0;
8034          if (saturation != 0){
8035              var rr = (max - red) / delta;
8036              var gr = (max - green) / delta;
8037              var br = (max - blue) / delta;
8038              if (red == max) hue = br - gr;
8039              else if (green == max) hue = 2 + rr - br;
8040              else hue = 4 + gr - rr;
8041              hue /= 6;
8042              if (hue < 0) hue++;
8043          }
8044          return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
8045      },
8046  
8047      hsbToRgb: function(){
8048          var br = Math.round(this[2] / 100 * 255);
8049          if (this[1] == 0){
8050              return [br, br, br];
8051          } else {
8052              var hue = this[0] % 360;
8053              var f = hue % 60;
8054              var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
8055              var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
8056              var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
8057              switch (Math.floor(hue / 60)){
8058                  case 0: return [br, t, p];
8059                  case 1: return [q, br, p];
8060                  case 2: return [p, br, t];
8061                  case 3: return [p, q, br];
8062                  case 4: return [t, p, br];
8063                  case 5: return [br, p, q];
8064              }
8065          }
8066          return false;
8067      }
8068  
8069  });
8070  
8071  String.implement({
8072  
8073      rgbToHsb: function(){
8074          var rgb = this.match(/\d{1,3}/g);
8075          return (rgb) ? rgb.rgbToHsb() : null;
8076      },
8077  
8078      hsbToRgb: function(){
8079          var hsb = this.match(/\d{1,3}/g);
8080          return (hsb) ? hsb.hsbToRgb() : null;
8081      }
8082  
8083  });
8084  
8085  })();
8086  
8087  
8088  
8089  /*
8090  ---
8091  
8092  script: Group.js
8093  
8094  name: Group
8095  
8096  description: Class for monitoring collections of events
8097  
8098  license: MIT-style license
8099  
8100  authors:
8101    - Valerio Proietti
8102  
8103  requires:
8104    - Core/Events
8105    - /MooTools.More
8106  
8107  provides: [Group]
8108  
8109  ...
8110  */
8111  
8112  (function(){
8113  
8114  this.Group = new Class({
8115  
8116      initialize: function(){
8117          this.instances = Array.flatten(arguments);
8118      },
8119  
8120      addEvent: function(type, fn){
8121          var instances = this.instances,
8122              len = instances.length,
8123              togo = len,
8124              args = new Array(len),
8125              self = this;
8126  
8127          instances.each(function(instance, i){
8128              instance.addEvent(type, function(){
8129                  if (!args[i]) togo--;
8130                  args[i] = arguments;
8131                  if (!togo){
8132                      fn.call(self, instances, instance, args);
8133                      togo = len;
8134                      args = new Array(len);
8135                  }
8136              });
8137          });
8138      }
8139  
8140  });
8141  
8142  })();
8143  
8144  
8145  /*
8146  ---
8147  
8148  script: Hash.Cookie.js
8149  
8150  name: Hash.Cookie
8151  
8152  description: Class for creating, reading, and deleting Cookies in JSON format.
8153  
8154  license: MIT-style license
8155  
8156  authors:
8157    - Valerio Proietti
8158    - Aaron Newton
8159  
8160  requires:
8161    - Core/Cookie
8162    - Core/JSON
8163    - /MooTools.More
8164    - /Hash
8165  
8166  provides: [Hash.Cookie]
8167  
8168  ...
8169  */
8170  
8171  Hash.Cookie = new Class({
8172  
8173      Extends: Cookie,
8174  
8175      options: {
8176          autoSave: true
8177      },
8178  
8179      initialize: function(name, options){
8180          this.parent(name, options);
8181          this.load();
8182      },
8183  
8184      save: function(){
8185          var value = JSON.encode(this.hash);
8186          if (!value || value.length > 4096) return false; //cookie would be truncated!
8187          if (value == '{}') this.dispose();
8188          else this.write(value);
8189          return true;
8190      },
8191  
8192      load: function(){
8193          this.hash = new Hash(JSON.decode(this.read(), true));
8194          return this;
8195      }
8196  
8197  });
8198  
8199  Hash.each(Hash.prototype, function(method, name){
8200      if (typeof method == 'function') Hash.Cookie.implement(name, function(){
8201          var value = method.apply(this.hash, arguments);
8202          if (this.options.autoSave) this.save();
8203          return value;
8204      });
8205  });
8206  
8207  
8208  /*
8209  ---
8210  name: Table
8211  description: LUA-Style table implementation.
8212  license: MIT-style license
8213  authors:
8214    - Valerio Proietti
8215  requires: [Core/Array]
8216  provides: [Table]
8217  ...
8218  */
8219  
8220  (function(){
8221  
8222  var Table = this.Table = function(){
8223  
8224      this.length = 0;
8225      var keys = [],
8226          values = [];
8227      
8228      this.set = function(key, value){
8229          var index = keys.indexOf(key);
8230          if (index == -1){
8231              var length = keys.length;
8232              keys[length] = key;
8233              values[length] = value;
8234              this.length++;
8235          } else {
8236              values[index] = value;
8237          }
8238          return this;
8239      };
8240  
8241      this.get = function(key){
8242          var index = keys.indexOf(key);
8243          return (index == -1) ? null : values[index];
8244      };
8245  
8246      this.erase = function(key){
8247          var index = keys.indexOf(key);
8248          if (index != -1){
8249              this.length--;
8250              keys.splice(index, 1);
8251              return values.splice(index, 1)[0];
8252          }
8253          return null;
8254      };
8255  
8256      this.each = this.forEach = function(fn, bind){
8257          for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this);
8258      };
8259      
8260  };
8261  
8262  if (this.Type) new Type('Table', Table);
8263  
8264  })();
8265  
8266  
8267  /*
8268  ---
8269  
8270  script: HtmlTable.js
8271  
8272  name: HtmlTable
8273  
8274  description: Builds table elements with methods to add rows.
8275  
8276  license: MIT-style license
8277  
8278  authors:
8279    - Aaron Newton
8280  
8281  requires:
8282    - Core/Options
8283    - Core/Events
8284    - /Class.Occlude
8285  
8286  provides: [HtmlTable]
8287  
8288  ...
8289  */
8290  
8291  var HtmlTable = new Class({
8292  
8293      Implements: [Options, Events, Class.Occlude],
8294  
8295      options: {
8296          properties: {
8297              cellpadding: 0,
8298              cellspacing: 0,
8299              border: 0
8300          },
8301          rows: [],
8302          headers: [],
8303          footers: []
8304      },
8305  
8306      property: 'HtmlTable',
8307  
8308      initialize: function(){
8309          var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString});
8310          this.setOptions(params.options);
8311          if (!params.table && params.id) params.table = document.id(params.id);
8312          this.element = params.table || new Element('table', this.options.properties);
8313          if (this.occlude()) return this.occluded;
8314          this.build();
8315      },
8316  
8317      build: function(){
8318          this.element.store('HtmlTable', this);
8319  
8320          this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
8321          $$(this.body.rows);
8322  
8323          if (this.options.headers.length) this.setHeaders(this.options.headers);
8324          else this.thead = document.id(this.element.tHead);
8325  
8326          if (this.thead) this.head = this.getHead();
8327          if (this.options.footers.length) this.setFooters(this.options.footers);
8328  
8329          this.tfoot = document.id(this.element.tFoot);
8330          if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]);
8331  
8332          this.options.rows.each(function(row){
8333              this.push(row);
8334          }, this);
8335      },
8336  
8337      toElement: function(){
8338          return this.element;
8339      },
8340  
8341      empty: function(){
8342          this.body.empty();
8343          return this;
8344      },
8345  
8346      set: function(what, items){
8347          var target = (what == 'headers') ? 'tHead' : 'tFoot',
8348              lower = target.toLowerCase();
8349  
8350          this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty();
8351          var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td');
8352  
8353          if (what == 'headers') this.head = this.getHead();
8354          else this.foot = this.getHead();
8355  
8356          return data;
8357      },
8358  
8359      getHead: function(){
8360          var rows = this.thead.rows;
8361          return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false;
8362      },
8363  
8364      setHeaders: function(headers){
8365          this.set('headers', headers);
8366          return this;
8367      },
8368  
8369      setFooters: function(footers){
8370          this.set('footers', footers);
8371          return this;
8372      },
8373  
8374      update: function(tr, row, tag){
8375          var tds = tr.getChildren(tag || 'td'), last = tds.length - 1;
8376  
8377          row.each(function(data, index){
8378              var td = tds[index] || new Element(tag || 'td').inject(tr),
8379                  content = (data ? data.content : '') || data,
8380                  type = typeOf(content);
8381  
8382              if (data && data.properties) td.set(data.properties);
8383              if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content);
8384              else td.set('html', content);
8385  
8386              if (index > last) tds.push(td);
8387              else tds[index] = td;
8388          });
8389  
8390          return {
8391              tr: tr,
8392              tds: tds
8393          };
8394      },
8395  
8396      push: function(row, rowProperties, target, tag, where){
8397          if (typeOf(row) == 'element' && row.get('tag') == 'tr'){
8398              row.inject(target || this.body, where);
8399              return {
8400                  tr: row,
8401                  tds: row.getChildren('td')
8402              };
8403          }
8404          return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag);
8405      },
8406  
8407      pushMany: function(rows, rowProperties, target, tag, where){
8408          return rows.map(function(row){
8409              return this.push(row, rowProperties, target, tag, where);
8410          }, this);
8411      }
8412  
8413  });
8414  
8415  
8416  ['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
8417      HtmlTable.implement(method, function(){
8418          this.element[method].apply(this.element, arguments);
8419          return this;
8420      });
8421  });
8422  
8423  
8424  
8425  
8426  /*
8427  ---
8428  
8429  script: HtmlTable.Zebra.js
8430  
8431  name: HtmlTable.Zebra
8432  
8433  description: Builds a stripy table with methods to add rows.
8434  
8435  license: MIT-style license
8436  
8437  authors:
8438    - Harald Kirschner
8439    - Aaron Newton
8440  
8441  requires:
8442    - /HtmlTable
8443    - /Element.Shortcuts
8444    - /Class.refactor
8445  
8446  provides: [HtmlTable.Zebra]
8447  
8448  ...
8449  */
8450  
8451  HtmlTable = Class.refactor(HtmlTable, {
8452  
8453      options: {
8454          classZebra: 'table-tr-odd',
8455          zebra: true,
8456          zebraOnlyVisibleRows: true
8457      },
8458  
8459      initialize: function(){
8460          this.previous.apply(this, arguments);
8461          if (this.occluded) return this.occluded;
8462          if (this.options.zebra) this.updateZebras();
8463      },
8464  
8465      updateZebras: function(){
8466          var index = 0;
8467          Array.each(this.body.rows, function(row){
8468              if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){
8469                  this.zebra(row, index++);
8470              }
8471          }, this);
8472      },
8473  
8474      setRowStyle: function(row, i){
8475          if (this.previous) this.previous(row, i);
8476          this.zebra(row, i);
8477      },
8478  
8479      zebra: function(row, i){
8480          return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
8481      },
8482  
8483      push: function(){
8484          var pushed = this.previous.apply(this, arguments);
8485          if (this.options.zebra) this.updateZebras();
8486          return pushed;
8487      }
8488  
8489  });
8490  
8491  
8492  /*
8493  ---
8494  
8495  script: HtmlTable.Sort.js
8496  
8497  name: HtmlTable.Sort
8498  
8499  description: Builds a stripy, sortable table with methods to add rows.
8500  
8501  license: MIT-style license
8502  
8503  authors:
8504    - Harald Kirschner
8505    - Aaron Newton
8506    - Jacob Thornton
8507  
8508  requires:
8509    - Core/Hash
8510    - /HtmlTable
8511    - /Class.refactor
8512    - /Element.Delegation
8513    - /String.Extras
8514    - /Date
8515  
8516  provides: [HtmlTable.Sort]
8517  
8518  ...
8519  */
8520  
8521  HtmlTable = Class.refactor(HtmlTable, {
8522  
8523      options: {/*
8524          onSort: function(){}, */
8525          sortIndex: 0,
8526          sortReverse: false,
8527          parsers: [],
8528          defaultParser: 'string',
8529          classSortable: 'table-sortable',
8530          classHeadSort: 'table-th-sort',
8531          classHeadSortRev: 'table-th-sort-rev',
8532          classNoSort: 'table-th-nosort',
8533          classGroupHead: 'table-tr-group-head',
8534          classGroup: 'table-tr-group',
8535          classCellSort: 'table-td-sort',
8536          classSortSpan: 'table-th-sort-span',
8537          sortable: false,
8538          thSelector: 'th'
8539      },
8540  
8541      initialize: function (){
8542          this.previous.apply(this, arguments);
8543          if (this.occluded) return this.occluded;
8544          this.sorted = {index: null, dir: 1};
8545          if (!this.bound) this.bound = {};
8546          this.bound.headClick = this.headClick.bind(this);
8547          this.sortSpans = new Elements();
8548          if (this.options.sortable){
8549              this.enableSort();
8550              if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
8551          }
8552      },
8553  
8554      attachSorts: function(attach){
8555          this.detachSorts();
8556          if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick);
8557      },
8558  
8559      detachSorts: function(){
8560          this.element.removeEvents('click:relay(' + this.options.thSelector + ')');
8561      },
8562  
8563      setHeaders: function(){
8564          this.previous.apply(this, arguments);
8565          if (this.sortEnabled) this.setParsers();
8566      },
8567  
8568      setParsers: function(){
8569          this.parsers = this.detectParsers();
8570      },
8571  
8572      detectParsers: function(){
8573          return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this);
8574      },
8575  
8576      detectParser: function(cell, index){
8577          if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser');
8578          var thDiv = new Element('div');
8579          thDiv.adopt(cell.childNodes).inject(cell);
8580          var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top');
8581          this.sortSpans.push(sortSpan);
8582          var parser = this.options.parsers[index],
8583              rows = this.body.rows,
8584              cancel;
8585          switch (typeOf(parser)){
8586              case 'function': parser = {convert: parser}; cancel = true; break;
8587              case 'string': parser = parser; cancel = true; break;
8588          }
8589          if (!cancel){
8590              HtmlTable.ParserPriority.some(function(parserName){
8591                  var current = HtmlTable.Parsers[parserName],
8592                      match = current.match;
8593                  if (!match) return false;
8594                  for (var i = 0, j = rows.length; i < j; i++){
8595                      var cell = document.id(rows[i].cells[index]),
8596                          text = cell ? cell.get('html').clean() : '';
8597                      if (text && match.test(text)){
8598                          parser = current;
8599                          return true;
8600                      }
8601                  }
8602              });
8603          }
8604          if (!parser) parser = this.options.defaultParser;
8605          cell.store('htmltable-parser', parser);
8606          return parser;
8607      },
8608  
8609      headClick: function(event, el){
8610          if (!this.head || el.hasClass(this.options.classNoSort)) return;
8611          return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length);
8612      },
8613  
8614      serialize: function(){
8615          var previousSerialization = this.previous.apply(this, arguments) || {};
8616          if (this.options.sortable){
8617              previousSerialization.sortIndex = this.sorted.index;
8618              previousSerialization.sortReverse = this.sorted.reverse;
8619          }
8620          return previousSerialization;
8621      },
8622  
8623      restore: function(tableState){
8624          if(this.options.sortable && tableState.sortIndex){
8625              this.sort(tableState.sortIndex, tableState.sortReverse);
8626          }
8627          this.previous.apply(this, arguments);
8628      },
8629  
8630      setSortedState: function(index, reverse){
8631          if (reverse != null) this.sorted.reverse = reverse;
8632          else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse;
8633          else this.sorted.reverse = this.sorted.index == null;
8634  
8635          if (index != null) this.sorted.index = index;
8636      },
8637  
8638      setHeadSort: function(sorted){
8639          var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){
8640              return row.getElements(this.options.thSelector)[this.sorted.index];
8641          }, this).clean());
8642          if (!head.length) return;
8643          if (sorted){
8644              head.addClass(this.options.classHeadSort);
8645              if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
8646              else head.removeClass(this.options.classHeadSortRev);
8647          } else {
8648              head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
8649          }
8650      },
8651  
8652      setRowSort: function(data, pre){
8653          var count = data.length,
8654              body = this.body,
8655              group,
8656              rowIndex;
8657  
8658          while (count){
8659              var item = data[--count],
8660                  position = item.position,
8661                  row = body.rows[position];
8662  
8663              if (row.disabled) continue;
8664              if (!pre){
8665                  group = this.setGroupSort(group, row, item);
8666                  this.setRowStyle(row, count);
8667              }
8668              body.appendChild(row);
8669  
8670              for (rowIndex = 0; rowIndex < count; rowIndex++){
8671                  if (data[rowIndex].position > position) data[rowIndex].position--;
8672              }
8673          }
8674      },
8675  
8676      setRowStyle: function(row, i){
8677          this.previous(row, i);
8678          row.cells[this.sorted.index].addClass(this.options.classCellSort);
8679      },
8680  
8681      setGroupSort: function(group, row, item){
8682          if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup);
8683          else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead);
8684          return item.value;
8685      },
8686  
8687      getParser: function(){
8688          var parser = this.parsers[this.sorted.index];
8689          return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser;
8690      },
8691  
8692      sort: function(index, reverse, pre){
8693          if (!this.head) return;
8694  
8695          if (!pre){
8696              this.clearSort();
8697              this.setSortedState(index, reverse);
8698              this.setHeadSort(true);
8699          }
8700  
8701          var parser = this.getParser();
8702          if (!parser) return;
8703  
8704          var rel;
8705          if (!Browser.ie){
8706              rel = this.body.getParent();
8707              this.body.dispose();
8708          }
8709  
8710          var data = this.parseData(parser).sort(function(a, b){
8711              if (a.value === b.value) return 0;
8712              return a.value > b.value ? 1 : -1;
8713          });
8714  
8715          if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true);
8716          this.setRowSort(data, pre);
8717  
8718          if (rel) rel.grab(this.body);
8719          this.fireEvent('stateChanged');
8720          return this.fireEvent('sort', [this.body, this.sorted.index]);
8721      },
8722  
8723      parseData: function(parser){
8724          return Array.map(this.body.rows, function(row, i){
8725              var value = parser.convert.call(document.id(row.cells[this.sorted.index]));
8726              return {
8727                  position: i,
8728                  value: value
8729              };
8730          }, this);
8731      },
8732  
8733      clearSort: function(){
8734          this.setHeadSort(false);
8735          this.body.getElements('td').removeClass(this.options.classCellSort);
8736      },
8737  
8738      reSort: function(){
8739          if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
8740          return this;
8741      },
8742  
8743      enableSort: function(){
8744          this.element.addClass(this.options.classSortable);
8745          this.attachSorts(true);
8746          this.setParsers();
8747          this.sortEnabled = true;
8748          return this;
8749      },
8750  
8751      disableSort: function(){
8752          this.element.removeClass(this.options.classSortable);
8753          this.attachSorts(false);
8754          this.sortSpans.each(function(span){
8755              span.destroy();
8756          });
8757          this.sortSpans.empty();
8758          this.sortEnabled = false;
8759          return this;
8760      }
8761  
8762  });
8763  
8764  HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number'];
8765  
8766  HtmlTable.Parsers = {
8767  
8768      'date': {
8769          match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
8770          convert: function(){
8771              var d = Date.parse(this.get('text').stripTags());
8772              return (typeOf(d) == 'date') ? d.format('db') : '';
8773          },
8774          type: 'date'
8775      },
8776      'input-checked': {
8777          match: / type="(radio|checkbox)" /,
8778          convert: function(){
8779              return this.getElement('input').checked;
8780          }
8781      },
8782      'input-value': {
8783          match: /<input/,
8784          convert: function(){
8785              return this.getElement('input').value;
8786          }
8787      },
8788      'number': {
8789          match: /^\d+[^\d.,]*$/,
8790          convert: function(){
8791              return this.get('text').stripTags().toInt();
8792          },
8793          number: true
8794      },
8795      'numberLax': {
8796          match: /^[^\d]+\d+$/,
8797          convert: function(){
8798              return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
8799          },
8800          number: true
8801      },
8802      'float': {
8803          match: /^[\d]+\.[\d]+/,
8804          convert: function(){
8805              return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
8806          },
8807          number: true
8808      },
8809      'floatLax': {
8810          match: /^[^\d]+[\d]+\.[\d]+$/,
8811          convert: function(){
8812              return this.get('text').replace(/[^-?^\d.]/, '').stripTags();
8813          },
8814          number: true
8815      },
8816      'string': {
8817          match: null,
8818          convert: function(){
8819              return this.get('text').stripTags().toLowerCase();
8820          }
8821      },
8822      'title': {
8823          match: null,
8824          convert: function(){
8825              return this.title;
8826          }
8827      }
8828  
8829  };
8830  
8831  
8832  
8833  HtmlTable.defineParsers = function(parsers){
8834      HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
8835      for (var parser in parsers){
8836          HtmlTable.ParserPriority.unshift(parser);
8837      }
8838  };
8839  
8840  
8841  /*
8842  ---
8843  
8844  script: Keyboard.js
8845  
8846  name: Keyboard
8847  
8848  description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
8849  
8850  license: MIT-style license
8851  
8852  authors:
8853    - Perrin Westrich
8854    - Aaron Newton
8855    - Scott Kyle
8856  
8857  requires:
8858    - Core/Events
8859    - Core/Options
8860    - Core/Element.Event
8861    - Element.Event.Pseudos.Keys
8862  
8863  provides: [Keyboard]
8864  
8865  ...
8866  */
8867  
8868  (function(){
8869  
8870      var Keyboard = this.Keyboard = new Class({
8871  
8872          Extends: Events,
8873  
8874          Implements: [Options],
8875  
8876          options: {/*
8877              onActivate: function(){},
8878              onDeactivate: function(){},*/
8879              defaultEventType: 'keydown',
8880              active: false,
8881              manager: null,
8882              events: {},
8883              nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
8884          },
8885  
8886          initialize: function(options){
8887              if (options && options.manager){
8888                  this._manager = options.manager;
8889                  delete options.manager;
8890              }
8891              this.setOptions(options);
8892              this._setup();
8893          },
8894  
8895          addEvent: function(type, fn, internal){
8896              return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
8897          },
8898  
8899          removeEvent: function(type, fn){
8900              return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
8901          },
8902  
8903          toggleActive: function(){
8904              return this[this.isActive() ? 'deactivate' : 'activate']();
8905          },
8906  
8907          activate: function(instance){
8908              if (instance){
8909                  if (instance.isActive()) return this;
8910                  //if we're stealing focus, store the last keyboard to have it so the relinquish command works
8911                  if (this._activeKB && instance != this._activeKB){
8912                      this.previous = this._activeKB;
8913                      this.previous.fireEvent('deactivate');
8914                  }
8915                  //if we're enabling a child, assign it so that events are now passed to it
8916                  this._activeKB = instance.fireEvent('activate');
8917                  Keyboard.manager.fireEvent('changed');
8918              } else if (this._manager){
8919                  //else we're enabling ourselves, we must ask our parent to do it for us
8920                  this._manager.activate(this);
8921              }
8922              return this;
8923          },
8924  
8925          isActive: function(){
8926              return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this);
8927          },
8928  
8929          deactivate: function(instance){
8930              if (instance){
8931                  if (instance === this._activeKB){
8932                      this._activeKB = null;
8933                      instance.fireEvent('deactivate');
8934                      Keyboard.manager.fireEvent('changed');
8935                  }
8936              } else if (this._manager){
8937                  this._manager.deactivate(this);
8938              }
8939              return this;
8940          },
8941  
8942          relinquish: function(){
8943              if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous);
8944              else this.deactivate();
8945              return this;
8946          },
8947  
8948          //management logic
8949          manage: function(instance){
8950              if (instance._manager) instance._manager.drop(instance);
8951              this._instances.push(instance);
8952              instance._manager = this;
8953              if (!this._activeKB) this.activate(instance);
8954              return this;
8955          },
8956  
8957          drop: function(instance){
8958              instance.relinquish();
8959              this._instances.erase(instance);
8960              if (this._activeKB == instance){
8961                  if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous);
8962                  else this._activeKB = this._instances[0];
8963              }
8964              return this;
8965          },
8966  
8967          trace: function(){
8968              Keyboard.trace(this);
8969          },
8970  
8971          each: function(fn){
8972              Keyboard.each(this, fn);
8973          },
8974  
8975          /*
8976              PRIVATE METHODS
8977          */
8978  
8979          _instances: [],
8980  
8981          _disable: function(instance){
8982              if (this._activeKB == instance) this._activeKB = null;
8983          },
8984  
8985          _setup: function(){
8986              this.addEvents(this.options.events);
8987              //if this is the root manager, nothing manages it
8988              if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this);
8989              if (this.options.active) this.activate();
8990              else this.relinquish();
8991          },
8992  
8993          _handle: function(event, type){
8994              //Keyboard.stop(event) prevents key propagation
8995              if (event.preventKeyboardPropagation) return;
8996  
8997              var bubbles = !!this._manager;
8998              if (bubbles && this._activeKB){
8999                  this._activeKB._handle(event, type);
9000                  if (event.preventKeyboardPropagation) return;
9001              }
9002              this.fireEvent(type, event);
9003  
9004              if (!bubbles && this._activeKB) this._activeKB._handle(event, type);
9005          }
9006  
9007      });
9008  
9009      var parsed = {};
9010      var modifiers = ['shift', 'control', 'alt', 'meta'];
9011      var regex = /^(?:shift|control|ctrl|alt|meta)$/;
9012  
9013      Keyboard.parse = function(type, eventType, ignore){
9014          if (ignore && ignore.contains(type.toLowerCase())) return type;
9015  
9016          type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
9017              eventType = $1;
9018              return '';
9019          });
9020  
9021          if (!parsed[type]){
9022              var key, mods = {};
9023              type.split('+').each(function(part){
9024                  if (regex.test(part)) mods[part] = true;
9025                  else key = part;
9026              });
9027  
9028              mods.control = mods.control || mods.ctrl; // allow both control and ctrl
9029  
9030              var keys = [];
9031              modifiers.each(function(mod){
9032                  if (mods[mod]) keys.push(mod);
9033              });
9034  
9035              if (key) keys.push(key);
9036              parsed[type] = keys.join('+');
9037          }
9038  
9039          return eventType + ':keys(' + parsed[type] + ')';
9040      };
9041  
9042      Keyboard.each = function(keyboard, fn){
9043          var current = keyboard || Keyboard.manager;
9044          while (current){
9045              fn.run(current);
9046              current = current._activeKB;
9047          }
9048      };
9049  
9050      Keyboard.stop = function(event){
9051          event.preventKeyboardPropagation = true;
9052      };
9053  
9054      Keyboard.manager = new Keyboard({
9055          active: true
9056      });
9057  
9058      Keyboard.trace = function(keyboard){
9059          keyboard = keyboard || Keyboard.manager;
9060          var hasConsole = window.console && console.log;
9061          if (hasConsole) console.log('the following items have focus: ');
9062          Keyboard.each(keyboard, function(current){
9063              if (hasConsole) console.log(document.id(current.widget) || current.wiget || current);
9064          });
9065      };
9066  
9067      var handler = function(event){
9068          var keys = [];
9069          modifiers.each(function(mod){
9070              if (event[mod]) keys.push(mod);
9071          });
9072  
9073          if (!regex.test(event.key)) keys.push(event.key);
9074          Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')');
9075      };
9076  
9077      document.addEvents({
9078          'keyup': handler,
9079          'keydown': handler
9080      });
9081  
9082  })();
9083  
9084  
9085  /*
9086  ---
9087  
9088  script: Keyboard.Extras.js
9089  
9090  name: Keyboard.Extras
9091  
9092  description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
9093  
9094  license: MIT-style license
9095  
9096  authors:
9097    - Perrin Westrich
9098  
9099  requires:
9100    - /Keyboard
9101    - /MooTools.More
9102  
9103  provides: [Keyboard.Extras]
9104  
9105  ...
9106  */
9107  Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
9108  
9109  Keyboard.implement({
9110  
9111      /*
9112          shortcut should be in the format of:
9113          {
9114              'keys': 'shift+s', // the default to add as an event.
9115              'description': 'blah blah blah', // a brief description of the functionality.
9116              'handler': function(){} // the event handler to run when keys are pressed.
9117          }
9118      */
9119      addShortcut: function(name, shortcut){
9120          this._shortcuts = this._shortcuts || [];
9121          this._shortcutIndex = this._shortcutIndex || {};
9122  
9123          shortcut.getKeyboard = Function.from(this);
9124          shortcut.name = name;
9125          this._shortcutIndex[name] = shortcut;
9126          this._shortcuts.push(shortcut);
9127          if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
9128          return this;
9129      },
9130  
9131      addShortcuts: function(obj){
9132          for (var name in obj) this.addShortcut(name, obj[name]);
9133          return this;
9134      },
9135  
9136      removeShortcut: function(name){
9137          var shortcut = this.getShortcut(name);
9138          if (shortcut && shortcut.keys){
9139              this.removeEvent(shortcut.keys, shortcut.handler);
9140              delete this._shortcutIndex[name];
9141              this._shortcuts.erase(shortcut);
9142          }
9143          return this;
9144      },
9145  
9146      removeShortcuts: function(names){
9147          names.each(this.removeShortcut, this);
9148          return this;
9149      },
9150  
9151      getShortcuts: function(){
9152          return this._shortcuts || [];
9153      },
9154  
9155      getShortcut: function(name){
9156          return (this._shortcutIndex || {})[name];
9157      }
9158  
9159  });
9160  
9161  Keyboard.rebind = function(newKeys, shortcuts){
9162      Array.from(shortcuts).each(function(shortcut){
9163          shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
9164          shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
9165          shortcut.keys = newKeys;
9166          shortcut.getKeyboard().fireEvent('rebound');
9167      });
9168  };
9169  
9170  
9171  Keyboard.getActiveShortcuts = function(keyboard){
9172      var activeKBS = [], activeSCS = [];
9173      Keyboard.each(keyboard, [].push.bind(activeKBS));
9174      activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
9175      return activeSCS;
9176  };
9177  
9178  Keyboard.getShortcut = function(name, keyboard, opts){
9179      opts = opts || {};
9180      var shortcuts = opts.many ? [] : null,
9181          set = opts.many ? function(kb){
9182                  var shortcut = kb.getShortcut(name);
9183                  if (shortcut) shortcuts.push(shortcut);
9184              } : function(kb){
9185                  if (!shortcuts) shortcuts = kb.getShortcut(name);
9186              };
9187      Keyboard.each(keyboard, set);
9188      return shortcuts;
9189  };
9190  
9191  Keyboard.getShortcuts = function(name, keyboard){
9192      return Keyboard.getShortcut(name, keyboard, { many: true });
9193  };
9194  
9195  
9196  /*
9197  ---
9198  
9199  script: HtmlTable.Select.js
9200  
9201  name: HtmlTable.Select
9202  
9203  description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
9204  
9205  license: MIT-style license
9206  
9207  authors:
9208    - Harald Kirschner
9209    - Aaron Newton
9210  
9211  requires:
9212    - /Keyboard
9213    - /Keyboard.Extras
9214    - /HtmlTable
9215    - /Class.refactor
9216    - /Element.Delegation
9217    - /Element.Shortcuts
9218  
9219  provides: [HtmlTable.Select]
9220  
9221  ...
9222  */
9223  
9224  HtmlTable = Class.refactor(HtmlTable, {
9225  
9226      options: {
9227          /*onRowFocus: function(){},
9228          onRowUnfocus: function(){},*/
9229          useKeyboard: true,
9230          classRowSelected: 'table-tr-selected',
9231          classRowHovered: 'table-tr-hovered',
9232          classSelectable: 'table-selectable',
9233          shiftForMultiSelect: true,
9234          allowMultiSelect: true,
9235          selectable: false,
9236          selectHiddenRows: false
9237      },
9238  
9239      initialize: function(){
9240          this.previous.apply(this, arguments);
9241          if (this.occluded) return this.occluded;
9242  
9243          this.selectedRows = new Elements();
9244  
9245          if (!this.bound) this.bound = {};
9246          this.bound.mouseleave = this.mouseleave.bind(this);
9247          this.bound.clickRow = this.clickRow.bind(this);
9248          this.bound.activateKeyboard = function(){
9249              if (this.keyboard && this.selectEnabled) this.keyboard.activate();
9250          }.bind(this);
9251  
9252          if (this.options.selectable) this.enableSelect();
9253      },
9254  
9255      empty: function(){
9256          this.selectNone();
9257          return this.previous();
9258      },
9259  
9260      enableSelect: function(){
9261          this.selectEnabled = true;
9262          this.attachSelects();
9263          this.element.addClass(this.options.classSelectable);
9264          return this;
9265      },
9266  
9267      disableSelect: function(){
9268          this.selectEnabled = false;
9269          this.attachSelects(false);
9270          this.element.removeClass(this.options.classSelectable);
9271          return this;
9272      },
9273  
9274      push: function(){
9275          var ret = this.previous.apply(this, arguments);
9276          this.updateSelects();
9277          return ret;
9278      },
9279  
9280      toggleRow: function(row){
9281          return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row);
9282      },
9283  
9284      selectRow: function(row, _nocheck){
9285          //private variable _nocheck: boolean whether or not to confirm the row is in the table body
9286          //added here for optimization when selecting ranges
9287          if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
9288          if (!this.options.allowMultiSelect) this.selectNone();
9289  
9290          if (!this.isSelected(row)){
9291              this.selectedRows.push(row);
9292              row.addClass(this.options.classRowSelected);
9293              this.fireEvent('rowFocus', [row, this.selectedRows]);
9294              this.fireEvent('stateChanged');
9295          }
9296  
9297          this.focused = row;
9298          document.clearSelection();
9299  
9300          return this;
9301      },
9302  
9303      isSelected: function(row){
9304          return this.selectedRows.contains(row);
9305      },
9306  
9307      getSelected: function(){
9308          return this.selectedRows;
9309      },
9310  
9311      getSelected: function(){
9312          return this.selectedRows;
9313      },
9314  
9315      serialize: function(){
9316          var previousSerialization = this.previous.apply(this, arguments) || {};
9317          if (this.options.selectable){
9318              previousSerialization.selectedRows = this.selectedRows.map(function(row){
9319                  return Array.indexOf(this.body.rows, row);
9320              }.bind(this));
9321          }
9322          return previousSerialization;
9323      },
9324  
9325      restore: function(tableState){
9326          if(this.options.selectable && tableState.selectedRows){
9327              tableState.selectedRows.each(function(index){
9328                  this.selectRow(this.body.rows[index]);
9329              }.bind(this));
9330          }
9331          this.previous.apply(this, arguments);
9332      },
9333  
9334      deselectRow: function(row, _nocheck){
9335          if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return;
9336  
9337          this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row));
9338          row.removeClass(this.options.classRowSelected);
9339          this.fireEvent('rowUnfocus', [row, this.selectedRows]);
9340          this.fireEvent('stateChanged');
9341          return this;
9342      },
9343  
9344      selectAll: function(selectNone){
9345          if (!selectNone && !this.options.allowMultiSelect) return;
9346          this.selectRange(0, this.body.rows.length, selectNone);
9347          return this;
9348      },
9349  
9350      selectNone: function(){
9351          return this.selectAll(true);
9352      },
9353  
9354      selectRange: function(startRow, endRow, _deselect){
9355          if (!this.options.allowMultiSelect && !_deselect) return;
9356          var method = _deselect ? 'deselectRow' : 'selectRow',
9357              rows = Array.clone(this.body.rows);
9358  
9359          if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow);
9360          if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow);
9361          endRow = endRow < rows.length - 1 ? endRow : rows.length - 1;
9362  
9363          if (endRow < startRow){
9364              var tmp = startRow;
9365              startRow = endRow;
9366              endRow = tmp;
9367          }
9368  
9369          for (var i = startRow; i <= endRow; i++){
9370              if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true);
9371          }
9372  
9373          return this;
9374      },
9375  
9376      deselectRange: function(startRow, endRow){
9377          this.selectRange(startRow, endRow, true);
9378      },
9379  
9380      getSelected: function(){
9381          return this.selectedRows;
9382      },
9383  
9384  /*
9385      Private methods:
9386  */
9387  
9388      enterRow: function(row){
9389          if (this.hovered) this.hovered = this.leaveRow(this.hovered);
9390          this.hovered = row.addClass(this.options.classRowHovered);
9391      },
9392  
9393      leaveRow: function(row){
9394          row.removeClass(this.options.classRowHovered);
9395      },
9396  
9397      updateSelects: function(){
9398          Array.each(this.body.rows, function(row){
9399              var binders = row.retrieve('binders');
9400              if (!binders && !this.selectEnabled) return;
9401              if (!binders){
9402                  binders = {
9403                      mouseenter: this.enterRow.pass([row], this),
9404                      mouseleave: this.leaveRow.pass([row], this)
9405                  };
9406                  row.store('binders', binders);
9407              }
9408              if (this.selectEnabled) row.addEvents(binders);
9409              else row.removeEvents(binders);
9410          }, this);
9411      },
9412  
9413      shiftFocus: function(offset, event){
9414          if (!this.focused) return this.selectRow(this.body.rows[0], event);
9415          var to = this.getRowByOffset(offset, this.options.selectHiddenRows);
9416          if (to === null || this.focused == this.body.rows[to]) return this;
9417          this.toggleRow(this.body.rows[to], event);
9418      },
9419  
9420      clickRow: function(event, row){
9421          var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect;
9422          if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone();
9423  
9424          if (event.rightClick) this.selectRow(row);
9425          else this.toggleRow(row);
9426  
9427          if (event.shift){
9428              this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true);
9429              this.focused = row;
9430          }
9431          this.rangeStart = row;
9432      },
9433  
9434      getRowByOffset: function(offset, includeHiddenRows){
9435          if (!this.focused) return 0;
9436          var index = Array.indexOf(this.body.rows, this.focused);
9437          if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null;
9438          if (includeHiddenRows){
9439              index += offset;
9440          } else {
9441              var limit = 0,
9442                  count = 0;
9443              if (offset > 0){
9444                  while (count < offset && index < this.body.rows.length -1){
9445                      if (this.body.rows[++index].isDisplayed()) count++;
9446                  }
9447              } else {
9448                  while (count > offset && index > 0){
9449                      if (this.body.rows[--index].isDisplayed()) count--;
9450                  }
9451              }
9452          }
9453          return index;
9454      },
9455  
9456      attachSelects: function(attach){
9457          attach = attach != null ? attach : true;
9458  
9459          var method = attach ? 'addEvents' : 'removeEvents';
9460          this.element[method]({
9461              mouseleave: this.bound.mouseleave,
9462              click: this.bound.activateKeyboard
9463          });
9464  
9465          this.body[method]({
9466              'click:relay(tr)': this.bound.clickRow,
9467              'contextmenu:relay(tr)': this.bound.clickRow
9468          });
9469  
9470          if (this.options.useKeyboard || this.keyboard){
9471              if (!this.keyboard) this.keyboard = new Keyboard();
9472              if (!this.selectKeysDefined){
9473                  this.selectKeysDefined = true;
9474                  var timer, held;
9475  
9476                  var move = function(offset){
9477                      var mover = function(e){
9478                          clearTimeout(timer);
9479                          e.preventDefault();
9480                          var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)];
9481                          if (e.shift && to && this.isSelected(to)){
9482                              this.deselectRow(this.focused);
9483                              this.focused = to;
9484                          } else {
9485                              if (to && (!this.options.allowMultiSelect || !e.shift)){
9486                                  this.selectNone();
9487                              }
9488                              this.shiftFocus(offset, e);
9489                          }
9490  
9491                          if (held){
9492                              timer = mover.delay(100, this, e);
9493                          } else {
9494                              timer = (function(){
9495                                  held = true;
9496                                  mover(e);
9497                              }).delay(400);
9498                          }
9499                      }.bind(this);
9500                      return mover;
9501                  }.bind(this);
9502  
9503                  var clear = function(){
9504                      clearTimeout(timer);
9505                      held = false;
9506                  };
9507  
9508                  this.keyboard.addEvents({
9509                      'keydown:shift+up': move(-1),
9510                      'keydown:shift+down': move(1),
9511                      'keyup:shift+up': clear,
9512                      'keyup:shift+down': clear,
9513                      'keyup:up': clear,
9514                      'keyup:down': clear
9515                  });
9516  
9517                  var shiftHint = '';
9518                  if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){
9519                      shiftHint = " (Shift multi-selects).";
9520                  }
9521  
9522                  this.keyboard.addShortcuts({
9523                      'Select Previous Row': {
9524                          keys: 'up',
9525                          shortcut: 'up arrow',
9526                          handler: move(-1),
9527                          description: 'Select the previous row in the table.' + shiftHint
9528                      },
9529                      'Select Next Row': {
9530                          keys: 'down',
9531                          shortcut: 'down arrow',
9532                          handler: move(1),
9533                          description: 'Select the next row in the table.' + shiftHint
9534                      }
9535                  });
9536  
9537              }
9538              this.keyboard[attach ? 'activate' : 'deactivate']();
9539          }
9540          this.updateSelects();
9541      },
9542  
9543      mouseleave: function(){
9544          if (this.hovered) this.leaveRow(this.hovered);
9545      }
9546  
9547  });
9548  
9549  
9550  /*
9551  ---
9552  
9553  script: Scroller.js
9554  
9555  name: Scroller
9556  
9557  description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
9558  
9559  license: MIT-style license
9560  
9561  authors:
9562    - Valerio Proietti
9563  
9564  requires:
9565    - Core/Events
9566    - Core/Options
9567    - Core/Element.Event
9568    - Core/Element.Dimensions
9569    - MooTools.More
9570  
9571  provides: [Scroller]
9572  
9573  ...
9574  */
9575  
9576  var Scroller = new Class({
9577  
9578      Implements: [Events, Options],
9579  
9580      options: {
9581          area: 20,
9582          velocity: 1,
9583          onChange: function(x, y){
9584              this.element.scrollTo(x, y);
9585          },
9586          fps: 50
9587      },
9588  
9589      initialize: function(element, options){
9590          this.setOptions(options);
9591          this.element = document.id(element);
9592          this.docBody = document.id(this.element.getDocument().body);
9593          this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element;
9594          this.timer = null;
9595          this.bound = {
9596              attach: this.attach.bind(this),
9597              detach: this.detach.bind(this),
9598              getCoords: this.getCoords.bind(this)
9599          };
9600      },
9601  
9602      start: function(){
9603          this.listener.addEvents({
9604              mouseover: this.bound.attach,
9605              mouseleave: this.bound.detach
9606          });
9607          return this;
9608      },
9609  
9610      stop: function(){
9611          this.listener.removeEvents({
9612              mouseover: this.bound.attach,
9613              mouseleave: this.bound.detach
9614          });
9615          this.detach();
9616          this.timer = clearInterval(this.timer);
9617          return this;
9618      },
9619  
9620      attach: function(){
9621          this.listener.addEvent('mousemove', this.bound.getCoords);
9622      },
9623  
9624      detach: function(){
9625          this.listener.removeEvent('mousemove', this.bound.getCoords);
9626          this.timer = clearInterval(this.timer);
9627      },
9628  
9629      getCoords: function(event){
9630          this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
9631          if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
9632      },
9633  
9634      scroll: function(){
9635          var size = this.element.getSize(),
9636              scroll = this.element.getScroll(),
9637              pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0},
9638              scrollSize = this.element.getScrollSize(),
9639              change = {x: 0, y: 0},
9640              top = this.options.area.top || this.options.area,
9641              bottom = this.options.area.bottom || this.options.area;
9642          for (var z in this.page){
9643              if (this.page[z] < (top + pos[z]) && scroll[z] != 0){
9644                  change[z] = (this.page[z] - top - pos[z]) * this.options.velocity;
9645              } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){
9646                  change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity;
9647              }
9648              change[z] = change[z].round();