├── Demos ├── custom-event.js ├── demo.css ├── index.html ├── mootools-core.js ├── pinch.html ├── style.css ├── swipe.html └── touchhold.html ├── LICENSE ├── README.md ├── Source ├── Browser │ ├── Features.Touch.js │ └── Mobile.js ├── Desktop │ └── Mouse.js └── Touch │ ├── Click.js │ ├── Pinch.js │ ├── Swipe.js │ ├── Touch.js │ └── Touchhold.js └── package.yml /Demos/custom-event.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Element.defineCustomEvent 5 | 6 | description: Allows to create custom events based on other custom events. 7 | 8 | authors: Christoph Pojer (@cpojer) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Core/Element.Event] 13 | 14 | provides: Element.defineCustomEvent 15 | 16 | ... 17 | */ 18 | 19 | (function(){ 20 | 21 | [Element, Window, Document].invoke('implement', {hasEvent: function(event){ 22 | var events = this.retrieve('events'), 23 | list = (events && events[event]) ? events[event].values : null; 24 | if (list){ 25 | for (var i = list.length; i--;) if (i in list){ 26 | return true; 27 | } 28 | } 29 | return false; 30 | }}); 31 | 32 | var wrap = function(custom, method, extended, name){ 33 | method = custom[method]; 34 | extended = custom[extended]; 35 | 36 | return function(fn, customName){ 37 | if (!customName) customName = name; 38 | 39 | if (extended && !this.hasEvent(customName)) extended.call(this, fn, customName); 40 | if (method) method.call(this, fn, customName); 41 | }; 42 | }; 43 | 44 | var inherit = function(custom, base, method, name){ 45 | return function(fn, customName){ 46 | base[method].call(this, fn, customName || name); 47 | custom[method].call(this, fn, customName || name); 48 | }; 49 | }; 50 | 51 | var events = Element.Events; 52 | 53 | Element.defineCustomEvent = function(name, custom){ 54 | 55 | var base = events[custom.base]; 56 | 57 | custom.onAdd = wrap(custom, 'onAdd', 'onSetup', name); 58 | custom.onRemove = wrap(custom, 'onRemove', 'onTeardown', name); 59 | 60 | events[name] = base ? Object.append({}, custom, { 61 | 62 | base: base.base, 63 | 64 | condition: function(event){ 65 | return (!base.condition || base.condition.call(this, event)) && 66 | (!custom.condition || custom.condition.call(this, event)); 67 | }, 68 | 69 | onAdd: inherit(custom, base, 'onAdd', name), 70 | onRemove: inherit(custom, base, 'onRemove', name) 71 | 72 | }) : custom; 73 | 74 | return this; 75 | 76 | }; 77 | 78 | var loop = function(name){ 79 | var method = 'on' + name.capitalize(); 80 | Element[name + 'CustomEvents'] = function(){ 81 | Object.each(events, function(event, name){ 82 | if (event[method]) event[method].call(event, name); 83 | }); 84 | }; 85 | return loop; 86 | }; 87 | 88 | loop('enable')('disable'); 89 | 90 | })(); 91 | 92 | -------------------------------------------------------------------------------- /Demos/demo.css: -------------------------------------------------------------------------------- 1 | body > div { 2 | float: left; 3 | } 4 | 5 | div#more { 6 | width: 150px; 7 | 8 | font-weight: bold; 9 | } 10 | 11 | div#more span { 12 | font-size: 1.2em; 13 | } 14 | -------------------------------------------------------------------------------- /Demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MooTools Mobile 5 | 6 | 7 | 8 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 83 | 84 | 85 |
86 | Demos 87 | Home 88 | Swipe 89 | Pinch 90 | Touchhold 91 |
92 |
93 | 94 |
95 |
96 | Click here to see which event handler was used 97 |
98 | 99 | 100 | -------------------------------------------------------------------------------- /Demos/mootools-core.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Core 5 | 6 | description: The heart of MooTools. 7 | 8 | license: MIT-style license. 9 | 10 | copyright: Copyright (c) 2006-2010 [Valerio Proietti](http://mad4milk.net/). 11 | 12 | authors: The MooTools production team (http://mootools.net/developers/) 13 | 14 | inspiration: 15 | - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) 16 | - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) 17 | 18 | provides: [Core, MooTools, Type, typeOf, instanceOf, Native] 19 | 20 | ... 21 | */ 22 | 23 | (function(){ 24 | 25 | this.MooTools = { 26 | version: '1.3dev', 27 | build: '0a7aeabbbac5bc23b021b4c1aa9ba722c40e303d' 28 | }; 29 | 30 | // typeOf, instanceOf 31 | 32 | var typeOf = this.typeOf = function(item){ 33 | if (item == null) return 'null'; 34 | if (item.$family) return item.$family(); 35 | 36 | if (item.nodeName){ 37 | if (item.nodeType == 1) return 'element'; 38 | if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace'; 39 | } else if (typeof item.length == 'number'){ 40 | if (item.callee) return 'arguments'; 41 | if ('item' in item) return 'collection'; 42 | } 43 | 44 | return typeof item; 45 | }; 46 | 47 | var instanceOf = this.instanceOf = function(item, object){ 48 | if (item == null) return false; 49 | var constructor = item.$constructor || item.constructor; 50 | while (constructor){ 51 | if (constructor === object) return true; 52 | constructor = constructor.parent; 53 | } 54 | return item instanceof object; 55 | }; 56 | 57 | // Function overloading 58 | 59 | var Function = this.Function; 60 | 61 | var enumerables = true; 62 | for (var i in {toString: 1}) enumerables = null; 63 | if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor']; 64 | 65 | Function.prototype.overloadSetter = function(usePlural){ 66 | var self = this; 67 | return function(a, b){ 68 | if (a == null) return this; 69 | if (usePlural || typeof a != 'string'){ 70 | for (var k in a) self.call(this, k, a[k]); 71 | if (enumerables) for (var i = enumerables.length; i--;){ 72 | k = enumerables[i]; 73 | if (a.hasOwnProperty(k)) self.call(this, k, a[k]); 74 | } 75 | } else { 76 | self.call(this, a, b); 77 | } 78 | return this; 79 | }; 80 | }; 81 | 82 | Function.prototype.overloadGetter = function(usePlural){ 83 | var self = this; 84 | return function(a){ 85 | var args, result; 86 | if (usePlural || typeof a != 'string') args = a; 87 | else if (arguments.length > 1) args = arguments; 88 | if (args){ 89 | result = {}; 90 | for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]); 91 | } else { 92 | result = self.call(this, a); 93 | } 94 | return result; 95 | }; 96 | }; 97 | 98 | Function.prototype.extend = function(key, value){ 99 | this[key] = value; 100 | }.overloadSetter(); 101 | 102 | Function.prototype.implement = function(key, value){ 103 | this.prototype[key] = value; 104 | }.overloadSetter(); 105 | 106 | // From 107 | 108 | var slice = Array.prototype.slice; 109 | 110 | Function.from = function(item){ 111 | return (typeOf(item) == 'function') ? item : function(){ 112 | return item; 113 | }; 114 | }; 115 | 116 | Array.from = function(item){ 117 | if (item == null) return []; 118 | return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item]; 119 | }; 120 | 121 | Number.from = function(item){ 122 | var number = parseFloat(item); 123 | return isFinite(number) ? number : null; 124 | }; 125 | 126 | String.from = function(item){ 127 | return item + ''; 128 | }; 129 | 130 | // hide, protect 131 | 132 | Function.implement({ 133 | 134 | hide: function(){ 135 | this.$hidden = true; 136 | return this; 137 | }, 138 | 139 | protect: function(){ 140 | this.$protected = true; 141 | return this; 142 | } 143 | 144 | }); 145 | 146 | // Type 147 | 148 | var Type = this.Type = function(name, object){ 149 | if (name){ 150 | var lower = name.toLowerCase(); 151 | var typeCheck = function(item){ 152 | return (typeOf(item) == lower); 153 | }; 154 | 155 | Type['is' + name] = typeCheck; 156 | if (object != null){ 157 | object.prototype.$family = (function(){ 158 | return lower; 159 | }).hide(); 160 | 161 | } 162 | } 163 | 164 | if (object == null) return null; 165 | 166 | object.extend(this); 167 | object.$constructor = Type; 168 | object.prototype.$constructor = object; 169 | 170 | return object; 171 | }; 172 | 173 | var toString = Object.prototype.toString; 174 | 175 | Type.isEnumerable = function(item){ 176 | return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' ); 177 | }; 178 | 179 | var hooks = {}; 180 | 181 | var hooksOf = function(object){ 182 | var type = typeOf(object.prototype); 183 | return hooks[type] || (hooks[type] = []); 184 | }; 185 | 186 | var implement = function(name, method){ 187 | if (method && method.$hidden) return this; 188 | 189 | var hooks = hooksOf(this); 190 | 191 | for (var i = 0; i < hooks.length; i++){ 192 | var hook = hooks[i]; 193 | if (typeOf(hook) == 'type') implement.call(hook, name, method); 194 | else hook.call(this, name, method); 195 | } 196 | 197 | var previous = this.prototype[name]; 198 | if (previous == null || !previous.$protected) this.prototype[name] = method; 199 | 200 | if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){ 201 | return method.apply(item, slice.call(arguments, 1)); 202 | }); 203 | 204 | return this; 205 | }; 206 | 207 | var extend = function(name, method){ 208 | if (method && method.$hidden) return this; 209 | var previous = this[name]; 210 | if (previous == null || !previous.$protected) this[name] = method; 211 | return this; 212 | }; 213 | 214 | Type.implement({ 215 | 216 | implement: implement.overloadSetter(), 217 | 218 | extend: extend.overloadSetter(), 219 | 220 | alias: function(name, existing){ 221 | implement.call(this, name, this.prototype[existing]); 222 | }.overloadSetter(), 223 | 224 | mirror: function(hook){ 225 | hooksOf(this).push(hook); 226 | return this; 227 | } 228 | 229 | }); 230 | 231 | new Type('Type', Type); 232 | 233 | // Default Types 234 | 235 | var force = function(name, object, methods){ 236 | var isType = (object != Object), 237 | prototype = object.prototype; 238 | 239 | if (isType) object = new Type(name, object); 240 | 241 | for (var i = 0, l = methods.length; i < l; i++){ 242 | var key = methods[i], 243 | generic = object[key], 244 | proto = prototype[key]; 245 | 246 | if (generic) generic.protect(); 247 | 248 | if (isType && proto){ 249 | delete prototype[key]; 250 | prototype[key] = proto.protect(); 251 | } 252 | } 253 | 254 | if (isType) object.implement(prototype); 255 | 256 | return force; 257 | }; 258 | 259 | force('String', String, [ 260 | 'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search', 261 | 'slice', 'split', 'substr', 'substring', 'toLowerCase', 'toUpperCase' 262 | ])('Array', Array, [ 263 | 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', 264 | 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight' 265 | ])('Number', Number, [ 266 | 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision' 267 | ])('Function', Function, [ 268 | 'apply', 'call', 'bind' 269 | ])('RegExp', RegExp, [ 270 | 'exec', 'test' 271 | ])('Object', Object, [ 272 | 'create', 'defineProperty', 'defineProperties', 'keys', 273 | 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames', 274 | 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen' 275 | ])('Date', Date, ['now']); 276 | 277 | Object.extend = extend.overloadSetter(); 278 | 279 | Date.extend('now', function(){ 280 | return +(new Date); 281 | }); 282 | 283 | new Type('Boolean', Boolean); 284 | 285 | // fixes NaN returning as Number 286 | 287 | Number.prototype.$family = function(){ 288 | return isFinite(this) ? 'number' : 'null'; 289 | }.hide(); 290 | 291 | // Number.random 292 | 293 | Number.extend('random', function(min, max){ 294 | return Math.floor(Math.random() * (max - min + 1) + min); 295 | }); 296 | 297 | // forEach, each 298 | 299 | Object.extend('forEach', function(object, fn, bind){ 300 | for (var key in object){ 301 | if (object.hasOwnProperty(key)) fn.call(bind, object[key], key, object); 302 | } 303 | }); 304 | 305 | Object.each = Object.forEach; 306 | 307 | Array.implement({ 308 | 309 | forEach: function(fn, bind){ 310 | for (var i = 0, l = this.length; i < l; i++){ 311 | if (i in this) fn.call(bind, this[i], i, this); 312 | } 313 | }, 314 | 315 | each: function(fn, bind){ 316 | Array.forEach(this, fn, bind); 317 | return this; 318 | } 319 | 320 | }); 321 | 322 | // Array & Object cloning, Object merging and appending 323 | 324 | var cloneOf = function(item){ 325 | switch (typeOf(item)){ 326 | case 'array': return item.clone(); 327 | case 'object': return Object.clone(item); 328 | default: return item; 329 | } 330 | }; 331 | 332 | Array.implement('clone', function(){ 333 | var i = this.length, clone = new Array(i); 334 | while (i--) clone[i] = cloneOf(this[i]); 335 | return clone; 336 | }); 337 | 338 | var mergeOne = function(source, key, current){ 339 | switch (typeOf(current)){ 340 | case 'object': 341 | if (typeOf(source[key]) == 'object') Object.merge(source[key], current); 342 | else source[key] = Object.clone(current); 343 | break; 344 | case 'array': source[key] = current.clone(); break; 345 | default: source[key] = current; 346 | } 347 | return source; 348 | }; 349 | 350 | Object.extend({ 351 | 352 | merge: function(source, k, v){ 353 | if (typeOf(k) == 'string') return mergeOne(source, k, v); 354 | for (var i = 1, l = arguments.length; i < l; i++){ 355 | var object = arguments[i]; 356 | for (var key in object) mergeOne(source, key, object[key]); 357 | } 358 | return source; 359 | }, 360 | 361 | clone: function(object){ 362 | var clone = {}; 363 | for (var key in object) clone[key] = cloneOf(object[key]); 364 | return clone; 365 | }, 366 | 367 | append: function(original){ 368 | for (var i = 1, l = arguments.length; i < l; i++){ 369 | var extended = arguments[i] || {}; 370 | for (var key in extended) original[key] = extended[key]; 371 | } 372 | return original; 373 | } 374 | 375 | }); 376 | 377 | // Object-less types 378 | 379 | ['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){ 380 | new Type(name); 381 | }); 382 | 383 | // Unique ID 384 | 385 | var UID = Date.now(); 386 | 387 | String.extend('generateUID', function(){ 388 | return (UID++).toString(36); 389 | }); 390 | 391 | 392 | 393 | })(); 394 | 395 | 396 | /* 397 | --- 398 | 399 | name: Array 400 | 401 | description: Contains Array Prototypes like each, contains, and erase. 402 | 403 | license: MIT-style license. 404 | 405 | requires: Type 406 | 407 | provides: Array 408 | 409 | ... 410 | */ 411 | 412 | Array.implement({ 413 | 414 | invoke: function(methodName){ 415 | var args = Array.slice(arguments, 1); 416 | return this.map(function(item){ 417 | return item[methodName].apply(item, args); 418 | }); 419 | }, 420 | 421 | every: function(fn, bind){ 422 | for (var i = 0, l = this.length; i < l; i++){ 423 | if ((i in this) && !fn.call(bind, this[i], i, this)) return false; 424 | } 425 | return true; 426 | }, 427 | 428 | filter: function(fn, bind){ 429 | var results = []; 430 | for (var i = 0, l = this.length; i < l; i++){ 431 | if ((i in this) && fn.call(bind, this[i], i, this)) results.push(this[i]); 432 | } 433 | return results; 434 | }, 435 | 436 | clean: function(){ 437 | return this.filter(function(item){ 438 | return item != null; 439 | }); 440 | }, 441 | 442 | indexOf: function(item, from){ 443 | var len = this.length; 444 | for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){ 445 | if (this[i] === item) return i; 446 | } 447 | return -1; 448 | }, 449 | 450 | map: function(fn, bind){ 451 | var results = []; 452 | for (var i = 0, l = this.length; i < l; i++){ 453 | if (i in this) results[i] = fn.call(bind, this[i], i, this); 454 | } 455 | return results; 456 | }, 457 | 458 | some: function(fn, bind){ 459 | for (var i = 0, l = this.length; i < l; i++){ 460 | if ((i in this) && fn.call(bind, this[i], i, this)) return true; 461 | } 462 | return false; 463 | }, 464 | 465 | associate: function(keys){ 466 | var obj = {}, length = Math.min(this.length, keys.length); 467 | for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; 468 | return obj; 469 | }, 470 | 471 | link: function(object){ 472 | var result = {}; 473 | for (var i = 0, l = this.length; i < l; i++){ 474 | for (var key in object){ 475 | if (object[key](this[i])){ 476 | result[key] = this[i]; 477 | delete object[key]; 478 | break; 479 | } 480 | } 481 | } 482 | return result; 483 | }, 484 | 485 | contains: function(item, from){ 486 | return this.indexOf(item, from) != -1; 487 | }, 488 | 489 | append: function(array){ 490 | this.push.apply(this, array); 491 | return this; 492 | }, 493 | 494 | getLast: function(){ 495 | return (this.length) ? this[this.length - 1] : null; 496 | }, 497 | 498 | getRandom: function(){ 499 | return (this.length) ? this[Number.random(0, this.length - 1)] : null; 500 | }, 501 | 502 | include: function(item){ 503 | if (!this.contains(item)) this.push(item); 504 | return this; 505 | }, 506 | 507 | combine: function(array){ 508 | for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); 509 | return this; 510 | }, 511 | 512 | erase: function(item){ 513 | for (var i = this.length; i--;){ 514 | if (this[i] === item) this.splice(i, 1); 515 | } 516 | return this; 517 | }, 518 | 519 | empty: function(){ 520 | this.length = 0; 521 | return this; 522 | }, 523 | 524 | flatten: function(){ 525 | var array = []; 526 | for (var i = 0, l = this.length; i < l; i++){ 527 | var type = typeOf(this[i]); 528 | if (type == 'null') continue; 529 | array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]); 530 | } 531 | return array; 532 | }, 533 | 534 | pick: function(){ 535 | for (var i = 0, l = this.length; i < l; i++){ 536 | if (this[i] != null) return this[i]; 537 | } 538 | return null; 539 | }, 540 | 541 | hexToRgb: function(array){ 542 | if (this.length != 3) return null; 543 | var rgb = this.map(function(value){ 544 | if (value.length == 1) value += value; 545 | return value.toInt(16); 546 | }); 547 | return (array) ? rgb : 'rgb(' + rgb + ')'; 548 | }, 549 | 550 | rgbToHex: function(array){ 551 | if (this.length < 3) return null; 552 | if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; 553 | var hex = []; 554 | for (var i = 0; i < 3; i++){ 555 | var bit = (this[i] - 0).toString(16); 556 | hex.push((bit.length == 1) ? '0' + bit : bit); 557 | } 558 | return (array) ? hex : '#' + hex.join(''); 559 | } 560 | 561 | }); 562 | 563 | 564 | 565 | 566 | /* 567 | --- 568 | 569 | name: String 570 | 571 | description: Contains String Prototypes like camelCase, capitalize, test, and toInt. 572 | 573 | license: MIT-style license. 574 | 575 | requires: Type 576 | 577 | provides: String 578 | 579 | ... 580 | */ 581 | 582 | String.implement({ 583 | 584 | test: function(regex, params){ 585 | return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this); 586 | }, 587 | 588 | contains: function(string, separator){ 589 | return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1; 590 | }, 591 | 592 | trim: function(){ 593 | return this.replace(/^\s+|\s+$/g, ''); 594 | }, 595 | 596 | clean: function(){ 597 | return this.replace(/\s+/g, ' ').trim(); 598 | }, 599 | 600 | camelCase: function(){ 601 | return this.replace(/-\D/g, function(match){ 602 | return match.charAt(1).toUpperCase(); 603 | }); 604 | }, 605 | 606 | hyphenate: function(){ 607 | return this.replace(/[A-Z]/g, function(match){ 608 | return ('-' + match.charAt(0).toLowerCase()); 609 | }); 610 | }, 611 | 612 | capitalize: function(){ 613 | return this.replace(/\b[a-z]/g, function(match){ 614 | return match.toUpperCase(); 615 | }); 616 | }, 617 | 618 | escapeRegExp: function(){ 619 | return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); 620 | }, 621 | 622 | toInt: function(base){ 623 | return parseInt(this, base || 10); 624 | }, 625 | 626 | toFloat: function(){ 627 | return parseFloat(this); 628 | }, 629 | 630 | hexToRgb: function(array){ 631 | var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); 632 | return (hex) ? hex.slice(1).hexToRgb(array) : null; 633 | }, 634 | 635 | rgbToHex: function(array){ 636 | var rgb = this.match(/\d{1,3}/g); 637 | return (rgb) ? rgb.rgbToHex(array) : null; 638 | }, 639 | 640 | substitute: function(object, regexp){ 641 | return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ 642 | if (match.charAt(0) == '\\') return match.slice(1); 643 | return (object[name] != null) ? object[name] : ''; 644 | }); 645 | } 646 | 647 | }); 648 | 649 | 650 | /* 651 | --- 652 | 653 | name: Function 654 | 655 | description: Contains Function Prototypes like create, bind, pass, and delay. 656 | 657 | license: MIT-style license. 658 | 659 | requires: Type 660 | 661 | provides: Function 662 | 663 | ... 664 | */ 665 | 666 | Function.extend({ 667 | 668 | attempt: function(){ 669 | for (var i = 0, l = arguments.length; i < l; i++){ 670 | try { 671 | return arguments[i](); 672 | } catch (e){} 673 | } 674 | return null; 675 | } 676 | 677 | }); 678 | 679 | Function.implement({ 680 | 681 | attempt: function(args, bind){ 682 | try { 683 | return this.apply(bind, Array.from(args)); 684 | } catch (e){} 685 | 686 | return null; 687 | }, 688 | 689 | bind: function(bind){ 690 | var self = this, 691 | args = (arguments.length > 1) ? Array.slice(arguments, 1) : null; 692 | 693 | return function(){ 694 | if (!args && !arguments.length) return self.call(bind); 695 | if (args && arguments.length) return self.apply(bind, args.concat(Array.from(arguments))); 696 | return self.apply(bind, args || arguments); 697 | }; 698 | }, 699 | 700 | pass: function(args, bind){ 701 | var self = this; 702 | if (args != null) args = Array.from(args); 703 | return function(){ 704 | return self.apply(bind, args || arguments); 705 | }; 706 | }, 707 | 708 | delay: function(delay, bind, args){ 709 | return setTimeout(this.pass(args, bind), delay); 710 | }, 711 | 712 | periodical: function(periodical, bind, args){ 713 | return setInterval(this.pass(args, bind), periodical); 714 | } 715 | 716 | }); 717 | 718 | 719 | 720 | 721 | /* 722 | --- 723 | 724 | name: Number 725 | 726 | description: Contains Number Prototypes like limit, round, times, and ceil. 727 | 728 | license: MIT-style license. 729 | 730 | requires: Type 731 | 732 | provides: Number 733 | 734 | ... 735 | */ 736 | 737 | Number.implement({ 738 | 739 | limit: function(min, max){ 740 | return Math.min(max, Math.max(min, this)); 741 | }, 742 | 743 | round: function(precision){ 744 | precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0); 745 | return Math.round(this * precision) / precision; 746 | }, 747 | 748 | times: function(fn, bind){ 749 | for (var i = 0; i < this; i++) fn.call(bind, i, this); 750 | }, 751 | 752 | toFloat: function(){ 753 | return parseFloat(this); 754 | }, 755 | 756 | toInt: function(base){ 757 | return parseInt(this, base || 10); 758 | } 759 | 760 | }); 761 | 762 | Number.alias('each', 'times'); 763 | 764 | (function(math){ 765 | var methods = {}; 766 | math.each(function(name){ 767 | if (!Number[name]) methods[name] = function(){ 768 | return Math[name].apply(null, [this].concat(Array.from(arguments))); 769 | }; 770 | }); 771 | Number.implement(methods); 772 | })(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); 773 | 774 | 775 | /* 776 | --- 777 | 778 | name: Class 779 | 780 | description: Contains the Class Function for easily creating, extending, and implementing reusable Classes. 781 | 782 | license: MIT-style license. 783 | 784 | requires: [Array, String, Function, Number] 785 | 786 | provides: Class 787 | 788 | ... 789 | */ 790 | 791 | (function(){ 792 | 793 | var Class = this.Class = new Type('Class', function(params){ 794 | if (instanceOf(params, Function)) params = {initialize: params}; 795 | 796 | var newClass = function(){ 797 | reset(this); 798 | if (newClass.$prototyping) return this; 799 | this.$caller = null; 800 | var value = (this.initialize) ? this.initialize.apply(this, arguments) : this; 801 | this.$caller = this.caller = null; 802 | return value; 803 | }.extend(this).implement(params); 804 | 805 | newClass.$constructor = Class; 806 | newClass.prototype.$constructor = newClass; 807 | newClass.prototype.parent = parent; 808 | 809 | return newClass; 810 | }); 811 | 812 | var parent = function(){ 813 | if (!this.$caller) throw new Error('The method "parent" cannot be called.'); 814 | var name = this.$caller.$name, 815 | parent = this.$caller.$owner.parent, 816 | previous = (parent) ? parent.prototype[name] : null; 817 | if (!previous) throw new Error('The method "' + name + '" has no parent.'); 818 | return previous.apply(this, arguments); 819 | }; 820 | 821 | var reset = function(object){ 822 | for (var key in object){ 823 | var value = object[key]; 824 | switch (typeOf(value)){ 825 | case 'object': 826 | var F = function(){}; 827 | F.prototype = value; 828 | object[key] = reset(new F); 829 | break; 830 | case 'array': object[key] = value.clone(); break; 831 | } 832 | } 833 | return object; 834 | }; 835 | 836 | var wrap = function(self, key, method){ 837 | if (method.$origin) method = method.$origin; 838 | var wrapper = function(){ 839 | if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.'); 840 | var caller = this.caller, current = this.$caller; 841 | this.caller = current; this.$caller = wrapper; 842 | var result = method.apply(this, arguments); 843 | this.$caller = current; this.caller = caller; 844 | return result; 845 | }.extend({$owner: self, $origin: method, $name: key}); 846 | return wrapper; 847 | }; 848 | 849 | var implement = function(key, value, retain){ 850 | if (Class.Mutators.hasOwnProperty(key)){ 851 | value = Class.Mutators[key].call(this, value); 852 | if (value == null) return this; 853 | } 854 | 855 | if (typeOf(value) == 'function'){ 856 | if (value.$hidden) return this; 857 | this.prototype[key] = (retain) ? value : wrap(this, key, value); 858 | } else { 859 | Object.merge(this.prototype, key, value); 860 | } 861 | 862 | return this; 863 | }; 864 | 865 | var getInstance = function(klass){ 866 | klass.$prototyping = true; 867 | var proto = new klass; 868 | delete klass.$prototyping; 869 | return proto; 870 | }; 871 | 872 | Class.implement('implement', implement.overloadSetter()); 873 | 874 | Class.Mutators = { 875 | 876 | Extends: function(parent){ 877 | this.parent = parent; 878 | this.prototype = getInstance(parent); 879 | }, 880 | 881 | Implements: function(items){ 882 | Array.from(items).each(function(item){ 883 | var instance = new item; 884 | for (var key in instance) implement.call(this, key, instance[key], true); 885 | }, this); 886 | } 887 | }; 888 | 889 | })(); 890 | 891 | 892 | /* 893 | --- 894 | 895 | name: Class.Extras 896 | 897 | description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. 898 | 899 | license: MIT-style license. 900 | 901 | requires: Class 902 | 903 | provides: [Class.Extras, Chain, Events, Options] 904 | 905 | ... 906 | */ 907 | 908 | (function(){ 909 | 910 | this.Chain = new Class({ 911 | 912 | $chain: [], 913 | 914 | chain: function(){ 915 | this.$chain.append(Array.flatten(arguments)); 916 | return this; 917 | }, 918 | 919 | callChain: function(){ 920 | return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; 921 | }, 922 | 923 | clearChain: function(){ 924 | this.$chain.empty(); 925 | return this; 926 | } 927 | 928 | }); 929 | 930 | var removeOn = function(string){ 931 | return string.replace(/^on([A-Z])/, function(full, first){ 932 | return first.toLowerCase(); 933 | }); 934 | }; 935 | 936 | this.Events = new Class({ 937 | 938 | $events: {}, 939 | 940 | addEvent: function(type, fn, internal){ 941 | type = removeOn(type); 942 | 943 | 944 | 945 | this.$events[type] = (this.$events[type] || []).include(fn); 946 | if (internal) fn.internal = true; 947 | return this; 948 | }, 949 | 950 | addEvents: function(events){ 951 | for (var type in events) this.addEvent(type, events[type]); 952 | return this; 953 | }, 954 | 955 | fireEvent: function(type, args, delay){ 956 | type = removeOn(type); 957 | var events = this.$events[type]; 958 | if (!events) return this; 959 | args = Array.from(args); 960 | events.each(function(fn){ 961 | if (delay) fn.delay(delay, this, args); 962 | else fn.apply(this, args); 963 | }, this); 964 | return this; 965 | }, 966 | 967 | removeEvent: function(type, fn){ 968 | type = removeOn(type); 969 | var events = this.$events[type]; 970 | if (events && !fn.internal){ 971 | var index = events.indexOf(fn); 972 | if (index != -1) delete events[index]; 973 | } 974 | return this; 975 | }, 976 | 977 | removeEvents: function(events){ 978 | var type; 979 | if (typeOf(events) == 'object'){ 980 | for (type in events) this.removeEvent(type, events[type]); 981 | return this; 982 | } 983 | if (events) events = removeOn(events); 984 | for (type in this.$events){ 985 | if (events && events != type) continue; 986 | var fns = this.$events[type]; 987 | for (var i = fns.length; i--;) this.removeEvent(type, fns[i]); 988 | } 989 | return this; 990 | } 991 | 992 | }); 993 | 994 | this.Options = new Class({ 995 | 996 | setOptions: function(){ 997 | var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments)); 998 | if (!this.addEvent) return this; 999 | for (var option in options){ 1000 | if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; 1001 | this.addEvent(option, options[option]); 1002 | delete options[option]; 1003 | } 1004 | return this; 1005 | } 1006 | 1007 | }); 1008 | 1009 | })(); 1010 | 1011 | 1012 | /* 1013 | --- 1014 | 1015 | name: Browser 1016 | 1017 | description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash. 1018 | 1019 | license: MIT-style license. 1020 | 1021 | requires: [Array, Function, Number, String] 1022 | 1023 | provides: [Browser, Window, Document] 1024 | 1025 | ... 1026 | */ 1027 | 1028 | (function(){ 1029 | 1030 | var document = this.document; 1031 | var window = document.window = this; 1032 | 1033 | var UID = 1; 1034 | 1035 | this.$uid = (window.ActiveXObject) ? function(item){ 1036 | return (item.uid || (item.uid = [UID++]))[0]; 1037 | } : function(item){ 1038 | return item.uid || (item.uid = UID++); 1039 | }; 1040 | 1041 | $uid(window); 1042 | $uid(document); 1043 | 1044 | var ua = navigator.userAgent.toLowerCase(), 1045 | platform = navigator.platform.toLowerCase(), 1046 | UA = ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0], 1047 | mode = UA[1] == 'ie' && document.documentMode; 1048 | 1049 | var Browser = this.Browser = { 1050 | 1051 | extend: Function.prototype.extend, 1052 | 1053 | name: (UA[1] == 'version') ? UA[3] : UA[1], 1054 | 1055 | version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]), 1056 | 1057 | Platform: { 1058 | name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0] 1059 | }, 1060 | 1061 | Features: { 1062 | xpath: !!(document.evaluate), 1063 | air: !!(window.runtime), 1064 | query: !!(document.querySelector), 1065 | json: !!(window.JSON) 1066 | }, 1067 | 1068 | Plugins: {} 1069 | 1070 | }; 1071 | 1072 | Browser[Browser.name] = true; 1073 | Browser[Browser.name + parseInt(Browser.version, 10)] = true; 1074 | Browser.Platform[Browser.Platform.name] = true; 1075 | 1076 | // Request 1077 | 1078 | Browser.Request = (function(){ 1079 | 1080 | var XMLHTTP = function(){ 1081 | return new XMLHttpRequest(); 1082 | }; 1083 | 1084 | var MSXML2 = function(){ 1085 | return new ActiveXObject('MSXML2.XMLHTTP'); 1086 | }; 1087 | 1088 | var MSXML = function(){ 1089 | return new ActiveXObject('Microsoft.XMLHTTP'); 1090 | }; 1091 | 1092 | return Function.attempt(function(){ 1093 | XMLHTTP(); 1094 | return XMLHTTP; 1095 | }, function(){ 1096 | MSXML2(); 1097 | return MSXML2; 1098 | }, function(){ 1099 | MSXML(); 1100 | return MSXML; 1101 | }); 1102 | 1103 | })(); 1104 | 1105 | Browser.Features.xhr = !!(Browser.Request); 1106 | 1107 | // Flash detection 1108 | 1109 | var version = (Function.attempt(function(){ 1110 | return navigator.plugins['Shockwave Flash'].description; 1111 | }, function(){ 1112 | return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version'); 1113 | }) || '0 r0').match(/\d+/g); 1114 | 1115 | Browser.Plugins.Flash = { 1116 | version: Number(version[0] || '0.' + version[1]) || 0, 1117 | build: Number(version[2]) || 0 1118 | }; 1119 | 1120 | // String scripts 1121 | 1122 | Browser.exec = function(text){ 1123 | if (!text) return text; 1124 | if (window.execScript){ 1125 | window.execScript(text); 1126 | } else { 1127 | var script = document.createElement('script'); 1128 | script.setAttribute('type', 'text/javascript'); 1129 | script.text = text; 1130 | document.head.appendChild(script); 1131 | document.head.removeChild(script); 1132 | } 1133 | return text; 1134 | }; 1135 | 1136 | String.implement('stripScripts', function(exec){ 1137 | var scripts = ''; 1138 | var text = this.replace(/]*>([\s\S]*?)<\/script>/gi, function(all, code){ 1139 | scripts += code + '\n'; 1140 | return ''; 1141 | }); 1142 | if (exec === true) Browser.exec(scripts); 1143 | else if (typeOf(exec) == 'function') exec(scripts, text); 1144 | return text; 1145 | }); 1146 | 1147 | // Window, Document 1148 | 1149 | Browser.extend({ 1150 | Document: this.Document, 1151 | Window: this.Window, 1152 | Element: this.Element, 1153 | Event: this.Event 1154 | }); 1155 | 1156 | this.Window = this.$constructor = new Type('Window', function(){}); 1157 | 1158 | this.$family = Function.from('window').hide(); 1159 | 1160 | Window.mirror(function(name, method){ 1161 | window[name] = method; 1162 | }); 1163 | 1164 | this.Document = document.$constructor = new Type('Document', function(){}); 1165 | 1166 | document.$family = Function.from('document').hide(); 1167 | 1168 | Document.mirror(function(name, method){ 1169 | document[name] = method; 1170 | }); 1171 | 1172 | document.html = document.documentElement; 1173 | document.head = document.getElementsByTagName('head')[0]; 1174 | 1175 | if (document.execCommand) try { 1176 | document.execCommand("BackgroundImageCache", false, true); 1177 | } catch (e){} 1178 | 1179 | if (this.attachEvent && !this.addEventListener){ 1180 | var unloadEvent = function(){ 1181 | this.detachEvent('onunload', unloadEvent); 1182 | document.head = document.html = document.window = null; 1183 | }; 1184 | this.attachEvent('onunload', unloadEvent); 1185 | } 1186 | 1187 | // IE fails on collections and ) 1188 | var arrayFrom = Array.from; 1189 | try { 1190 | arrayFrom(document.html.childNodes); 1191 | } catch(e){ 1192 | Array.from = function(item){ 1193 | if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){ 1194 | var i = item.length, array = new Array(i); 1195 | while (i--) array[i] = item[i]; 1196 | return array; 1197 | } 1198 | return arrayFrom(item); 1199 | }; 1200 | 1201 | var prototype = Array.prototype, 1202 | slice = prototype.slice; 1203 | ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){ 1204 | var method = prototype[name]; 1205 | Array[name] = function(item){ 1206 | return method.apply(Array.from(item), slice.call(arguments, 1)); 1207 | }; 1208 | }); 1209 | } 1210 | 1211 | 1212 | 1213 | })(); 1214 | 1215 | 1216 | /* 1217 | --- 1218 | name: Slick.Parser 1219 | description: Standalone CSS3 Selector parser 1220 | provides: Slick.Parser 1221 | ... 1222 | */ 1223 | 1224 | (function(){ 1225 | 1226 | var parsed, 1227 | separatorIndex, 1228 | combinatorIndex, 1229 | reversed, 1230 | cache = {}, 1231 | reverseCache = {}, 1232 | reUnescape = /\\/g; 1233 | 1234 | var parse = function(expression, isReversed){ 1235 | if (expression == null) return null; 1236 | if (expression.Slick === true) return expression; 1237 | expression = ('' + expression).replace(/^\s+|\s+$/g, ''); 1238 | reversed = !!isReversed; 1239 | var currentCache = (reversed) ? reverseCache : cache; 1240 | if (currentCache[expression]) return currentCache[expression]; 1241 | parsed = {Slick: true, expressions: [], raw: expression, reverse: function(){ 1242 | return parse(this.raw, true); 1243 | }}; 1244 | separatorIndex = -1; 1245 | while (expression != (expression = expression.replace(regexp, parser))); 1246 | parsed.length = parsed.expressions.length; 1247 | return currentCache[expression] = (reversed) ? reverse(parsed) : parsed; 1248 | }; 1249 | 1250 | var reverseCombinator = function(combinator){ 1251 | if (combinator === '!') return ' '; 1252 | else if (combinator === ' ') return '!'; 1253 | else if ((/^!/).test(combinator)) return combinator.replace(/^!/, ''); 1254 | else return '!' + combinator; 1255 | }; 1256 | 1257 | var reverse = function(expression){ 1258 | var expressions = expression.expressions; 1259 | for (var i = 0; i < expressions.length; i++){ 1260 | var exp = expressions[i]; 1261 | var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)}; 1262 | 1263 | for (var j = 0; j < exp.length; j++){ 1264 | var cexp = exp[j]; 1265 | if (!cexp.reverseCombinator) cexp.reverseCombinator = ' '; 1266 | cexp.combinator = cexp.reverseCombinator; 1267 | delete cexp.reverseCombinator; 1268 | } 1269 | 1270 | exp.reverse().push(last); 1271 | } 1272 | return expression; 1273 | }; 1274 | 1275 | var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan MIT License 1276 | return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, "\\$&"); 1277 | }; 1278 | 1279 | var regexp = new RegExp( 1280 | /* 1281 | #!/usr/bin/env ruby 1282 | puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'') 1283 | __END__ 1284 | "(?x)^(?:\ 1285 | \\s* ( , ) \\s* # Separator \n\ 1286 | | \\s* ( + ) \\s* # Combinator \n\ 1287 | | ( \\s+ ) # CombinatorChildren \n\ 1288 | | ( + | \\* ) # Tag \n\ 1289 | | \\# ( + ) # ID \n\ 1290 | | \\. ( + ) # ClassName \n\ 1291 | | # Attribute \n\ 1292 | \\[ \ 1293 | \\s* (+) (?: \ 1294 | \\s* ([*^$!~|]?=) (?: \ 1295 | \\s* (?:\ 1296 | ([\"']?)(.*?)\\9 \ 1297 | )\ 1298 | ) \ 1299 | )? \\s* \ 1300 | \\](?!\\]) \n\ 1301 | | :+ ( + )(?:\ 1302 | \\( (?:\ 1303 | (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\ 1304 | ) \\)\ 1305 | )?\ 1306 | )" 1307 | */ 1308 | "^(?:\\s*(,)\\s*|\\s*(+)\\s*|(\\s+)|(+|\\*)|\\#(+)|\\.(+)|\\[\\s*(+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|:+(+)(?:\\((?:(?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+))\\))?)" 1309 | .replace(//, '[' + escapeRegExp(">+~`!@$%^&={}\\;/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') 1311 | .replace(//g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') 1312 | ); 1313 | 1314 | function parser( 1315 | rawMatch, 1316 | 1317 | separator, 1318 | combinator, 1319 | combinatorChildren, 1320 | 1321 | tagName, 1322 | id, 1323 | className, 1324 | 1325 | attributeKey, 1326 | attributeOperator, 1327 | attributeQuote, 1328 | attributeValue, 1329 | 1330 | pseudoClass, 1331 | pseudoQuote, 1332 | pseudoClassQuotedValue, 1333 | pseudoClassValue 1334 | ){ 1335 | if (separator || separatorIndex === -1){ 1336 | parsed.expressions[++separatorIndex] = []; 1337 | combinatorIndex = -1; 1338 | if (separator) return ''; 1339 | } 1340 | 1341 | if (combinator || combinatorChildren || combinatorIndex === -1){ 1342 | combinator = combinator || ' '; 1343 | var currentSeparator = parsed.expressions[separatorIndex]; 1344 | if (reversed && currentSeparator[combinatorIndex]) 1345 | currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator); 1346 | currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'}; 1347 | } 1348 | 1349 | var currentParsed = parsed.expressions[separatorIndex][combinatorIndex]; 1350 | 1351 | if (tagName){ 1352 | currentParsed.tag = tagName.replace(reUnescape, ''); 1353 | 1354 | } else if (id){ 1355 | currentParsed.id = id.replace(reUnescape, ''); 1356 | 1357 | } else if (className){ 1358 | className = className.replace(reUnescape, ''); 1359 | 1360 | if (!currentParsed.classList) currentParsed.classList = []; 1361 | if (!currentParsed.classes) currentParsed.classes = []; 1362 | currentParsed.classList.push(className); 1363 | currentParsed.classes.push({ 1364 | value: className, 1365 | regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)') 1366 | }); 1367 | 1368 | } else if (pseudoClass){ 1369 | pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue; 1370 | pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null; 1371 | 1372 | if (!currentParsed.pseudos) currentParsed.pseudos = []; 1373 | currentParsed.pseudos.push({ 1374 | key: pseudoClass.replace(reUnescape, ''), 1375 | value: pseudoClassValue 1376 | }); 1377 | 1378 | } else if (attributeKey){ 1379 | attributeKey = attributeKey.replace(reUnescape, ''); 1380 | attributeValue = (attributeValue || '').replace(reUnescape, ''); 1381 | 1382 | var test, regexp; 1383 | 1384 | switch (attributeOperator){ 1385 | case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break; 1386 | case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break; 1387 | case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break; 1388 | case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break; 1389 | case '=' : test = function(value){ 1390 | return attributeValue == value; 1391 | }; break; 1392 | case '*=' : test = function(value){ 1393 | return value && value.indexOf(attributeValue) > -1; 1394 | }; break; 1395 | case '!=' : test = function(value){ 1396 | return attributeValue != value; 1397 | }; break; 1398 | default : test = function(value){ 1399 | return !!value; 1400 | }; 1401 | } 1402 | 1403 | if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){ 1404 | return false; 1405 | }; 1406 | 1407 | if (!test) test = function(value){ 1408 | return value && regexp.test(value); 1409 | }; 1410 | 1411 | if (!currentParsed.attributes) currentParsed.attributes = []; 1412 | currentParsed.attributes.push({ 1413 | key: attributeKey, 1414 | operator: attributeOperator, 1415 | value: attributeValue, 1416 | test: test 1417 | }); 1418 | 1419 | } 1420 | 1421 | return ''; 1422 | }; 1423 | 1424 | // Slick NS 1425 | 1426 | var Slick = (this.Slick || {}); 1427 | 1428 | Slick.parse = function(expression){ 1429 | return parse(expression); 1430 | }; 1431 | 1432 | Slick.escapeRegExp = escapeRegExp; 1433 | 1434 | if (!this.Slick) this.Slick = Slick; 1435 | 1436 | }).apply(/**/(typeof exports != 'undefined') ? exports : /**/this); 1437 | 1438 | 1439 | /* 1440 | --- 1441 | name: Slick.Finder 1442 | description: The new, superfast css selector engine. 1443 | provides: Slick.Finder 1444 | requires: Slick.Parser 1445 | ... 1446 | */ 1447 | 1448 | (function(){ 1449 | 1450 | var local = {}; 1451 | 1452 | // Feature / Bug detection 1453 | 1454 | local.isNativeCode = function(fn){ 1455 | return (/\{\s*\[native code\]\s*\}/).test('' + fn); 1456 | }; 1457 | 1458 | local.isXML = function(document){ 1459 | return (!!document.xmlVersion) || (!!document.xml) || (Object.prototype.toString.call(document) === '[object XMLDocument]') || 1460 | (document.nodeType === 9 && document.documentElement.nodeName !== 'HTML'); 1461 | }; 1462 | 1463 | local.setDocument = function(document){ 1464 | 1465 | // convert elements / window arguments to document. if document cannot be extrapolated, the function returns. 1466 | 1467 | if (document.nodeType === 9); // document 1468 | else if (document.ownerDocument) document = document.ownerDocument; // node 1469 | else if (document.navigator) document = document.document; // window 1470 | else return; 1471 | 1472 | // check if it's the old document 1473 | 1474 | if (this.document === document) return; 1475 | this.document = document; 1476 | var root = this.root = document.documentElement; 1477 | 1478 | this.isXMLDocument = this.isXML(document); 1479 | 1480 | this.brokenStarGEBTN 1481 | = this.starSelectsClosedQSA 1482 | = this.idGetsName 1483 | = this.brokenMixedCaseQSA 1484 | = this.brokenGEBCN 1485 | = this.brokenCheckedQSA 1486 | = this.brokenEmptyAttributeQSA 1487 | = this.isHTMLDocument 1488 | = false; 1489 | 1490 | var starSelectsClosed, starSelectsComments, 1491 | brokenSecondClassNameGEBCN, cachedGetElementsByClassName; 1492 | 1493 | var selected, id; 1494 | var testNode = document.createElement('div'); 1495 | root.appendChild(testNode); 1496 | 1497 | // on non-HTML documents innerHTML and getElementsById doesnt work properly 1498 | try { 1499 | id = 'slick_getbyid_test'; 1500 | testNode.innerHTML = ''; 1501 | this.isHTMLDocument = !!document.getElementById(id); 1502 | } catch(e){}; 1503 | 1504 | if (this.isHTMLDocument){ 1505 | 1506 | testNode.style.display = 'none'; 1507 | 1508 | // IE returns comment nodes for getElementsByTagName('*') for some documents 1509 | testNode.appendChild(document.createComment('')); 1510 | starSelectsComments = (testNode.getElementsByTagName('*').length > 0); 1511 | 1512 | // IE returns closed nodes (EG:"") for getElementsByTagName('*') for some documents 1513 | try { 1514 | testNode.innerHTML = 'foo'; 1515 | selected = testNode.getElementsByTagName('*'); 1516 | starSelectsClosed = (selected && selected.length && selected[0].nodeName.charAt(0) == '/'); 1517 | } catch(e){}; 1518 | 1519 | this.brokenStarGEBTN = starSelectsComments || starSelectsClosed; 1520 | 1521 | // IE 8 returns closed nodes (EG:"") for querySelectorAll('*') for some documents 1522 | if (testNode.querySelectorAll) try { 1523 | testNode.innerHTML = 'foo'; 1524 | selected = testNode.querySelectorAll('*'); 1525 | this.starSelectsClosedQSA = (selected && selected.length && selected[0].nodeName.charAt(0) == '/'); 1526 | } catch(e){}; 1527 | 1528 | // IE returns elements with the name instead of just id for getElementsById for some documents 1529 | try { 1530 | id = 'slick_id_gets_name'; 1531 | testNode.innerHTML = ''; 1532 | this.idGetsName = document.getElementById(id) === testNode.firstChild; 1533 | } catch(e){}; 1534 | 1535 | // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode 1536 | try { 1537 | testNode.innerHTML = ''; 1538 | this.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiXedCaSe').length; 1539 | } catch(e){}; 1540 | 1541 | try { 1542 | testNode.innerHTML = ''; 1543 | testNode.getElementsByClassName('b').length; 1544 | testNode.firstChild.className = 'b'; 1545 | cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2); 1546 | } catch(e){}; 1547 | 1548 | // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one 1549 | try { 1550 | testNode.innerHTML = ''; 1551 | brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2); 1552 | } catch(e){}; 1553 | 1554 | this.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN; 1555 | 1556 | // Webkit dont return selected options on querySelectorAll 1557 | try { 1558 | testNode.innerHTML = ''; 1559 | this.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0); 1560 | } catch(e){}; 1561 | 1562 | // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll 1563 | try { 1564 | testNode.innerHTML = ''; 1565 | this.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0); 1566 | } catch(e){}; 1567 | 1568 | } 1569 | 1570 | root.removeChild(testNode); 1571 | testNode = null; 1572 | 1573 | // hasAttribute 1574 | 1575 | this.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute) { 1576 | return node.hasAttribute(attribute); 1577 | } : function(node, attribute) { 1578 | node = node.getAttributeNode(attribute); 1579 | return !!(node && (node.specified || node.nodeValue)); 1580 | }; 1581 | 1582 | // contains 1583 | // FIXME: Add specs: local.contains should be different for xml and html documents? 1584 | this.contains = (root && this.isNativeCode(root.contains)) ? function(context, node){ 1585 | return context.contains(node); 1586 | } : (root && root.compareDocumentPosition) ? function(context, node){ 1587 | return context === node || !!(context.compareDocumentPosition(node) & 16); 1588 | } : function(context, node){ 1589 | if (node) do { 1590 | if (node === context) return true; 1591 | } while ((node = node.parentNode)); 1592 | return false; 1593 | }; 1594 | 1595 | // document order sorting 1596 | // credits to Sizzle (http://sizzlejs.com/) 1597 | 1598 | this.documentSorter = (root.compareDocumentPosition) ? function(a, b){ 1599 | if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0; 1600 | return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; 1601 | } : ('sourceIndex' in root) ? function(a, b){ 1602 | if (!a.sourceIndex || !b.sourceIndex) return 0; 1603 | return a.sourceIndex - b.sourceIndex; 1604 | } : (document.createRange) ? function(a, b){ 1605 | if (!a.ownerDocument || !b.ownerDocument) return 0; 1606 | var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); 1607 | aRange.setStart(a, 0); 1608 | aRange.setEnd(a, 0); 1609 | bRange.setStart(b, 0); 1610 | bRange.setEnd(b, 0); 1611 | return aRange.compareBoundaryPoints(Range.START_TO_END, bRange); 1612 | } : null ; 1613 | 1614 | this.getUID = (this.isHTMLDocument) ? this.getUIDHTML : this.getUIDXML; 1615 | 1616 | }; 1617 | 1618 | // Main Method 1619 | 1620 | local.search = function(context, expression, append, first){ 1621 | 1622 | var found = this.found = (first) ? null : (append || []); 1623 | 1624 | // context checks 1625 | 1626 | if (!context) return found; // No context 1627 | if (context.navigator) context = context.document; // Convert the node from a window to a document 1628 | else if (!context.nodeType) return found; // Reject misc junk input 1629 | 1630 | // setup 1631 | 1632 | var parsed, i; 1633 | 1634 | var uniques = this.uniques = {}; 1635 | 1636 | if (this.document !== (context.ownerDocument || context)) this.setDocument(context); 1637 | 1638 | // should sort if there are nodes in append and if you pass multiple expressions. 1639 | // should remove duplicates if append already has items 1640 | var shouldUniques = !!(append && append.length); 1641 | 1642 | // avoid duplicating items already in the append array 1643 | if (shouldUniques) for (i = found.length; i--;) this.uniques[this.getUID(found[i])] = true; 1644 | 1645 | // expression checks 1646 | 1647 | if (typeof expression == 'string'){ // expression is a string 1648 | 1649 | // Overrides 1650 | 1651 | for (i = this.overrides.length; i--;){ 1652 | var override = this.overrides[i]; 1653 | if (override.regexp.test(expression)){ 1654 | var result = override.method.call(context, expression, found, first); 1655 | if (result === false) continue; 1656 | if (result === true) return found; 1657 | return result; 1658 | } 1659 | } 1660 | 1661 | parsed = this.Slick.parse(expression); 1662 | if (!parsed.length) return found; 1663 | } else if (expression == null){ // there is no expression 1664 | return found; 1665 | } else if (expression.Slick){ // expression is a parsed Slick object 1666 | parsed = expression; 1667 | } else if (this.contains(context.documentElement || context, expression)){ // expression is a node 1668 | (found) ? found.push(expression) : found = expression; 1669 | return found; 1670 | } else { // other junk 1671 | return found; 1672 | } 1673 | 1674 | // cache elements for the nth selectors 1675 | 1676 | /**//**/ 1677 | 1678 | this.posNTH = {}; 1679 | this.posNTHLast = {}; 1680 | this.posNTHType = {}; 1681 | this.posNTHTypeLast = {}; 1682 | 1683 | /**//**/ 1684 | 1685 | // if append is null and there is only a single selector with one expression use pushArray, else use pushUID 1686 | this.push = (!shouldUniques && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID; 1687 | 1688 | if (found == null) found = []; 1689 | 1690 | // default engine 1691 | 1692 | var j, m, n; 1693 | var combinator, tag, id, classList, classes, attributes, pseudos; 1694 | var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions; 1695 | 1696 | search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){ 1697 | 1698 | combinator = 'combinator:' + currentBit.combinator; 1699 | if (!this[combinator]) continue search; 1700 | 1701 | tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase(); 1702 | id = currentBit.id; 1703 | classList = currentBit.classList; 1704 | classes = currentBit.classes; 1705 | attributes = currentBit.attributes; 1706 | pseudos = currentBit.pseudos; 1707 | lastBit = (j === (currentExpression.length - 1)); 1708 | 1709 | this.bitUniques = {}; 1710 | 1711 | if (lastBit){ 1712 | this.uniques = uniques; 1713 | this.found = found; 1714 | } else { 1715 | this.uniques = {}; 1716 | this.found = []; 1717 | } 1718 | 1719 | if (j === 0){ 1720 | this[combinator](context, tag, id, classes, attributes, pseudos, classList); 1721 | if (first && lastBit && found.length) break search; 1722 | } else { 1723 | if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){ 1724 | this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); 1725 | if (found.length) break search; 1726 | } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); 1727 | } 1728 | 1729 | currentItems = this.found; 1730 | } 1731 | 1732 | if (shouldUniques || (parsed.expressions.length > 1)) this.sort(found); 1733 | 1734 | return (first) ? (found[0] || null) : found; 1735 | }; 1736 | 1737 | // Utils 1738 | 1739 | local.uidx = 1; 1740 | local.uidk = 'slick:uniqueid'; 1741 | 1742 | local.getUIDXML = function(node){ 1743 | var uid = node.getAttribute(this.uidk); 1744 | if (!uid){ 1745 | uid = this.uidx++; 1746 | node.setAttribute(this.uidk, uid); 1747 | } 1748 | return uid; 1749 | }; 1750 | 1751 | local.getUIDHTML = function(node){ 1752 | return node.uniqueNumber || (node.uniqueNumber = this.uidx++); 1753 | }; 1754 | 1755 | // sort based on the setDocument documentSorter method. 1756 | 1757 | local.sort = function(results){ 1758 | if (!this.documentSorter) return results; 1759 | results.sort(this.documentSorter); 1760 | return results; 1761 | }; 1762 | 1763 | /**//**/ 1764 | 1765 | local.cacheNTH = {}; 1766 | 1767 | local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/; 1768 | 1769 | local.parseNTHArgument = function(argument){ 1770 | var parsed = argument.match(this.matchNTH); 1771 | if (!parsed) return false; 1772 | var special = parsed[2] || false; 1773 | var a = parsed[1] || 1; 1774 | if (a == '-') a = -1; 1775 | var b = +parsed[3] || 0; 1776 | parsed = 1777 | (special == 'n') ? {a: a, b: b} : 1778 | (special == 'odd') ? {a: 2, b: 1} : 1779 | (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a}; 1780 | 1781 | return (this.cacheNTH[argument] = parsed); 1782 | }; 1783 | 1784 | local.createNTHPseudo = function(child, sibling, positions, ofType){ 1785 | return function(node, argument){ 1786 | var uid = this.getUID(node); 1787 | if (!this[positions][uid]){ 1788 | var parent = node.parentNode; 1789 | if (!parent) return false; 1790 | var el = parent[child], count = 1; 1791 | if (ofType){ 1792 | var nodeName = node.nodeName; 1793 | do { 1794 | if (el.nodeName !== nodeName) continue; 1795 | this[positions][this.getUID(el)] = count++; 1796 | } while ((el = el[sibling])); 1797 | } else { 1798 | do { 1799 | if (el.nodeType !== 1) continue; 1800 | this[positions][this.getUID(el)] = count++; 1801 | } while ((el = el[sibling])); 1802 | } 1803 | } 1804 | argument = argument || 'n'; 1805 | var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument); 1806 | if (!parsed) return false; 1807 | var a = parsed.a, b = parsed.b, pos = this[positions][uid]; 1808 | if (a == 0) return b == pos; 1809 | if (a > 0){ 1810 | if (pos < b) return false; 1811 | } else { 1812 | if (b < pos) return false; 1813 | } 1814 | return ((pos - b) % a) == 0; 1815 | }; 1816 | }; 1817 | 1818 | /**//**/ 1819 | 1820 | local.pushArray = function(node, tag, id, classes, attributes, pseudos){ 1821 | if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node); 1822 | }; 1823 | 1824 | local.pushUID = function(node, tag, id, classes, attributes, pseudos){ 1825 | var uid = this.getUID(node); 1826 | if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){ 1827 | this.uniques[uid] = true; 1828 | this.found.push(node); 1829 | } 1830 | }; 1831 | 1832 | local.matchNode = function(node, selector){ 1833 | var parsed = this.Slick.parse(selector); 1834 | if (!parsed) return true; 1835 | 1836 | // simple (single) selectors 1837 | if(parsed.length == 1 && parsed.expressions[0].length == 1){ 1838 | var exp = parsed.expressions[0][0]; 1839 | return this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos); 1840 | } 1841 | 1842 | var nodes = this.search(this.document, parsed); 1843 | for (var i = 0, item; item = nodes[i++];){ 1844 | if (item === node) return true; 1845 | } 1846 | return false; 1847 | }; 1848 | 1849 | local.matchPseudo = function(node, name, argument){ 1850 | var pseudoName = 'pseudo:' + name; 1851 | if (this[pseudoName]) return this[pseudoName](node, argument); 1852 | var attribute = this.getAttribute(node, name); 1853 | return (argument) ? argument == attribute : !!attribute; 1854 | }; 1855 | 1856 | local.matchSelector = function(node, tag, id, classes, attributes, pseudos){ 1857 | if (tag){ 1858 | if (tag == '*'){ 1859 | if (node.nodeName < '@') return false; // Fix for comment nodes and closed nodes 1860 | } else { 1861 | if (node.nodeName != tag) return false; 1862 | } 1863 | } 1864 | 1865 | if (id && node.getAttribute('id') != id) return false; 1866 | 1867 | var i, part, cls; 1868 | if (classes) for (i = classes.length; i--;){ 1869 | cls = ('className' in node) ? node.className : node.getAttribute('class'); 1870 | if (!(cls && classes[i].regexp.test(cls))) return false; 1871 | } 1872 | if (attributes) for (i = attributes.length; i--;){ 1873 | part = attributes[i]; 1874 | if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false; 1875 | } 1876 | if (pseudos) for (i = pseudos.length; i--;){ 1877 | part = pseudos[i]; 1878 | if (!this.matchPseudo(node, part.key, part.value)) return false; 1879 | } 1880 | return true; 1881 | }; 1882 | 1883 | var combinators = { 1884 | 1885 | ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level 1886 | 1887 | var i, item, children; 1888 | 1889 | if (this.isHTMLDocument){ 1890 | getById: if (id){ 1891 | item = this.document.getElementById(id); 1892 | if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){ 1893 | // all[id] returns all the elements with that name or id inside node 1894 | // if theres just one it will return the element, else it will be a collection 1895 | children = node.all[id]; 1896 | if (!children) return; 1897 | if (!children[0]) children = [children]; 1898 | for (i = 0; item = children[i++];) if (item.getAttributeNode('id').nodeValue == id){ 1899 | this.push(item, tag, null, classes, attributes, pseudos); 1900 | break; 1901 | } 1902 | return; 1903 | } 1904 | if (!item){ 1905 | // if the context is in the dom we return, else we will try GEBTN, breaking the getById label 1906 | if (this.contains(this.document.documentElement, node)) return; 1907 | else break getById; 1908 | } else if (this.document !== node && !this.contains(node, item)) return; 1909 | this.push(item, tag, null, classes, attributes, pseudos); 1910 | return; 1911 | } 1912 | getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){ 1913 | children = node.getElementsByClassName(classList.join(' ')); 1914 | if (!(children && children.length)) break getByClass; 1915 | for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos); 1916 | return; 1917 | } 1918 | } 1919 | getByTag: { 1920 | children = node.getElementsByTagName(tag); 1921 | if (!(children && children.length)) break getByTag; 1922 | if (!this.brokenStarGEBTN) tag = null; 1923 | for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos); 1924 | } 1925 | }, 1926 | 1927 | '>': function(node, tag, id, classes, attributes, pseudos){ // direct children 1928 | if ((node = node.firstChild)) do { 1929 | if (node.nodeType === 1) this.push(node, tag, id, classes, attributes, pseudos); 1930 | } while ((node = node.nextSibling)); 1931 | }, 1932 | 1933 | '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling 1934 | while ((node = node.nextSibling)) if (node.nodeType === 1){ 1935 | this.push(node, tag, id, classes, attributes, pseudos); 1936 | break; 1937 | } 1938 | }, 1939 | 1940 | '^': function(node, tag, id, classes, attributes, pseudos){ // first child 1941 | node = node.firstChild; 1942 | if (node){ 1943 | if (node.nodeType === 1) this.push(node, tag, id, classes, attributes, pseudos); 1944 | else this['combinator:+'](node, tag, id, classes, attributes, pseudos); 1945 | } 1946 | }, 1947 | 1948 | '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings 1949 | while ((node = node.nextSibling)){ 1950 | if (node.nodeType !== 1) continue; 1951 | var uid = this.getUID(node); 1952 | if (this.bitUniques[uid]) break; 1953 | this.bitUniques[uid] = true; 1954 | this.push(node, tag, id, classes, attributes, pseudos); 1955 | } 1956 | }, 1957 | 1958 | '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling 1959 | this['combinator:+'](node, tag, id, classes, attributes, pseudos); 1960 | this['combinator:!+'](node, tag, id, classes, attributes, pseudos); 1961 | }, 1962 | 1963 | '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings 1964 | this['combinator:~'](node, tag, id, classes, attributes, pseudos); 1965 | this['combinator:!~'](node, tag, id, classes, attributes, pseudos); 1966 | }, 1967 | 1968 | '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document 1969 | while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); 1970 | }, 1971 | 1972 | '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level) 1973 | node = node.parentNode; 1974 | if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); 1975 | }, 1976 | 1977 | '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling 1978 | while ((node = node.previousSibling)) if (node.nodeType === 1){ 1979 | this.push(node, tag, id, classes, attributes, pseudos); 1980 | break; 1981 | } 1982 | }, 1983 | 1984 | '!^': function(node, tag, id, classes, attributes, pseudos){ // last child 1985 | node = node.lastChild; 1986 | if (node){ 1987 | if (node.nodeType === 1) this.push(node, tag, id, classes, attributes, pseudos); 1988 | else this['combinator:!+'](node, tag, id, classes, attributes, pseudos); 1989 | } 1990 | }, 1991 | 1992 | '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings 1993 | while ((node = node.previousSibling)){ 1994 | if (node.nodeType !== 1) continue; 1995 | var uid = this.getUID(node); 1996 | if (this.bitUniques[uid]) break; 1997 | this.bitUniques[uid] = true; 1998 | this.push(node, tag, id, classes, attributes, pseudos); 1999 | } 2000 | } 2001 | 2002 | }; 2003 | 2004 | for (var c in combinators) local['combinator:' + c] = combinators[c]; 2005 | 2006 | var pseudos = { 2007 | 2008 | /**/ 2009 | 2010 | 'empty': function(node){ 2011 | var child = node.firstChild; 2012 | return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length; 2013 | }, 2014 | 2015 | 'not': function(node, expression){ 2016 | return !this.matchNode(node, expression); 2017 | }, 2018 | 2019 | 'contains': function(node, text){ 2020 | return (node.innerText || node.textContent || '').indexOf(text) > -1; 2021 | }, 2022 | 2023 | 'first-child': function(node){ 2024 | while ((node = node.previousSibling)) if (node.nodeType === 1) return false; 2025 | return true; 2026 | }, 2027 | 2028 | 'last-child': function(node){ 2029 | while ((node = node.nextSibling)) if (node.nodeType === 1) return false; 2030 | return true; 2031 | }, 2032 | 2033 | 'only-child': function(node){ 2034 | var prev = node; 2035 | while ((prev = prev.previousSibling)) if (prev.nodeType === 1) return false; 2036 | var next = node; 2037 | while ((next = next.nextSibling)) if (next.nodeType === 1) return false; 2038 | return true; 2039 | }, 2040 | 2041 | /**/ 2042 | 2043 | 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'), 2044 | 2045 | 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'), 2046 | 2047 | 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true), 2048 | 2049 | 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true), 2050 | 2051 | 'index': function(node, index){ 2052 | return this['pseudo:nth-child'](node, '' + index + 1); 2053 | }, 2054 | 2055 | 'even': function(node, argument){ 2056 | return this['pseudo:nth-child'](node, '2n'); 2057 | }, 2058 | 2059 | 'odd': function(node, argument){ 2060 | return this['pseudo:nth-child'](node, '2n+1'); 2061 | }, 2062 | 2063 | /**/ 2064 | 2065 | /**/ 2066 | 2067 | 'first-of-type': function(node){ 2068 | var nodeName = node.nodeName; 2069 | while ((node = node.previousSibling)) if (node.nodeName === nodeName) return false; 2070 | return true; 2071 | }, 2072 | 2073 | 'last-of-type': function(node){ 2074 | var nodeName = node.nodeName; 2075 | while ((node = node.nextSibling)) if (node.nodeName === nodeName) return false; 2076 | return true; 2077 | }, 2078 | 2079 | 'only-of-type': function(node){ 2080 | var prev = node, nodeName = node.nodeName; 2081 | while ((prev = prev.previousSibling)) if (prev.nodeName === nodeName) return false; 2082 | var next = node; 2083 | while ((next = next.nextSibling)) if (next.nodeName === nodeName) return false; 2084 | return true; 2085 | }, 2086 | 2087 | /**/ 2088 | 2089 | // custom pseudos 2090 | 2091 | 'enabled': function(node){ 2092 | return (node.disabled === false); 2093 | }, 2094 | 2095 | 'disabled': function(node){ 2096 | return (node.disabled === true); 2097 | }, 2098 | 2099 | 'checked': function(node){ 2100 | return node.checked || node.selected; 2101 | }, 2102 | 2103 | 'focus': function(node){ 2104 | return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex')); 2105 | }, 2106 | 2107 | 'root': function(node){ 2108 | return (node === this.root); 2109 | }, 2110 | 2111 | 'selected': function(node){ 2112 | return node.selected; 2113 | } 2114 | 2115 | /**/ 2116 | }; 2117 | 2118 | for (var p in pseudos) local['pseudo:' + p] = pseudos[p]; 2119 | 2120 | // attributes methods 2121 | 2122 | local.attributeGetters = { 2123 | 2124 | 'class': function(){ 2125 | return ('className' in this) ? this.className : this.getAttribute('class'); 2126 | }, 2127 | 2128 | 'for': function(){ 2129 | return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for'); 2130 | }, 2131 | 2132 | 'href': function(){ 2133 | return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href'); 2134 | }, 2135 | 2136 | 'style': function(){ 2137 | return (this.style) ? this.style.cssText : this.getAttribute('style'); 2138 | } 2139 | 2140 | }; 2141 | 2142 | local.getAttribute = function(node, name){ 2143 | // FIXME: check if getAttribute() will get input elements on a form on this browser 2144 | // getAttribute is faster than getAttributeNode().nodeValue 2145 | var method = this.attributeGetters[name]; 2146 | if (method) return method.call(node); 2147 | var attributeNode = node.getAttributeNode(name); 2148 | return attributeNode ? attributeNode.nodeValue : null; 2149 | }; 2150 | 2151 | // overrides 2152 | 2153 | local.overrides = []; 2154 | 2155 | local.override = function(regexp, method){ 2156 | this.overrides.push({regexp: regexp, method: method}); 2157 | }; 2158 | 2159 | /**/ 2160 | 2161 | /**/ 2162 | 2163 | var reEmptyAttribute = /\[.*[*$^]=(?:["']{2})?\]/; 2164 | 2165 | local.override(/./, function(expression, found, first){ //querySelectorAll override 2166 | 2167 | if (!this.querySelectorAll || this.nodeType != 9 || !local.isHTMLDocument || local.brokenMixedCaseQSA || 2168 | (local.brokenCheckedQSA && expression.indexOf(':checked') > -1) || 2169 | (local.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression)) || Slick.disableQSA) return false; 2170 | 2171 | var nodes, node; 2172 | try { 2173 | if (first) return this.querySelector(expression) || null; 2174 | else nodes = this.querySelectorAll(expression); 2175 | } catch(error){ 2176 | return false; 2177 | } 2178 | 2179 | var i, hasOthers = !!(found.length); 2180 | 2181 | if (local.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){ 2182 | if (node.nodeName > '@' && (!hasOthers || !local.uniques[local.getUIDHTML(node)])) found.push(node); 2183 | } else for (i = 0; node = nodes[i++];){ 2184 | if (!hasOthers || !local.uniques[local.getUIDHTML(node)]) found.push(node); 2185 | } 2186 | 2187 | if (hasOthers) local.sort(found); 2188 | 2189 | return true; 2190 | 2191 | }); 2192 | 2193 | /**/ 2194 | 2195 | /**/ 2196 | 2197 | local.override(/^[\w-]+$|^\*$/, function(expression, found, first){ // tag override 2198 | var tag = expression; 2199 | if (tag == '*' && local.brokenStarGEBTN) return false; 2200 | 2201 | var nodes = this.getElementsByTagName(tag); 2202 | 2203 | if (first) return nodes[0] || null; 2204 | var i, node, hasOthers = !!(found.length); 2205 | 2206 | for (i = 0; node = nodes[i++];){ 2207 | if (!hasOthers || !local.uniques[local.getUID(node)]) found.push(node); 2208 | } 2209 | 2210 | if (hasOthers) local.sort(found); 2211 | 2212 | return true; 2213 | }); 2214 | 2215 | /**/ 2216 | 2217 | /**/ 2218 | 2219 | local.override(/^\.[\w-]+$/, function(expression, found, first){ // class override 2220 | if (!local.isHTMLDocument || (!this.getElementsByClassName && this.querySelectorAll)) return false; 2221 | 2222 | var nodes, node, i, hasOthers = !!(found && found.length), className = expression.substring(1); 2223 | if (this.getElementsByClassName && !local.brokenGEBCN){ 2224 | nodes = this.getElementsByClassName(className); 2225 | if (first) return nodes[0] || null; 2226 | for (i = 0; node = nodes[i++];){ 2227 | if (!hasOthers || !local.uniques[local.getUIDHTML(node)]) found.push(node); 2228 | } 2229 | } else { 2230 | var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(className) +'(\\s|$)'); 2231 | nodes = this.getElementsByTagName('*'); 2232 | for (i = 0; node = nodes[i++];){ 2233 | className = node.className; 2234 | if (!className || !matchClass.test(className)) continue; 2235 | if (first) return node; 2236 | if (!hasOthers || !local.uniques[local.getUIDHTML(node)]) found.push(node); 2237 | } 2238 | } 2239 | if (hasOthers) local.sort(found); 2240 | return (first) ? null : true; 2241 | }); 2242 | 2243 | /**/ 2244 | 2245 | /**/ 2246 | 2247 | local.override(/^#[\w-]+$/, function(expression, found, first){ // ID override 2248 | if (!local.isHTMLDocument || this.nodeType != 9) return false; 2249 | 2250 | var id = expression.substring(1), el = this.getElementById(id); 2251 | if (!el) return found; 2252 | if (local.idGetsName && el.getAttributeNode('id').nodeValue != id) return false; 2253 | if (first) return el || null; 2254 | var hasOthers = !!(found.length); 2255 | if (!hasOthers || !local.uniques[local.getUIDHTML(el)]) found.push(el); 2256 | if (hasOthers) local.sort(found); 2257 | return true; 2258 | }); 2259 | 2260 | /**/ 2261 | 2262 | /**/ 2263 | 2264 | if (typeof document != 'undefined') local.setDocument(document); 2265 | 2266 | // Slick 2267 | 2268 | var Slick = local.Slick = (this.Slick || {}); 2269 | 2270 | Slick.version = '0.9dev'; 2271 | 2272 | // Slick finder 2273 | 2274 | Slick.search = function(context, expression, append){ 2275 | return local.search(context, expression, append); 2276 | }; 2277 | 2278 | Slick.find = function(context, expression){ 2279 | return local.search(context, expression, null, true); 2280 | }; 2281 | 2282 | // Slick containment checker 2283 | 2284 | Slick.contains = function(container, node){ 2285 | local.setDocument(container); 2286 | return local.contains(container, node); 2287 | }; 2288 | 2289 | // Slick attribute getter 2290 | 2291 | Slick.getAttribute = function(node, name){ 2292 | return local.getAttribute(node, name); 2293 | }; 2294 | 2295 | // Slick matcher 2296 | 2297 | Slick.match = function(node, selector){ 2298 | if (!(node && selector)) return false; 2299 | if (!selector || selector === node) return true; 2300 | if (typeof selector != 'string') return false; 2301 | local.setDocument(node); 2302 | return local.matchNode(node, selector); 2303 | }; 2304 | 2305 | // Slick attribute accessor 2306 | 2307 | Slick.defineAttributeGetter = function(name, fn){ 2308 | local.attributeGetters[name] = fn; 2309 | return this; 2310 | }; 2311 | 2312 | Slick.lookupAttributeGetter = function(name){ 2313 | return local.attributeGetters[name]; 2314 | }; 2315 | 2316 | // Slick pseudo accessor 2317 | 2318 | Slick.definePseudo = function(name, fn){ 2319 | local['pseudo:' + name] = function(node, argument){ 2320 | return fn.call(node, argument); 2321 | }; 2322 | return this; 2323 | }; 2324 | 2325 | Slick.lookupPseudo = function(name){ 2326 | var pseudo = local['pseudo:' + name]; 2327 | if (pseudo) return function(argument){ 2328 | return pseudo.call(this, argument); 2329 | }; 2330 | return null; 2331 | }; 2332 | 2333 | // Slick overrides accessor 2334 | 2335 | Slick.override = function(regexp, fn){ 2336 | local.override(regexp, fn); 2337 | return this; 2338 | }; 2339 | 2340 | Slick.isXML = local.isXML; 2341 | 2342 | Slick.uidOf = function(node){ 2343 | return local.getUIDHTML(node); 2344 | }; 2345 | 2346 | if (!this.Slick) this.Slick = Slick; 2347 | 2348 | }).apply(/**/(typeof exports != 'undefined') ? exports : /**/this); 2349 | 2350 | 2351 | /* 2352 | --- 2353 | 2354 | name: Element 2355 | 2356 | description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. 2357 | 2358 | license: MIT-style license. 2359 | 2360 | requires: [Window, Document, Array, String, Function, Number, Slick.Parser, Slick.Finder] 2361 | 2362 | provides: [Element, Elements, $, $$, Iframe, Selectors] 2363 | 2364 | ... 2365 | */ 2366 | 2367 | var Element = function(tag, props){ 2368 | var konstructor = Element.Constructors[tag]; 2369 | if (konstructor) return konstructor(props); 2370 | if (typeof tag != 'string') return document.id(tag).set(props); 2371 | 2372 | if (!props) props = {}; 2373 | 2374 | if (!tag.test(/^[\w-]+$/)){ 2375 | var parsed = Slick.parse(tag).expressions[0][0]; 2376 | tag = (parsed.tag == '*') ? 'div' : parsed.tag; 2377 | if (parsed.id && props.id == null) props.id = parsed.id; 2378 | 2379 | var attributes = parsed.attributes; 2380 | if (attributes) for (var i = 0, l = attributes.length; i < l; i++){ 2381 | var attr = attributes[i]; 2382 | if (attr.value != null && attr.operator == '=' && props[attr.key] == null) 2383 | props[attr.key] = attr.value; 2384 | } 2385 | 2386 | if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' '); 2387 | } 2388 | 2389 | return document.newElement(tag, props); 2390 | }; 2391 | 2392 | if (Browser.Element) Element.prototype = Browser.Element.prototype; 2393 | 2394 | new Type('Element', Element).mirror(function(name){ 2395 | if (Array.prototype[name]) return; 2396 | 2397 | var obj = {}; 2398 | obj[name] = function(){ 2399 | var results = [], args = arguments, elements = true; 2400 | for (var i = 0, l = this.length; i < l; i++){ 2401 | var element = this[i], result = results[i] = element[name].apply(element, args); 2402 | elements = (elements && typeOf(result) == 'element'); 2403 | } 2404 | return (elements) ? new Elements(results) : results; 2405 | }; 2406 | 2407 | Elements.implement(obj); 2408 | }); 2409 | 2410 | if (!Browser.Element){ 2411 | Element.parent = Object; 2412 | 2413 | Element.Prototype = {'$family': Function.from('element').hide()}; 2414 | 2415 | Element.mirror(function(name, method){ 2416 | Element.Prototype[name] = method; 2417 | }); 2418 | } 2419 | 2420 | Element.Constructors = {}; 2421 | 2422 | 2423 | 2424 | var IFrame = new Type('IFrame', function(){ 2425 | var params = Array.link(arguments, { 2426 | properties: Type.isObject, 2427 | iframe: function(obj){ 2428 | return (obj != null); 2429 | } 2430 | }); 2431 | 2432 | var props = params.properties || {}, iframe; 2433 | if (params.iframe) iframe = document.id(params.iframe); 2434 | var onload = props.onload || function(){}; 2435 | delete props.onload; 2436 | props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.generateUID()].pick(); 2437 | iframe = new Element(iframe || 'iframe', props); 2438 | 2439 | var onLoad = function(){ 2440 | onload.call(iframe.contentWindow); 2441 | }; 2442 | 2443 | if (window.frames[props.id]) onLoad(); 2444 | else iframe.addListener('load', onLoad); 2445 | return iframe; 2446 | }); 2447 | 2448 | var Elements = this.Elements = function(nodes){ 2449 | if (nodes && nodes.length){ 2450 | var uniques = {}, node; 2451 | for (var i = 0; node = nodes[i++];){ 2452 | var uid = Slick.uidOf(node); 2453 | if (!uniques[uid]){ 2454 | uniques[uid] = true; 2455 | this.push(node); 2456 | } 2457 | } 2458 | } 2459 | }; 2460 | 2461 | Elements.prototype = {length: 0}; 2462 | Elements.parent = Array; 2463 | 2464 | new Type('Elements', Elements).implement({ 2465 | 2466 | filter: function(filter, bind){ 2467 | if (!filter) return this; 2468 | return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){ 2469 | return item.match(filter); 2470 | } : filter, bind)); 2471 | }.protect(), 2472 | 2473 | push: function(){ 2474 | var length = this.length; 2475 | for (var i = 0, l = arguments.length; i < l; i++){ 2476 | var item = document.id(arguments[i]); 2477 | if (item) this[length++] = item; 2478 | } 2479 | return (this.length = length); 2480 | }.protect(), 2481 | 2482 | concat: function(){ 2483 | var newElements = new Elements(this); 2484 | for (var i = 0, l = arguments.length; i < l; i++){ 2485 | var item = arguments[i]; 2486 | if (Type.isEnumerable(item)) newElements.append(item); 2487 | else newElements.push(item); 2488 | } 2489 | return newElements; 2490 | }.protect(), 2491 | 2492 | append: function(collection){ 2493 | for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]); 2494 | return this; 2495 | }.protect(), 2496 | 2497 | empty: function(){ 2498 | while (this.length) delete this[--this.length]; 2499 | return this; 2500 | }.protect() 2501 | 2502 | }); 2503 | 2504 | (function(){ 2505 | 2506 | // FF, IE 2507 | var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2}; 2508 | 2509 | splice.call(object, 1, 1); 2510 | if (object[1] == 1) Elements.implement('splice', function(){ 2511 | var length = this.length; 2512 | splice.apply(this, arguments); 2513 | while (length >= this.length) delete this[length--]; 2514 | return this; 2515 | }.protect()); 2516 | 2517 | Elements.implement(Array.prototype); 2518 | 2519 | Array.mirror(Elements); 2520 | 2521 | /**/ 2522 | var createElementAcceptsHTML; 2523 | try { 2524 | var x = document.createElement(''); 2525 | createElementAcceptsHTML = (x.name == 'x'); 2526 | } catch(e){} 2527 | 2528 | var escapeQuotes = function(html){ 2529 | return ('' + html).replace(/&/g, '&').replace(/"/g, '"'); 2530 | }; 2531 | /**/ 2532 | 2533 | Document.implement({ 2534 | 2535 | newElement: function(tag, props){ 2536 | if (props && props.checked != null) props.defaultChecked = props.checked; 2537 | /**/// Fix for readonly name and type properties in IE < 8 2538 | if (createElementAcceptsHTML && props){ 2539 | tag = '<' + tag; 2540 | if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"'; 2541 | if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"'; 2542 | tag += '>'; 2543 | delete props.name; 2544 | delete props.type; 2545 | } 2546 | /**/ 2547 | return this.id(this.createElement(tag)).set(props); 2548 | } 2549 | 2550 | }); 2551 | 2552 | })(); 2553 | 2554 | Document.implement({ 2555 | 2556 | newTextNode: function(text){ 2557 | return this.createTextNode(text); 2558 | }, 2559 | 2560 | getDocument: function(){ 2561 | return this; 2562 | }, 2563 | 2564 | getWindow: function(){ 2565 | return this.window; 2566 | }, 2567 | 2568 | id: (function(){ 2569 | 2570 | var types = { 2571 | 2572 | string: function(id, nocash, doc){ 2573 | id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1')); 2574 | return (id) ? types.element(id, nocash) : null; 2575 | }, 2576 | 2577 | element: function(el, nocash){ 2578 | $uid(el); 2579 | if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){ 2580 | Object.append(el, Element.Prototype); 2581 | } 2582 | return el; 2583 | }, 2584 | 2585 | object: function(obj, nocash, doc){ 2586 | if (obj.toElement) return types.element(obj.toElement(doc), nocash); 2587 | return null; 2588 | } 2589 | 2590 | }; 2591 | 2592 | types.textnode = types.whitespace = types.window = types.document = function(zero){ 2593 | return zero; 2594 | }; 2595 | 2596 | return function(el, nocash, doc){ 2597 | if (el && el.$family && el.uid) return el; 2598 | var type = typeOf(el); 2599 | return (types[type]) ? types[type](el, nocash, doc || document) : null; 2600 | }; 2601 | 2602 | })() 2603 | 2604 | }); 2605 | 2606 | if (window.$ == null) Window.implement('$', function(el, nc){ 2607 | return document.id(el, nc, this.document); 2608 | }); 2609 | 2610 | Window.implement({ 2611 | 2612 | getDocument: function(){ 2613 | return this.document; 2614 | }, 2615 | 2616 | getWindow: function(){ 2617 | return this; 2618 | } 2619 | 2620 | }); 2621 | 2622 | [Document, Element].invoke('implement', { 2623 | 2624 | getElements: function(expression){ 2625 | return Slick.search(this, expression, new Elements); 2626 | }, 2627 | 2628 | getElement: function(expression){ 2629 | return document.id(Slick.find(this, expression)); 2630 | } 2631 | 2632 | }); 2633 | 2634 | 2635 | 2636 | if (window.$$ == null) Window.implement('$$', function(selector){ 2637 | if (arguments.length == 1){ 2638 | if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements); 2639 | else if (Type.isEnumerable(selector)) return new Elements(selector); 2640 | } 2641 | return new Elements(arguments); 2642 | }); 2643 | 2644 | (function(){ 2645 | 2646 | var collected = {}, storage = {}; 2647 | var props = {input: 'checked', option: 'selected', textarea: 'value'}; 2648 | 2649 | var get = function(uid){ 2650 | return (storage[uid] || (storage[uid] = {})); 2651 | }; 2652 | 2653 | var clean = function(item){ 2654 | if (item.removeEvents) item.removeEvents(); 2655 | if (item.clearAttributes) item.clearAttributes(); 2656 | var uid = item.uid; 2657 | if (uid != null){ 2658 | delete collected[uid]; 2659 | delete storage[uid]; 2660 | } 2661 | return item; 2662 | }; 2663 | 2664 | var camels = ['defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 2665 | 'rowSpan', 'tabIndex', 'useMap' 2666 | ]; 2667 | var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readOnly', 'multiple', 'selected', 2668 | 'noresize', 'defer' 2669 | ]; 2670 | var attributes = { 2671 | 'html': 'innerHTML', 2672 | 'class': 'className', 2673 | 'for': 'htmlFor', 2674 | 'text': (function(){ 2675 | var temp = document.createElement('div'); 2676 | return (temp.innerText == null) ? 'textContent' : 'innerText'; 2677 | })() 2678 | }; 2679 | var readOnly = ['type']; 2680 | var expandos = ['value', 'defaultValue']; 2681 | var uriAttrs = /^(?:href|src|usemap)$/i; 2682 | 2683 | bools = bools.associate(bools); 2684 | camels = camels.associate(camels.map(String.toLowerCase)); 2685 | readOnly = readOnly.associate(readOnly); 2686 | 2687 | Object.append(attributes, expandos.associate(expandos)); 2688 | 2689 | var inserters = { 2690 | 2691 | before: function(context, element){ 2692 | var parent = element.parentNode; 2693 | if (parent) parent.insertBefore(context, element); 2694 | }, 2695 | 2696 | after: function(context, element){ 2697 | var parent = element.parentNode; 2698 | if (parent) parent.insertBefore(context, element.nextSibling); 2699 | }, 2700 | 2701 | bottom: function(context, element){ 2702 | element.appendChild(context); 2703 | }, 2704 | 2705 | top: function(context, element){ 2706 | element.insertBefore(context, element.firstChild); 2707 | } 2708 | 2709 | }; 2710 | 2711 | inserters.inside = inserters.bottom; 2712 | 2713 | 2714 | 2715 | var injectCombinator = function(expression, combinator){ 2716 | if (!expression) return combinator; 2717 | 2718 | expression = Slick.parse(expression); 2719 | 2720 | var expressions = expression.expressions; 2721 | for (var i = expressions.length; i--;) 2722 | expressions[i][0].combinator = combinator; 2723 | 2724 | return expression; 2725 | }; 2726 | 2727 | Element.implement({ 2728 | 2729 | set: function(prop, value){ 2730 | var property = Element.Properties[prop]; 2731 | (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value); 2732 | }.overloadSetter(), 2733 | 2734 | get: function(prop){ 2735 | var property = Element.Properties[prop]; 2736 | return (property && property.get) ? property.get.apply(this) : this.getProperty(prop); 2737 | }.overloadGetter(), 2738 | 2739 | erase: function(prop){ 2740 | var property = Element.Properties[prop]; 2741 | (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); 2742 | return this; 2743 | }, 2744 | 2745 | setProperty: function(attribute, value){ 2746 | attribute = camels[attribute] || attribute; 2747 | if (value == null) return this.removeProperty(attribute); 2748 | var key = attributes[attribute]; 2749 | (key) ? this[key] = value : 2750 | (bools[attribute]) ? this[attribute] = !!value : this.setAttribute(attribute, '' + value); 2751 | return this; 2752 | }, 2753 | 2754 | setProperties: function(attributes){ 2755 | for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); 2756 | return this; 2757 | }, 2758 | 2759 | getProperty: function(attribute){ 2760 | attribute = camels[attribute] || attribute; 2761 | var key = attributes[attribute] || readOnly[attribute]; 2762 | return (key) ? this[key] : 2763 | (bools[attribute]) ? !!this[attribute] : 2764 | (uriAttrs.test(attribute) ? this.getAttribute(attribute, 2) : 2765 | (key = this.getAttributeNode(attribute)) ? key.nodeValue : null) || null; 2766 | }, 2767 | 2768 | getProperties: function(){ 2769 | var args = Array.from(arguments); 2770 | return args.map(this.getProperty, this).associate(args); 2771 | }, 2772 | 2773 | removeProperty: function(attribute){ 2774 | attribute = camels[attribute] || attribute; 2775 | var key = attributes[attribute]; 2776 | (key) ? this[key] = '' : 2777 | (bools[attribute]) ? this[attribute] = false : this.removeAttribute(attribute); 2778 | return this; 2779 | }, 2780 | 2781 | removeProperties: function(){ 2782 | Array.each(arguments, this.removeProperty, this); 2783 | return this; 2784 | }, 2785 | 2786 | hasClass: function(className){ 2787 | return this.className.clean().contains(className, ' '); 2788 | }, 2789 | 2790 | addClass: function(className){ 2791 | if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean(); 2792 | return this; 2793 | }, 2794 | 2795 | removeClass: function(className){ 2796 | this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1'); 2797 | return this; 2798 | }, 2799 | 2800 | toggleClass: function(className, force){ 2801 | if (force == null) force = !this.hasClass(className); 2802 | return (force) ? this.addClass(className) : this.removeClass(className); 2803 | }, 2804 | 2805 | adopt: function(){ 2806 | var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length; 2807 | if (length > 1) parent = fragment = document.createDocumentFragment(); 2808 | 2809 | for (var i = 0; i < length; i++){ 2810 | var element = document.id(elements[i], true); 2811 | if (element) parent.appendChild(element); 2812 | } 2813 | 2814 | if (fragment) this.appendChild(fragment); 2815 | 2816 | return this; 2817 | }, 2818 | 2819 | appendText: function(text, where){ 2820 | return this.grab(this.getDocument().newTextNode(text), where); 2821 | }, 2822 | 2823 | grab: function(el, where){ 2824 | inserters[where || 'bottom'](document.id(el, true), this); 2825 | return this; 2826 | }, 2827 | 2828 | inject: function(el, where){ 2829 | inserters[where || 'bottom'](this, document.id(el, true)); 2830 | return this; 2831 | }, 2832 | 2833 | replaces: function(el){ 2834 | el = document.id(el, true); 2835 | el.parentNode.replaceChild(this, el); 2836 | return this; 2837 | }, 2838 | 2839 | wraps: function(el, where){ 2840 | el = document.id(el, true); 2841 | return this.replaces(el).grab(el, where); 2842 | }, 2843 | 2844 | getPrevious: function(expression){ 2845 | return document.id(Slick.find(this, injectCombinator(expression, '!~'))); 2846 | }, 2847 | 2848 | getAllPrevious: function(expression){ 2849 | return Slick.search(this, injectCombinator(expression, '!~'), new Elements); 2850 | }, 2851 | 2852 | getNext: function(expression){ 2853 | return document.id(Slick.find(this, injectCombinator(expression, '~'))); 2854 | }, 2855 | 2856 | getAllNext: function(expression){ 2857 | return Slick.search(this, injectCombinator(expression, '~'), new Elements); 2858 | }, 2859 | 2860 | getFirst: function(expression){ 2861 | return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]); 2862 | }, 2863 | 2864 | getLast: function(expression){ 2865 | return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast()); 2866 | }, 2867 | 2868 | getParent: function(expression){ 2869 | return document.id(Slick.find(this, injectCombinator(expression, '!'))); 2870 | }, 2871 | 2872 | getParents: function(expression){ 2873 | return Slick.search(this, injectCombinator(expression, '!'), new Elements); 2874 | }, 2875 | 2876 | getSiblings: function(expression){ 2877 | return Slick.search(this, injectCombinator(expression, '~~'), new Elements); 2878 | }, 2879 | 2880 | getChildren: function(expression){ 2881 | return Slick.search(this, injectCombinator(expression, '>'), new Elements); 2882 | }, 2883 | 2884 | getWindow: function(){ 2885 | return this.ownerDocument.window; 2886 | }, 2887 | 2888 | getDocument: function(){ 2889 | return this.ownerDocument; 2890 | }, 2891 | 2892 | getElementById: function(id){ 2893 | return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1'))); 2894 | }, 2895 | 2896 | getSelected: function(){ 2897 | this.selectedIndex; // Safari 3.2.1 2898 | return new Elements(Array.from(this.options).filter(function(option){ 2899 | return option.selected; 2900 | })); 2901 | }, 2902 | 2903 | toQueryString: function(){ 2904 | var queryString = []; 2905 | this.getElements('input, select, textarea').each(function(el){ 2906 | var type = el.type; 2907 | if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return; 2908 | 2909 | var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){ 2910 | // IE 2911 | return document.id(opt).get('value'); 2912 | }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value'); 2913 | 2914 | Array.from(value).each(function(val){ 2915 | if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val)); 2916 | }); 2917 | }); 2918 | return queryString.join('&'); 2919 | }, 2920 | 2921 | clone: function(contents, keepid){ 2922 | contents = contents !== false; 2923 | var clone = this.cloneNode(contents); 2924 | var clean = function(node, element){ 2925 | if (!keepid) node.removeAttribute('id'); 2926 | if (Browser.ie){ 2927 | node.clearAttributes(); 2928 | node.mergeAttributes(element); 2929 | node.removeAttribute('uid'); 2930 | if (node.options){ 2931 | var no = node.options, eo = element.options; 2932 | for (var j = no.length; j--;) no[j].selected = eo[j].selected; 2933 | } 2934 | } 2935 | var prop = props[element.tagName.toLowerCase()]; 2936 | if (prop && element[prop]) node[prop] = element[prop]; 2937 | }; 2938 | 2939 | var i; 2940 | if (contents){ 2941 | var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*'); 2942 | for (i = ce.length; i--;) clean(ce[i], te[i]); 2943 | } 2944 | 2945 | clean(clone, this); 2946 | if (Browser.ie){ 2947 | var ts = this.getElementsByTagName('object'), 2948 | cs = clone.getElementsByTagName('object'), 2949 | tl = ts.length, cl = cs.length; 2950 | for (i = 0; i < tl && i < cl; i++) 2951 | cs[i].outerHTML = ts[i].outerHTML; 2952 | } 2953 | return document.id(clone); 2954 | }, 2955 | 2956 | destroy: function(){ 2957 | var children = clean(this).getElementsByTagName('*'); 2958 | Array.each(children, clean); 2959 | Element.dispose(this); 2960 | return null; 2961 | }, 2962 | 2963 | empty: function(){ 2964 | Array.from(this.childNodes).each(Element.dispose); 2965 | return this; 2966 | }, 2967 | 2968 | dispose: function(){ 2969 | return (this.parentNode) ? this.parentNode.removeChild(this) : this; 2970 | }, 2971 | 2972 | match: function(expression){ 2973 | return !expression || Slick.match(this, expression); 2974 | } 2975 | 2976 | }); 2977 | 2978 | var contains = {contains: function(element){ 2979 | return Slick.contains(this, element); 2980 | }}; 2981 | 2982 | if (!document.contains) Document.implement(contains); 2983 | if (!document.createElement('div').contains) Element.implement(contains); 2984 | 2985 | 2986 | 2987 | [Element, Window, Document].invoke('implement', { 2988 | 2989 | addListener: function(type, fn){ 2990 | if (type == 'unload'){ 2991 | var old = fn, self = this; 2992 | fn = function(){ 2993 | self.removeListener('unload', fn); 2994 | old(); 2995 | }; 2996 | } else { 2997 | collected[this.uid] = this; 2998 | } 2999 | if (this.addEventListener) this.addEventListener(type, fn, false); 3000 | else this.attachEvent('on' + type, fn); 3001 | return this; 3002 | }, 3003 | 3004 | removeListener: function(type, fn){ 3005 | if (this.removeEventListener) this.removeEventListener(type, fn, false); 3006 | else this.detachEvent('on' + type, fn); 3007 | return this; 3008 | }, 3009 | 3010 | retrieve: function(property, dflt){ 3011 | var storage = get(this.uid), prop = storage[property]; 3012 | if (dflt != null && prop == null) prop = storage[property] = dflt; 3013 | return prop != null ? prop : null; 3014 | }, 3015 | 3016 | store: function(property, value){ 3017 | var storage = get(this.uid); 3018 | storage[property] = value; 3019 | return this; 3020 | }, 3021 | 3022 | eliminate: function(property){ 3023 | var storage = get(this.uid); 3024 | delete storage[property]; 3025 | return this; 3026 | } 3027 | 3028 | }); 3029 | 3030 | // IE purge 3031 | if (window.attachEvent && !window.addEventListener) window.addListener('unload', function(){ 3032 | Object.each(collected, clean); 3033 | if (window.CollectGarbage) CollectGarbage(); 3034 | }); 3035 | 3036 | })(); 3037 | 3038 | Element.Properties = {}; 3039 | 3040 | 3041 | 3042 | Element.Properties.style = { 3043 | 3044 | set: function(style){ 3045 | this.style.cssText = style; 3046 | }, 3047 | 3048 | get: function(){ 3049 | return this.style.cssText; 3050 | }, 3051 | 3052 | erase: function(){ 3053 | this.style.cssText = ''; 3054 | } 3055 | 3056 | }; 3057 | 3058 | Element.Properties.tag = { 3059 | 3060 | get: function(){ 3061 | return this.tagName.toLowerCase(); 3062 | } 3063 | 3064 | }; 3065 | 3066 | (function(maxLength){ 3067 | if (maxLength != null) Element.Properties.maxlength = Element.Properties.maxLength = { 3068 | get: function(){ 3069 | var maxlength = this.getAttribute('maxLength'); 3070 | return maxlength == maxLength ? null : maxlength; 3071 | } 3072 | }; 3073 | })(document.createElement('input').getAttribute('maxLength')); 3074 | 3075 | Element.Properties.html = (function(){ 3076 | 3077 | var tableTest = Function.attempt(function(){ 3078 | var table = document.createElement('table'); 3079 | table.innerHTML = ''; 3080 | }); 3081 | 3082 | var wrapper = document.createElement('div'); 3083 | 3084 | var translations = { 3085 | table: [1, '', '
'], 3086 | select: [1, ''], 3087 | tbody: [2, '', '
'], 3088 | tr: [3, '', '
'] 3089 | }; 3090 | translations.thead = translations.tfoot = translations.tbody; 3091 | 3092 | var html = { 3093 | set: function(){ 3094 | var html = Array.flatten(arguments).join(''); 3095 | var wrap = (!tableTest && translations[this.get('tag')]); 3096 | if (wrap){ 3097 | var first = wrapper; 3098 | first.innerHTML = wrap[1] + html + wrap[2]; 3099 | for (var i = wrap[0]; i--;) first = first.firstChild; 3100 | this.empty().adopt(first.childNodes); 3101 | } else { 3102 | this.innerHTML = html; 3103 | } 3104 | } 3105 | }; 3106 | 3107 | html.erase = html.set; 3108 | 3109 | return html; 3110 | })(); 3111 | 3112 | 3113 | /* 3114 | --- 3115 | 3116 | name: Element.Style 3117 | 3118 | description: Contains methods for interacting with the styles of Elements in a fashionable way. 3119 | 3120 | license: MIT-style license. 3121 | 3122 | requires: Element 3123 | 3124 | provides: Element.Style 3125 | 3126 | ... 3127 | */ 3128 | 3129 | (function(){ 3130 | 3131 | var html = document.html; 3132 | 3133 | Element.Properties.styles = {set: function(styles){ 3134 | this.setStyles(styles); 3135 | }}; 3136 | 3137 | var hasOpacity = (html.style.opacity != null); 3138 | var reAlpha = /alpha\(opacity=([\d.]+)\)/i; 3139 | 3140 | var setOpacity = function(element, opacity){ 3141 | if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1; 3142 | if (hasOpacity){ 3143 | element.style.opacity = opacity; 3144 | } else { 3145 | opacity = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')'; 3146 | var filter = element.style.filter || element.getComputedStyle('filter') || ''; 3147 | element.style.filter = filter.test(reAlpha) ? filter.replace(reAlpha, opacity) : filter + opacity; 3148 | } 3149 | }; 3150 | 3151 | Element.Properties.opacity = { 3152 | 3153 | set: function(opacity){ 3154 | var visibility = this.style.visibility; 3155 | if (opacity == 0 && visibility != 'hidden') this.style.visibility = 'hidden'; 3156 | else if (opacity != 0 && visibility != 'visible') this.style.visibility = 'visible'; 3157 | 3158 | setOpacity(this, opacity); 3159 | }, 3160 | 3161 | get: (hasOpacity) ? function(){ 3162 | var opacity = this.style.opacity || this.getComputedStyle('opacity'); 3163 | return (opacity == '') ? 1 : opacity; 3164 | } : function(){ 3165 | var opacity, filter = (this.style.filter || this.getComputedStyle('filter')); 3166 | if (filter) opacity = filter.match(reAlpha); 3167 | return (opacity == null || filter == null) ? 1 : (opacity[1] / 100); 3168 | } 3169 | 3170 | }; 3171 | 3172 | var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat'; 3173 | 3174 | Element.implement({ 3175 | 3176 | getComputedStyle: function(property){ 3177 | if (this.currentStyle) return this.currentStyle[property.camelCase()]; 3178 | var defaultView = Element.getDocument(this).defaultView, 3179 | computed = defaultView ? defaultView.getComputedStyle(this, null) : null; 3180 | return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : null; 3181 | }, 3182 | 3183 | setOpacity: function(value){ 3184 | setOpacity(this, value); 3185 | return this; 3186 | }, 3187 | 3188 | getOpacity: function(){ 3189 | return this.get('opacity'); 3190 | }, 3191 | 3192 | setStyle: function(property, value){ 3193 | switch (property){ 3194 | case 'opacity': return this.set('opacity', parseFloat(value)); 3195 | case 'float': property = floatName; 3196 | } 3197 | property = property.camelCase(); 3198 | if (typeOf(value) != 'string'){ 3199 | var map = (Element.Styles[property] || '@').split(' '); 3200 | value = Array.from(value).map(function(val, i){ 3201 | if (!map[i]) return ''; 3202 | return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; 3203 | }).join(' '); 3204 | } else if (value == String(Number(value))){ 3205 | value = Math.round(value); 3206 | } 3207 | this.style[property] = value; 3208 | return this; 3209 | }, 3210 | 3211 | getStyle: function(property){ 3212 | switch (property){ 3213 | case 'opacity': return this.get('opacity'); 3214 | case 'float': property = floatName; 3215 | } 3216 | property = property.camelCase(); 3217 | var result = this.style[property]; 3218 | if (!result || property == 'zIndex'){ 3219 | result = []; 3220 | for (var style in Element.ShortStyles){ 3221 | if (property != style) continue; 3222 | for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s)); 3223 | return result.join(' '); 3224 | } 3225 | result = this.getComputedStyle(property); 3226 | } 3227 | if (result){ 3228 | result = String(result); 3229 | var color = result.match(/rgba?\([\d\s,]+\)/); 3230 | if (color) result = result.replace(color[0], color[0].rgbToHex()); 3231 | } 3232 | if (Browser.opera || (Browser.ie && isNaN(parseFloat(result)))){ 3233 | if (property.test(/^(height|width)$/)){ 3234 | var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; 3235 | values.each(function(value){ 3236 | size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); 3237 | }, this); 3238 | return this['offset' + property.capitalize()] - size + 'px'; 3239 | } 3240 | if (Browser.opera && String(result).indexOf('px') != -1) return result; 3241 | if (property.test(/(border(.+)Width|margin|padding)/)) return '0px'; 3242 | } 3243 | return result; 3244 | }, 3245 | 3246 | setStyles: function(styles){ 3247 | for (var style in styles) this.setStyle(style, styles[style]); 3248 | return this; 3249 | }, 3250 | 3251 | getStyles: function(){ 3252 | var result = {}; 3253 | Array.flatten(arguments).each(function(key){ 3254 | result[key] = this.getStyle(key); 3255 | }, this); 3256 | return result; 3257 | } 3258 | 3259 | }); 3260 | 3261 | Element.Styles = { 3262 | left: '@px', top: '@px', bottom: '@px', right: '@px', 3263 | width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', 3264 | backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', 3265 | fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', 3266 | margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', 3267 | borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', 3268 | zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@' 3269 | }; 3270 | 3271 | 3272 | 3273 | Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; 3274 | 3275 | ['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ 3276 | var Short = Element.ShortStyles; 3277 | var All = Element.Styles; 3278 | ['margin', 'padding'].each(function(style){ 3279 | var sd = style + direction; 3280 | Short[style][sd] = All[sd] = '@px'; 3281 | }); 3282 | var bd = 'border' + direction; 3283 | Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; 3284 | var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; 3285 | Short[bd] = {}; 3286 | Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; 3287 | Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; 3288 | Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; 3289 | }); 3290 | 3291 | })(); 3292 | 3293 | 3294 | /* 3295 | --- 3296 | 3297 | name: Object 3298 | 3299 | description: Object generic methods 3300 | 3301 | license: MIT-style license. 3302 | 3303 | requires: Type 3304 | 3305 | provides: [Object, Hash] 3306 | 3307 | ... 3308 | */ 3309 | 3310 | 3311 | Object.extend({ 3312 | 3313 | subset: function(object, keys){ 3314 | var results = {}; 3315 | for (var i = 0, l = keys.length; i < l; i++){ 3316 | var k = keys[i]; 3317 | results[k] = object[k]; 3318 | } 3319 | return results; 3320 | }, 3321 | 3322 | map: function(object, fn, bind){ 3323 | var results = {}; 3324 | for (var key in object){ 3325 | if (object.hasOwnProperty(key)) results[key] = fn.call(bind, object[key], key, object); 3326 | } 3327 | return results; 3328 | }, 3329 | 3330 | filter: function(object, fn, bind){ 3331 | var results = {}; 3332 | Object.each(object, function(value, key){ 3333 | if (fn.call(bind, value, key, object)) results[key] = value; 3334 | }); 3335 | return results; 3336 | }, 3337 | 3338 | every: function(object, fn, bind){ 3339 | for (var key in object){ 3340 | if (object.hasOwnProperty(key) && !fn.call(bind, object[key], key)) return false; 3341 | } 3342 | return true; 3343 | }, 3344 | 3345 | some: function(object, fn, bind){ 3346 | for (var key in object){ 3347 | if (object.hasOwnProperty(key) && fn.call(bind, object[key], key)) return true; 3348 | } 3349 | return false; 3350 | }, 3351 | 3352 | keys: function(object){ 3353 | var keys = []; 3354 | for (var key in object){ 3355 | if (object.hasOwnProperty(key)) keys.push(key); 3356 | } 3357 | return keys; 3358 | }, 3359 | 3360 | values: function(object){ 3361 | var values = []; 3362 | for (var key in object){ 3363 | if (object.hasOwnProperty(key)) values.push(object[key]); 3364 | } 3365 | return values; 3366 | }, 3367 | 3368 | getLength: function(object){ 3369 | return Object.keys(object).length; 3370 | }, 3371 | 3372 | keyOf: function(object, value){ 3373 | for (var key in object){ 3374 | if (object.hasOwnProperty(key) && object[key] === value) return key; 3375 | } 3376 | return null; 3377 | }, 3378 | 3379 | contains: function(object, value){ 3380 | return Object.keyOf(object, value) != null; 3381 | }, 3382 | 3383 | toQueryString: function(object, base){ 3384 | var queryString = []; 3385 | 3386 | Object.each(object, function(value, key){ 3387 | if (base) key = base + '[' + key + ']'; 3388 | var result; 3389 | switch (typeOf(value)){ 3390 | case 'object': result = Object.toQueryString(value, key); break; 3391 | case 'array': 3392 | var qs = {}; 3393 | value.each(function(val, i){ 3394 | qs[i] = val; 3395 | }); 3396 | result = Object.toQueryString(qs, key); 3397 | break; 3398 | default: result = key + '=' + encodeURIComponent(value); 3399 | } 3400 | if (value != null) queryString.push(result); 3401 | }); 3402 | 3403 | return queryString.join('&'); 3404 | } 3405 | 3406 | }); 3407 | 3408 | 3409 | 3410 | 3411 | 3412 | /* 3413 | --- 3414 | 3415 | name: Event 3416 | 3417 | description: Contains the Event Class, to make the event object cross-browser. 3418 | 3419 | license: MIT-style license. 3420 | 3421 | requires: [Window, Document, Array, Function, String, Object] 3422 | 3423 | provides: Event 3424 | 3425 | ... 3426 | */ 3427 | 3428 | var Event = new Type('Event', function(event, win){ 3429 | if (!win) win = window; 3430 | var doc = win.document; 3431 | event = event || win.event; 3432 | if (event.$extended) return event; 3433 | this.$extended = true; 3434 | var type = event.type, 3435 | target = event.target || event.srcElement, 3436 | page = {}, 3437 | client = {}; 3438 | while (target && target.nodeType == 3) target = target.parentNode; 3439 | 3440 | if (type.indexOf('key') != -1){ 3441 | var code = event.which || event.keyCode; 3442 | var key = Object.keyOf(Event.Keys, code); 3443 | if (type == 'keydown'){ 3444 | var fKey = code - 111; 3445 | if (fKey > 0 && fKey < 13) key = 'f' + fKey; 3446 | } 3447 | if (!key) key = String.fromCharCode(code).toLowerCase(); 3448 | } else if (type.test(/click|mouse|menu/i)){ 3449 | doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; 3450 | page = { 3451 | x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft, 3452 | y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop 3453 | }; 3454 | client = { 3455 | x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX, 3456 | y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY 3457 | }; 3458 | if (type.test(/DOMMouseScroll|mousewheel/)){ 3459 | var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3; 3460 | } 3461 | var rightClick = (event.which == 3) || (event.button == 2), 3462 | related = null; 3463 | if (type.test(/over|out/)){ 3464 | related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element']; 3465 | var testRelated = function(){ 3466 | while (related && related.nodeType == 3) related = related.parentNode; 3467 | return true; 3468 | }; 3469 | var hasRelated = (Browser.firefox2) ? testRelated.attempt() : testRelated(); 3470 | related = (hasRelated) ? related : null; 3471 | } 3472 | } else if (type.test(/gesture|touch/i)){ 3473 | this.rotation = event.rotation; 3474 | this.scale = event.scale; 3475 | this.targetTouches = event.targetTouches; 3476 | this.changedTouches = event.changedTouches; 3477 | var touches = this.touches = event.touches; 3478 | if (touches && touches[0]){ 3479 | var touch = touches[0]; 3480 | page = {x: touch.pageX, y: touch.pageY}; 3481 | client = {x: touch.clientX, y: touch.clientY}; 3482 | } 3483 | } 3484 | 3485 | return Object.append(this, { 3486 | event: event, 3487 | type: type, 3488 | 3489 | page: page, 3490 | client: client, 3491 | rightClick: rightClick, 3492 | 3493 | wheel: wheel, 3494 | 3495 | relatedTarget: document.id(related), 3496 | target: document.id(target), 3497 | 3498 | code: code, 3499 | key: key, 3500 | 3501 | shift: event.shiftKey, 3502 | control: event.ctrlKey, 3503 | alt: event.altKey, 3504 | meta: event.metaKey 3505 | }); 3506 | }); 3507 | 3508 | Event.Keys = { 3509 | 'enter': 13, 3510 | 'up': 38, 3511 | 'down': 40, 3512 | 'left': 37, 3513 | 'right': 39, 3514 | 'esc': 27, 3515 | 'space': 32, 3516 | 'backspace': 8, 3517 | 'tab': 9, 3518 | 'delete': 46 3519 | }; 3520 | 3521 | 3522 | 3523 | Event.implement({ 3524 | 3525 | stop: function(){ 3526 | return this.stopPropagation().preventDefault(); 3527 | }, 3528 | 3529 | stopPropagation: function(){ 3530 | if (this.event.stopPropagation) this.event.stopPropagation(); 3531 | else this.event.cancelBubble = true; 3532 | return this; 3533 | }, 3534 | 3535 | preventDefault: function(){ 3536 | if (this.event.preventDefault) this.event.preventDefault(); 3537 | else this.event.returnValue = false; 3538 | return this; 3539 | } 3540 | 3541 | }); 3542 | 3543 | 3544 | /* 3545 | --- 3546 | 3547 | name: Element.Event 3548 | 3549 | description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events. 3550 | 3551 | license: MIT-style license. 3552 | 3553 | requires: [Element, Event] 3554 | 3555 | provides: Element.Event 3556 | 3557 | ... 3558 | */ 3559 | 3560 | (function(){ 3561 | 3562 | Element.Properties.events = {set: function(events){ 3563 | this.addEvents(events); 3564 | }}; 3565 | 3566 | [Element, Window, Document].invoke('implement', { 3567 | 3568 | addEvent: function(type, fn){ 3569 | var events = this.retrieve('events', {}); 3570 | if (!events[type]) events[type] = {keys: [], values: []}; 3571 | if (events[type].keys.contains(fn)) return this; 3572 | events[type].keys.push(fn); 3573 | var realType = type, 3574 | custom = Element.Events[type], 3575 | condition = fn, 3576 | self = this; 3577 | if (custom){ 3578 | if (custom.onAdd) custom.onAdd.call(this, fn); 3579 | if (custom.condition){ 3580 | condition = function(event){ 3581 | if (custom.condition.call(this, event)) return fn.call(this, event); 3582 | return true; 3583 | }; 3584 | } 3585 | realType = custom.base || realType; 3586 | } 3587 | var defn = function(){ 3588 | return fn.call(self); 3589 | }; 3590 | var nativeEvent = Element.NativeEvents[realType]; 3591 | if (nativeEvent){ 3592 | if (nativeEvent == 2){ 3593 | defn = function(event){ 3594 | event = new Event(event, self.getWindow()); 3595 | if (condition.call(self, event) === false) event.stop(); 3596 | }; 3597 | } 3598 | this.addListener(realType, defn); 3599 | } 3600 | events[type].values.push(defn); 3601 | return this; 3602 | }, 3603 | 3604 | removeEvent: function(type, fn){ 3605 | var events = this.retrieve('events'); 3606 | if (!events || !events[type]) return this; 3607 | var list = events[type]; 3608 | var index = list.keys.indexOf(fn); 3609 | if (index == -1) return this; 3610 | var value = list.values[index]; 3611 | delete list.keys[index]; 3612 | delete list.values[index]; 3613 | var custom = Element.Events[type]; 3614 | if (custom){ 3615 | if (custom.onRemove) custom.onRemove.call(this, fn); 3616 | type = custom.base || type; 3617 | } 3618 | return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this; 3619 | }, 3620 | 3621 | addEvents: function(events){ 3622 | for (var event in events) this.addEvent(event, events[event]); 3623 | return this; 3624 | }, 3625 | 3626 | removeEvents: function(events){ 3627 | var type; 3628 | if (typeOf(events) == 'object'){ 3629 | for (type in events) this.removeEvent(type, events[type]); 3630 | return this; 3631 | } 3632 | var attached = this.retrieve('events'); 3633 | if (!attached) return this; 3634 | if (!events){ 3635 | for (type in attached) this.removeEvents(type); 3636 | this.eliminate('events'); 3637 | } else if (attached[events]){ 3638 | attached[events].keys.each(function(fn){ 3639 | this.removeEvent(events, fn); 3640 | }, this); 3641 | delete attached[events]; 3642 | } 3643 | return this; 3644 | }, 3645 | 3646 | fireEvent: function(type, args, delay){ 3647 | var events = this.retrieve('events'); 3648 | if (!events || !events[type]) return this; 3649 | args = Array.from(args); 3650 | 3651 | events[type].keys.each(function(fn){ 3652 | if (delay) fn.delay(delay, this, args); 3653 | else fn.apply(this, args); 3654 | }, this); 3655 | return this; 3656 | }, 3657 | 3658 | cloneEvents: function(from, type){ 3659 | from = document.id(from); 3660 | var events = from.retrieve('events'); 3661 | if (!events) return this; 3662 | if (!type){ 3663 | for (var eventType in events) this.cloneEvents(from, eventType); 3664 | } else if (events[type]){ 3665 | events[type].keys.each(function(fn){ 3666 | this.addEvent(type, fn); 3667 | }, this); 3668 | } 3669 | return this; 3670 | } 3671 | 3672 | }); 3673 | 3674 | // IE9 3675 | try { 3676 | if (typeof HTMLElement != 'undefined') 3677 | HTMLElement.prototype.fireEvent = Element.prototype.fireEvent; 3678 | } catch(e){} 3679 | 3680 | Element.NativeEvents = { 3681 | click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons 3682 | mousewheel: 2, DOMMouseScroll: 2, //mouse wheel 3683 | mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement 3684 | keydown: 2, keypress: 2, keyup: 2, //keyboard 3685 | orientationchange: 2, // mobile 3686 | touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch 3687 | gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture 3688 | focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements 3689 | load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window 3690 | error: 1, abort: 1, scroll: 1 //misc 3691 | }; 3692 | 3693 | var check = function(event){ 3694 | var related = event.relatedTarget; 3695 | if (related == null) return true; 3696 | if (!related) return false; 3697 | return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related)); 3698 | }; 3699 | 3700 | Element.Events = { 3701 | 3702 | mouseenter: { 3703 | base: 'mouseover', 3704 | condition: check 3705 | }, 3706 | 3707 | mouseleave: { 3708 | base: 'mouseout', 3709 | condition: check 3710 | }, 3711 | 3712 | mousewheel: { 3713 | base: (Browser.firefox) ? 'DOMMouseScroll' : 'mousewheel' 3714 | } 3715 | 3716 | }; 3717 | 3718 | 3719 | 3720 | })(); 3721 | 3722 | 3723 | /* 3724 | --- 3725 | 3726 | name: DOMReady 3727 | 3728 | description: Contains the custom event domready. 3729 | 3730 | license: MIT-style license. 3731 | 3732 | requires: [Browser, Element, Element.Event] 3733 | 3734 | provides: [DOMReady, DomReady] 3735 | 3736 | ... 3737 | */ 3738 | 3739 | (function(window, document){ 3740 | 3741 | var ready, 3742 | loaded, 3743 | checks = [], 3744 | shouldPoll, 3745 | timer, 3746 | isFramed = true; 3747 | 3748 | // Thanks to Rich Dougherty 3749 | try { 3750 | isFramed = window.frameElement != null; 3751 | } catch(e){} 3752 | 3753 | var domready = function(){ 3754 | clearTimeout(timer); 3755 | if (ready) return; 3756 | Browser.loaded = ready = true; 3757 | document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check); 3758 | 3759 | document.fireEvent('domready'); 3760 | window.fireEvent('domready'); 3761 | }; 3762 | 3763 | var check = function(){ 3764 | for (var i = checks.length; i--;) if (checks[i]()){ 3765 | domready(); 3766 | return true; 3767 | } 3768 | 3769 | return false; 3770 | }; 3771 | 3772 | var poll = function(){ 3773 | clearTimeout(timer); 3774 | if (!check()) timer = setTimeout(poll, 10); 3775 | }; 3776 | 3777 | document.addListener('DOMContentLoaded', domready); 3778 | 3779 | // doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/ 3780 | var testElement = document.createElement('div'); 3781 | if (testElement.doScroll && !isFramed){ 3782 | checks.push(function(){ 3783 | try { 3784 | testElement.doScroll(); 3785 | return true; 3786 | } catch (e){} 3787 | 3788 | return false; 3789 | }); 3790 | shouldPoll = true; 3791 | } 3792 | 3793 | if (document.readyState) checks.push(function(){ 3794 | var state = document.readyState; 3795 | return (state == 'loaded' || state == 'complete'); 3796 | }); 3797 | 3798 | if ('onreadystatechange' in document) document.addListener('readystatechange', check); 3799 | else shouldPoll = true; 3800 | 3801 | if (shouldPoll) poll(); 3802 | 3803 | Element.Events.domready = { 3804 | onAdd: function(fn){ 3805 | if (ready) fn.call(this); 3806 | } 3807 | }; 3808 | 3809 | // Make sure that domready fires before load 3810 | Element.Events.load = { 3811 | base: 'load', 3812 | onAdd: function(fn){ 3813 | if (loaded && this == window) fn.call(this); 3814 | }, 3815 | condition: function(){ 3816 | if (this == window){ 3817 | domready(); 3818 | delete Element.Events.load; 3819 | } 3820 | 3821 | return true; 3822 | } 3823 | }; 3824 | 3825 | // This is based on the custom load event 3826 | window.addEvent('load', function(){ 3827 | loaded = true; 3828 | }); 3829 | 3830 | })(window, document); 3831 | 3832 | -------------------------------------------------------------------------------- /Demos/pinch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MooTools Mobile 5 | 6 | 7 | 8 | 9 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 47 | 48 | 49 |
50 | Demos 51 | Home 52 | Swipe 53 | Pinch 54 | Touchhold 55 |
56 |
57 | Pinch 58 |
59 | 60 | -------------------------------------------------------------------------------- /Demos/style.css: -------------------------------------------------------------------------------- 1 | /* reset.css */ 2 | html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} 3 | body {line-height:1.5;} 4 | table {border-collapse:separate;border-spacing:0;} 5 | caption, th, td {text-align:left;font-weight:normal;} 6 | table, td, th {vertical-align:middle;} 7 | blockquote:before, blockquote:after, q:before, q:after {content:"";} 8 | blockquote, q {quotes:"" "";} 9 | a img {border:none;} 10 | 11 | /* typography.css */ 12 | html {font-size:100.01%;} 13 | body {font-size:80%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;} 14 | h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;} 15 | h1 {font-size:3em;line-height:1;margin-bottom:0.5em;} 16 | h2 {font-size:2em;margin-bottom:0.75em;} 17 | h3 {font-size:1.5em;line-height:1;margin-bottom:1em;} 18 | h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;} 19 | h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;} 20 | h6 {font-size:1em;font-weight:bold;} 21 | h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;} 22 | p {margin:0 0 1.5em;} 23 | p img.left {float:left;margin:1.5em 1.5em 1.5em 0;padding:0;} 24 | p img.right {float:right;margin:1.5em 0 1.5em 1.5em;} 25 | a:focus, a:hover {color:#000;} 26 | a {color:#009;text-decoration:underline;} 27 | blockquote {margin:1.5em;color:#666;font-style:italic;} 28 | strong {font-weight:bold;} 29 | em, dfn {font-style:italic;} 30 | dfn {font-weight:bold;} 31 | sup, sub {line-height:0;} 32 | abbr, acronym {border-bottom:1px dotted #666;} 33 | address {margin:0 0 1.5em;font-style:italic;} 34 | del {color:#666;} 35 | pre {margin:1.5em 0;white-space:pre;} 36 | pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;} 37 | li ul, li ol {margin:0;} 38 | ul, ol {margin:0 1.5em 1.5em 0;padding-left:3.333em;} 39 | ul {list-style-type:disc;} 40 | ol {list-style-type:decimal;} 41 | 42 | /* CUSTOM */ 43 | body { 44 | -webkit-tap-highlight-color: transparent; 45 | 46 | background-color: #f2f2f2; 47 | } 48 | 49 | body > div { 50 | margin: 10px; 51 | } 52 | 53 | a.link, div#more a { 54 | font-weight: bold; 55 | text-align: center; 56 | margin: 5px 0; 57 | display: block; 58 | background: #4791d9; 59 | text-decoration: none; 60 | color: #fff; 61 | padding: 4px; 62 | outline: 0; 63 | border: 0; 64 | -moz-border-radius: 4px; 65 | -webkit-border-radius: 4px; 66 | -o-border-radius: 4px; 67 | -ms-border-radius: 4px; 68 | -khtml-border-radius: 4px; 69 | border-radius: 4px; 70 | -moz-transition-duration: 0.25s; 71 | -webkit-transition-duration: 0.25s; 72 | -o-transition-duration: 0.25s; 73 | transition-duration: 0.25s; 74 | -webkit-transition-property: -webkit-transform; 75 | -moz-transition-property: -moz-transform; 76 | -o-transition-property: -o-transform; 77 | transition-property: transform; 78 | -webkit-transform: scale(1) rotate(0); 79 | -moz-transform: scale(1) rotate(0); 80 | -o-transform: scale(1) rotate(0); 81 | transform: scale(1) rotate(0); 82 | } 83 | 84 | a.link:hover, div#more a:hover { 85 | background: #4791d9; 86 | text-decoration: none; 87 | color: #fff; 88 | -webkit-transform: scale(1.05) rotate(-1deg); 89 | -moz-transform: scale(1.05) rotate(-1deg); 90 | -o-transform: scale(1.05) rotate(-1deg); 91 | transform: scale(1.05) rotate(-1deg); 92 | } 93 | 94 | a.link:nth-child(2n):hover, div#more a:nth-child(2n):hover { 95 | -webkit-transform: scale(1.05) rotate(1deg); 96 | -moz-transform: scale(1.05) rotate(1deg); 97 | -o-transform: scale(1.05) rotate(1deg); 98 | transform: scale(1.05) rotate(1deg); 99 | } 100 | 101 | div.box { 102 | width: 20em; 103 | height: 20em; 104 | 105 | line-height: 20em; 106 | text-align: center; 107 | font-size: 2em; 108 | 109 | background: #a647db; 110 | color: #fff; 111 | } 112 | 113 | @media only screen and (max-device-width: 480px) { 114 | div.box { 115 | width: 10em; 116 | height: 10em; 117 | 118 | line-height: 10em; 119 | font-size: 5em; 120 | } 121 | } -------------------------------------------------------------------------------- /Demos/swipe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MooTools Mobile 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 32 | 33 | 34 |
35 | Demos 36 | Home 37 | Swipe 38 | Pinch 39 | Touchhold 40 |
41 |
42 | Swipe 43 |
44 | 45 | -------------------------------------------------------------------------------- /Demos/touchhold.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MooTools Mobile 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 37 | 38 | 39 |
40 | Demos 41 | Home 42 | Swipe 43 | Pinch 44 | Touchhold 45 |
46 |
47 | Touchhold 48 |
49 | 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MooTools Mobile - MIT License 2 | 3 | Copyright (c) 2011 Christoph Pojer 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Mobile 2 | ====== 3 | 4 | Makes your web applications more touching. Provides custom events and useful browser information for mobile web (application) development. On iOS it provides a touch event handler that automatically replaces all your click handlers with touch events to overcome the ~300ms click delay. Requires MooTools Core 1.3. 5 | 6 | ![Screenshot](http://cpojer.net/Logo/mobile.png) 7 | 8 | This Plugin is part of MooTools [PowerTools!](http://cpojer.net/PowerTools). 9 | 10 | * [Build PowerTools!](http://cpojer.net/PowerTools) 11 | * [Fork PowerTools!](https://github.com/cpojer/PowerTools) 12 | 13 | Build 14 | ----- 15 | 16 | Build via [Packager](http://github.com/kamicane/packager), requires [MooTools Core](http://github.com/mootools/mootools-core) and [MooTools Custom Event](http://github.com/cpojer/mootools-custom-event) to be registered to Packager already 17 | 18 | packager register /path/to/mobile 19 | packager build Mobile/* > mobile.js 20 | 21 | To build this plugin without external dependencies use 22 | 23 | packager build Mobile/* +use-only Mobile > mobile.js 24 | 25 | Supported Devices 26 | ----------------- 27 | 28 | Tested and supported are the following devices: 29 | 30 | * **iOS** 31 | * **iPhone 4** with iOS 4.0.2 32 | * **iPad** with iOS 3.2.2 33 | * **iPod Touch 2g** with iOS 4.0.1 34 | 35 | * **Android** (see Notes) 36 | * **HTC Magic** with Android 2.2 (Cyanogenmod6): Android Browser, Dolfin HD 37 | * **Motorola Droid / Milestone** with Android 2.1: Android Browser 38 | * **Nexus One** with Android 2.2 (Cyanogenmod6): Android Browser 39 | * **HTC Desire** with Android 2.2: Android Browser 40 | * **Samsung Galaxy S** with Android 2.1: Android Browser 41 | 42 | * Notes (Android) 43 | * Multitouch in the browser is currently not available on (most?) Android phones. 44 | * The custom pinch event will not work if the browser has pinch-to-zoom (See Cyanogenmod). It does work on more recent Android models. 45 | * Dolphin HD slides to the right/left which can be disabled in the settings. Swipe does not work if this setting is enabled. Pinch does not work yet even though the browser has multitouch support. A fix will may be added in the future. 46 | * TODO try to fix: Not all Android devices prevent text-selection for the touchhold event. 47 | 48 | Note: no other mobile browsers support touch events currently. 49 | 50 | How To Use 51 | ---------- 52 | 53 | If you include Touch/Click.js (and dependencies) into your application, all click events will automatically be replaced with touch events. 54 | 55 | myElement.addEvent('click', function(event){ 56 | // on iOS the click handler has been replaced with touchend 57 | 58 | // doSomething 59 | }): 60 | 61 | For more information see the included Demo. 62 | 63 | Touch Custom Event 64 | ------------------ 65 | 66 | The replacement for click events is optional. If you choose to include Touch/Touch.js (and dependencies) without Click.js, your click event listeners will stay untouched and you get a custom 'touch' event instead. 67 | 68 | myElement.addEvent('touch', function(event){ 69 | // Now this only does work on devices with touch support 70 | }); 71 | 72 | The requirement for the touch and click events to fire is to start and end the touch on the same element. 73 | 74 | Swipe Custom Event 75 | ------------------ 76 | 77 | The file Touch/Swipe.js provides a custom swipe event for your elements. Only works for 'left' and 'right' as moving up and down is reserved for scrolling. 78 | 79 | myElement.addEvent('swipe', function(event){ 80 | event.direction // either 'left' or 'right' 81 | 82 | event.start // {x: Number, y: Number} where the swipe started 83 | event.end // {x: Number, y: Number} where the swipe ended 84 | }); 85 | 86 | Additionally there are some options for swipe events 87 | 88 | myElement.store('swipe:distance', 20); // (defaults to 50) amount of pixels to be moved until swipe is being fired 89 | myElement.store('swipe:cancelVertical', true); // (defaults to false) Whether to cancel swipes if the user moved vertically 90 | 91 | Pinch Custom Event 92 | ------------------ 93 | 94 | The file Touch/Pinch.js provides a custom pinch event for your elements 95 | 96 | myElement.addEvent('pinch', function(event){ 97 | event.pinch // Either 'in' or 'out' 98 | }); 99 | 100 | Additionally there is a threshold option for pinch events 101 | 102 | myElement.store('pinch:threshold', 0.4); // (defaults to 0.5) the amount of scaling to be done to fire the pinch event 103 | 104 | Touchhold Custom Event 105 | ---------------------- 106 | 107 | The file Touch/Touchhold.js provides a custom touchhold event for your elements 108 | 109 | myElement.addEvent('touchhold', function(event){ 110 | // Touchhold fired 111 | }); 112 | 113 | Additionally there is delay option for touchhold events 114 | 115 | myElement.store('touchhold:delay', 1000); // (defaults to 750) the amount of time in ms to wait until the touchhold event gets fired 116 | 117 | Browser Information 118 | ------------------- 119 | 120 | To support "touchhold" and "swipe" on desktop devices without touch input you can include Desktop/Mouse.js. It maps mouse* events to touch* events. 121 | 122 | To execute code on browsers with touch events available use Browser/Features.Touch.js 123 | 124 | if (Browser.Features.Touch){ 125 | // This browser has touch events! 126 | } 127 | 128 | Note that Chrome 5 reports to support touch events. This behavior has been fixed in Chrome 6 at least when no touch input devices are available. The "iOSTouch" property is only true on more sophisticated platforms such as mobile safari. This property is useful to detect whether click events should be replaced with touch events on iOS. You shouldn't need to access this property, but you may find it useful. 129 | 130 | if (Browser.Features.iOSTouch){ 131 | // This browser has touch events 132 | } 133 | 134 | Access useful information about the browser environment via Browser/Mobile.js 135 | 136 | Browser.Device // Object added to Browser 137 | 138 | Browser.Device.name // ipad / iphone / ipod OR other 139 | 140 | Browser.Device.ipad // true if on ipad 141 | Browser.Device.iphone // true if on iphone 142 | Browser.Device.ipod // true if on ipod 143 | 144 | Browser.hasHighResolution // true on iPhone 4 145 | 146 | Browser.isMobile // True on any platform that is not Windows / Linux / Mac 147 | 148 | Tips 149 | ---- 150 | 151 | For an optimal experience use the following CSS Styles 152 | 153 | body { 154 | -webkit-tap-highlight-color: transparent; 155 | } 156 | 157 | For elements with touch events use 158 | 159 | #myElement { 160 | -webkit-user-select: none; 161 | } 162 | 163 | In addition to that, because the code uses "document.elementFromPoint", it is wise to set pointer-events to none for elements within click handler elements. Usually "a *" as a selector is sufficient, but it should be applied to any overlaying elements that might get in your way. 164 | 165 | a * { 166 | pointer-events: none; 167 | } 168 | 169 | Also, to prevent moving the whole page in iOS you can add this script 170 | 171 | document.addEvent('touchmove', function(event){ 172 | event.preventDefault(); 173 | }); 174 | 175 | ToDo 176 | ---- 177 | 178 | * Click overwrite should probably pass a fake click event (?) 179 | * Add useful Android information 180 | * Add webOS support and add useful webOS information -------------------------------------------------------------------------------- /Source/Browser/Features.Touch.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Browser.Features.Touch 5 | 6 | description: Checks whether the used Browser has touch events 7 | 8 | authors: Christoph Pojer (@cpojer) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Core/Browser] 13 | 14 | provides: Browser.Features.Touch 15 | 16 | ... 17 | */ 18 | 19 | Browser.Features.Touch = (function(){ 20 | return true == ('ontouchstart' in window || window.DocumentTouch && document instanceof DocumentTouch); 21 | })(); 22 | 23 | // Android doesn't have a touch delay and dispatchEvent does not fire the handler 24 | Browser.Features.iOSTouch = (function(){ 25 | var name = 'cantouch', // Name does not matter 26 | html = document.html, 27 | hasTouch = false; 28 | 29 | if (!html.addEventListener) return false; 30 | 31 | var handler = function(){ 32 | html.removeEventListener(name, handler, true); 33 | hasTouch = true; 34 | }; 35 | 36 | try { 37 | html.addEventListener(name, handler, true); 38 | var event = document.createEvent('TouchEvent'); 39 | event.initTouchEvent(name); 40 | html.dispatchEvent(event); 41 | return hasTouch; 42 | } catch (exception){} 43 | 44 | handler(); // Remove listener 45 | return false; 46 | })(); 47 | -------------------------------------------------------------------------------- /Source/Browser/Mobile.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Browser.Mobile 5 | 6 | description: Provides useful information about the browser environment 7 | 8 | authors: Christoph Pojer (@cpojer) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Core/Browser] 13 | 14 | provides: Browser.Mobile 15 | 16 | ... 17 | */ 18 | 19 | (function(){ 20 | 21 | Browser.Device = { 22 | name: 'other' 23 | }; 24 | 25 | if (Browser.ios){ 26 | var device = navigator.userAgent.toLowerCase().match(/(ip(ad|od|hone))/)[0]; 27 | 28 | Browser.Device[device] = true; 29 | Browser.Device.name = device; 30 | } 31 | 32 | if (this.devicePixelRatio == 2) 33 | Browser.hasHighResolution = true; 34 | 35 | Browser.isMobile = !['mac', 'linux', 'win'].contains(Browser.name); 36 | 37 | }).call(this); 38 | -------------------------------------------------------------------------------- /Source/Desktop/Mouse.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Mouse 5 | 6 | description: Maps mouse events to their touch counterparts 7 | 8 | authors: Christoph Pojer (@cpojer) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] 13 | 14 | provides: Mouse 15 | 16 | ... 17 | */ 18 | 19 | if (!Browser.Features.Touch) (function(){ 20 | 21 | var down = false; 22 | var condition = function(event, type){ 23 | if (type == 'touchstart') down = true; 24 | else if (type == 'touchend') down = false; 25 | else if (type == 'touchmove' && !down) return false; 26 | 27 | event.targetTouches = []; 28 | event.changedTouches = event.touches = [{ 29 | pageX: event.page.x, pageY: event.page.y, 30 | clientX: event.client.x, clientY: event.client.y 31 | }]; 32 | 33 | return true; 34 | }; 35 | 36 | Element.defineCustomEvent('touchstart', { 37 | 38 | base: 'mousedown', 39 | condition: condition 40 | 41 | }).defineCustomEvent('touchmove', { 42 | 43 | base: 'mousemove', 44 | condition: condition 45 | 46 | }).defineCustomEvent('touchend', { 47 | 48 | base: 'mouseup', 49 | condition: condition 50 | 51 | }); 52 | 53 | document.addEvent('mouseup', function() { 54 | down = false; 55 | }); 56 | 57 | })(); 58 | -------------------------------------------------------------------------------- /Source/Touch/Click.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Click 5 | 6 | description: Provides a replacement for click events on mobile devices 7 | 8 | authors: Christoph Pojer (@cpojer) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Touch] 13 | 14 | provides: Click 15 | 16 | ... 17 | */ 18 | 19 | if (Browser.Features.iOSTouch) (function(){ 20 | 21 | var name = 'click'; 22 | delete Element.NativeEvents[name]; 23 | 24 | Element.defineCustomEvent(name, { 25 | 26 | base: 'touch' 27 | 28 | }); 29 | 30 | })(); 31 | -------------------------------------------------------------------------------- /Source/Touch/Pinch.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Pinch 5 | 6 | description: Provides a custom pinch event for touch devices 7 | 8 | authors: Christopher Beloch (@C_BHole), Christoph Pojer (@cpojer) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Core/Element.Event, Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] 13 | 14 | provides: Pinch 15 | 16 | ... 17 | */ 18 | 19 | if (Browser.Features.Touch) (function(){ 20 | 21 | var name = 'pinch', 22 | thresholdKey = name + ':threshold', 23 | disabled, active; 24 | 25 | var events = { 26 | 27 | touchstart: function(event){ 28 | if (event.targetTouches.length == 2) active = true; 29 | }, 30 | 31 | touchmove: function(event){ 32 | if (disabled || !active) return; 33 | 34 | event.preventDefault(); 35 | 36 | var threshold = this.retrieve(thresholdKey, 0.5); 37 | if (event.scale < (1 + threshold) && event.scale > (1 - threshold)) return; 38 | 39 | active = false; 40 | event.pinch = (event.scale > 1) ? 'in' : 'out'; 41 | this.fireEvent(name, event); 42 | } 43 | 44 | }; 45 | 46 | Element.defineCustomEvent(name, { 47 | 48 | onSetup: function(){ 49 | this.addEvents(events); 50 | }, 51 | 52 | onTeardown: function(){ 53 | this.removeEvents(events); 54 | }, 55 | 56 | onEnable: function(){ 57 | disabled = false; 58 | }, 59 | 60 | onDisable: function(){ 61 | disabled = true; 62 | } 63 | 64 | }); 65 | 66 | })(); 67 | -------------------------------------------------------------------------------- /Source/Touch/Swipe.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Swipe 5 | 6 | description: Provides a custom swipe event for touch devices 7 | 8 | authors: Christopher Beloch (@C_BHole), Christoph Pojer (@cpojer), Ian Collins (@3n) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Core/Element.Event, Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] 13 | 14 | provides: Swipe 15 | 16 | ... 17 | */ 18 | 19 | (function(){ 20 | 21 | var name = 'swipe', 22 | distanceKey = name + ':distance', 23 | cancelKey = name + ':cancelVertical', 24 | dflt = 50; 25 | 26 | var start = {}, disabled, active; 27 | 28 | var clean = function(){ 29 | active = false; 30 | }; 31 | 32 | var events = { 33 | 34 | touchstart: function(event){ 35 | if (event.touches.length > 1) return; 36 | 37 | var touch = event.touches[0]; 38 | active = true; 39 | start = {x: touch.pageX, y: touch.pageY}; 40 | }, 41 | 42 | touchmove: function(event){ 43 | if (disabled || !active) return; 44 | 45 | var touch = event.changedTouches[0], 46 | end = {x: touch.pageX, y: touch.pageY}; 47 | if (this.retrieve(cancelKey) && Math.abs(start.y - end.y) > 10){ 48 | active = false; 49 | return; 50 | } 51 | 52 | var distance = this.retrieve(distanceKey, dflt), 53 | delta = end.x - start.x, 54 | isLeftSwipe = delta < -distance, 55 | isRightSwipe = delta > distance; 56 | 57 | if (!isRightSwipe && !isLeftSwipe) 58 | return; 59 | 60 | event.preventDefault(); 61 | active = false; 62 | event.direction = (isLeftSwipe ? 'left' : 'right'); 63 | event.start = start; 64 | event.end = end; 65 | 66 | this.fireEvent(name, event); 67 | }, 68 | 69 | touchend: clean, 70 | touchcancel: clean 71 | 72 | }; 73 | 74 | Element.defineCustomEvent(name, { 75 | 76 | onSetup: function(){ 77 | this.addEvents(events); 78 | }, 79 | 80 | onTeardown: function(){ 81 | this.removeEvents(events); 82 | }, 83 | 84 | onEnable: function(){ 85 | disabled = false; 86 | }, 87 | 88 | onDisable: function(){ 89 | disabled = true; 90 | clean(); 91 | } 92 | 93 | }); 94 | 95 | })(); 96 | -------------------------------------------------------------------------------- /Source/Touch/Touch.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Touch 5 | 6 | description: Provides a custom touch event on mobile devices 7 | 8 | authors: Christoph Pojer (@cpojer) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Core/Element.Event, Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] 13 | 14 | provides: Touch 15 | 16 | ... 17 | */ 18 | 19 | (function(){ 20 | 21 | var disabled; 22 | 23 | Element.defineCustomEvent('touch', { 24 | 25 | base: 'touchend', 26 | 27 | condition: function(event){ 28 | if (disabled || event.targetTouches.length !== 0) return false; 29 | 30 | var touch = event.changedTouches[0], 31 | target = document.elementFromPoint(touch.clientX, touch.clientY); 32 | 33 | do { 34 | if (target == this) return true; 35 | } while (target && (target = target.parentNode)); 36 | 37 | return false; 38 | }, 39 | 40 | onEnable: function(){ 41 | disabled = false; 42 | }, 43 | 44 | onDisable: function(){ 45 | disabled = true; 46 | } 47 | 48 | }); 49 | 50 | })(); 51 | -------------------------------------------------------------------------------- /Source/Touch/Touchhold.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | 4 | name: Touchhold 5 | 6 | description: Provides a custom touchhold event for touch devices 7 | 8 | authors: Christoph Pojer (@cpojer) 9 | 10 | license: MIT-style license. 11 | 12 | requires: [Core/Element.Event, Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] 13 | 14 | provides: Touchhold 15 | 16 | ... 17 | */ 18 | 19 | (function(){ 20 | 21 | var name = 'touchhold', 22 | delayKey = name + ':delay', 23 | disabled, timer; 24 | 25 | var clear = function(e){ 26 | clearTimeout(timer); 27 | }; 28 | 29 | var events = { 30 | 31 | touchstart: function(event){ 32 | if (event.touches.length > 1){ 33 | clear(); 34 | return; 35 | } 36 | 37 | timer = (function(){ 38 | this.fireEvent(name, event); 39 | }).delay(this.retrieve(delayKey) || 750, this); 40 | }, 41 | 42 | touchmove: clear, 43 | touchcancel: clear, 44 | touchend: clear 45 | 46 | }; 47 | 48 | Element.defineCustomEvent(name, { 49 | 50 | onSetup: function(){ 51 | this.addEvents(events); 52 | }, 53 | 54 | onTeardown: function(){ 55 | this.removeEvents(events); 56 | }, 57 | 58 | onEnable: function(){ 59 | disabled = false; 60 | }, 61 | 62 | onDisable: function(){ 63 | disabled = true; 64 | clear(); 65 | } 66 | 67 | }); 68 | 69 | })(); 70 | -------------------------------------------------------------------------------- /package.yml: -------------------------------------------------------------------------------- 1 | name: "Mobile" 2 | 3 | exports: "mobile.js" 4 | 5 | web: "[cpojer.net](http://cpojer.net)" 6 | demo: http://cpojer.net/MooTools/mobile/Demos/ 7 | 8 | description: "Makes your web applications more touching." 9 | 10 | license: "[MIT License](http://mootools.net/license.txt)" 11 | 12 | copyright: "© [Christoph Pojer](http://cpojer.net/)" 13 | 14 | author: "cpojer" 15 | authors: "[Christoph Pojer](http://cpojer.net)" 16 | category: Utilities 17 | tags: [mobile, browser, touch, swipe, pinch, touchhold] 18 | 19 | sources: 20 | - "Source/Browser/Mobile.js" 21 | - "Source/Browser/Features.Touch.js" 22 | - "Source/Desktop/Mouse.js" 23 | - "Source/Touch/Touch.js" 24 | - "Source/Touch/Click.js" 25 | - "Source/Touch/Pinch.js" 26 | - "Source/Touch/Swipe.js" 27 | - "Source/Touch/Touchhold.js" 28 | --------------------------------------------------------------------------------