1/* 2 * JavaScript Custom Forms Module 3 */ 4jcf = { 5 // global options 6 modules: {}, 7 plugins: {}, 8 baseOptions: { 9 unselectableClass:'jcf-unselectable', 10 labelActiveClass:'jcf-label-active', 11 labelDisabledClass:'jcf-label-disabled', 12 classPrefix: 'jcf-class-', 13 hiddenClass:'jcf-hidden', 14 focusClass:'jcf-focus', 15 wrapperTag: 'div' 16 }, 17 // replacer function 18 customForms: { 19 setOptions: function(obj) { 20 for(var p in obj) { 21 if(obj.hasOwnProperty(p) && typeof obj[p] === 'object') { 22 jcf.lib.extend(jcf.modules[p].prototype.defaultOptions, obj[p]); 23 } 24 } 25 }, 26 replaceAll: function() { 27 for(var k in jcf.modules) { 28 var els = jcf.lib.queryBySelector(jcf.modules[k].prototype.selector); 29 for(var i = 0; i<els.length; i++) { 30 if(els[i].jcf) { 31 // refresh form element state 32 els[i].jcf.refreshState(); 33 } else { 34 // replace form element 35 if(!jcf.lib.hasClass(els[i], 'default') && jcf.modules[k].prototype.checkElement(els[i])) { 36 new jcf.modules[k]({ 37 replaces:els[i] 38 }); 39 } 40 } 41 } 42 } 43 }, 44 refreshAll: function() { 45 for(var k in jcf.modules) { 46 var els = jcf.lib.queryBySelector(jcf.modules[k].prototype.selector); 47 for(var i = 0; i<els.length; i++) { 48 if(els[i].jcf) { 49 // refresh form element state 50 els[i].jcf.refreshState(); 51 } 52 } 53 } 54 }, 55 refreshElement: function(obj) { 56 if(obj && obj.jcf) { 57 obj.jcf.refreshState(); 58 } 59 }, 60 destroyAll: function() { 61 for(var k in jcf.modules) { 62 var els = jcf.lib.queryBySelector(jcf.modules[k].prototype.selector); 63 for(var i = 0; i<els.length; i++) { 64 if(els[i].jcf) { 65 els[i].jcf.destroy(); 66 } 67 } 68 } 69 } 70 }, 71 // detect device type 72 isTouchDevice: ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch, 73 isWinPhoneDevice: navigator.msPointerEnabled && /MSIE 10.*Touch/.test(navigator.userAgent), 74 // define base module 75 setBaseModule: function(obj) { 76 jcf.customControl = function(opt){ 77 this.options = jcf.lib.extend({}, jcf.baseOptions, this.defaultOptions, opt); 78 this.init(); 79 }; 80 for(var p in obj) { 81 jcf.customControl.prototype[p] = obj[p]; 82 } 83 }, 84 // add module to jcf.modules 85 addModule: function(obj) { 86 if(obj.name){ 87 // create new module proto class 88 jcf.modules[obj.name] = function(){ 89 jcf.modules[obj.name].superclass.constructor.apply(this, arguments); 90 }; 91 jcf.lib.inherit(jcf.modules[obj.name], jcf.customControl); 92 for(var p in obj) { 93 jcf.modules[obj.name].prototype[p] = obj[p]; 94 } 95 // on create module 96 jcf.modules[obj.name].prototype.onCreateModule(); 97 // make callback for exciting modules 98 for(var mod in jcf.modules) { 99 if(jcf.modules[mod] != jcf.modules[obj.name]) { 100 jcf.modules[mod].prototype.onModuleAdded(jcf.modules[obj.name]); 101 } 102 } 103 } 104 }, 105 // add plugin to jcf.plugins 106 addPlugin: function(obj) { 107 if(obj && obj.name) { 108 jcf.plugins[obj.name] = function() { 109 this.init.apply(this, arguments); 110 }; 111 for(var p in obj) { 112 jcf.plugins[obj.name].prototype[p] = obj[p]; 113 } 114 } 115 }, 116 // miscellaneous init 117 init: function(){ 118 if(navigator.msPointerEnabled) { 119 this.eventPress = 'MSPointerDown'; 120 this.eventMove = 'MSPointerMove'; 121 this.eventRelease = 'MSPointerUp'; 122 } else { 123 this.eventPress = this.isTouchDevice ? 'touchstart' : 'mousedown'; 124 this.eventMove = this.isTouchDevice ? 'touchmove' : 'mousemove'; 125 this.eventRelease = this.isTouchDevice ? 'touchend' : 'mouseup'; 126 } 127 128 // init jcf styles 129 setTimeout(function(){ 130 jcf.lib.domReady(function(){ 131 jcf.initStyles(); 132 }); 133 },1); 134 return this; 135 }, 136 initStyles: function() { 137 // create <style> element and rules 138 var head = document.getElementsByTagName('head')[0], 139 style = document.createElement('style'), 140 rules = document.createTextNode('.'+jcf.baseOptions.unselectableClass+'{'+ 141 '-moz-user-select:none;'+ 142 '-webkit-tap-highlight-color:rgba(255,255,255,0);'+ 143 '-webkit-user-select:none;'+ 144 'user-select:none;'+ 145 '}'); 146 147 // append style element 148 style.type = 'text/css'; 149 if(style.styleSheet) { 150 style.styleSheet.cssText = rules.nodeValue; 151 } else { 152 style.appendChild(rules); 153 } 154 head.appendChild(style); 155 } 156}.init(); 157 158/* 159 * Custom Form Control prototype 160 */ 161jcf.setBaseModule({ 162 init: function(){ 163 if(this.options.replaces) { 164 this.realElement = this.options.replaces; 165 this.realElement.jcf = this; 166 this.replaceObject(); 167 } 168 }, 169 defaultOptions: { 170 // default module options (will be merged with base options) 171 }, 172 checkElement: function(el){ 173 return true;
173 // additional check for correct form element 174 }, 175 replaceObject: function(){ 176 this.createWrapper(); 177 this.attachEvents(); 178 this.fixStyles(); 179 this.setupWrapper(); 180 }, 181 createWrapper: function(){ 182 this.fakeElement = jcf.lib.createElement(this.options.wrapperTag); 183 this.labelFor = jcf.lib.getLabelFor(this.realElement); 184 jcf.lib.disableTextSelection(this.fakeElement); 185 jcf.lib.addClass(this.fakeElement, jcf.lib.getAllClasses(this.realElement.className, this.options.classPrefix)); 186 jcf.lib.addClass(this.realElement, jcf.baseOptions.hiddenClass); 187 }, 188 attachEvents: function(){ 189 jcf.lib.event.add(this.realElement, 'focus', this.onFocusHandler, this); 190 jcf.lib.event.add(this.realElement, 'blur', this.onBlurHandler, this); 191 jcf.lib.event.add(this.fakeElement, 'click', this.onFakeClick, this); 192 jcf.lib.event.add(this.fakeElement, jcf.eventPress, this.onFakePressed, this); 193 jcf.lib.event.add(this.fakeElement, jcf.eventRelease, this.onFakeReleased, this); 194 195 if(this.labelFor) { 196 this.labelFor.jcf = this; 197 jcf.lib.event.add(this.labelFor, 'click', this.onFakeClick, this); 198 jcf.lib.event.add(this.labelFor, jcf.eventPress, this.onFakePressed, this); 199 jcf.lib.event.add(this.labelFor, jcf.eventRelease, this.onFakeReleased, this); 200 } 201 }, 202 fixStyles: function() { 203 // hide mobile webkit tap effect 204 if(jcf.isTouchDevice) { 205 var tapStyle = 'rgba(255,255,255,0)'; 206 this.realElement.style.webkitTapHighlightColor = tapStyle; 207 this.fakeElement.style.webkitTapHighlightColor = tapStyle; 208 if(this.labelFor) { 209 this.labelFor.style.webkitTapHighlightColor = tapStyle; 210 } 211 } 212 }, 213 setupWrapper: function(){ 214 // implement in subclass 215 }, 216 refreshState: function(){ 217 // implement in subclass 218 }, 219 destroy: function() { 220 if(this.fakeElement && this.fakeElement.parentNode) { 221 this.fakeElement.parentNode.removeChild(this.fakeElement); 222 } 223 jcf.lib.removeClass(this.realElement, jcf.baseOptions.hiddenClass); 224 this.realElement.jcf = null; 225 }, 226 onFocus: function(){ 227 // emulated focus event 228 jcf.lib.addClass(this.fakeElement,this.options.focusClass); 229 }, 230 onBlur: function(cb){ 231 // emulated blur event 232 jcf.lib.removeClass(this.fakeElement,this.options.focusClass); 233 }, 234 onFocusHandler: function() { 235 // handle focus loses 236 if(this.focused) return; 237 this.focused = true; 238 239 // handle touch devices also 240 if(jcf.isTouchDevice) { 241 if(jcf.focusedInstance && jcf.focusedInstance.realElement != this.realElement) { 242 jcf.focusedInstance.onBlur(); 243 jcf.focusedInstance.realElement.blur(); 244 } 245 jcf.focusedInstance = this; 246 } 247 this.onFocus.apply(this, arguments); 248 }, 249 onBlurHandler: function() { 250 // handle focus loses 251 if(!this.pressedFlag) { 252 this.focused = false; 253 this.onBlur.apply(this, arguments); 254 } 255 }, 256 onFakeClick: function(){ 257 if(jcf.isTouchDevice) { 258 this.onFocus(); 259 } else if(!this.realElement.disabled) { 260 this.realElement.focus(); 261 } 262 }, 263 onFakePressed: function(e){ 264 this.pressedFlag = true; 265 }, 266 onFakeReleased: function(){ 267 this.pressedFlag = false; 268 }, 269 onCreateModule: function(){ 270 // implement in subclass 271 }, 272 onModuleAdded: function(module) { 273 // implement in subclass 274 }, 275 onControlReady: function() { 276 // implement in subclass 277 } 278}); 279 280/* 281 * JCF Utility Library 282 */ 283jcf.lib = { 284 bind: function(func, scope){ 285 return function() { 286 return func.apply(scope, arguments); 287 } 288 }, 289 browser: (function() { 290 var ua = navigator.userAgent.toLowerCase(), res = {}, 291 match = /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version)?[ \/]([\w.]+)/.exec(ua) || 292 /(msie) ([\w.]+)/.exec(ua) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+))?/.exec(ua) || []; 293 res[match[1]] = true; 294 res.version = match[2] || "0"; 295 res.safariMac = ua.indexOf('mac') != -1 && ua.indexOf('safari') != -1; 296 return res; 297 })(), 298 getOffset: function (obj) { 299 if (obj.getBoundingClientRect && !navigator.msPointerEnabled) { 300 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; 301 var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; 302 var clientLeft = document.documentElement.clientLeft || document.body.clientLeft || 0; 303 var clientTop = document.documentElement.clientTop || document.body.clientTop || 0; 304 return { 305 top:Math.round(obj.getBoundingClientRect().top + scrollTop - clientTop), 306 left:Math.round(obj.getBoundingClientRect().left + scrollLeft - clientLeft) 307 } 308 } else { 309 var posLeft = 0, posTop = 0; 310 while (obj.offsetParent) {posLeft += obj.offsetLeft; posTop += obj.offsetTop; obj = obj.offsetParent;}
311 return {top:posTop,left:posLeft}; 312 } 313 }, 314 getScrollTop: function() { 315 return window.pageYOffset || document.documentElement.scrollTop; 316 }, 317 getScrollLeft: function() { 318 return window.pageXOffset || document.documentElement.scrollLeft; 319 }, 320 getWindowWidth: function(){ 321 return document.compatMode=='CSS1Compat' ? document.documentElement.clientWidth : document.body.clientWidth; 322 }, 323 getWindowHeight: function(){ 324 return document.compatMode=='CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight; 325 }, 326 getStyle: function(el, prop) { 327 if (document.defaultView && document.defaultView.getComputedStyle) { 328 return document.defaultView.getComputedStyle(el, null)[prop]; 329 } else if (el.currentStyle) { 330 return el.currentStyle[prop]; 331 } else { 332 return el.style[prop]; 333 } 334 }, 335 getParent: function(obj, selector) { 336 while(obj.parentNode && obj.parentNode != document.body) { 337 if(obj.parentNode.tagName.toLowerCase() == selector.toLowerCase()) { 338 return obj.parentNode; 339 } 340 obj = obj.parentNode; 341 } 342 return false; 343 }, 344 isParent: function(child, parent) { 345 while(child.parentNode) { 346 if(child.parentNode === parent) { 347 return true; 348 } 349 child = child.parentNode; 350 } 351 return false; 352 }, 353 getLabelFor: function(object) { 354 var parentLabel = jcf.lib.getParent(object,'label'); 355 if(parentLabel) { 356 return parentLabel; 357 } else if(object.id) { 358 return jcf.lib.queryBySelector('label[for="' + object.id + '"]')[0]; 359 } 360 }, 361 disableTextSelection: function(el){ 362 if (typeof el.onselectstart !== 'undefined') { 363 el.onselectstart = function() {return false}; 364 } else if(window.opera) { 365 el.setAttribute('unselectable', 'on'); 366 } else { 367 jcf.lib.addClass(el, jcf.baseOptions.unselectableClass); 368 } 369 }, 370 enableTextSelection: function(el) { 371 if (typeof el.onselectstart !== 'undefined') { 372 el.onselectstart = null; 373 } else if(window.opera) { 374 el.removeAttribute('unselectable'); 375 } else { 376 jcf.lib.removeClass(el, jcf.baseOptions.unselectableClass); 377 } 378 }, 379 queryBySelector: function(selector, scope){ 380 return this.getElementsBySelector(selector, scope); 381 }, 382 prevSibling: function(node) { 383 while(node = node.previousSibling) if(node.nodeType == 1) break; 384 return node; 385 }, 386 nextSibling: function(node) { 387 while(node = node.nextSibling) if(node.nodeType == 1) break; 388 return node; 389 }, 390 fireEvent: function(element,event) { 391 if(element.dispatchEvent){ 392 var evt = document.createEvent('HTMLEvents'); 393 evt.initEvent(event, true, true ); 394 return !element.dispatchEvent(evt); 395 }else if(document.createEventObject){ 396 var evt = document.createEventObject(); 397 return element.fireEvent('on'+event,evt); 398 } 399 }, 400 isParent: function(p, c) { 401 while(c.parentNode) { 402 if(p == c) { 403 return true; 404 } 405 c = c.parentNode; 406 } 407 return false; 408 }, 409 inherit: function(Child, Parent) { 410 var F = function() { } 411 F.prototype = Parent.prototype 412 Child.prototype = new F() 413 Child.prototype.constructor = Child 414 Child.superclass = Parent.prototype 415 }, 416 extend: function(obj) { 417 for(var i = 1; i < arguments.length; i++) { 418 for(var p in arguments[i]) { 419 if(arguments[i].hasOwnProperty(p)) { 420 obj[p] = arguments[i][p]; 421 } 422 } 423 } 424 return obj; 425 }, 426 hasClass: function (obj,cname) { 427 return (obj.className ? obj.className.match(new RegExp('(\\s|^)'+cname+'(\\s|$)')) : false); 428 }, 429 addClass: function (obj,cname) { 430 if (!this.hasClass(obj,cname)) obj.className += (!obj.className.length || obj.className.charAt(obj.className.length - 1) === ' ' ? '' : ' ') + cname; 431 }, 432 removeClass: function (obj,cname) { 433 if (this.hasClass(obj,cname)) obj.className=obj.className.replace(new RegExp('(\\s|^)'+cname+'(\\s|$)'),' ').replace(/\s+$/, ''); 434 }, 435 toggleClass: function(obj, cname, condition) { 436 if(condition) this.addClass(obj, cname); else this.removeClass(obj, cname); 437 }, 438 createElement: function(tagName, options) { 439 var el = document.createElement(tagName); 440 for(var p in options) { 441 if(options.hasOwnProperty(p)) { 442 switch (p) { 443 case 'class': el.className = options[p]; break; 444 case 'html': el.innerHTML = options[p]; break; 445 case 'style': this.setStyles(el, options[p]); break; 446 default: el.setAttribute(p, options[p]); 447 } 448 } 449 } 450 return el; 451 }, 452 setStyles: function(el, styles) { 453 for(var p in styles) { 454 if(styles.hasOwnProperty(p)) { 455 switch (p) { 456 case 'float': el.style.cssFloat = styles[p]; break; 457 case 'opacity': el.style.filter = 'progid:DXImageTransform.Microsoft.Alpha(opacity='+styles[p]*100+')'; el.style.opacity = styles[p]; break; 458 default: el.style[p] = (typeof styles[p] === 'undefined' ? 0 : styles[p]) + (typeof styles[p] === 'number' ? 'px' : ''); 459 } 460 } 461 } 462 return el; 463 }, 464 getInnerWidth: function(el) { 465 return el.offsetWidth - (parseInt(this.getStyle(el,'paddingLeft')) || 0) - (parseInt(this.getStyle(el,'paddingRight')) || 0); 466 }, 467 getInnerHeight: function(el) { 468 return el.offsetHeight - (parseInt(this.getStyle(el,'paddingTop')) || 0) - (parseInt(this.getStyle(el,'paddingBottom')) || 0); 469 }, 470 getAllClasses: function(cname, prefix, skip) { 471 if(!skip) skip = ''; 472 if(!prefix) prefix = ''; 473 return cname ? cname.replace(new RegExp('(\\s|^)'+skip+'(\\s|$)'),' ').replace(/[\s]*([\S]+)+[\s]*/gi,prefix+"$1 ") : ''; 474 }, 475 getElementsBySelector: function(selector, scope) { 476 if(typeof document.querySelectorAll === 'function') { 477 return (scope || document).querySelectorAll(selector); 478 } 479 var selectors = selector.split(','); 480 var resultList = []; 481 for(var s = 0; s < selectors.length; s++) { 482 var currentContext = [scope || document]; 483 var tokens = selectors[s].replace(/^\s+/,'').replace(/\s+$/,'').split(' '); 484 for (var i = 0; i < tokens.length; i++) { 485 token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,''); 486 if (token.indexOf('#') > -1) { 487 var bits = token.split('#'), tagName = bits[0], id = bits[1]; 488 var element = document.getElementById(id); 489 if (tagName && element.nodeName.toLowerCase() != tagName) { 490 return []; 491 } 492 currentContext = [element]; 493 continue; 494 } 495 if (token.indexOf('.') > -1) { 496 var bits = token.split('.'), tagName = bits[0] || '*', className = bits[1], found = [], foundCount = 0; 497 for (var h = 0; h < currentContext.length; h++) { 498 var elements; 499 if (tagName == '*') { 500 elements = currentContext[h].getElementsByTagName('*'); 501 } else { 502 elements = currentContext[h].getElementsByTagName(tagName); 503 } 504 for (var j = 0; j < elements.length; j++) { 505 found[foundCount++] = elements[j]; 506 } 507 } 508 currentContext = []; 509 var currentContextIndex = 0; 510 for (var k = 0; k < found.length; k++) { 511 if (found[k].className && found[k].className.match(new RegExp('(\\s|^)'+className+'(\\s|$)'))) { 512 currentContext[currentContextIndex++] = found[k]; 513 } 514 } 515 continue; 516 } 517 if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { 518 var tagName = RegExp.$1 || '*', attrName = RegExp.$2, attrOperator = RegExp.$3, attrValue = RegExp.$4; 519 if(attrName.toLowerCase() == 'for' && this.browser.msie && this.browser.version < 8) { 520 attrName = 'htmlFor'; 521 } 522 var found = [], foundCount = 0; 523 for (var h = 0; h < currentContext.length; h++) { 524 var elements; 525 if (tagName == '*') { 526 elements = currentContext[h].getElementsByTagName('*'); 527 } else { 528 elements = currentContext[h].getElementsByTagName(tagName); 529 } 530 for (var j = 0; elements[j]; j++) { 531 found[foundCount++] = elements[j]; 532 } 533 } 534 currentContext = []; 535 var currentContextIndex = 0, checkFunction; 536 switch (attrOperator) { 537 case '=': checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue) }; break; 538 case '~': checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('(\\s|^)'+attrValue+'(\\s|$)'))) }; break; 539 case '|': checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))) }; break; 540 case '^': checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0) }; break; 541 case '$': checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length) }; break; 542 case '*': checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1) }; break; 543 default : checkFunction = function(e) { return e.getAttribute(attrName) }; 544 } 545 currentContext = []; 546 var currentContextIndex = 0; 547 for (var k = 0; k < found.length; k++) { 548 if (checkFunction(found[k])) { 549 currentContext[currentContextIndex++] = found[k]; 550 } 551 } 552 continue; 553 } 554 tagName = token; 555 var found = [], foundCount = 0; 556 for (var h = 0; h < currentContext.length; h++) { 557 var elements = currentContext[h].getElementsByTagName(tagName); 558 for (var j = 0; j < elements.length; j++) { 559 found[foundCount++] = elements[j]; 560 } 561 } 562 currentContext = found; 563 } 564 resultList = [].concat(resultList,currentContext); 565 } 566 return resultList; 567 }, 568 scrollSize: (function(){ 569 var content, hold, sizeBefore, sizeAfter; 570 function buildSizer(){ 571 if(hold) removeSizer(); 572 content = document.createElement('div'); 573 hold = document.createElement('div'); 574 hold.style.cssText = 'position:absolute;overflow:hidden;width:100px;height:100px'; 575 hold.appendChild(content); 576 document.body.appendChild(hold); 577 } 578 function removeSizer(){ 579 document.body.removeChild(hold); 580 hold = null; 581 } 582 function calcSize(vertical) { 583 buildSizer(); 584 content.style.cssText = 'height:'+(vertical ? '100%' : '200px'); 585 sizeBefore = (vertical ? content.offsetHeight : content.offsetWidth);
586 hold.style.overflow = 'scroll'; content.innerHTML = 1; 587 sizeAfter = (vertical ? content.offsetHeight : content.offsetWidth); 588 if(vertical && hold.clientHeight) sizeAfter = hold.clientHeight; 589 removeSizer(); 590 return sizeBefore - sizeAfter; 591 } 592 return { 593 getWidth:function(){ 594 return calcSize(false); 595 }, 596 getHeight:function(){ 597 return calcSize(true) 598 } 599 } 600 }()), 601 domReady: function (handler){ 602 var called = false 603 function ready() { 604 if (called) return; 605 called = true; 606 handler(); 607 } 608 if (document.addEventListener) { 609 document.addEventListener("DOMContentLoaded", ready, false); 610 } else if (document.attachEvent) { 611 if (document.documentElement.doScroll && window == window.top) { 612 function tryScroll(){ 613 if (called) return 614 if (!document.body) return 615 try { 616 document.documentElement.doScroll("left") 617 ready() 618 } catch(e) { 619 setTimeout(tryScroll, 0) 620 } 621 } 622 tryScroll() 623 } 624 document.attachEvent("onreadystatechange", function(){ 625 if (document.readyState === "complete") { 626 ready() 627 } 628 }) 629 } 630 if (window.addEventListener) window.addEventListener('load', ready, false) 631 else if (window.attachEvent) window.attachEvent('onload', ready) 632 }, 633 event: (function(){ 634 var guid = 0; 635 function fixEvent(e) { 636 e = e || window.event; 637 if (e.isFixed) { 638 return e; 639 } 640 e.isFixed = true; 641 e.preventDefault = e.preventDefault || function(){this.returnValue = false} 642 e.stopPropagation = e.stopPropagaton || function(){this.cancelBubble = true} 643 if (!e.target) { 644 e.target = e.srcElement 645 } 646 if (!e.relatedTarget && e.fromElement) { 647 e.relatedTarget = e.fromElement == e.target ? e.toElement : e.fromElement; 648 } 649 if (e.pageX == null && e.clientX != null) { 650 var html = document.documentElement, body = document.body; 651 e.pageX = e.clientX + (html && html.scrollLeft || body && body.scrollLeft || 0) - (html.clientLeft || 0); 652 e.pageY = e.clientY + (html && html.scrollTop || body && body.scrollTop || 0) - (html.clientTop || 0); 653 } 654 if (!e.which && e.button) { 655 e.which = e.button & 1 ? 1 : (e.button & 2 ? 3 : (e.button & 4 ? 2 : 0)); 656 } 657 if(e.type === "DOMMouseScroll" || e.type === 'mousewheel') { 658 e.mWheelDelta = 0; 659 if (e.wheelDelta) { 660 e.mWheelDelta = e.wheelDelta/120; 661 } else if (e.detail) { 662 e.mWheelDelta = -e.detail/3; 663 } 664 } 665 return e; 666 } 667 function commonHandle(event, customScope) { 668 event = fixEvent(event); 669 var handlers = this.events[event.type]; 670 for (var g in handlers) { 671 var handler = handlers[g]; 672 var ret = handler.call(customScope || this, event); 673 if (ret === false) { 674 event.preventDefault() 675 event.stopPropagation() 676 } 677 } 678 } 679 var publicAPI = { 680 add: function(elem, type, handler, forcedScope) { 681 if (elem.setInterval && (elem != window && !elem.frameElement)) { 682 elem = window; 683 } 684 if (!handler.guid) { 685 handler.guid = ++guid; 686 } 687 if (!elem.events) { 688 elem.events = {}; 689 elem.handle = function(event) { 690 return commonHandle.call(elem, event); 691 } 692 } 693 if (!elem.events[type]) { 694 elem.events[type] = {}; 695 if (elem.addEventListener) elem.addEventListener(type, elem.handle, false); 696 else if (elem.attachEvent) elem.attachEvent("on" + type, elem.handle); 697 if(type === 'mousewheel') { 698 publicAPI.add(elem, 'DOMMouseScroll', handler, forcedScope); 699 } 700 } 701 var fakeHandler = jcf.lib.bind(handler, forcedScope); 702 fakeHandler.guid = handler.guid; 703 elem.events[type][handler.guid] = forcedScope ? fakeHandler : handler; 704 }, 705 remove: function(elem, type, handler) { 706 var handlers = elem.events && elem.events[type]; 707 if (!handlers) return; 708 delete handlers[handler.guid]; 709 for(var any in handlers) return; 710 if (elem.removeEventListener) elem.removeEventListener(type, elem.handle, false); 711 else if (elem.detachEvent) elem.detachEvent("on" + type, elem.handle); 712 delete elem.events[type]; 713 for (var any in elem.events) return; 714 try { 715 delete elem.handle; 716 delete elem.events; 717 } catch(e) { 718 if(elem.removeAttribute) { 719 elem.removeAttribute("handle"); 720 elem.removeAttribute("events"); 721 } 722 } 723 if(type === 'mousewheel') { 724 publicAPI.remove(elem, 'DOMMouseScroll', handler); 725 } 726 } 727 } 728 return publicAPI; 729 }()) 730} 731 732// custom select module 733jcf.addModule({ 734 name:'select', 735 selector:'select', 736 defaultOptions: { 737 useNativeDropOnMobileDevices: true, 738 hideDropOnScroll: true, 739 showNativeDrop: false, 740 handleDropPosition: true, 741 selectDropPosition: 'bottom', // or 'top' 742 wrapperClass:'select-area', 743 focusClass:'select-focus', 744 dropActiveClass:'select-active', 745 selectedClass:'item-selected', 746 currentSelectedClass:'current-selected', 747 disabledClass:'select-disabled', 748 valueSelector:'span.center', 749 optGroupClass:'optgroup', 750 openerSelector:'a.select-opener', 751 selectStructure:'<span class="left"></span><span class="center"></span><a class="select-opener"></a>', 752 wrapperTag: 'span', 753 classPrefix:'select-', 754 dropMaxHeight: 200, 755 dropFlippedClass: 'select-options-flipped', 756 dropHiddenClass:'options-hidden', 757 dropScrollableClass:'options-overflow', 758 dropClass:'select-options', 759 dropClassPrefix:'drop-', 760 dropStructure:'<div class="drop-holder"><div class="drop-list"></div></div>', 761 dropSelector:'div.drop-list' 762 }, 763 checkElement: function(el){ 764 return (!el.size && !el.multiple); 765 }, 766 setupWrapper: function(){ 767 jcf.lib.addClass(this.fakeElement, this.options.wrapperClass); 768 this.realElement.parentNode.insertBefore(this.fakeElement, this.realElement); 769 this.fakeElement.innerHTML = this.options.selectStructure; 770 this.fakeElement.style.width = (this.realElement.offsetWidth > 0 ? this.realElement.offsetWidth + 'px' : 'auto'); 771 772 // show native drop if specified in options 773 if(jcf.baseOptions.useNativeDropOnMobileDevices && (jcf.isTouchDevice || jcf.isWinPhoneDevice)) { 774 this.options.showNativeDrop = true; 775 } 776 if(this.options.showNativeDrop) { 777 this.fakeElement.appendChild(this.realElement); 778 jcf.lib.removeClass(this.realElement, this.options.hiddenClass); 779 jcf.lib.setStyles(this.realElement, { 780 top:0, 781 left:0, 782 margin:0, 783 padding:0, 784 opacity:0, 785 border:'none', 786 position:'absolute', 787 width: jcf.lib.getInnerWidth(this.fakeElement) - 1, 788 height: jcf.lib.getInnerHeight(this.fakeElement) - 1 789 }); 790 jcf.lib.event.add(this.realElement, 'touchstart', function(){ 791 this.realElement.title = ''; 792 }, this) 793 } 794 795 // create select body 796 this.opener = jcf.lib.queryBySelector(this.options.openerSelector, this.fakeElement)[0]; 797 this.valueText = jcf.lib.queryBySelector(this.options.valueSelector, this.fakeElement)[0]; 798 jcf.lib.disableTextSelection(this.valueText); 799 this.opener.jcf = this; 800 801 if(!this.options.showNativeDrop) { 802 this.createDropdown(); 803 this.refreshState(); 804 this.onControlReady(this); 805 this.hideDropdown(true); 806 } else { 807 this.refreshState(); 808 } 809 this.addEvents(); 810 }, 811 addEvents: function(){ 812 if(this.options.showNativeDrop) { 813 jcf.lib.event.add(this.realElement, 'click', this.onChange, this); 814 } else { 815 jcf.lib.event.add(this.fakeElement, 'click', this.toggleDropdown, this); 816 } 817 jcf.lib.event.add(this.realElement, 'change', this.onChange, this); 818 }, 819 onFakeClick: function() { 820 // do nothing (drop toggles by toggleDropdown method) 821 }, 822 onFocus: function(){ 823 jcf.modules[this.name].superclass.onFocus.apply(this, arguments); 824 if(!this.options.showNativeDrop) { 825 // Mac Safari Fix 826 if(jcf.lib.browser.safariMac) { 827 this.realElement.setAttribute('size','2'); 828 } 829 jcf.lib.event.add(this.realElement, 'keydown', this.onKeyDown, this); 830 if(jcf.activeControl && jcf.activeControl != this) { 831 jcf.activeControl.hideDropdown(); 832 jcf.activeControl = this; 833 } 834 } 835 }, 836 onBlur: function(){ 837 if(!this.options.showNativeDrop) { 838 // Mac Safari Fix 839 if(jcf.lib.browser.safariMac) { 840 this.realElement.removeAttribute('size'); 841 } 842 if(!this.isActiveDrop() || !this.isOverDrop()) { 843 jcf.modules[this.name].superclass.onBlur.apply(this); 844 if(jcf.activeControl === this) jcf.activeControl = null; 845 if(!jcf.isTouchDevice) { 846 this.hideDropdown(); 847 } 848 } 849 jcf.lib.event.remove(this.realElement, 'keydown', this.onKeyDown); 850 } else { 851 jcf.modules[this.name].superclass.onBlur.apply(this); 852 } 853 }, 854 onChange: function() { 855 this.refreshState(); 856 }, 857 onKeyDown: function(e){
858 this.dropOpened = true; 859 jcf.tmpFlag = true; 860 setTimeout(function(){jcf.tmpFlag = false},100); 861 var context = this; 862 context.keyboardFix = true; 863 setTimeout(function(){ 864 context.refreshState(); 865 },10); 866 if(e.keyCode == 13) { 867 context.toggleDropdown.apply(context); 868 return false; 869 } 870 }, 871 onResizeWindow: function(e){ 872 if(this.isActiveDrop()) { 873 this.hideDropdown(); 874 } 875 }, 876 onScrollWindow: function(e){ 877 if(this.options.hideDropOnScroll) { 878 this.hideDropdown(); 879 } else if(this.isActiveDrop()) { 880 this.positionDropdown(); 881 } 882 }, 883 onOptionClick: function(e){ 884 var opener = e.target && e.target.tagName && e.target.tagName.toLowerCase() == 'li' ? e.target : jcf.lib.getParent(e.target, 'li'); 885 if(opener) { 886 this.dropOpened = true; 887 this.realElement.selectedIndex = parseInt(opener.getAttribute('rel')); 888 if(jcf.isTouchDevice) { 889 this.onFocus(); 890 } else { 891 this.realElement.focus(); 892 } 893 this.refreshState(); 894 895 this.hideDropdown(); 896 jcf.lib.fireEvent(this.realElement, 'change'); 897 $(e.target).attr('href', $(this.realElement).val()); 898 } 899 return false; 900 }, 901 onClickOutside: function(e){ 902 if(jcf.tmpFlag) { 903 jcf.tmpFlag = false; 904 return; 905 } 906 if(!jcf.lib.isParent(this.fakeElement, e.target) && !jcf.lib.isParent(this.selectDrop, e.target)) { 907 this.hideDropdown(); 908 } 909 }, 910 onDropHover: function(e){ 911 if(!this.keyboardFix) { 912 this.hoverFlag = true; 913 var opener = e.target && e.target.tagName && e.target.tagName.toLowerCase() == 'li' ? e.target : jcf.lib.getParent(e.target, 'li'); 914 if(opener) { 915 this.realElement.selectedIndex = parseInt(opener.getAttribute('rel')); 916 this.refreshSelectedClass(parseInt(opener.getAttribute('rel'))); 917 } 918 } else { 919 this.keyboardFix = false; 920 } 921 }, 922 onDropLeave: function(){ 923 this.hoverFlag = false; 924 }, 925 isActiveDrop: function(){ 926 return !jcf.lib.hasClass(this.selectDrop, this.options.dropHiddenClass); 927 }, 928 isOverDrop: function(){ 929 return this.hoverFlag; 930 }, 931 createDropdown: function(){ 932 // remove old dropdown if exists 933 if(this.selectDrop) { 934 this.selectDrop.parentNode.removeChild(this.selectDrop); 935 } 936 937 // create dropdown holder 938 this.selectDrop = document.createElement('div'); 939 this.selectDrop.className = this.options.dropClass; 940 this.selectDrop.innerHTML = this.options.dropStructure; 941 jcf.lib.setStyles(this.selectDrop, {position:'absolute'}); 942 this.selectList = jcf.lib.queryBySelector(this.options.dropSelector,this.selectDrop)[0]; 943 jcf.lib.addClass(this.selectDrop, this.options.dropHiddenClass); 944 document.body.appendChild(this.selectDrop); 945 this.selectDrop.jcf = this; 946 jcf.lib.event.add(this.selectDrop, 'click', this.onOptionClick, this); 947 jcf.lib.event.add(this.selectDrop, 'mouseover', this.onDropHover, this); 948 jcf.lib.event.add(this.selectDrop, 'mouseout', this.onDropLeave, this); 949 this.buildDropdown(); 950 }, 951 buildDropdown: function() { 952 // build select options / optgroups 953 this.buildDropdownOptions(); 954 955 // position and resize dropdown 956 this.positionDropdown(); 957 958 // cut dropdown if height exceedes 959 this.buildDropdownScroll(); 960 }, 961 buildDropdownOptions: function() { 962 this.resStructure = ''; 963 this.optNum = 0; 964 for(var i = 0; i < this.realElement.children.length; i++) { 965 this.resStructure += this.buildElement(this.realElement.children[i], i) +'\n'; 966 } 967 this.selectList.innerHTML = this.resStructure; 968 }, 969 buildDropdownScroll: function() { 970 if(this.options.dropMaxHeight) { 971 if(this.selectDrop.offsetHeight > this.options.dropMaxHeight) { 972 this.selectList.style.height = this.options.dropMaxHeight+'px'; 973 this.selectList.style.overflow = 'auto'; 974 this.selectList.style.overflowX = 'hidden'; 975 jcf.lib.addClass(this.selectDrop, this.options.dropScrollableClass); 976 } 977 } 978 jcf.lib.addClass(this.selectDrop, jcf.lib.getAllClasses(this.realElement.className, this.options.dropClassPrefix, jcf.baseOptions.hiddenClass)); 979 }, 980 parseOptionTitle: function(optTitle) { 981 return (typeof optTitle === 'string' && /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i.test(optTitle)) ? optTitle : ''; 982 }, 983 buildElement: function(obj, index){ 984 // build option 985 var res = '', optImage; 986 if(obj.tagName.toLowerCase() == 'option') { 987 if(!jcf.lib.prevSibling(obj) || jcf.lib.prevSibling(obj).tagName.toLowerCase() != 'option') { 988 res += '<ul>'; 989 } 990 991 optImage = this.parseOptionTitle(obj.title); 992 res += '<li rel="'+(this.optNum++)+'" class="'+(obj.className? obj.className + ' ' : '')+(index % 2 ? 'option-even ' : '')+'jcfcalc"><a href="#">'+(optImage ? '<img src="'+optImage+'" alt="" />
992' : '')+'<span>' + obj.innerHTML + '</span></a></li>'; 993 if(!jcf.lib.nextSibling(obj) || jcf.lib.nextSibling(obj).tagName.toLowerCase() != 'option') { 994 res += '</ul>'; 995 } 996 return res; 997 } 998 // build option group with options 999 else if(obj.tagName.toLowerCase() == 'optgroup' && obj.label) { 1000 res += '<div class="'+this.options.optGroupClass+'">'; 1001 res += '<strong class="jcfcalc"><em>'+(obj.label)+'</em></strong>'; 1002 for(var i = 0; i < obj.children.length; i++) { 1003 res += this.buildElement(obj.children[i], i); 1004 } 1005 res += '</div>'; 1006 return res; 1007 } 1008 }, 1009 positionDropdown: function(){ 1010 var ofs = jcf.lib.getOffset(this.fakeElement), selectAreaHeight = this.fakeElement.offsetHeight, selectDropHeight = this.selectDrop.offsetHeight; 1011 var fitInTop = ofs.top - selectDropHeight >= jcf.lib.getScrollTop() && jcf.lib.getScrollTop() + jcf.lib.getWindowHeight() < ofs.top + selectAreaHeight + selectDropHeight; 1012 1013 1014 if((this.options.handleDropPosition && fitInTop) || this.options.selectDropPosition === 'top') { 1015 this.selectDrop.style.top = (ofs.top - selectDropHeight)+'px'; 1016 jcf.lib.addClass(this.selectDrop, this.options.dropFlippedClass); 1017 } else { 1018 this.selectDrop.style.top = (ofs.top + selectAreaHeight)+'px'; 1019 jcf.lib.removeClass(this.selectDrop, this.options.dropFlippedClass); 1020 } 1021 this.selectDrop.style.left = ofs.left+'px'; 1022 this.selectDrop.style.width = this.fakeElement.offsetWidth+'px'; 1023 }, 1024 showDropdown: function(){ 1025 document.body.appendChild(this.selectDrop); 1026 jcf.lib.removeClass(this.selectDrop, this.options.dropHiddenClass); 1027 jcf.lib.addClass(this.fakeElement,this.options.dropActiveClass); 1028 this.positionDropdown(); 1029 1030 // highlight current active item 1031 var activeItem = this.getFakeActiveOption(); 1032 this.removeClassFromItems(this.options.currentSelectedClass); 1033 jcf.lib.addClass(activeItem, this.options.currentSelectedClass); 1034 1035 // show current dropdown 1036 jcf.lib.event.add(window, 'resize', this.onResizeWindow, this); 1037 jcf.lib.event.add(window, 'scroll', this.onScrollWindow, this); 1038 jcf.lib.event.add(document, jcf.eventPress, this.onClickOutside, this); 1039 this.positionDropdown(); 1040 }, 1041 hideDropdown: function(partial){ 1042 if(this.selectDrop.parentNode) { 1043 if(this.selectDrop.offsetWidth) { 1044 this.selectDrop.parentNode.removeChild(this.selectDrop); 1045 } 1046 if(partial) { 1047 return; 1048 } 1049 } 1050 if(typeof this.origSelectedIndex === 'number') { 1051 this.realElement.selectedIndex = this.origSelectedIndex; 1052 } 1053 jcf.lib.removeClass(this.fakeElement,this.options.dropActiveClass); 1054 jcf.lib.addClass(this.selectDrop, this.options.dropHiddenClass); 1055 jcf.lib.event.remove(window, 'resize', this.onResizeWindow); 1056 jcf.lib.event.remove(window, 'scroll', this.onScrollWindow); 1057 jcf.lib.event.remove(document.documentElement, jcf.eventPress, this.onClickOutside); 1058 if(jcf.isTouchDevice) { 1059 this.onBlur(); 1060 } 1061 }, 1062 toggleDropdown: function(){ 1063 if(!this.realElement.disabled) { 1064 if(jcf.isTouchDevice) { 1065 this.onFocus(); 1066 } else { 1067 this.realElement.focus(); 1068 } 1069 if(this.isActiveDrop()) { 1070 this.hideDropdown(); 1071 } else { 1072 this.showDropdown(); 1073 } 1074 this.refreshState(); 1075 } 1076 }, 1077 scrollToItem: function(){ 1078 if(this.isActiveDrop()) { 1079 var dropHeight = this.selectList.offsetHeight; 1080 var offsetTop = this.calcOptionOffset(this.getFakeActiveOption()); 1081 var sTop = this.selectList.scrollTop; 1082 var oHeight = this.getFakeActiveOption().offsetHeight; 1083 //offsetTop+=sTop; 1084 1085 if(offsetTop >= sTop + dropHeight) { 1086 this.selectList.scrollTop = offsetTop - dropHeight + oHeight; 1087 } else if(offsetTop < sTop) { 1088 this.selectList.scrollTop = offsetTop; 1089 } 1090 } 1091 }, 1092 getFakeActiveOption: function(c) { 1093 return jcf.lib.queryBySelector('li[rel="'+(typeof c === 'number' ? c : this.realElement.selectedIndex) +'"]',this.selectList)[0]; 1094 }, 1095 calcOptionOffset: function(fake) { 1096 var h = 0;
1097 var els = jcf.lib.queryBySelector('.jcfcalc',this.selectList); 1098 for(var i = 0; i < els.length; i++) { 1099 if(els[i] == fake) break; 1100 h+=els[i].offsetHeight; 1101 } 1102 return h; 1103 }, 1104 childrenHasItem: function(hold,item) { 1105 var items = hold.getElementsByTagName('*'); 1106 for(i = 0; i < items.length; i++) { 1107 if(items[i] == item) return true; 1108 } 1109 return false; 1110 }, 1111 removeClassFromItems: function(className){ 1112 var children = jcf.lib.queryBySelector('li',this.selectList); 1113 for(var i = children.length - 1; i >= 0; i--) { 1114 jcf.lib.removeClass(children[i], className); 1115 } 1116 }, 1117 setSelectedClass: function(c){ 1118 jcf.lib.addClass(this.getFakeActiveOption(c), this.options.selectedClass); 1119 }, 1120 refreshSelectedClass: function(c){ 1121 if(!this.options.showNativeDrop) { 1122 this.removeClassFromItems(this.options.selectedClass); 1123 this.setSelectedClass(c); 1124 } 1125 if(this.realElement.disabled) { 1126 jcf.lib.addClass(this.fakeElement, this.options.disabledClass); 1127 if(this.labelFor) { 1128 jcf.lib.addClass(this.labelFor, this.options.labelDisabledClass); 1129 } 1130 } else { 1131 jcf.lib.removeClass(this.fakeElement, this.options.disabledClass); 1132 if(this.labelFor) { 1133 jcf.lib.removeClass(this.labelFor, this.options.labelDisabledClass); 1134 } 1135 } 1136 }, 1137 refreshSelectedText: function() { 1138 if(!this.dropOpened && this.realElement.title) { 1139 this.valueText.innerHTML = this.realElement.title; 1140 } else { 1141 if(this.realElement.options[this.realElement.selectedIndex].title) { 1142 var optImage = this.parseOptionTitle(this.realElement.options[this.realElement.selectedIndex].title); 1143 this.valueText.innerHTML = (optImage ? '<img src="'+optImage+'" alt="" />' : '') + this.realElement.options[this.realElement.selectedIndex].innerHTML; 1144 } else { 1145 this.valueText.innerHTML = this.realElement.options[this.realElement.selectedIndex].innerHTML; 1146 } 1147 } 1148 }, 1149 refreshState: function(){ 1150 this.origSelectedIndex = this.realElement.selectedIndex; 1151 this.refreshSelectedClass(); 1152 this.refreshSelectedText(); 1153 if(!this.options.showNativeDrop) { 1154 this.positionDropdown(); 1155 if(this.selectDrop.offsetWidth) { 1156 this.scrollToItem(); 1157 } 1158 } 1159 } 1160}); 1161 1162 1163/* 1164 * Utility module 1165 */ 1166lib = { 1167 hasClass: function(el,cls) { 1168 return el && el.className ? el.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)')) : false; 1169 }, 1170 addClass: function(el,cls) { 1171 if (el && !this.hasClass(el,cls)) el.className += " "+cls; 1172 }, 1173 removeClass: function(el,cls) { 1174 if (el && this.hasClass(el,cls)) {el.className=el.className.replace(new RegExp('(\\s|^)'+cls+'(\\s|$)'), '');} 1175 }, 1176 extend: function(obj) { 1177 for(var i = 1; i < arguments.length; i++) { 1178 for(var p in arguments[i]) { 1179 if(arguments[i].hasOwnProperty(p)) { 1180 obj[p] = arguments[i][p]; 1181 } 1182 } 1183 } 1184 return obj; 1185 }, 1186 each: function(obj, callback) { 1187 var property, len; 1188 if(typeof obj.length === 'number') { 1189 for(property = 0, len = obj.length; property < len; property++) { 1190 if(callback.call(obj[property], property, obj[property]) === false) { 1191 break; 1192 } 1193 } 1194 } else { 1195 for(property in obj) { 1196 if(obj.hasOwnProperty(property)) { 1197 if(callback.call(obj[property], property, obj[property]) === false) { 1198 break; 1199 } 1200 } 1201 } 1202 } 1203 }, 1204 event: (function() { 1205 var fixEvent = function(e) { 1206 e = e || window.event; 1207 if(e.isFixed) return e; else e.isFixed = true; 1208 if(!e.target) e.target = e.srcElement; 1209 e.preventDefault = e.preventDefault || function() {this.returnValue = false;}; 1210 e.stopPropagation = e.stopPropagation || function() {this.cancelBubble = true;}; 1211 return e; 1212 }; 1213 return { 1214 add: function(elem, event, handler) { 1215 if(!elem.events) { 1216 elem.events = {}; 1217 elem.handle = function(e) { 1218 var ret, handlers = elem.events[e.type]; 1219 e = fixEvent(e); 1220 for(var i = 0, len = handlers.length; i < len; i++) { 1221 if(handlers[i]) { 1222 ret = handlers[i].call(elem, e); 1223 if(ret === false) { 1224 e.preventDefault(); 1225 e.stopPropagation(); 1226 } 1227 } 1228 } 1229 }; 1230 } 1231 if(!elem.events[event]) { 1232 elem.events[event] = []; 1233 if(elem.addEventListener) elem.addEventListener(event, elem.handle, false); 1234 else if(elem.attachEvent) elem.attachEvent('on'+event, elem.handle); 1235 } 1236 elem.events[event].push(handler); 1237 }, 1238 remove: function(elem, event, handler) { 1239 var handlers = elem.events[event]; 1240 for(var i = handlers.length - 1; i >= 0; i--) { 1241 if(handlers[i] === handler) { 1242 handlers.splice(i,1); 1243 } 1244 } 1245 if(!handlers.length) { 1246 delete elem.events[event]; 1247 if(elem.removeEventListener) elem.removeEventListener(event, elem.handle, false); 1248 else if(elem.detachEvent) elem.detachEvent('on'+event, elem.handle); 1249 } 1250 } 1251 }; 1252 }()), 1253 queryElementsBySelector: function(selector, scope) { 1254 scope = scope || document; 1255 if(!selector) return []; 1256 if(selector === '>*') return scope.children; 1257 if(typeof document.querySelectorAll === 'function') { 1258 return scope.querySelectorAll(selector); 1259 } 1260 var selectors = selector.split(','); 1261 var resultList = []; 1262 for(var s = 0; s < selectors.length; s++) { 1263 var currentContext = [scope || document]; 1264 var tokens = selectors[s].replace(/^\s+/,'').replace(/\s+$/,'').split(' '); 1265 for (var i = 0; i < tokens.length; i++) { 1266 token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,''); 1267 if (token.indexOf('#') > -1) { 1268 var bits = token.split('#'), tagName = bits[0], id = bits[1]; 1269 var element = document.getElementById(id); 1270 if (element && tagName && element.nodeName.toLowerCase() != tagName) { 1271 return []; 1272 } 1273 currentContext = element ? [element] : []; 1274 continue; 1275 } 1276 if (token.indexOf('.') > -1) { 1277 var bits = token.split('.'), tagName = bits[0] || '*', className = bits[1], found = [], foundCount = 0; 1278 for (var h = 0; h < currentContext.length; h++) { 1279 var elements; 1280 if (tagName == '*') { 1281 elements = currentContext[h].getElementsByTagName('*'); 1282 } else { 1283 elements = currentContext[h].getElementsByTagName(tagName); 1284 } 1285 for (var j = 0; j < elements.length; j++) { 1286 found[foundCount++] = elements[j]; 1287 } 1288 } 1289 currentContext = []; 1290 var currentContextIndex = 0; 1291 for (var k = 0; k < found.length; k++) { 1292 if (found[k].className && found[k].className.match(new RegExp('(\\s|^)'+className+'(\\s|$)'))) { 1293 currentContext[currentContextIndex++] = found[k]; 1294 } 1295 } 1296 continue; 1297 } 1298 if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) { 1299 var tagName = RegExp.$1 || '*', attrName = RegExp.$2, attrOperator = RegExp.$3, attrValue = RegExp.$4; 1300 if(attrName.toLowerCase() == 'for' && this.browser.msie && this.browser.version < 8) { 1301 attrName = 'htmlFor'; 1302 } 1303 var found = [], foundCount = 0; 1304 for (var h = 0; h < currentContext.length; h++) { 1305 var elements; 1306 if (tagName == '*') { 1307 elements = currentContext[h].getElementsByTagName('*'); 1308 } else { 1309 elements = currentContext[h].getElementsByTagName(tagName); 1310 } 1311 for (var j = 0; elements[j]; j++) { 1312 found[foundCount++] = elements[j]; 1313 } 1314 } 1315 currentContext = []; 1316 var currentContextIndex = 0, checkFunction; 1317 switch (attrOperator) { 1318 case '=': checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue) }; break; 1319 case '~': checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('(\\s|^)'+attrValue+'(\\s|$)'))) }; break; 1320 case '|': checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))) }; break; 1321 case '^': checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0) }; break; 1322 case '$': checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length) }; break; 1323 case '*': checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1) }; break; 1324 default : checkFunction = function(e) { return e.getAttribute(attrName) }; 1325 } 1326 currentContext = []; 1327 var currentContextIndex = 0; 1328 for (var k = 0; k < found.length; k++) { 1329 if (checkFunction(found[k])) { 1330 currentContext[currentContextIndex++] = found[k]; 1331 } 1332 } 1333 continue; 1334 } 1335 tagName = token; 1336 var found = [], foundCount = 0; 1337 for (var h = 0; h < currentContext.length; h++) { 1338 var elements = currentContext[h].getElementsByTagName(tagName); 1339 for (var j = 0; j < elements.length; j++) { 1340 found[foundCount++] = elements[j]; 1341 } 1342 } 1343 currentContext = found; 1344 } 1345 resultList = [].concat(resultList,currentContext); 1346 } 1347 return resultList; 1348 }, 1349 trim: function (str) { 1350 return str.replace(/^\s+/, '').replace(/\s+$/, ''); 1351 }, 1352 bind: function(f, scope, forceArgs){ 1353 return function() {return f.apply(scope, typeof forceArgs !== 'undefined' ? [forceArgs] : arguments);}; 1354 } 1355}; 1356 1357/* 1358 * jQuery Accordion plugin 1359 */ 1360;(function($){ 1361 $.fn.slideAccordion = function(opt){ 1362 // default options 1363 var options = $.extend({ 1364 addClassBeforeAnimation: false, 1365 activeClass:'active', 1366 opener:'.opener', 1367 slider:'.slide', 1368 animSpeed: 300, 1369 collapsible:true, 1370 event:'click' 1371 },opt); 1372 1373 return this.each(function(){ 1374 // options 1375 var accordion = $(this); 1376 var items = accordion.find(':has('+options.slider+')'); 1377 1378 items.each(function(){ 1379 var item = $(this); 1380 var opener = item.find(options.opener); 1381 var slider = item.find(options.slider); 1382 opener.bind(options.event, function(e){
1383 if(!slider.is(':animated')) { 1384 if(item.hasClass(options.activeClass)) { 1385 if(options.collapsible) { 1386 slider.slideUp(options.animSpeed, function(){ 1387 hideSlide(slider); 1388 item.removeClass(options.activeClass); 1389 }); 1390 } 1391 } else { 1392 // show active 1393 var levelItems = item.siblings('.'+options.activeClass); 1394 var sliderElements = levelItems.find(options.slider); 1395 item.addClass(options.activeClass); 1396 showSlide(slider).hide().slideDown(options.animSpeed); 1397 1398 // collapse others 1399 sliderElements.slideUp(options.animSpeed, function(){ 1400 levelItems.removeClass(options.activeClass); 1401 hideSlide(sliderElements); 1402 }); 1403 } 1404 } 1405 e.preventDefault(); 1406 }); 1407 if(item.hasClass(options.activeClass)) showSlide(slider); else hideSlide(slider); 1408 }); 1409 }); 1410 }; 1411 1412 // accordion slide visibility 1413 var showSlide = function(slide) { 1414 return slide.css({position:'', top: '', left: '', width: '' }); 1415 }; 1416 var hideSlide = function(slide) { 1417 return slide.show().css({position:'absolute', top: -9999, left: -9999, width: slide.width() }); 1418 }; 1419}(jQuery)); 1420 1421/* 1422 * Popups plugin 1423 */ 1424;(function($) { 1425 function ContentPopup(opt) { 1426 this.options = $.extend({ 1427 holder: null, 1428 popup: '.popup', 1429 btnOpen: '.open', 1430 btnClose: '.close', 1431 openClass: 'popup-active', 1432 clickEvent: 'click', 1433 mode: 'click', 1434 hideOnClickLink: true, 1435 hideOnClickOutside: true, 1436 delay: 50 1437 }, opt); 1438 if(this.options.holder) { 1439 this.holder = $(this.options.holder); 1440 this.init(); 1441 } 1442 } 1443 ContentPopup.prototype = { 1444 init: function() { 1445 this.findElements(); 1446 this.attachEvents(); 1447 }, 1448 findElements: function() { 1449 this.popup = this.holder.find(this.options.popup); 1450 this.btnOpen = this.holder.find(this.options.btnOpen); 1451 this.btnClose = this.holder.find(this.options.btnClose); 1452 }, 1453 attachEvents: function() { 1454 // handle popup openers 1455 var self = this; 1456 this.clickMode = isTouchDevice || (self.options.mode === self.options.clickEvent); 1457 1458 if(this.clickMode) { 1459 // handle click mode 1460 this.btnOpen.bind(self.options.clickEvent, function(e) { 1461 if(self.holder.hasClass(self.options.openClass)) { 1462 if(self.options.hideOnClickLink) { 1463 self.hidePopup(); 1464 } 1465 } else { 1466 self.showPopup(); 1467 } 1468 e.preventDefault(); 1469 }); 1470 1471 // prepare outside click handler 1472 this.outsideClickHandler = this.bind(this.outsideClickHandler, this); 1473 } else { 1474 // handle hover mode 1475 var timer, delayedFunc = function(func) { 1476 clearTimeout(timer); 1477 timer = setTimeout(function() { 1478 func.call(self); 1479 }, self.options.delay); 1480 }; 1481 this.btnOpen.bind('mouseover', function() { 1482 delayedFunc(self.showPopup); 1483 }).bind('mouseout', function() { 1484 delayedFunc(self.hidePopup); 1485 }); 1486 this.popup.bind('mouseover', function() { 1487 delayedFunc(self.showPopup); 1488 }).bind('mouseout', function() { 1489 delayedFunc(self.hidePopup); 1490 }); 1491 } 1492 1493 // handle close buttons 1494 this.btnClose.bind(self.options.clickEvent, function(e) { 1495 self.hidePopup(); 1496 e.preventDefault(); 1497 }); 1498 }, 1499 outsideClickHandler: function(e) { 1500 // hide popup if clicked outside 1501 var currentNode = (e.changedTouches ? e.changedTouches[0] : e).target; 1502 if(!$(currentNode).parents().filter(this.holder).length) { 1503 this.hidePopup(); 1504 } 1505 }, 1506 showPopup: function() { 1507 // reveal popup 1508 this.holder.addClass(this.options.openClass); 1509 this.popup.css({display:'block'}); 1510 1511 // outside click handler 1512 if(this.clickMode && this.options.hideOnClickOutside && !this.outsideHandlerActive) { 1513 this.outsideHandlerActive = true; 1514 $(document).bind('click touchstart', this.outsideClickHandler); 1515 } 1516 }, 1517 hidePopup: function() { 1518 // hide popup 1519 this.holder.removeClass(this.options.openClass); 1520 this.popup.css({display:'none'}); 1521 1522 // outside click handler 1523 if(this.clickMode && this.options.hideOnClickOutside && this.outsideHandlerActive) { 1524 this.outsideHandlerActive = false; 1525 $(document).unbind('click touchstart', this.outsideClickHandler); 1526 } 1527 }, 1528 bind: function(f, scope, forceArgs){ 1529 return function() {return f.apply(scope, forceArgs ? [forceArgs] : arguments);}; 1530 } 1531 }; 1532 1533 // detect touch devices 1534 var isTouchDevice = /MSIE 10.*Touch/.test(navigator.userAgent) || ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch; 1535 1536 // jQuery plugin interface 1537 $.fn.contentPopup = function(opt) { 1538 return this.each(function() { 1539 new ContentPopup($.extend(opt, {holder: this})); 1540 }); 1541 }; 1542}(jQuery)); 1543 1544/* 1545 * Convert navigation to select 1546 */ 1547;(function($) { 1548 function NavigationSelect(options) { 1549 this.options = $.extend({ 1550 list: null, 1551 levelIndentHTML: ' • ', 1552 defaultOptionAttr: 'title',
1553 defaultOptionText: '...', 1554 selectClass: 'nav-select', 1555 activeClass: 'nav-active', 1556 defaultOptionClass: 'opt-default', 1557 hasDropClass: 'opt-sublevel', 1558 levelPrefixClass: 'opt-level-', 1559 useDefaultOption: true 1560 }, options); 1561 if(this.options.list) { 1562 this.createSelect(); 1563 this.attachEvents(); 1564 } 1565 } 1566 NavigationSelect.prototype = { 1567 createSelect: function() { 1568 var self = this; 1569 this.startIndex = 0; 1570 this.navigation = $(this.options.list); 1571 this.select = $('<select>').addClass(this.options.selectClass); 1572 this.createDefaultOption(); 1573 this.createList(this.navigation, 0); 1574 this.select.insertBefore(this.navigation); 1575 }, 1576 createDefaultOption: function() { 1577 if(this.options.useDefaultOption) { 1578 var attrText = this.navigation.attr(this.options.defaultOptionAttr); 1579 var defaultOption = $('<option>').addClass(this.options.defaultOptionClass).text(attrText || this.options.defaultOptionText); 1580 this.navigation.removeAttr(this.options.defaultOptionAttr); 1581 this.select.append(defaultOption); 1582 this.startIndex = 1; 1583 } 1584 }, 1585 createList: function(list, level) { 1586 var self = this; 1587 list.children().each(function(index, item) { 1588 var listItem = $(this), 1589 listLink = listItem.find('a').eq(0), 1590 listDrop = listItem.find('ul').eq(0), 1591 hasDrop = listDrop.length > 0; 1592 1593 if(listLink.length) { 1594 self.select.append(self.createOption(listLink, hasDrop, level, listLink.hasClass(self.options.activeClass))); 1595 } 1596 if(hasDrop) { 1597 self.createList(listDrop, level + 1); 1598 } 1599 }); 1600 }, 1601 createOption: function(link, hasDrop, level, selected) { 1602 var optionHTML = this.getLevelIndent(level) + link.html(); 1603 return $('<option>').html(optionHTML) 1604 .addClass(this.options.levelPrefixClass + (level + 1)) 1605 .toggleClass(this.options.hasDropClass, hasDrop) 1606 .val(link.attr('href')).attr('selected', selected ? 'selected' : false); 1607 }, 1608 getLevelIndent: function(level) { 1609 return (new Array(level + 1)).join(this.options.levelIndentHTML); 1610 }, 1611 attachEvents: function() { 1612 // redirect on select change 1613 var self = this; 1614 this.select.change(function() { 1615 if(this.selectedIndex >= self.startIndex) { 1616 location.href = this.value; 1617 } 1618 }); 1619 } 1620 }; 1621 1622 // jquery pluginm interface 1623 $.fn.navigationSelect = function(opt) { 1624 return this.each(function() { 1625 new NavigationSelect($.extend({list: this}, opt)); 1626 }); 1627 }; 1628}(jQuery)); 1629 1630/* 1631 * Responsive Layout helper 1632 */ 1633 1634ResponsiveHelper = (function($){ 1635 // init variables 1636 var handlers = []; 1637 var win = $(window), prevWinWidth; 1638 var scrollBarWidth = 0; 1639 1640 // prepare resize handler 1641 function resizeHandler() { 1642 var winWidth = win.width() + scrollBarWidth; 1643 if(winWidth !== prevWinWidth) { 1644 prevWinWidth = winWidth; 1645 1646 // loop through range groups 1647 $.each(handlers, function(index, rangeObject){ 1648 // disable current active area if needed 1649 $.each(rangeObject.data, function(property, item) { 1650 if((winWidth < item.range[0] || winWidth > item.range[1]) && item.currentActive) { 1651 item.currentActive = false; 1652 if(typeof item.disableCallback === 'function') { 1653 item.disableCallback(); 1654 } 1655 } 1656 }); 1657 1658 // enable areas that match current width 1659 $.each(rangeObject.data, function(property, item) { 1660 if(winWidth >= item.range[0] && winWidth <= item.range[1] && !item.currentActive) { 1661 // make callback 1662 item.currentActive = true; 1663 if(typeof item.enableCallback === 'function') { 1664 item.enableCallback(); 1665 } 1666 } 1667 }); 1668 }); 1669 } 1670 } 1671 win.bind('load', function(){ 1672 if( cr_browser == 'Opera' || cr_browser == 'Firefox' ) { 1673 scrollBarWidth = window.innerWidth - $('body').width(); 1674 resizeHandler(); 1675 } 1676 win.bind('resize orientationchange', resizeHandler); 1677 }); 1678 1679 // range parser 1680 function parseRange(rangeStr) { 1681 var rangeData = rangeStr.split('..'); 1682 var x1 = parseInt(rangeData[0], 10) || -Infinity; 1683 var x2 = parseInt(rangeData[1], 10) || Infinity; 1684 return [x1, x2].sort(function(a, b){ 1685 return a - b; 1686 }); 1687 } 1688 1689 // export public functions 1690 return { 1691 addRange: function(ranges) { 1692 // parse data and add items to collection 1693 var result = {data:{}}; 1694 $.each(ranges, function(property, data){ 1695 result.data[property] = { 1696 range: parseRange(property), 1697 enableCallback: data.on, 1698 disableCallback: data.off 1699 }; 1700 }); 1701 handlers.push(result); 1702 1703 // call resizeHandler to recalculate all events 1704 resizeHandler(); 1705 } 1706 }; 1707}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.