1/*! 2 * JavaScript Custom Forms 3 * 4 * Copyright 2014-2015 PSD2HTML - http://psd2html.com/jcf 5 * Released under the MIT license (LICENSE.txt) 6 * 7 * Version: 1.1.3 8 */ 9; (function (root, factory) { 10 11 'use strict'; 12 if (typeof define === 'function' && define.amd) { 13 define(['jquery'], factory); 14 } else if (typeof exports === 'object') { 15 module.exports = factory(require('jquery')); 16 } else { 17 root.jcf = factory(jQuery); 18 } 19}(this, function ($) { 20 21 'use strict'; 22 23 // define version 24 var version = '1.1.3'; 25 26 // private variables 27 var customInstances = []; 28 29 // default global options 30 var commonOptions = { 31 optionsKey: 'jcf', 32 dataKey: 'jcf-instance', 33 rtlClass: 'jcf-rtl', 34 focusClass: 'jcf-focus', 35 pressedClass: 'jcf-pressed', 36 disabledClass: 'jcf-disabled', 37 hiddenClass: 'jcf-hidden', 38 resetAppearanceClass: 'jcf-reset-appearance', 39 unselectableClass: 'jcf-unselectable' 40 }; 41 42 // detect device type 43 var isTouchDevice = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch, 44 isWinPhoneDevice = /Windows Phone/.test(navigator.userAgent); 45 commonOptions.isMobileDevice = !!(isTouchDevice || isWinPhoneDevice); 46 47 var isIOS = /(iPad|iPhone).*OS ([0-9_]*) .*/.exec(navigator.userAgent); 48 if (isIOS) isIOS = parseFloat(isIOS[2].replace(/_/g, '.')); 49 commonOptions.ios = isIOS; 50 51 // create global stylesheet if custom forms are used 52 var createStyleSheet = function () { 53 var styleTag = $('<style>').appendTo('head'), 54 styleSheet = styleTag.prop('sheet') || styleTag.prop('styleSheet'); 55 56 // crossbrowser style handling 57 var addCSSRule = function (selector, rules, index) { 58 if (styleSheet.insertRule) { 59 styleSheet.insertRule(selector + '{' + rules + '}', index); 60 } else { 61 styleSheet.addRule(selector, rules, index); 62 } 63 }; 64 65 // add special rules 66 addCSSRule('.' + commonOptions.hiddenClass, 'position:absolute !important;left:-9999px !important;height:1px !important;width:1px !important;margin:0 !important;border-width:0 !important;-webkit-appearance:none;-moz-appearance:none;appearance:none'); 67 addCSSRule('.' + commonOptions.rtlClass + ' .' + commonOptions.hiddenClass, 'right:-9999px !important; left: auto !important'); 68 addCSSRule('.' + commonOptions.unselectableClass, '-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; -webkit-tap-highlight-color: rgba(0,0,0,0);'); 69 addCSSRule('.' + commonOptions.resetAppearanceClass, 'background: none; border: none; -webkit-appearance: none; appearance: none; opacity: 0; filter: alpha(opacity=0);'); 70 71 // detect rtl pages 72 var html = $('html'), body = $('body'); 73 if (html.css('direction') === 'rtl' || body.css('direction') === 'rtl') { 74 html.addClass(commonOptions.rtlClass); 75 } 76 77 // handle form reset event 78 html.on('reset', function () { 79 setTimeout(function () { 80 api.refreshAll(); 81 }, 0); 82 }); 83 84 // mark stylesheet as created 85 commonOptions.styleSheetCreated = true; 86 }; 87 88 // simplified pointer events handler 89 (function () { 90 var pointerEventsSupported = navigator.pointerEnabled || navigator.msPointerEnabled, 91 touchEventsSupported = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch, 92 eventList, eventMap = {}, eventPrefix = 'jcf-'; 93 94 // detect events to attach 95 if (pointerEventsSupported) { 96 eventList = { 97 pointerover: navigator.pointerEnabled ? 'pointerover' : 'MSPointerOver', 98 pointerdown: navigator.pointerEnabled ? 'pointerdown' : 'MSPointerDown', 99 pointermove: navigator.pointerEnabled ? 'pointermove' : 'MSPointerMove', 100 pointerup: navigator.pointerEnabled ? 'pointerup' : 'MSPointerUp' 101 }; 102 } else { 103 eventList = { 104 pointerover: 'mouseover', 105 pointerdown: 'mousedown' + (touchEventsSupported ? ' touchstart' : ''), 106 pointermove: 'mousemove' + (touchEventsSupported ? ' touchmove' : ''), 107 pointerup: 'mouseup' + (touchEventsSupported ? ' touchend' : '') 108 }; 109 } 110 111 // create event map 112 $.each(eventList, function (targetEventName, fakeEventList) { 113 $.each(fakeEventList.split(' '), function (index, fakeEventName) { 114 eventMap[fakeEventName] = targetEventName; 115 }); 116 }); 117 118 // jQuery event hooks 119 $.each(eventList, function (eventName, eventHandlers) { 120 eventHandlers = eventHandlers.split(' '); 121 $.event.special[eventPrefix + eventName] = { 122 setup: function () { 123 var self = this; 124 $.each(eventHandlers, function (index, fallbackEvent) { 125 if (self.addEventListener) self.addEventListener(fallbackEvent, fixEvent, false); 126 else self['on' + fallbackEvent] = fixEvent; 127 }); 128 }, 129 teardown: function () { 130 var self = this; 131 $.each(eventHandlers, function (index, fallbackEvent) { 132 if (self.addEventListener) self.removeEventListener(fallbackEvent, fixEvent, false); 133 else self['on' + fallbackEvent] = null; 134 }); 135 } 136 }; 137 }); 138 139 // check that mouse event are not simulated by mobile browsers 140 var lastTouch = null; 141 var mouseEventSimulated = function (e) { 142 var dx = Math.abs(e.pageX - lastTouch.x), 143 dy = Math.abs(e.pageY - lastTouch.y), 144 rangeDistance = 25; 145 146 if (dx <= rangeDistance && dy <= rangeDistance) { 147 return true; 148 } 149 }; 150 151 // normalize event 152 var fixEvent = function (e) { 153 var origEvent = e || window.event, 154 touchEventData = null, 155 targetEventName = eventMap[origEvent.type]; 156 157 e = $.event.fix(origEvent); 158 e.type = eventPrefix + targetEventName; 159 160 if (origEvent.pointerType) { 161 switch (origEvent.pointerType) { 162 case 2: e.pointerType = 'touch'; break; 163 case 3: e.pointerType = 'pen'; break; 164 case 4: e.pointerType = 'mouse'; break; 165 default: e.pointerType = origEvent.pointerType; 166 } 167 } else { 168 e.pointerType = origEvent.type.substr(0, 5); // "mouse" or "touch" word length 169 } 170 171 if (!e.pageX && !e.pageY) { 172 touchEventData = origEvent.changedTouches ? origEvent.changedTouches[0] : origEvent; 173 e.pageX = touchEventData.pageX; 174 e.pageY = touchEventData.pageY; 175 } 176 177 if (origEvent.type === 'touchend') { 178 lastTouch = { x: e.pageX, y: e.pageY }; 179 } 180 if (e.pointerType === 'mouse' && lastTouch && mouseEventSimulated(e)) { 181 return; 182 } else { 183 return ($.event.dispatch || $.event.handle).call(this, e); 184 } 185 }; 186 }()); 187 188 // custom mousewheel/trackpad handler 189 (function () { 190 var wheelEvents = ('onwheel' in document || document.documentMode >= 9 ? 'wheel' : 'mousewheel DOMMouseScroll').split(' '), 191 shimEventName = 'jcf-mousewheel'; 192 193 $.event.special[shimEventName] = { 194 setup: function () { 195 var self = this; 196 $.each(wheelEvents, function (index, fallbackEvent) { 197 if (self.addEventListener) self.addEventListener(fallbackEvent, fixEvent, false); 198 else self['on' + fallbackEvent] = fixEvent; 199 }); 200 }, 201 teardown: function () { 202 var self = this; 203 $.each(wheelEvents, function (index, fallbackEvent) { 204 if (self.addEventListener) self.removeEventListener(fallbackEvent, fixEvent, false); 205 else self['on' + fallbackEvent] = null; 206 }); 207 } 208 }; 209 210 var fixEvent = function (e) { 211 var origEvent = e || window.event; 212 e = $.event.fix(origEvent); 213 e.type = shimEventName; 214 215 // old wheel events handler 216 if ('detail' in origEvent) { e.deltaY = -origEvent.detail; } 217 if ('wheelDelta' in origEvent) { e.deltaY = -origEvent.wheelDelta; } 218 if ('wheelDeltaY' in origEvent) { e.deltaY = -origEvent.wheelDeltaY; } 219 if ('wheelDeltaX' in origEvent) { e.deltaX = -origEvent.wheelDeltaX; } 220 221 // modern wheel event handler 222 if ('deltaY' in origEvent) { 223 e.deltaY = origEvent.deltaY; 224 } 225 if ('deltaX' in origEvent) { 226 e.deltaX = origEvent.deltaX; 227 } 228 229 // handle deltaMode for mouse wheel 230 e.delta = e.deltaY || e.deltaX; 231 if (origEvent.deltaMode === 1) { 232 var lineHeight = 16; 233 e.delta *= lineHeight; 234 e.deltaY *= lineHeight; 235 e.deltaX *= lineHeight; 236 } 237 238 return ($.event.dispatch || $.event.handle).call(this, e); 239 }; 240 }()); 241 242 // extra module methods 243 var moduleMixin = { 244 // provide function for firing native events 245 fireNativeEvent: function (elements, eventName) { 246 $(elements).each(function () { 247 var element = this, eventObject; 248 if (element.dispatchEvent) { 249 eventObject = document.createEvent('HTMLEvents'); 250 eventObject.initEvent(eventName, true, true); 251 element.dispatchEvent(eventObject); 252 } else if (document.createEventObject) { 253 eventObject = document.createEventObject(); 254 eventObject.target = element; 255 element.fireEvent('on' + eventName, eventObject); 256 } 257 }); 258 }, 259 // bind event handlers for module instance (functions beggining with "on") 260 bindHandlers: function () { 261 var self = this; 262 $.each(self, function (propName, propValue) { 263 if (propName.indexOf('on') === 0 && $.isFunction(propValue)) { 264 // dont use $.proxy here because it doesn't create unique handler 265 self[propName] = function () { 266 return propValue.apply(self, arguments); 267 }; 268 } 269 }); 270 } 271 }; 272 273 // public API 274 var api = { 275 version: version, 276 modules: {}, 277 getOptions: function () { 278 return $.extend({}, commonOptions); 279 }, 280 setOptions: function (moduleName, moduleOptions) { 281 if (arguments.length > 1) { 282 // set module options 283 if (this.modules[moduleName]) { 284 $.extend(this.modules[moduleName].prototype.options, moduleOptions); 285 } 286 } else { 287 // set common options 288 $.extend(commonOptions, moduleName); 289 } 290 }, 291 addModule: function (proto) { 292 // add module to list 293 var Module = function (options) { 294 // save instance to collection 295 if (!options.element.data(commonOptions.dataKey)) { 296 options.element.data(commonOptions.dataKey, this); 297 } 298 customInstances.push(this); 299 300 // save options 301 this.options = $.extend({}, commonOptions, this.options, getInlineOptions(options.element), options); 302 303 // bind event handlers to instance 304 this.bindHandlers(); 305 306 // call constructor 307 this.init.apply(this, arguments); 308 }; 309 310 // parse options from HTML attribute 311 var getInlineOptions = function (element) { 312 var dataOptions = element.data(commonOptions.optionsKey), 313 attrOptions = element.attr(commonOptions.optionsKey); 314 315 if (dataOptions) { 316 return dataOptions; 317 } else if (attrOptions) { 318 try { 319 return $.parseJSON(attrOptions); 320 } catch (e) { 321 // ignore invalid attributes 322 } 323 } 324 }; 325 326 // set proto as prototype for new module 327 Module.prototype = proto; 328 329 // add mixin methods to module proto 330 $.extend(proto, moduleMixin); 331 if (proto.plugins) { 332 $.each(proto.plugins, function (pluginName, plugin) { 333 $.extend(plugin.prototype, moduleMixin); 334 }); 335 } 336 337 // override destroy method 338 var originalDestroy = Module.prototype.destroy; 339 Module.prototype.destroy = function () { 340 this.options.element.removeData(this.options.dataKey); 341 342 for (var i = customInstances.length - 1; i >= 0; i--) { 343 if (customInstances[i] === this) { 344 customInstances.splice(i, 1); 345 break; 346 } 347 } 348 349 if (originalDestroy) { 350 originalDestroy.apply(this, arguments); 351 } 352 }; 353 354 // save module to list 355 this.modules[proto.name] = Module; 356 }, 357 getInstance: function (element) { 358 return $(element).data(commonOptions.dataKey); 359 }, 360 replace: function (elements, moduleName, customOptions) { 361 var self = this, 362 instance; 363 364 if (!commonOptions.styleSheetCreated) { 365 createStyleSheet(); 366 } 367 368 $(elements).each(function () { 369 var moduleOptions, 370 element = $(this); 371 372 instance = element.data(commonOptions.dataKey); 373 if (instance) { 374 instance.refresh(); 375 } else { 376 if (!moduleName) { 377 $.each(self.modules, function (currentModuleName, module) { 378 if (module.prototype.matchElement.call(module.prototype, element)) { 379 moduleName = currentModuleName; 380 return false;
381 } 382 }); 383 } 384 if (moduleName) { 385 moduleOptions = $.extend({ element: element }, customOptions); 386 instance = new self.modules[moduleName](moduleOptions); 387 } 388 } 389 }); 390 return instance; 391 }, 392 refresh: function (elements) { 393 $(elements).each(function () { 394 var instance = $(this).data(commonOptions.dataKey); 395 if (instance) { 396 instance.refresh(); 397 } 398 }); 399 }, 400 destroy: function (elements) { 401 $(elements).each(function () { 402 var instance = $(this).data(commonOptions.dataKey); 403 if (instance) { 404 instance.destroy(); 405 } 406 }); 407 }, 408 replaceAll: function (context) { 409 var self = this; 410 $.each(this.modules, function (moduleName, module) { 411 $(module.prototype.selector, context).each(function () { 412 if (this.className.indexOf('jcf-ignore') < 0) { 413 self.replace(this, moduleName); 414 } 415 }); 416 }); 417 }, 418 refreshAll: function (context) { 419 if (context) { 420 $.each(this.modules, function (moduleName, module) { 421 $(module.prototype.selector, context).each(function () { 422 var instance = $(this).data(commonOptions.dataKey); 423 if (instance) { 424 instance.refresh(); 425 } 426 }); 427 }); 428 } else { 429 for (var i = customInstances.length - 1; i >= 0; i--) { 430 customInstances[i].refresh(); 431 } 432 } 433 }, 434 destroyAll: function (context) { 435 if (context) { 436 $.each(this.modules, function (moduleName, module) { 437 $(module.prototype.selector, context).each(function (index, element) { 438 var instance = $(element).data(commonOptions.dataKey); 439 if (instance) { 440 instance.destroy(); 441 } 442 }); 443 }); 444 } else { 445 while (customInstances.length) { 446 customInstances[0].destroy(); 447 } 448 } 449 } 450 }; 451 452 // always export API to the global window object 453 window.jcf = api; 454 455 return api; 456})); 457 458/*! 459* JavaScript Custom Forms : Select Module 460* 461* Copyright 2014-2015 PSD2HTML - http://psd2html.com/jcf 462* Released under the MIT license (LICENSE.txt) 463* 464* Version: 1.1.3 465*/ 466; (function ($, window) { 467 468 'use strict'; 469 470 jcf.addModule({ 471 name: 'Select', 472 selector: 'select', 473 options: { 474 element: null, 475 multipleCompactStyle: false 476 }, 477 plugins: { 478 ListBox: ListBox, 479 ComboBox: ComboBox, 480 SelectList: SelectList 481 }, 482 matchElement: function (element) { 483 return element.is('select'); 484 }, 485 init: function () { 486 this.element = $(this.options.element); 487 this.createInstance(); 488 }, 489 isListBox: function () { 490 return this.element.is('[size]:not([jcf-size]), [multiple]'); 491 }, 492 createInstance: function () { 493 if (this.instance) { 494 this.instance.destroy(); 495 } 496 if (this.isListBox() && !this.options.multipleCompactStyle) { 497 this.instance = new ListBox(this.options); 498 } else { 499 this.instance = new ComboBox(this.options); 500 } 501 }, 502 refresh: function () { 503 var typeMismatch = (this.isListBox() && this.instance instanceof ComboBox) || 504 (!this.isListBox() && this.instance instanceof ListBox); 505 506 if (typeMismatch) { 507 this.createInstance(); 508 } else { 509 this.instance.refresh(); 510 } 511 }, 512 destroy: function () { 513 this.instance.destroy(); 514 } 515 }); 516 517 // combobox module 518 function ComboBox(options) { 519 this.options = $.extend({ 520 wrapNative: true, 521 wrapNativeOnMobile: true, 522 fakeDropInBody: true, 523 useCustomScroll: true, 524 flipDropToFit: true, 525 maxVisibleItems: 10, 526 fakeAreaStructure: '<span class="jcf-select"><span class="jcf-select-text"></span><span class="jcf-select-opener"></span></span>', 527 fakeDropStructure: '<div class="jcf-select-drop"><div class="jcf-select-drop-content"></div></div>', 528 optionClassPrefix: 'jcf-option-', 529 selectClassPrefix: 'jcf-select-', 530 dropContentSelector: '.jcf-select-drop-content', 531 selectTextSelector: '.jcf-select-text', 532 dropActiveClass: 'jcf-drop-active', 533 flipDropClass: 'jcf-drop-flipped' 534 }, options); 535 this.init(); 536 } 537 $.extend(ComboBox.prototype, { 538 init: function () { 539 this.initStructure(); 540 this.bindHandlers(); 541 this.attachEvents(); 542 this.refresh(); 543 }, 544 initStructure: function () { 545 // prepare structure 546 this.win = $(window); 547 this.doc = $(document); 548 this.realElement = $(this.options.element); 549 this.fakeElement = $(this.options.fakeAreaStructure).insertAfter(this.realElement); 550 this.selectTextContainer = this.fakeElement.find(this.options.selectTextSelector); 551 this.selectText = $('<span></span>').appendTo(this.selectTextContainer); 552 makeUnselectable(this.fakeElement); 553 554 // copy classes from original select
555 this.fakeElement.addClass(getPrefixedClasses(this.realElement.prop('className'), this.options.selectClassPrefix)); 556 557 // handle compact multiple style 558 if (this.realElement.prop('multiple')) { 559 this.fakeElement.addClass('jcf-compact-multiple'); 560 } 561 562 // detect device type and dropdown behavior 563 if (this.options.isMobileDevice && this.options.wrapNativeOnMobile && !this.options.wrapNative) { 564 this.options.wrapNative = true; 565 } 566 567 if (this.options.wrapNative) { 568 // wrap native select inside fake block 569 this.realElement.prependTo(this.fakeElement).css({ 570 position: 'absolute', 571 height: '100%', 572 width: '100%' 573 }).addClass(this.options.resetAppearanceClass); 574 } else { 575 // just hide native select 576 this.realElement.addClass(this.options.hiddenClass); 577 this.fakeElement.attr('title', this.realElement.attr('title')); 578 this.fakeDropTarget = this.options.fakeDropInBody ? $('body') : this.fakeElement; 579 } 580 }, 581 attachEvents: function () { 582 // delayed refresh handler 583 var self = this; 584 this.delayedRefresh = function () { 585 setTimeout(function () { 586 self.refresh(); 587 if (self.list) { 588 self.list.refresh(); 589 self.list.scrollToActiveOption(); 590 } 591 }, 1); 592 }; 593 594 // native dropdown event handlers 595 if (this.options.wrapNative) { 596 this.realElement.on({ 597 focus: this.onFocus, 598 change: this.onChange, 599 click: this.onChange, 600 keydown: this.onChange 601 }); 602 } else { 603 // custom dropdown event handlers 604 this.realElement.on({ 605 focus: this.onFocus, 606 change: this.onChange, 607 keydown: this.onKeyDown 608 }); 609 this.fakeElement.on({ 610 'jcf-pointerdown': this.onSelectAreaPress 611 }); 612 } 613 }, 614 onKeyDown: function (e) { 615 if (e.which === 13) { 616 this.toggleDropdown(); 617 } else if (this.dropActive) { 618 this.delayedRefresh(); 619 } 620 }, 621 onChange: function () { 622 this.refresh(); 623 }, 624 onFocus: function () { 625 if (!this.pressedFlag || !this.focusedFlag) { 626 this.fakeElement.addClass(this.options.focusClass); 627 this.realElement.on('blur', this.onBlur); 628 this.toggleListMode(true); 629 this.focusedFlag = true; 630 } 631 }, 632 onBlur: function () { 633 if (!this.pressedFlag) { 634 this.fakeElement.removeClass(this.options.focusClass); 635 this.realElement.off('blur', this.onBlur); 636 this.toggleListMode(false); 637 this.focusedFlag = false; 638 } 639 }, 640 onResize: function () { 641 if (this.dropActive) { 642 this.hideDropdown(); 643 } 644 }, 645 onSelectDropPress: function () { 646 this.pressedFlag = true; 647 }, 648 onSelectDropRelease: function (e, pointerEvent) { 649 this.pressedFlag = false; 650 if (pointerEvent.pointerType === 'mouse') { 651 this.realElement.focus(); 652 } 653 }, 654 onSelectAreaPress: function (e) { 655 // skip click if drop inside fake element or real select is disabled 656 var dropClickedInsideFakeElement = !this.options.fakeDropInBody && $(e.target).closest(this.dropdown).length; 657 if (dropClickedInsideFakeElement || e.button > 1 || this.realElement.is(':disabled')) { 658 return; 659 } 660 661 // toggle dropdown visibility 662 this.selectOpenedByEvent = e.pointerType; 663 this.toggleDropdown(); 664 665 // misc handlers 666 if (!this.focusedFlag) { 667 if (e.pointerType === 'mouse') { 668 this.realElement.focus(); 669 } else { 670 this.onFocus(e); 671 } 672 } 673 this.pressedFlag = true; 674 this.fakeElement.addClass(this.options.pressedClass); 675 this.doc.on('jcf-pointerup', this.onSelectAreaRelease); 676 }, 677 onSelectAreaRelease: function (e) { 678 if (this.focusedFlag && e.pointerType === 'mouse') { 679 this.realElement.focus(); 680 } 681 this.pressedFlag = false; 682 this.fakeElement.removeClass(this.options.pressedClass); 683 this.doc.off('jcf-pointerup', this.onSelectAreaRelease); 684 }, 685 onOutsideClick: function (e) { 686 var target = $(e.target), 687 clickedInsideSelect = target.closest(this.fakeElement).length || target.closest(this.dropdown).length; 688 689 if (!clickedInsideSelect) { 690 this.hideDropdown(); 691 } 692 }, 693 onSelect: function () { 694 this.refresh(); 695 696 if (this.realElement.prop('multiple')) { 697 this.repositionDropdown(); 698 } else { 699 this.hideDropdown(); 700 } 701 702 this.fireNativeEvent(this.realElement, 'change'); 703 }, 704 toggleListMode: function (state) { 705 if (!this.options.wrapNative) { 706 if (state) { 707 // temporary change select to list to avoid appearing of native dropdown 708 this.realElement.attr({ 709 size: 4, 710 'jcf-size': '' 711 }); 712 } else { 713 // restore select from list mode to dropdown select 714 if (!this.options.wrapNative) { 715 this.realElement.removeAttr('size jcf-size'); 716 } 717 } 718 } 719 }, 720 createDropdown: function () { 721 // destroy previous dropdown if needed 722 if (this.dropdown) { 723 this.list.destroy(); 724 this.dropdown.remove(); 725 } 726 727 // create new drop container 728 this.dropdown = $(this.options.fakeDropStructure).appendTo(this.fakeDropTarget);
729 this.dropdown.addClass(getPrefixedClasses(this.realElement.prop('className'), this.options.selectClassPrefix)); 730 makeUnselectable(this.dropdown); 731 732 // handle compact multiple style 733 if (this.realElement.prop('multiple')) { 734 this.dropdown.addClass('jcf-compact-multiple'); 735 } 736 737 // set initial styles for dropdown in body 738 if (this.options.fakeDropInBody) { 739 this.dropdown.css({ 740 position: 'absolute', 741 top: -9999 742 }); 743 } 744 745 // create new select list instance 746 this.list = new SelectList({ 747 useHoverClass: true, 748 handleResize: false, 749 alwaysPreventMouseWheel: true, 750 maxVisibleItems: this.options.maxVisibleItems, 751 useCustomScroll: this.options.useCustomScroll, 752 holder: this.dropdown.find(this.options.dropContentSelector), 753 multipleSelectWithoutKey: this.realElement.prop('multiple'), 754 element: this.realElement 755 }); 756 $(this.list).on({ 757 select: this.onSelect, 758 press: this.onSelectDropPress, 759 release: this.onSelectDropRelease 760 }); 761 }, 762 repositionDropdown: function () { 763 var selectOffset = this.fakeElement.offset(), 764 selectWidth = this.fakeElement.outerWidth(), 765 selectHeight = this.fakeElement.outerHeight(), 766 dropHeight = this.dropdown.css('width', selectWidth).outerHeight(), 767 winScrollTop = this.win.scrollTop(), 768 winHeight = this.win.height(), 769 calcTop, calcLeft, bodyOffset, needFlipDrop = false; 770 771 // check flip drop position 772 if (selectOffset.top + selectHeight + dropHeight > winScrollTop + winHeight && selectOffset.top - dropHeight > winScrollTop) { 773 needFlipDrop = true; 774 } 775 776 if (this.options.fakeDropInBody) { 777 bodyOffset = this.fakeDropTarget.css('position') !== 'static' ? this.fakeDropTarget.offset().top : 0; 778 if (this.options.flipDropToFit && needFlipDrop) { 779 // calculate flipped dropdown position 780 calcLeft = selectOffset.left; 781 calcTop = selectOffset.top - dropHeight - bodyOffset; 782 } else { 783 // calculate default drop position 784 calcLeft = selectOffset.left; 785 calcTop = selectOffset.top + selectHeight - bodyOffset; 786 } 787 788 // update drop styles 789 this.dropdown.css({ 790 width: selectWidth, 791 left: calcLeft, 792 top: calcTop 793 }); 794 } 795 796 // refresh flipped class 797 this.dropdown.add(this.fakeElement).toggleClass(this.options.flipDropClass, this.options.flipDropToFit && needFlipDrop); 798 }, 799 showDropdown: function () { 800 // do not show empty custom dropdown 801 if (!this.realElement.prop('options').length) { 802 return; 803 } 804 805 // create options list if not created 806 if (!this.dropdown) { 807 this.createDropdown(); 808 } 809 810 // show dropdown 811 this.dropActive = true; 812 this.dropdown.appendTo(this.fakeDropTarget); 813 this.fakeElement.addClass(this.options.dropActiveClass); 814 this.refreshSelectedText(); 815 this.repositionDropdown(); 816 this.list.setScrollTop(this.savedScrollTop); 817 this.list.refresh(); 818 819 // add temporary event handlers 820 this.win.on('resize', this.onResize); 821 this.doc.on('jcf-pointerdown', this.onOutsideClick); 822 }, 823 hideDropdown: function () { 824 if (this.dropdown) { 825 this.savedScrollTop = this.list.getScrollTop(); 826 this.fakeElement.removeClass(this.options.dropActiveClass + ' ' + this.options.flipDropClass); 827 this.dropdown.removeClass(this.options.flipDropClass).detach(); 828 this.doc.off('jcf-pointerdown', this.onOutsideClick); 829 this.win.off('resize', this.onResize); 830 this.dropActive = false; 831 if (this.selectOpenedByEvent === 'touch') { 832 this.onBlur(); 833 } 834 } 835 }, 836 toggleDropdown: function () { 837 if (this.dropActive) { 838 this.hideDropdown(); 839 } else { 840 this.showDropdown(); 841 } 842 }, 843 refreshSelectedText: function () { 844 // redraw selected area 845 var selectedIndex = this.realElement.prop('selectedIndex'), 846 selectedOption = this.realElement.prop('options')[selectedIndex], 847 selectedOptionImage = selectedOption ? selectedOption.getAttribute('data-image') : null, 848 selectedOptionText = '', 849 selectedOptionClasses, 850 self = this; 851 852 if (this.realElement.prop('multiple')) { 853 $.each(this.realElement.prop('options'), function (index, option) { 854 if (option.selected) {
855 selectedOptionText += (selectedOptionText ? ', ' : '') + option.innerHTML; 856 } 857 }); 858 if (!selectedOptionText) { 859 selectedOptionText = self.realElement.attr('placeholder') || ''; 860 } 861 this.selectText.removeAttr('class').html(selectedOptionText); 862 } else if (!selectedOption) { 863 if (this.selectImage) { 864 this.selectImage.hide(); 865 } 866 this.selectText.removeAttr('class').empty(); 867 } else if (this.currentSelectedText !== selectedOption.innerHTML || this.currentSelectedImage !== selectedOptionImage) { 868 selectedOptionClasses = getPrefixedClasses(selectedOption.className, this.options.optionClassPrefix); 869 this.selectText.attr('class', selectedOptionClasses).html(selectedOption.innerHTML); 870 871 if (selectedOptionImage) { 872 if (!this.selectImage) { 873 this.selectImage = $('<img>').prependTo(this.selectTextContainer).hide(); 874 } 875 this.selectImage.attr('src', selectedOptionImage).show(); 876 } else if (this.selectImage) { 877 this.selectImage.hide(); 878 } 879 880 this.currentSelectedText = selectedOption.innerHTML; 881 this.currentSelectedImage = selectedOptionImage; 882 } 883 }, 884 refresh: function () { 885 // refresh fake select visibility 886 if (this.realElement.prop('style').display === 'none') { 887 this.fakeElement.hide(); 888 } else { 889 this.fakeElement.show(); 890 } 891 892 // refresh selected text 893 this.refreshSelectedText(); 894 895 // handle disabled state 896 this.fakeElement.toggleClass(this.options.disabledClass, this.realElement.is(':disabled')); 897 }, 898 destroy: function () { 899 // restore structure 900 if (this.options.wrapNative) { 901 this.realElement.insertBefore(this.fakeElement).css({ 902 position: '', 903 height: '', 904 width: '' 905 }).removeClass(this.options.resetAppearanceClass); 906 } else { 907 this.realElement.removeClass(this.options.hiddenClass); 908 if (this.realElement.is('[jcf-size]')) { 909 this.realElement.removeAttr('size jcf-size'); 910 } 911 } 912 913 // removing element will also remove its event handlers 914 this.fakeElement.remove(); 915 916 // remove other event handlers 917 this.doc.off('jcf-pointerup', this.onSelectAreaRelease); 918 this.realElement.off({ 919 focus: this.onFocus 920 }); 921 } 922 }); 923 924 // listbox module 925 function ListBox(options) { 926 this.options = $.extend({ 927 wrapNative: true, 928 useCustomScroll: true, 929 fakeStructure: '<span class="jcf-list-box"><span class="jcf-list-wrapper"></span></span>', 930 selectClassPrefix: 'jcf-select-', 931 listHolder: '.jcf-list-wrapper' 932 }, options); 933 this.init(); 934 } 935 $.extend(ListBox.prototype, { 936 init: function () { 937 this.bindHandlers(); 938 this.initStructure(); 939 this.attachEvents(); 940 }, 941 initStructure: function () { 942 this.realElement = $(this.options.element); 943 this.fakeElement = $(this.options.fakeStructure).insertAfter(this.realElement); 944 this.listHolder = this.fakeElement.find(this.options.listHolder); 945 makeUnselectable(this.fakeElement); 946 947 // copy classes from original select 948 this.fakeElement.addClass(getPrefixedClasses(this.realElement.prop('className'), this.options.selectClassPrefix)); 949 this.realElement.addClass(this.options.hiddenClass); 950 951 this.list = new SelectList({ 952 useCustomScroll: this.options.useCustomScroll, 953 holder: this.listHolder, 954 selectOnClick: false, 955 element: this.realElement 956 }); 957 }, 958 attachEvents: function () { 959 // delayed refresh handler 960 var self = this; 961 this.delayedRefresh = function (e) { 962 if (e && e.which === 16) { 963 // ignore SHIFT key 964 return; 965 } else { 966 clearTimeout(self.refreshTimer); 967 self.refreshTimer = setTimeout(function () { 968 self.refresh(); 969 self.list.scrollToActiveOption(); 970 }, 1); 971 } 972 }; 973 974 // other event handlers 975 this.realElement.on({ 976 focus: this.onFocus, 977 click: this.delayedRefresh, 978 keydown: this.delayedRefresh 979 }); 980 981 // select list event handlers 982 $(this.list).on({ 983 select: this.onSelect, 984 press: this.onFakeOptionsPress, 985 release: this.onFakeOptionsRelease 986 }); 987 }, 988 onFakeOptionsPress: function (e, pointerEvent) { 989 this.pressedFlag = true; 990 if (pointerEvent.pointerType === 'mouse') { 991 this.realElement.focus(); 992 } 993 }, 994 onFakeOptionsRelease: function (e, pointerEvent) { 995 this.pressedFlag = false;
996 if (pointerEvent.pointerType === 'mouse') { 997 this.realElement.focus(); 998 } 999 }, 1000 onSelect: function () { 1001 this.fireNativeEvent(this.realElement, 'change'); 1002 this.fireNativeEvent(this.realElement, 'click'); 1003 }, 1004 onFocus: function () { 1005 if (!this.pressedFlag || !this.focusedFlag) { 1006 this.fakeElement.addClass(this.options.focusClass); 1007 this.realElement.on('blur', this.onBlur); 1008 this.focusedFlag = true; 1009 } 1010 }, 1011 onBlur: function () { 1012 if (!this.pressedFlag) { 1013 this.fakeElement.removeClass(this.options.focusClass); 1014 this.realElement.off('blur', this.onBlur); 1015 this.focusedFlag = false; 1016 } 1017 }, 1018 refresh: function () { 1019 this.fakeElement.toggleClass(this.options.disabledClass, this.realElement.is(':disabled')); 1020 this.list.refresh(); 1021 }, 1022 destroy: function () { 1023 this.list.destroy(); 1024 this.realElement.insertBefore(this.fakeElement).removeClass(this.options.hiddenClass); 1025 this.fakeElement.remove(); 1026 } 1027 }); 1028 1029 // options list module 1030 function SelectList(options) { 1031 this.options = $.extend({ 1032 holder: null, 1033 maxVisibleItems: 10, 1034 selectOnClick: true, 1035 useHoverClass: false, 1036 useCustomScroll: false, 1037 handleResize: true, 1038 multipleSelectWithoutKey: false, 1039 alwaysPreventMouseWheel: false, 1040 indexAttribute: 'data-index', 1041 cloneClassPrefix: 'jcf-option-', 1042 containerStructure: '<span class="jcf-list"><span class="jcf-list-content"></span></span>', 1043 containerSelector: '.jcf-list-content', 1044 captionClass: 'jcf-optgroup-caption', 1045 disabledClass: 'jcf-disabled', 1046 optionClass: 'jcf-option', 1047 groupClass: 'jcf-optgroup', 1048 hoverClass: 'jcf-hover', 1049 selectedClass: 'jcf-selected', 1050 scrollClass: 'jcf-scroll-active' 1051 }, options); 1052 this.init(); 1053 } 1054 $.extend(SelectList.prototype, { 1055 init: function () { 1056 this.initStructure(); 1057 this.refreshSelectedClass(); 1058 this.attachEvents(); 1059 }, 1060 initStructure: function () { 1061 this.element = $(this.options.element); 1062 this.indexSelector = '[' + this.options.indexAttribute + ']'; 1063 this.container = $(this.options.containerStructure).appendTo(this.options.holder); 1064 this.listHolder = this.container.find(this.options.containerSelector); 1065 this.lastClickedIndex = this.element.prop('selectedIndex'); 1066 this.rebuildList(); 1067 }, 1068 attachEvents: function () { 1069 this.bindHandlers(); 1070 this.listHolder.on('jcf-pointerdown', this.indexSelector, this.onItemPress); 1071 this.listHolder.on('jcf-pointerdown', this.onPress); 1072 1073 if (this.options.useHoverClass) { 1074 this.listHolder.on('jcf-pointerover', this.indexSelector, this.onHoverItem); 1075 } 1076 }, 1077 onPress: function (e) { 1078 $(this).trigger('press', e); 1079 this.listHolder.on('jcf-pointerup', this.onRelease); 1080 }, 1081 onRelease: function (e) { 1082 $(this).trigger('release', e); 1083 this.listHolder.off('jcf-pointerup', this.onRelease); 1084 }, 1085 onHoverItem: function (e) { 1086 var hoverIndex = parseFloat(e.currentTarget.getAttribute(this.options.indexAttribute)); 1087 this.fakeOptions.removeClass(this.options.hoverClass).eq(hoverIndex).addClass(this.options.hoverClass); 1088 }, 1089 onItemPress: function (e) { 1090 if (e.pointerType === 'touch' || this.options.selectOnClick) { 1091 // select option after "click" 1092 this.tmpListOffsetTop = this.list.offset().top; 1093 this.listHolder.on('jcf-pointerup', this.indexSelector, this.onItemRelease); 1094 } else { 1095 // select option immediately 1096 this.onSelectItem(e); 1097 } 1098 }, 1099 onItemRelease: function (e) { 1100 // remove event handlers and temporary data 1101 this.listHolder.off('jcf-pointerup', this.indexSelector, this.onItemRelease); 1102 1103 // simulate item selection 1104 if (this.tmpListOffsetTop === this.list.offset().top) { 1105 this.listHolder.on('click', this.indexSelector, { savedPointerType: e.pointerType }, this.onSelectItem); 1106 } 1107 delete this.tmpListOffsetTop; 1108 }, 1109 onSelectItem: function (e) { 1110 var clickedIndex = parseFloat(e.currentTarget.getAttribute(this.options.indexAttribute)), 1111 pointerType = e.data && e.data.savedPointerType || e.pointerType || 'mouse', 1112 range; 1113 1114 // remove click event handler 1115 this.listHolder.off('click', this.indexSelector, this.onSelectItem); 1116 1117 // ignore clicks on disabled options 1118 if (e.button > 1 || this.realOptions[clickedIndex].disabled) { 1119 return; 1120 } 1121 1122 if (this.element.prop('multiple')) { 1123 if (e.metaKey || e.ctrlKey || pointerType === 'touch' || this.options.multipleSelectWithoutKey) { 1124 // if CTRL/CMD pressed or touch devices - toggle selected option 1125 this.realOptions[clickedIndex].selected = !this.realOptions[clickedIndex].selected; 1126 } else if (e.shiftKey) { 1127 // if SHIFT pressed - update selection 1128 range = [this.lastClickedIndex, clickedIndex].sort(function (a, b) { 1129 return a - b; 1130 }); 1131 this.realOptions.each(function (index, option) { 1132 option.selected = (index >= range[0] && index <= range[1]); 1133 }); 1134 } else { 1135 // set single selected index 1136 this.element.prop('selectedIndex', clickedIndex); 1137 } 1138 } else { 1139 this.element.prop('selectedIndex', clickedIndex); 1140 } 1141 1142 // save last clicked option 1143 if (!e.shiftKey) { 1144 this.lastClickedIndex = clickedIndex; 1145 } 1146 1147 // refresh classes 1148 this.refreshSelectedClass(); 1149 1150 // scroll to active item in desktop browsers 1151 if (pointerType === 'mouse') { 1152 this.scrollToActiveOption(); 1153 } 1154 1155 // make callback when item selected 1156 $(this).trigger('select'); 1157 }, 1158 rebuildList: function () { 1159 // rebuild options 1160 var self = this, 1161 rootElement = this.element[0]; 1162 1163 // recursively create fake options 1164 this.storedSelectHTML = rootElement.innerHTML; 1165 this.optionIndex = 0; 1166 this.list = $(this.createOptionsList(rootElement)); 1167 this.listHolder.empty().append(this.list); 1168 this.realOptions = this.element.find('option'); 1169 this.fakeOptions = this.list.find(this.indexSelector); 1170 this.fakeListItems = this.list.find('.' + this.options.captionClass + ',' + this.indexSelector); 1171 delete this.optionIndex; 1172 1173 // detect max visible items 1174 var maxCount = this.options.maxVisibleItems, 1175 sizeValue = this.element.prop('size'); 1176 if (sizeValue > 1 && !this.element.is('[jcf-size]')) { 1177 maxCount = sizeValue; 1178 } 1179 1180 // handle scrollbar 1181 var needScrollBar = this.fakeOptions.length > maxCount; 1182 this.container.toggleClass(this.options.scrollClass, needScrollBar); 1183 if (needScrollBar) { 1184 // change max-height 1185 this.listHolder.css({ 1186 maxHeight: this.getOverflowHeight(maxCount), 1187 overflow: 'auto' 1188 }); 1189 1190 if (this.options.useCustomScroll && jcf.modules.Scrollable) { 1191 // add custom scrollbar if specified in options 1192 jcf.replace(this.listHolder, 'Scrollable', { 1193 handleResize: this.options.handleResize, 1194 alwaysPreventMouseWheel: this.options.alwaysPreventMouseWheel 1195 }); 1196 return; 1197 } 1198 } 1199 1200 // disable edge wheel scrolling 1201 if (this.options.alwaysPreventMouseWheel) { 1202 this.preventWheelHandler = function (e) { 1203 var currentScrollTop = self.listHolder.scrollTop(), 1204 maxScrollTop = self.listHolder.prop('scrollHeight') - self.listHolder.innerHe
1204ight(); 1205 1206 // check edge cases 1207 if ((currentScrollTop <= 0 && e.deltaY < 0) || (currentScrollTop >= maxScrollTop && e.deltaY > 0)) { 1208 e.preventDefault(); 1209 } 1210 }; 1211 this.listHolder.on('jcf-mousewheel', this.preventWheelHandler); 1212 } 1213 }, 1214 refreshSelectedClass: function () { 1215 var self = this, 1216 selectedItem, 1217 isMultiple = this.element.prop('multiple'), 1218 selectedIndex = this.element.prop('selectedIndex'); 1219 1220 if (isMultiple) { 1221 this.realOptions.each(function (index, option) { 1222 self.fakeOptions.eq(index).toggleClass(self.options.selectedClass, !!option.selected); 1223 }); 1224 } else { 1225 this.fakeOptions.removeClass(this.options.selectedClass + ' ' + this.options.hoverClass); 1226 selectedItem = this.fakeOptions.eq(selectedIndex).addClass(this.options.selectedClass); 1227 if (this.options.useHoverClass) { 1228 selectedItem.addClass(this.options.hoverClass); 1229 } 1230 } 1231 }, 1232 scrollToActiveOption: function () { 1233 // scroll to target option 1234 var targetOffset = this.getActiveOptionOffset(); 1235 if (typeof targetOffset === 'number') { 1236 this.listHolder.prop('scrollTop', targetOffset); 1237 } 1238 }, 1239 getSelectedIndexRange: function () { 1240 var firstSelected = -1, lastSelected = -1; 1241 this.realOptions.each(function (index, option) { 1242 if (option.selected) { 1243 if (firstSelected < 0) { 1244 firstSelected = index; 1245 } 1246 lastSelected = index; 1247 } 1248 }); 1249 return [firstSelected, lastSelected]; 1250 }, 1251 getChangedSelectedIndex: function () { 1252 var selectedIndex = this.element.prop('selectedIndex'), 1253 targetIndex; 1254 1255 if (this.element.prop('multiple')) { 1256 // multiple selects handling 1257 if (!this.previousRange) { 1258 this.previousRange = [selectedIndex, selectedIndex]; 1259 } 1260 this.currentRange = this.getSelectedIndexRange(); 1261 targetIndex = this.currentRange[this.currentRange[0] !== this.previousRange[0] ? 0 : 1]; 1262 this.previousRange = this.currentRange; 1263 return targetIndex; 1264 } else { 1265 // single choice selects handling 1266 return selectedIndex; 1267 } 1268 }, 1269 getActiveOptionOffset: function () { 1270 // calc values 1271 var dropHeight = this.listHolder.height(), 1272 dropScrollTop = this.listHolder.prop('scrollTop'), 1273 currentIndex = this.getChangedSelectedIndex(), 1274 fakeOption = this.fakeOptions.eq(currentIndex), 1275 fakeOptionOffset = fakeOption.offset().top - this.list.offset().top, 1276 fakeOptionHeight = fakeOption.innerHeight(); 1277 1278 // scroll list 1279 if (fakeOptionOffset + fakeOptionHeight >= dropScrollTop + dropHeight) { 1280 // scroll down (always scroll to option) 1281 return fakeOptionOffset - dropHeight + fakeOptionHeight; 1282 } else if (fakeOptionOffset < dropScrollTop) { 1283 // scroll up to option 1284 return fakeOptionOffset; 1285 } 1286 }, 1287 getOverflowHeight: function (sizeValue) { 1288 var item = this.fakeListItems.eq(sizeValue - 1), 1289 listOffset = this.list.offset().top, 1290 itemOffset = item.offset().top, 1291 itemHeight = item.innerHeight(); 1292 1293 return itemOffset + itemHeight - listOffset; 1294 }, 1295 getScrollTop: function () { 1296 return this.listHolder.scrollTop(); 1297 }, 1298 setScrollTop: function (value) { 1299 this.listHolder.scrollTop(value); 1300 }, 1301 createOption: function (option) { 1302 var newOption = document.createElement('span'); 1303 newOption.className = this.options.optionClass; 1304 newOption.innerHTML = option.innerHTML; 1305 newOption.setAttribute(this.options.indexAttribute, this.optionIndex++); 1306 1307 var optionImage, optionImageSrc = option.getAttribute('data-image'); 1308 if (optionImageSrc) { 1309 optionImage = document.createElement('img'); 1310 optionImage.src = optionImageSrc; 1311 newOption.insertBefore(optionImage, newOption.childNodes[0]); 1312 } 1313 if (option.disabled) { 1314 newOption.className += ' ' + this.options.disabledClass; 1315 } 1316 if (option.className) { 1317 newOption.className += ' ' + getPrefixedClasses(option.className, this.options.cloneClassPrefix); 1318 } 1319 return newOption; 1320 }, 1321 createOptGroup: function (optgroup) { 1322 var optGroupContainer = document.createElement('span'), 1323 optGroupName = optgroup.getAttribute('label'), 1324 optGroupCaption, optGroupList; 1325 1326 // create caption 1327 optGroupCaption = document.createElement('span'); 1328 optGroupCaption.className = this.options.captionClass; 1329 optGroupCaption.innerHTML = optGroupName; 1330 optGroupContainer.appendChild(optGroupCaption); 1331 1332 // create list of options 1333 if (optgroup.children.length) { 1334 optGroupList = this.createOptionsList(optgroup); 1335 optGroupContainer.appendChild(optGroupList); 1336 } 1337 1338 optGroupContainer.className = this.options.groupClass; 1339 return optGroupContainer; 1340 }, 1341 createOptionContainer: function () { 1342 var optionContainer = document.createElement('li'); 1343 return optionContainer; 1344 }, 1345 createOptionsList: function (container) { 1346 var self = this, 1347 list = document.createElement('ul'); 1348 1349 $.each(container.children, function (index, currentNode) { 1350 var item = self.createOptionContainer(currentNode), 1351 newNode; 1352 1353 switch (currentNode.tagName.toLowerCase()) { 1354 case 'option': newNode = self.createOption(currentNode); break; 1355 case 'optgroup': newNode = self.createOptGroup(currentNode); break; 1356 } 1357 list.appendChild(item).appendChild(newNode); 1358 }); 1359 return list; 1360 }, 1361 refresh: function () { 1362 // check for select innerHTML changes 1363 if (this.storedSelectHTML !== this.element.prop('innerHTML')) { 1364 this.rebuildList(); 1365 } 1366 1367 // refresh custom scrollbar 1368 var scrollInstance = jcf.getInstance(this.listHolder); 1369 if (scrollInstance) { 1370 scrollInstance.refresh(); 1371 } 1372 1373 // refresh selectes classes 1374 this.refreshSelectedClass(); 1375 }, 1376 destroy: function () { 1377 this.listHolder.off('jcf-mousewheel', this.preventWheelHandler); 1378 this.listHolder.off('jcf-pointerdown', this.indexSelector, this.onSelectItem); 1379 this.listHolder.off('jcf-pointerover', this.indexSelector, this.onHoverItem); 1380 this.listHolder.off('jcf-pointerdown', this.onPress); 1381 } 1382 }); 1383 1384 // helper functions
1385 var getPrefixedClasses = function (className, prefixToAdd) { 1386 return className ? className.replace(/[\s]*([\S]+)+[\s]*/gi, prefixToAdd + '$1 ') : ''; 1387 }; 1388 var makeUnselectable = (function () { 1389 var unselectableClass = jcf.getOptions().unselectableClass; 1390 function preventHandler(e) { 1391 e.preventDefault(); 1392 } 1393 return function (node) { 1394 node.addClass(unselectableClass).on('selectstart', preventHandler); 1395 }; 1396 }()); 1397 1398}(jQuery, this)); 1399 1400 1401/*! 1402* JavaScript Custom Forms : Scrollbar Module 1403* 1404* Copyright 2014-2015 PSD2HTML - http://psd2html.com/jcf 1405* Released under the MIT license (LICENSE.txt) 1406* 1407* Version: 1.1.3 1408*/ 1409; (function ($, window) { 1410 1411 'use strict'; 1412 1413 jcf.addModule({ 1414 name: 'Scrollable', 1415 selector: '.jcf-scrollable', 1416 plugins: { 1417 ScrollBar: ScrollBar 1418 }, 1419 options: { 1420 mouseWheelStep: 150, 1421 handleResize: true, 1422 alwaysShowScrollbars: false, 1423 alwaysPreventMouseWheel: false, 1424 scrollAreaStructure: '<div class="jcf-scrollable-wrapper"></div>' 1425 }, 1426 matchElement: function (element) { 1427 return element.is('.jcf-scrollable'); 1428 }, 1429 init: function () { 1430 this.initStructure(); 1431 this.attachEvents(); 1432 this.rebuildScrollbars(); 1433 }, 1434 initStructure: function () { 1435 // prepare structure 1436 this.doc = $(document); 1437 this.win = $(window); 1438 this.realElement = $(this.options.element); 1439 this.scrollWrapper = $(this.options.scrollAreaStructure).insertAfter(this.realElement); 1440 1441 // set initial styles 1442 this.scrollWrapper.css('position', 'relative'); 1443 // this.realElement.css('overflow', 'hidden'); 1444 this.realElement.css('overflow', this.options.ios && this.options.ios >= 10 ? 'auto' : 'hidden'); 1445 this.vBarEdge = 0; 1446 }, 1447 attachEvents: function () { 1448 // create scrollbars 1449 var self = this; 1450 this.vBar = new ScrollBar({ 1451 holder: this.scrollWrapper, 1452 vertical: true, 1453 onScroll: function (scrollTop) { 1454 self.realElement.scrollTop(scrollTop); 1455 } 1456 }); 1457 this.hBar = new ScrollBar({ 1458 holder: this.scrollWrapper, 1459 vertical: false, 1460 onScroll: function (scrollLeft) { 1461 self.realElement.scrollLeft(scrollLeft); 1462 } 1463 }); 1464 1465 // add event handlers 1466 this.realElement.on('scroll', this.onScroll); 1467 if (this.options.handleResize) { 1468 this.win.on('resize orientationchange load', this.onResize); 1469 } 1470 1471 // add pointer/wheel event handlers 1472 this.realElement.on('jcf-mousewheel', this.onMouseWheel); 1473 this.realElement.on('jcf-pointerdown', this.onTouchBody); 1474 }, 1475 onScroll: function () { 1476 this.redrawScrollbars(); 1477 }, 1478 onResize: function () { 1479 // do not rebuild scrollbars if form field is in focus 1480 if (!$(document.activeElement).is(':input')) { 1481 this.rebuildScrollbars(); 1482 } 1483 }, 1484 onTouchBody: function (e) { 1485 if (e.pointerType === 'touch') { 1486 this.touchData = { 1487 scrollTop: this.realElement.scrollTop(), 1488 scrollLeft: this.realElement.scrollLeft(), 1489 left: e.pageX, 1490 top: e.pageY 1491 }; 1492 this.doc.on({ 1493 'jcf-pointermove': this.onMoveBody, 1494 'jcf-pointerup': this.onReleaseBody 1495 }); 1496 } 1497 }, 1498 onMoveBody: function (e) { 1499 var targetScrollTop, 1500 targetScrollLeft, 1501 verticalScrollAllowed = this.verticalScrollActive, 1502 horizontalScrollAllowed = this.horizontalScrollActive; 1503 1504 if (e.pointerType === 'touch') { 1505 targetScrollTop = this.touchData.scrollTop - e.pageY + this.touchData.top; 1506 targetScrollLeft = this.touchData.scrollLeft - e.pageX + this.touchData.left; 1507 1508 // check that scrolling is ended and release outer scrolling 1509 if (this.verticalScrollActive && (targetScrollTop < 0 || targetScrollTop > this.vBar.maxValue)) { 1510 verticalScrollAllowed = false; 1511 } 1512 if (this.horizontalScrollActive && (targetScrollLeft < 0 || targetScrollLeft > this.hBar.maxValue)) { 1513 horizontalScrollAllowed = false; 1514 } 1515 1516 this.realElement.scrollTop(targetScrollTop); 1517 this.realElement.scrollLeft(targetScrollLeft); 1518 1519 if (verticalScrollAllowed || horizontalScrollAllowed) { 1520 e.preventDefault(); 1521 } else { 1522 this.onReleaseBody(e); 1523 } 1524 } 1525 }, 1526 onReleaseBody: function (e) { 1527 if (e.pointerType === 'touch') { 1528 delete this.touchData; 1529 this.doc.off({ 1530 'jcf-pointermove': this.onMoveBody, 1531 'jcf-pointerup': this.onReleaseBody 1532 }); 1533 } 1534 }, 1535 onMouseWheel: function (e) { 1536 var currentScrollTop = this.realElement.scrollTop(), 1537 currentScrollLeft = this.realElement.scrollLeft(), 1538 maxScrollTop = this.realElement.prop('scrollHeight') - this.embeddedDimensions.innerHeight, 1539 maxScrollLeft = this.realElement.prop('scrollWidth') - this.embeddedDimensions.innerWidth, 1540 extraLeft, extraTop, preventFlag; 1541
1542 // check edge cases 1543 if (!this.options.alwaysPreventMouseWheel) { 1544 if (this.verticalScrollActive && e.deltaY) { 1545 if (!(currentScrollTop <= 0 && e.deltaY < 0) && !(currentScrollTop >= maxScrollTop && e.deltaY > 0)) { 1546 preventFlag = true; 1547 } 1548 } 1549 if (this.horizontalScrollActive && e.deltaX) { 1550 if (!(currentScrollLeft <= 0 && e.deltaX < 0) && !(currentScrollLeft >= maxScrollLeft && e.deltaX > 0)) { 1551 preventFlag = true; 1552 } 1553 } 1554 if (!this.verticalScrollActive && !this.horizontalScrollActive) { 1555 return; 1556 } 1557 } 1558 1559 // prevent default action and scroll item 1560 if (preventFlag || this.options.alwaysPreventMouseWheel) { 1561 e.preventDefault(); 1562 } else { 1563 return; 1564 } 1565 1566 extraLeft = e.deltaX / 100 * this.options.mouseWheelStep; 1567 extraTop = e.deltaY / 100 * this.options.mouseWheelStep; 1568 1569 this.realElement.scrollTop(currentScrollTop + extraTop); 1570 this.realElement.scrollLeft(currentScrollLeft + extraLeft); 1571 }, 1572 setScrollBarEdge: function (edgeSize) { 1573 this.vBarEdge = edgeSize || 0; 1574 this.redrawScrollbars(); 1575 }, 1576 saveElementDimensions: function () { 1577 this.savedDimensions = { 1578 top: this.realElement.width(), 1579 left: this.realElement.height() 1580 }; 1581 return this; 1582 }, 1583 restoreElementDimensions: function () { 1584 if (this.savedDimensions) { 1585 this.realElement.css({ 1586 width: this.savedDimensions.width, 1587 height: this.savedDimensions.height 1588 }); 1589 } 1590 return this; 1591 }, 1592 saveScrollOffsets: function () { 1593 this.savedOffsets = { 1594 top: this.realElement.scrollTop(), 1595 left: this.realElement.scrollLeft() 1596 }; 1597 return this; 1598 }, 1599 restoreScrollOffsets: function () { 1600 if (this.savedOffsets) { 1601 this.realElement.scrollTop(this.savedOffsets.top); 1602 this.realElement.scrollLeft(this.savedOffsets.left); 1603 } 1604 return this; 1605 }, 1606 getContainerDimensions: function () { 1607 // save current styles 1608 var desiredDimensions, 1609 currentStyles, 1610 currentHeight, 1611 currentWidth; 1612 1613 if (this.isModifiedStyles) { 1614 desiredDimensions = { 1615 width: this.realElement.innerWidth() + this.vBar.getThickness(), 1616 height: this.realElement.innerHeight() + this.hBar.getThickness() 1617 }; 1618 } else { 1619 // unwrap real element and measure it according to CSS 1620 this.saveElementDimensions().saveScrollOffsets(); 1621 this.realElement.insertAfter(this.scrollWrapper); 1622 this.scrollWrapper.detach(); 1623 1624 // measure element 1625 currentStyles = this.realElement.prop('style'); 1626 currentWidth = parseFloat(currentStyles.width); 1627 currentHeight = parseFloat(currentStyles.height); 1628 1629 // reset styles if needed 1630 if (this.embeddedDimensions && currentWidth && currentHeight) { 1631 this.isModifiedStyles |= (currentWidth !== this.embeddedDimensions.width || currentHeight !== this.embeddedDimensions.height); 1632 this.realElement.css({ 1633 overflow: '', 1634 width: '', 1635 height: '' 1636 }); 1637 } 1638 1639 // calculate desired dimensions for real element 1640 desiredDimensions = { 1641 width: this.realElement.outerWidth(), 1642 height: this.realElement.outerHeight() 1643 }; 1644 1645 // restore structure and original scroll offsets 1646 this.scrollWrapper.insertAfter(this.realElement); 1647 this.realElement.css('overflow', this.options.ios && this.options.ios >= 10 ? 'auto' : 'hidden').prependTo(this.scrollWrapper); 1648 this.restoreElementDimensions().restoreScrollOffsets(); 1649 } 1650 1651 return desiredDimensions; 1652 }, 1653 getEmbeddedDimensions: function (dimensions) { 1654 // handle scrollbars cropping 1655 var fakeBarWidth = this.vBar.getThickness(), 1656 fakeBarHeight = this.hBar.getThickness(), 1657 paddingWidth = this.realElement.outerWidth() - this.realElement.width(), 1658 paddingHeight = this.realElement.outerHeight() - this.realElement.height(), 1659 resultDimensions; 1660 1661 if (this.options.alwaysShowScrollbars) { 1662 // simply return dimensions without custom scrollbars 1663 this.verticalScrollActive = true; 1664 this.horizontalScrollActive = true; 1665 resultDimensions = { 1666 innerWidth: dimensions.width - fakeBarWidth, 1667 innerHeight: dimensions.height - fakeBarHeight 1668 }; 1669 } else { 1670 // detect when to display each scrollbar 1671 this.saveElementDimensions(); 1672 this.verticalScrollActive = false;
1673 this.horizontalScrollActive = false; 1674 1675 // fill container with full size 1676 this.realElement.css({ 1677 width: dimensions.width - paddingWidth, 1678 height: dimensions.height - paddingHeight 1679 }); 1680 1681 this.horizontalScrollActive = this.realElement.prop('scrollWidth') > this.containerDimensions.width; 1682 this.verticalScrollActive = this.realElement.prop('scrollHeight') > this.containerDimensions.height; 1683 1684 this.restoreElementDimensions(); 1685 resultDimensions = { 1686 innerWidth: dimensions.width - (this.verticalScrollActive ? fakeBarWidth : 0), 1687 innerHeight: dimensions.height - (this.horizontalScrollActive ? fakeBarHeight : 0) 1688 }; 1689 } 1690 $.extend(resultDimensions, { 1691 width: resultDimensions.innerWidth - paddingWidth, 1692 height: resultDimensions.innerHeight - paddingHeight 1693 }); 1694 return resultDimensions; 1695 }, 1696 rebuildScrollbars: function () { 1697 // resize wrapper according to real element styles 1698 this.containerDimensions = this.getContainerDimensions(); 1699 this.embeddedDimensions = this.getEmbeddedDimensions(this.containerDimensions); 1700 1701 // resize wrapper to desired dimensions 1702 this.scrollWrapper.css({ 1703 width: this.containerDimensions.width, 1704 height: this.containerDimensions.height 1705 }); 1706 1707 // resize element inside wrapper excluding scrollbar size 1708 this.realElement.css({ 1709 overflow: this.options.ios && this.options.ios >= 10 ? 'auto' : 'hidden', 1710 width: this.embeddedDimensions.width, 1711 height: this.embeddedDimensions.height 1712 }); 1713 1714 // redraw scrollbar offset 1715 this.redrawScrollbars(); 1716 }, 1717 redrawScrollbars: function () { 1718 var viewSize, maxScrollValue; 1719 1720 // redraw vertical scrollbar 1721 if (this.verticalScrollActive) { 1722 viewSize = this.vBarEdge ? this.containerDimensions.height - this.vBarEdge : this.embeddedDimensions.innerHeight; 1723 maxScrollValue = Math.max(this.realElement.prop('offsetHeight'), this.realElement.prop('scrollHeight')) - this.vBarEdge; 1724 1725 this.vBar.show().setMaxValue(maxScrollValue - viewSize).setRatio(viewSize / maxScrollValue).setSize(viewSize); 1726 this.vBar.setValue(this.realElement.scrollTop()); 1727 } else { 1728 this.vBar.hide(); 1729 } 1730 1731 // redraw horizontal scrollbar 1732 if (this.horizontalScrollActive) { 1733 viewSize = this.embeddedDimensions.innerWidth; 1734 maxScrollValue = this.realElement.prop('scrollWidth'); 1735 1736 if (maxScrollValue === viewSize) { 1737 this.horizontalScrollActive = false; 1738 } 1739 this.hBar.show().setMaxValue(maxScrollValue - viewSize).setRatio(viewSize / maxScrollValue).setSize(viewSize); 1740 this.hBar.setValue(this.realElement.scrollLeft()); 1741 } else { 1742 this.hBar.hide(); 1743 } 1744 1745 // set "touch-action" style rule 1746 var touchAction = ''; 1747 if (this.verticalScrollActive && this.horizontalScrollActive) { 1748 touchAction = 'none'; 1749 } else if (this.verticalScrollActive) { 1750 touchAction = 'pan-x'; 1751 } else if (this.horizontalScrollActive) { 1752 touchAction = 'pan-y'; 1753 } 1754 this.realElement.css('touchAction', touchAction); 1755 }, 1756 refresh: function () { 1757 this.rebuildScrollbars(); 1758 }, 1759 destroy: function () { 1760 // remove event listeners 1761 this.win.off('resize orientationchange load', this.onResize); 1762 this.realElement.off({ 1763 'jcf-mousewheel': this.onMouseWheel, 1764 'jcf-pointerdown': this.onTouchBody 1765 }); 1766 this.doc.off({ 1767 'jcf-pointermove': this.onMoveBody, 1768 'jcf-pointerup': this.onReleaseBody 1769 }); 1770 1771 // restore structure 1772 this.saveScrollOffsets(); 1773 this.vBar.destroy(); 1774 this.hBar.destroy(); 1775 this.realElement.insertAfter(this.scrollWrapper).css({ 1776 touchAction: '', 1777 overflow: '', 1778 width: '', 1779 height: '' 1780 }); 1781 this.scrollWrapper.remove(); 1782 this.restoreScrollOffsets(); 1783 } 1784 }); 1785 1786 // custom scrollbar 1787 function ScrollBar(options) { 1788 this.options = $.extend({ 1789 holder: null, 1790 vertical: true, 1791 inactiveClass: 'jcf-inactive', 1792 verticalClass: 'jcf-scrollbar-vertical', 1793 horizontalClass: 'jcf-scrollbar-horizontal', 1794 scrollbarStructure: '<div class="jcf-scrollbar"><div class="jcf-scrollbar-dec"></div><div class="jcf-scrollbar-slider"><div class="jcf-scrollbar-handle"></div></div><div class="jcf-scrollbar-inc"></div></div>',
1795 btnDecSelector: '.jcf-scrollbar-dec', 1796 btnIncSelector: '.jcf-scrollbar-inc', 1797 sliderSelector: '.jcf-scrollbar-slider', 1798 handleSelector: '.jcf-scrollbar-handle', 1799 scrollInterval: 300, 1800 scrollStep: 400 // px/sec 1801 }, options); 1802 this.init(); 1803 } 1804 $.extend(ScrollBar.prototype, { 1805 init: function () { 1806 this.initStructure(); 1807 this.attachEvents(); 1808 }, 1809 initStructure: function () { 1810 // define proporties 1811 this.doc = $(document); 1812 this.isVertical = !!this.options.vertical; 1813 this.sizeProperty = this.isVertical ? 'height' : 'width'; 1814 this.fullSizeProperty = this.isVertical ? 'outerHeight' : 'outerWidth'; 1815 this.invertedSizeProperty = this.isVertical ? 'width' : 'height'; 1816 this.thicknessMeasureMethod = 'outer' + this.invertedSizeProperty.charAt(0).toUpperCase() + this.invertedSizeProperty.substr(1); 1817 this.offsetProperty = this.isVertical ? 'top' : 'left'; 1818 this.offsetEventProperty = this.isVertical ? 'pageY' : 'pageX'; 1819 1820 // initialize variables 1821 this.value = this.options.value || 0; 1822 this.maxValue = this.options.maxValue || 0; 1823 this.currentSliderSize = 0; 1824 this.handleSize = 0; 1825 1826 // find elements 1827 this.holder = $(this.options.holder); 1828 this.scrollbar = $(this.options.scrollbarStructure).appendTo(this.holder); 1829 this.btnDec = this.scrollbar.find(this.options.btnDecSelector); 1830 this.btnInc = this.scrollbar.find(this.options.btnIncSelector); 1831 this.slider = this.scrollbar.find(this.options.sliderSelector); 1832 this.handle = this.slider.find(this.options.handleSelector); 1833 1834 // set initial styles 1835 this.scrollbar.addClass(this.isVertical ? this.options.verticalClass : this.options.horizontalClass).css({ 1836 touchAction: this.isVertical ? 'pan-x' : 'pan-y', 1837 position: 'absolute' 1838 }); 1839 this.slider.css({ 1840 position: 'relative' 1841 }); 1842 this.handle.css({ 1843 touchAction: 'none', 1844 position: 'absolute' 1845 }); 1846 }, 1847 attachEvents: function () { 1848 this.bindHandlers(); 1849 this.handle.on('jcf-pointerdown', this.onHandlePress); 1850 this.slider.add(this.btnDec).add(this.btnInc).on('jcf-pointerdown', this.onButtonPress); 1851 }, 1852 onHandlePress: function (e) { 1853 if (e.pointerType === 'mouse' && e.button > 1) { 1854 return; 1855 } else { 1856 e.preventDefault(); 1857 this.handleDragActive = true; 1858 this.sliderOffset = this.slider.offset()[this.offsetProperty]; 1859 this.innerHandleOffset = e[this.offsetEventProperty] - this.handle.offset()[this.offsetProperty]; 1860 1861 this.doc.on('jcf-pointermove', this.onHandleDrag); 1862 this.doc.on('jcf-pointerup', this.onHandleRelease); 1863 } 1864 }, 1865 onHandleDrag: function (e) { 1866 e.preventDefault(); 1867 this.calcOffset = e[this.offsetEventProperty] - this.sliderOffset - this.innerHandleOffset; 1868 this.setValue(this.calcOffset / (this.currentSliderSize - this.handleSize) * this.maxValue); 1869 this.triggerScrollEvent(this.value); 1870 }, 1871 onHandleRelease: function () { 1872 this.handleDragActive = false; 1873 this.doc.off('jcf-pointermove', this.onHandleDrag); 1874 this.doc.off('jcf-pointerup', this.onHandleRelease); 1875 }, 1876 onButtonPress: function (e) { 1877 var direction, clickOffset; 1878 if (e.pointerType === 'mouse' && e.button > 1) { 1879 return; 1880 } else { 1881 e.preventDefault(); 1882 if (!this.handleDragActive) { 1883 if (this.slider.is(e.currentTarget)) { 1884 // slider pressed 1885 direction = this.handle.offset()[this.offsetProperty] > e[this.offsetEventProperty] ? -1 : 1; 1886 clickOffset = e[this.offsetEventProperty] - this.slider.offset()[this.offsetProperty]; 1887 this.startPageScrolling(direction, clickOffset); 1888 } else { 1889 // scrollbar buttons pressed 1890 direction = this.btnDec.is(e.currentTarget) ? -1 : 1; 1891 this.startSmoothScrolling(direction); 1892 } 1893 this.doc.on('jcf-pointerup', this.onButtonRelease); 1894 } 1895 } 1896 }, 1897 onButtonRelease: function () { 1898 this.stopPageScrolling(); 1899 this.stopSmoothScrolling(); 1900 this.doc.off('jcf-pointerup', this.onButtonRelease); 1901 }, 1902 startPageScrolling: function (direction, clickOffset) { 1903 var self = this, 1904 stepValue = direction * self.currentSize; 1905 1906 // limit checker 1907 var isFinishedScrolling = function () { 1908 var handleTop = (self.value / self.maxValue) * (self.currentSliderSize - self.handleSize); 1909 1910 if (direction > 0) { 1911 return handleTop + self.handleSize >= clickOffset; 1912 } else { 1913 return handleTop <= clickOffset; 1914 } 1915 }; 1916 1917 // scroll by page when track is pressed 1918 var doPageScroll = function () { 1919 self.value += stepValue; 1920 self.setValue(self.value); 1921 self.triggerScrollEvent(self.value); 1922 1923 if (isFinishedScrolling()) { 1924 clearInterval(self.pageScrollTimer); 1925 } 1926 }; 1927 1928 // start scrolling 1929 this.pageScrollTimer = setInterval(doPageScroll, this.options.scrollInterval); 1930 doPageScroll(); 1931 }, 1932 stopPageScrolling: function () { 1933 clearInterval(this.pageScrollTimer); 1934 }, 1935 startSmoothScrolling: function (direction) { 1936 var self = this, dt; 1937 this.stopSmoothScrolling(); 1938 1939 // simple animation functions 1940 var raf = window.requestAnimationFrame || function (func) { 1941 setTimeout(func, 16); 1942 }; 1943 var getTimestamp = function () { 1944 return Date.now ? Date.now() : new Date().getTime(); 1945 }; 1946 1947 // set animation limit 1948 var isFinishedScrolling = function () { 1949 if (direction > 0) { 1950 return self.value >= self.maxValue; 1951 } else { 1952 return self.value <= 0; 1953 } 1954 }; 1955 1956 // animation step 1957 var doScrollAnimation = function () { 1958 var stepValue = (getTimestamp() - dt) / 1000 * self.options.scrollStep; 1959 1960 if (self.smoothScrollActive) { 1961 self.value += stepValue * direction; 1962 self.setValue(self.value); 1963 self.triggerScrollEvent(self.value); 1964 1965 if (!isFinishedScrolling()) { 1966 dt = getTimestamp(); 1967 raf(doScrollAnimation); 1968 } 1969 } 1970 }; 1971 1972 // start animation 1973 self.smoothScrollActive = true; 1974 dt = getTimestamp(); 1975 raf(doScrollAnimation); 1976 }, 1977 stopSmoothScrolling: function () { 1978 this.smoothScrollActive = false;
1979 }, 1980 triggerScrollEvent: function (scrollValue) { 1981 if (this.options.onScroll) { 1982 this.options.onScroll(scrollValue); 1983 } 1984 }, 1985 getThickness: function () { 1986 return this.scrollbar[this.thicknessMeasureMethod](); 1987 }, 1988 setSize: function (size) { 1989 // resize scrollbar 1990 var btnDecSize = this.btnDec[this.fullSizeProperty](), 1991 btnIncSize = this.btnInc[this.fullSizeProperty](); 1992 1993 // resize slider 1994 this.currentSize = size; 1995 this.currentSliderSize = size - btnDecSize - btnIncSize; 1996 this.scrollbar.css(this.sizeProperty, size); 1997 this.slider.css(this.sizeProperty, this.currentSliderSize); 1998 this.currentSliderSize = this.slider[this.sizeProperty](); 1999 2000 // resize handle 2001 this.handleSize = Math.round(this.currentSliderSize * this.ratio); 2002 this.handle.css(this.sizeProperty, this.handleSize); 2003 this.handleSize = this.handle[this.fullSizeProperty](); 2004 2005 return this; 2006 }, 2007 setRatio: function (ratio) { 2008 this.ratio = ratio; 2009 return this; 2010 }, 2011 setMaxValue: function (maxValue) { 2012 this.maxValue = maxValue; 2013 this.setValue(Math.min(this.value, this.maxValue)); 2014 return this; 2015 }, 2016 setValue: function (value) { 2017 this.value = value; 2018 if (this.value < 0) { 2019 this.value = 0; 2020 } else if (this.value > this.maxValue) { 2021 this.value = this.maxValue; 2022 } 2023 this.refresh(); 2024 }, 2025 setPosition: function (styles) { 2026 this.scrollbar.css(styles); 2027 return this; 2028 }, 2029 hide: function () { 2030 this.scrollbar.detach(); 2031 return this; 2032 }, 2033 show: function () { 2034 this.scrollbar.appendTo(this.holder); 2035 return this; 2036 }, 2037 refresh: function () { 2038 // recalculate handle position 2039 if (this.value === 0 || this.maxValue === 0) { 2040 this.calcOffset = 0; 2041 } else { 2042 this.calcOffset = (this.value / this.maxValue) * (this.currentSliderSize - this.handleSize); 2043 } 2044 this.handle.css(this.offsetProperty, this.calcOffset); 2045 2046 // toggle inactive classes 2047 this.btnDec.toggleClass(this.options.inactiveClass, this.value === 0); 2048 this.btnInc.toggleClass(this.options.inactiveClass, this.value === this.maxValue); 2049 this.scrollbar.toggleClass(this.options.inactiveClass, this.maxValue === 0); 2050 }, 2051 destroy: function () { 2052 // remove event handlers and scrollbar block itself 2053 this.btnDec.add(this.btnInc).off('jcf-pointerdown', this.onButtonPress); 2054 this.handle.off('jcf-pointerdown', this.onHandlePress); 2055 this.doc.off('jcf-pointermove', this.onHandleDrag); 2056 this.doc.off('jcf-pointerup', this.onHandleRelease); 2057 this.doc.off('jcf-pointerup', this.onButtonRelease); 2058 this.stopSmoothScrolling(); 2059 this.stopPageScrolling(); 2060 this.scrollbar.remove(); 2061 } 2062 }); 2063 2064}(jQuery, this));
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.