initial commit
This commit is contained in:
450
webroot/js/selectbox1/jcf.js
Normal file
450
webroot/js/selectbox1/jcf.js
Normal file
@@ -0,0 +1,450 @@
|
||||
/*!
|
||||
* JavaScript Custom Forms
|
||||
*
|
||||
* Copyright 2014-2015 PSD2HTML - http://psd2html.com/jcf
|
||||
* Released under the MIT license (LICENSE.txt)
|
||||
*
|
||||
* Version: 1.1.3
|
||||
*/
|
||||
;(function(root, factory) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['jquery'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory(require('jquery'));
|
||||
} else {
|
||||
root.jcf = factory(jQuery);
|
||||
}
|
||||
}(this, function($) {
|
||||
'use strict';
|
||||
|
||||
// define version
|
||||
var version = '1.1.3';
|
||||
|
||||
// private variables
|
||||
var customInstances = [];
|
||||
|
||||
// default global options
|
||||
var commonOptions = {
|
||||
optionsKey: 'jcf',
|
||||
dataKey: 'jcf-instance',
|
||||
rtlClass: 'jcf-rtl',
|
||||
focusClass: 'jcf-focus',
|
||||
pressedClass: 'jcf-pressed',
|
||||
disabledClass: 'jcf-disabled',
|
||||
hiddenClass: 'jcf-hidden',
|
||||
resetAppearanceClass: 'jcf-reset-appearance',
|
||||
unselectableClass: 'jcf-unselectable'
|
||||
};
|
||||
|
||||
// detect device type
|
||||
var isTouchDevice = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch,
|
||||
isWinPhoneDevice = /Windows Phone/.test(navigator.userAgent);
|
||||
commonOptions.isMobileDevice = !!(isTouchDevice || isWinPhoneDevice);
|
||||
|
||||
// create global stylesheet if custom forms are used
|
||||
var createStyleSheet = function() {
|
||||
var styleTag = $('<style>').appendTo('head'),
|
||||
styleSheet = styleTag.prop('sheet') || styleTag.prop('styleSheet');
|
||||
|
||||
// crossbrowser style handling
|
||||
var addCSSRule = function(selector, rules, index) {
|
||||
if (styleSheet.insertRule) {
|
||||
styleSheet.insertRule(selector + '{' + rules + '}', index);
|
||||
} else {
|
||||
styleSheet.addRule(selector, rules, index);
|
||||
}
|
||||
};
|
||||
|
||||
// add special rules
|
||||
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');
|
||||
addCSSRule('.' + commonOptions.rtlClass + ' .' + commonOptions.hiddenClass, 'right:-9999px !important; left: auto !important');
|
||||
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);');
|
||||
addCSSRule('.' + commonOptions.resetAppearanceClass, 'background: none; border: none; -webkit-appearance: none; appearance: none; opacity: 0; filter: alpha(opacity=0);');
|
||||
|
||||
// detect rtl pages
|
||||
var html = $('html'), body = $('body');
|
||||
if (html.css('direction') === 'rtl' || body.css('direction') === 'rtl') {
|
||||
html.addClass(commonOptions.rtlClass);
|
||||
}
|
||||
|
||||
// handle form reset event
|
||||
html.on('reset', function() {
|
||||
setTimeout(function() {
|
||||
api.refreshAll();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// mark stylesheet as created
|
||||
commonOptions.styleSheetCreated = true;
|
||||
};
|
||||
|
||||
// simplified pointer events handler
|
||||
(function() {
|
||||
var pointerEventsSupported = navigator.pointerEnabled || navigator.msPointerEnabled,
|
||||
touchEventsSupported = ('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch,
|
||||
eventList, eventMap = {}, eventPrefix = 'jcf-';
|
||||
|
||||
// detect events to attach
|
||||
if (pointerEventsSupported) {
|
||||
eventList = {
|
||||
pointerover: navigator.pointerEnabled ? 'pointerover' : 'MSPointerOver',
|
||||
pointerdown: navigator.pointerEnabled ? 'pointerdown' : 'MSPointerDown',
|
||||
pointermove: navigator.pointerEnabled ? 'pointermove' : 'MSPointerMove',
|
||||
pointerup: navigator.pointerEnabled ? 'pointerup' : 'MSPointerUp'
|
||||
};
|
||||
} else {
|
||||
eventList = {
|
||||
pointerover: 'mouseover',
|
||||
pointerdown: 'mousedown' + (touchEventsSupported ? ' touchstart' : ''),
|
||||
pointermove: 'mousemove' + (touchEventsSupported ? ' touchmove' : ''),
|
||||
pointerup: 'mouseup' + (touchEventsSupported ? ' touchend' : '')
|
||||
};
|
||||
}
|
||||
|
||||
// create event map
|
||||
$.each(eventList, function(targetEventName, fakeEventList) {
|
||||
$.each(fakeEventList.split(' '), function(index, fakeEventName) {
|
||||
eventMap[fakeEventName] = targetEventName;
|
||||
});
|
||||
});
|
||||
|
||||
// jQuery event hooks
|
||||
$.each(eventList, function(eventName, eventHandlers) {
|
||||
eventHandlers = eventHandlers.split(' ');
|
||||
$.event.special[eventPrefix + eventName] = {
|
||||
setup: function() {
|
||||
var self = this;
|
||||
$.each(eventHandlers, function(index, fallbackEvent) {
|
||||
if (self.addEventListener) self.addEventListener(fallbackEvent, fixEvent, false);
|
||||
else self['on' + fallbackEvent] = fixEvent;
|
||||
});
|
||||
},
|
||||
teardown: function() {
|
||||
var self = this;
|
||||
$.each(eventHandlers, function(index, fallbackEvent) {
|
||||
if (self.addEventListener) self.removeEventListener(fallbackEvent, fixEvent, false);
|
||||
else self['on' + fallbackEvent] = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// check that mouse event are not simulated by mobile browsers
|
||||
var lastTouch = null;
|
||||
var mouseEventSimulated = function(e) {
|
||||
var dx = Math.abs(e.pageX - lastTouch.x),
|
||||
dy = Math.abs(e.pageY - lastTouch.y),
|
||||
rangeDistance = 25;
|
||||
|
||||
if (dx <= rangeDistance && dy <= rangeDistance) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// normalize event
|
||||
var fixEvent = function(e) {
|
||||
var origEvent = e || window.event,
|
||||
touchEventData = null,
|
||||
targetEventName = eventMap[origEvent.type];
|
||||
|
||||
e = $.event.fix(origEvent);
|
||||
e.type = eventPrefix + targetEventName;
|
||||
|
||||
if (origEvent.pointerType) {
|
||||
switch (origEvent.pointerType) {
|
||||
case 2: e.pointerType = 'touch'; break;
|
||||
case 3: e.pointerType = 'pen'; break;
|
||||
case 4: e.pointerType = 'mouse'; break;
|
||||
default: e.pointerType = origEvent.pointerType;
|
||||
}
|
||||
} else {
|
||||
e.pointerType = origEvent.type.substr(0, 5); // "mouse" or "touch" word length
|
||||
}
|
||||
|
||||
if (!e.pageX && !e.pageY) {
|
||||
touchEventData = origEvent.changedTouches ? origEvent.changedTouches[0] : origEvent;
|
||||
e.pageX = touchEventData.pageX;
|
||||
e.pageY = touchEventData.pageY;
|
||||
}
|
||||
|
||||
if (origEvent.type === 'touchend') {
|
||||
lastTouch = { x: e.pageX, y: e.pageY };
|
||||
}
|
||||
if (e.pointerType === 'mouse' && lastTouch && mouseEventSimulated(e)) {
|
||||
return;
|
||||
} else {
|
||||
return ($.event.dispatch || $.event.handle).call(this, e);
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
// custom mousewheel/trackpad handler
|
||||
(function() {
|
||||
var wheelEvents = ('onwheel' in document || document.documentMode >= 9 ? 'wheel' : 'mousewheel DOMMouseScroll').split(' '),
|
||||
shimEventName = 'jcf-mousewheel';
|
||||
|
||||
$.event.special[shimEventName] = {
|
||||
setup: function() {
|
||||
var self = this;
|
||||
$.each(wheelEvents, function(index, fallbackEvent) {
|
||||
if (self.addEventListener) self.addEventListener(fallbackEvent, fixEvent, false);
|
||||
else self['on' + fallbackEvent] = fixEvent;
|
||||
});
|
||||
},
|
||||
teardown: function() {
|
||||
var self = this;
|
||||
$.each(wheelEvents, function(index, fallbackEvent) {
|
||||
if (self.addEventListener) self.removeEventListener(fallbackEvent, fixEvent, false);
|
||||
else self['on' + fallbackEvent] = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var fixEvent = function(e) {
|
||||
var origEvent = e || window.event;
|
||||
e = $.event.fix(origEvent);
|
||||
e.type = shimEventName;
|
||||
|
||||
// old wheel events handler
|
||||
if ('detail' in origEvent) { e.deltaY = -origEvent.detail; }
|
||||
if ('wheelDelta' in origEvent) { e.deltaY = -origEvent.wheelDelta; }
|
||||
if ('wheelDeltaY' in origEvent) { e.deltaY = -origEvent.wheelDeltaY; }
|
||||
if ('wheelDeltaX' in origEvent) { e.deltaX = -origEvent.wheelDeltaX; }
|
||||
|
||||
// modern wheel event handler
|
||||
if ('deltaY' in origEvent) {
|
||||
e.deltaY = origEvent.deltaY;
|
||||
}
|
||||
if ('deltaX' in origEvent) {
|
||||
e.deltaX = origEvent.deltaX;
|
||||
}
|
||||
|
||||
// handle deltaMode for mouse wheel
|
||||
e.delta = e.deltaY || e.deltaX;
|
||||
if (origEvent.deltaMode === 1) {
|
||||
var lineHeight = 16;
|
||||
e.delta *= lineHeight;
|
||||
e.deltaY *= lineHeight;
|
||||
e.deltaX *= lineHeight;
|
||||
}
|
||||
|
||||
return ($.event.dispatch || $.event.handle).call(this, e);
|
||||
};
|
||||
}());
|
||||
|
||||
// extra module methods
|
||||
var moduleMixin = {
|
||||
// provide function for firing native events
|
||||
fireNativeEvent: function(elements, eventName) {
|
||||
$(elements).each(function() {
|
||||
var element = this, eventObject;
|
||||
if (element.dispatchEvent) {
|
||||
eventObject = document.createEvent('HTMLEvents');
|
||||
eventObject.initEvent(eventName, true, true);
|
||||
element.dispatchEvent(eventObject);
|
||||
} else if (document.createEventObject) {
|
||||
eventObject = document.createEventObject();
|
||||
eventObject.target = element;
|
||||
element.fireEvent('on' + eventName, eventObject);
|
||||
}
|
||||
});
|
||||
},
|
||||
// bind event handlers for module instance (functions beggining with "on")
|
||||
bindHandlers: function() {
|
||||
var self = this;
|
||||
$.each(self, function(propName, propValue) {
|
||||
if (propName.indexOf('on') === 0 && $.isFunction(propValue)) {
|
||||
// dont use $.proxy here because it doesn't create unique handler
|
||||
self[propName] = function() {
|
||||
return propValue.apply(self, arguments);
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// public API
|
||||
var api = {
|
||||
version: version,
|
||||
modules: {},
|
||||
getOptions: function() {
|
||||
return $.extend({}, commonOptions);
|
||||
},
|
||||
setOptions: function(moduleName, moduleOptions) {
|
||||
if (arguments.length > 1) {
|
||||
// set module options
|
||||
if (this.modules[moduleName]) {
|
||||
$.extend(this.modules[moduleName].prototype.options, moduleOptions);
|
||||
}
|
||||
} else {
|
||||
// set common options
|
||||
$.extend(commonOptions, moduleName);
|
||||
}
|
||||
},
|
||||
addModule: function(proto) {
|
||||
// add module to list
|
||||
var Module = function(options) {
|
||||
// save instance to collection
|
||||
if (!options.element.data(commonOptions.dataKey)) {
|
||||
options.element.data(commonOptions.dataKey, this);
|
||||
}
|
||||
customInstances.push(this);
|
||||
|
||||
// save options
|
||||
this.options = $.extend({}, commonOptions, this.options, getInlineOptions(options.element), options);
|
||||
|
||||
// bind event handlers to instance
|
||||
this.bindHandlers();
|
||||
|
||||
// call constructor
|
||||
this.init.apply(this, arguments);
|
||||
};
|
||||
|
||||
// parse options from HTML attribute
|
||||
var getInlineOptions = function(element) {
|
||||
var dataOptions = element.data(commonOptions.optionsKey),
|
||||
attrOptions = element.attr(commonOptions.optionsKey);
|
||||
|
||||
if (dataOptions) {
|
||||
return dataOptions;
|
||||
} else if (attrOptions) {
|
||||
try {
|
||||
return $.parseJSON(attrOptions);
|
||||
} catch (e) {
|
||||
// ignore invalid attributes
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// set proto as prototype for new module
|
||||
Module.prototype = proto;
|
||||
|
||||
// add mixin methods to module proto
|
||||
$.extend(proto, moduleMixin);
|
||||
if (proto.plugins) {
|
||||
$.each(proto.plugins, function(pluginName, plugin) {
|
||||
$.extend(plugin.prototype, moduleMixin);
|
||||
});
|
||||
}
|
||||
|
||||
// override destroy method
|
||||
var originalDestroy = Module.prototype.destroy;
|
||||
Module.prototype.destroy = function() {
|
||||
this.options.element.removeData(this.options.dataKey);
|
||||
|
||||
for (var i = customInstances.length - 1; i >= 0; i--) {
|
||||
if (customInstances[i] === this) {
|
||||
customInstances.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (originalDestroy) {
|
||||
originalDestroy.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
// save module to list
|
||||
this.modules[proto.name] = Module;
|
||||
},
|
||||
getInstance: function(element) {
|
||||
return $(element).data(commonOptions.dataKey);
|
||||
},
|
||||
replace: function(elements, moduleName, customOptions) {
|
||||
var self = this,
|
||||
instance;
|
||||
|
||||
if (!commonOptions.styleSheetCreated) {
|
||||
createStyleSheet();
|
||||
}
|
||||
|
||||
$(elements).each(function() {
|
||||
var moduleOptions,
|
||||
element = $(this);
|
||||
|
||||
instance = element.data(commonOptions.dataKey);
|
||||
if (instance) {
|
||||
instance.refresh();
|
||||
} else {
|
||||
if (!moduleName) {
|
||||
$.each(self.modules, function(currentModuleName, module) {
|
||||
if (module.prototype.matchElement.call(module.prototype, element)) {
|
||||
moduleName = currentModuleName;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (moduleName) {
|
||||
moduleOptions = $.extend({ element: element }, customOptions);
|
||||
instance = new self.modules[moduleName](moduleOptions);
|
||||
}
|
||||
}
|
||||
});
|
||||
return instance;
|
||||
},
|
||||
refresh: function(elements) {
|
||||
$(elements).each(function() {
|
||||
var instance = $(this).data(commonOptions.dataKey);
|
||||
if (instance) {
|
||||
instance.refresh();
|
||||
}
|
||||
});
|
||||
},
|
||||
destroy: function(elements) {
|
||||
$(elements).each(function() {
|
||||
var instance = $(this).data(commonOptions.dataKey);
|
||||
if (instance) {
|
||||
instance.destroy();
|
||||
}
|
||||
});
|
||||
},
|
||||
replaceAll: function(context) {
|
||||
var self = this;
|
||||
$.each(this.modules, function(moduleName, module) {
|
||||
$(module.prototype.selector, context).each(function() {
|
||||
if (this.className.indexOf('jcf-ignore') < 0) {
|
||||
self.replace(this, moduleName);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
refreshAll: function(context) {
|
||||
if (context) {
|
||||
$.each(this.modules, function(moduleName, module) {
|
||||
$(module.prototype.selector, context).each(function() {
|
||||
var instance = $(this).data(commonOptions.dataKey);
|
||||
if (instance) {
|
||||
instance.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
for (var i = customInstances.length - 1; i >= 0; i--) {
|
||||
customInstances[i].refresh();
|
||||
}
|
||||
}
|
||||
},
|
||||
destroyAll: function(context) {
|
||||
if (context) {
|
||||
$.each(this.modules, function(moduleName, module) {
|
||||
$(module.prototype.selector, context).each(function(index, element) {
|
||||
var instance = $(element).data(commonOptions.dataKey);
|
||||
if (instance) {
|
||||
instance.destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
while (customInstances.length) {
|
||||
customInstances[0].destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// always export API to the global window object
|
||||
window.jcf = api;
|
||||
|
||||
return api;
|
||||
}));
|
||||
662
webroot/js/selectbox1/jcf.scrollable.js
Normal file
662
webroot/js/selectbox1/jcf.scrollable.js
Normal file
@@ -0,0 +1,662 @@
|
||||
/*!
|
||||
* JavaScript Custom Forms : Scrollbar Module
|
||||
*
|
||||
* Copyright 2014-2015 PSD2HTML - http://psd2html.com/jcf
|
||||
* Released under the MIT license (LICENSE.txt)
|
||||
*
|
||||
* Version: 1.1.3
|
||||
*/
|
||||
;(function($, window) {
|
||||
'use strict';
|
||||
|
||||
jcf.addModule({
|
||||
name: 'Scrollable',
|
||||
selector: '.jcf-scrollable',
|
||||
plugins: {
|
||||
ScrollBar: ScrollBar
|
||||
},
|
||||
options: {
|
||||
mouseWheelStep: 150,
|
||||
handleResize: true,
|
||||
alwaysShowScrollbars: false,
|
||||
alwaysPreventMouseWheel: false,
|
||||
scrollAreaStructure: '<div class="jcf-scrollable-wrapper"></div>'
|
||||
},
|
||||
matchElement: function(element) {
|
||||
return element.is('.jcf-scrollable');
|
||||
},
|
||||
init: function() {
|
||||
this.initStructure();
|
||||
this.attachEvents();
|
||||
this.rebuildScrollbars();
|
||||
},
|
||||
initStructure: function() {
|
||||
// prepare structure
|
||||
this.doc = $(document);
|
||||
this.win = $(window);
|
||||
this.realElement = $(this.options.element);
|
||||
this.scrollWrapper = $(this.options.scrollAreaStructure).insertAfter(this.realElement);
|
||||
|
||||
// set initial styles
|
||||
this.scrollWrapper.css('position', 'relative');
|
||||
this.realElement.css('overflow', 'hidden');
|
||||
this.vBarEdge = 0;
|
||||
},
|
||||
attachEvents: function() {
|
||||
// create scrollbars
|
||||
var self = this;
|
||||
this.vBar = new ScrollBar({
|
||||
holder: this.scrollWrapper,
|
||||
vertical: true,
|
||||
onScroll: function(scrollTop) {
|
||||
self.realElement.scrollTop(scrollTop);
|
||||
}
|
||||
});
|
||||
this.hBar = new ScrollBar({
|
||||
holder: this.scrollWrapper,
|
||||
vertical: false,
|
||||
onScroll: function(scrollLeft) {
|
||||
self.realElement.scrollLeft(scrollLeft);
|
||||
}
|
||||
});
|
||||
|
||||
// add event handlers
|
||||
this.realElement.on('scroll', this.onScroll);
|
||||
if (this.options.handleResize) {
|
||||
this.win.on('resize orientationchange load', this.onResize);
|
||||
}
|
||||
|
||||
// add pointer/wheel event handlers
|
||||
this.realElement.on('jcf-mousewheel', this.onMouseWheel);
|
||||
this.realElement.on('jcf-pointerdown', this.onTouchBody);
|
||||
},
|
||||
onScroll: function() {
|
||||
this.redrawScrollbars();
|
||||
},
|
||||
onResize: function() {
|
||||
// do not rebuild scrollbars if form field is in focus
|
||||
if (!$(document.activeElement).is(':input')) {
|
||||
this.rebuildScrollbars();
|
||||
}
|
||||
},
|
||||
onTouchBody: function(e) {
|
||||
if (e.pointerType === 'touch') {
|
||||
this.touchData = {
|
||||
scrollTop: this.realElement.scrollTop(),
|
||||
scrollLeft: this.realElement.scrollLeft(),
|
||||
left: e.pageX,
|
||||
top: e.pageY
|
||||
};
|
||||
this.doc.on({
|
||||
'jcf-pointermove': this.onMoveBody,
|
||||
'jcf-pointerup': this.onReleaseBody
|
||||
});
|
||||
}
|
||||
},
|
||||
onMoveBody: function(e) {
|
||||
var targetScrollTop,
|
||||
targetScrollLeft,
|
||||
verticalScrollAllowed = this.verticalScrollActive,
|
||||
horizontalScrollAllowed = this.horizontalScrollActive;
|
||||
|
||||
if (e.pointerType === 'touch') {
|
||||
targetScrollTop = this.touchData.scrollTop - e.pageY + this.touchData.top;
|
||||
targetScrollLeft = this.touchData.scrollLeft - e.pageX + this.touchData.left;
|
||||
|
||||
// check that scrolling is ended and release outer scrolling
|
||||
if (this.verticalScrollActive && (targetScrollTop < 0 || targetScrollTop > this.vBar.maxValue)) {
|
||||
verticalScrollAllowed = false;
|
||||
}
|
||||
if (this.horizontalScrollActive && (targetScrollLeft < 0 || targetScrollLeft > this.hBar.maxValue)) {
|
||||
horizontalScrollAllowed = false;
|
||||
}
|
||||
|
||||
this.realElement.scrollTop(targetScrollTop);
|
||||
this.realElement.scrollLeft(targetScrollLeft);
|
||||
|
||||
if (verticalScrollAllowed || horizontalScrollAllowed) {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
this.onReleaseBody(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
onReleaseBody: function(e) {
|
||||
if (e.pointerType === 'touch') {
|
||||
delete this.touchData;
|
||||
this.doc.off({
|
||||
'jcf-pointermove': this.onMoveBody,
|
||||
'jcf-pointerup': this.onReleaseBody
|
||||
});
|
||||
}
|
||||
},
|
||||
onMouseWheel: function(e) {
|
||||
var currentScrollTop = this.realElement.scrollTop(),
|
||||
currentScrollLeft = this.realElement.scrollLeft(),
|
||||
maxScrollTop = this.realElement.prop('scrollHeight') - this.embeddedDimensions.innerHeight,
|
||||
maxScrollLeft = this.realElement.prop('scrollWidth') - this.embeddedDimensions.innerWidth,
|
||||
extraLeft, extraTop, preventFlag;
|
||||
|
||||
// check edge cases
|
||||
if (!this.options.alwaysPreventMouseWheel) {
|
||||
if (this.verticalScrollActive && e.deltaY) {
|
||||
if (!(currentScrollTop <= 0 && e.deltaY < 0) && !(currentScrollTop >= maxScrollTop && e.deltaY > 0)) {
|
||||
preventFlag = true;
|
||||
}
|
||||
}
|
||||
if (this.horizontalScrollActive && e.deltaX) {
|
||||
if (!(currentScrollLeft <= 0 && e.deltaX < 0) && !(currentScrollLeft >= maxScrollLeft && e.deltaX > 0)) {
|
||||
preventFlag = true;
|
||||
}
|
||||
}
|
||||
if (!this.verticalScrollActive && !this.horizontalScrollActive) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent default action and scroll item
|
||||
if (preventFlag || this.options.alwaysPreventMouseWheel) {
|
||||
e.preventDefault();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
extraLeft = e.deltaX / 100 * this.options.mouseWheelStep;
|
||||
extraTop = e.deltaY / 100 * this.options.mouseWheelStep;
|
||||
|
||||
this.realElement.scrollTop(currentScrollTop + extraTop);
|
||||
this.realElement.scrollLeft(currentScrollLeft + extraLeft);
|
||||
},
|
||||
setScrollBarEdge: function(edgeSize) {
|
||||
this.vBarEdge = edgeSize || 0;
|
||||
this.redrawScrollbars();
|
||||
},
|
||||
saveElementDimensions: function() {
|
||||
this.savedDimensions = {
|
||||
top: this.realElement.width(),
|
||||
left: this.realElement.height()
|
||||
};
|
||||
return this;
|
||||
},
|
||||
restoreElementDimensions: function() {
|
||||
if (this.savedDimensions) {
|
||||
this.realElement.css({
|
||||
width: this.savedDimensions.width,
|
||||
height: this.savedDimensions.height
|
||||
});
|
||||
}
|
||||
return this;
|
||||
},
|
||||
saveScrollOffsets: function() {
|
||||
this.savedOffsets = {
|
||||
top: this.realElement.scrollTop(),
|
||||
left: this.realElement.scrollLeft()
|
||||
};
|
||||
return this;
|
||||
},
|
||||
restoreScrollOffsets: function() {
|
||||
if (this.savedOffsets) {
|
||||
this.realElement.scrollTop(this.savedOffsets.top);
|
||||
this.realElement.scrollLeft(this.savedOffsets.left);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
getContainerDimensions: function() {
|
||||
// save current styles
|
||||
var desiredDimensions,
|
||||
currentStyles,
|
||||
currentHeight,
|
||||
currentWidth;
|
||||
|
||||
if (this.isModifiedStyles) {
|
||||
desiredDimensions = {
|
||||
width: this.realElement.innerWidth() + this.vBar.getThickness(),
|
||||
height: this.realElement.innerHeight() + this.hBar.getThickness()
|
||||
};
|
||||
} else {
|
||||
// unwrap real element and measure it according to CSS
|
||||
this.saveElementDimensions().saveScrollOffsets();
|
||||
this.realElement.insertAfter(this.scrollWrapper);
|
||||
this.scrollWrapper.detach();
|
||||
|
||||
// measure element
|
||||
currentStyles = this.realElement.prop('style');
|
||||
currentWidth = parseFloat(currentStyles.width);
|
||||
currentHeight = parseFloat(currentStyles.height);
|
||||
|
||||
// reset styles if needed
|
||||
if (this.embeddedDimensions && currentWidth && currentHeight) {
|
||||
this.isModifiedStyles |= (currentWidth !== this.embeddedDimensions.width || currentHeight !== this.embeddedDimensions.height);
|
||||
this.realElement.css({
|
||||
overflow: '',
|
||||
width: '',
|
||||
height: ''
|
||||
});
|
||||
}
|
||||
|
||||
// calculate desired dimensions for real element
|
||||
desiredDimensions = {
|
||||
width: this.realElement.outerWidth(),
|
||||
height: this.realElement.outerHeight()
|
||||
};
|
||||
|
||||
// restore structure and original scroll offsets
|
||||
this.scrollWrapper.insertAfter(this.realElement);
|
||||
this.realElement.css('overflow', 'hidden').prependTo(this.scrollWrapper);
|
||||
this.restoreElementDimensions().restoreScrollOffsets();
|
||||
}
|
||||
|
||||
return desiredDimensions;
|
||||
},
|
||||
getEmbeddedDimensions: function(dimensions) {
|
||||
// handle scrollbars cropping
|
||||
var fakeBarWidth = this.vBar.getThickness(),
|
||||
fakeBarHeight = this.hBar.getThickness(),
|
||||
paddingWidth = this.realElement.outerWidth() - this.realElement.width(),
|
||||
paddingHeight = this.realElement.outerHeight() - this.realElement.height(),
|
||||
resultDimensions;
|
||||
|
||||
if (this.options.alwaysShowScrollbars) {
|
||||
// simply return dimensions without custom scrollbars
|
||||
this.verticalScrollActive = true;
|
||||
this.horizontalScrollActive = true;
|
||||
resultDimensions = {
|
||||
innerWidth: dimensions.width - fakeBarWidth,
|
||||
innerHeight: dimensions.height - fakeBarHeight
|
||||
};
|
||||
} else {
|
||||
// detect when to display each scrollbar
|
||||
this.saveElementDimensions();
|
||||
this.verticalScrollActive = false;
|
||||
this.horizontalScrollActive = false;
|
||||
|
||||
// fill container with full size
|
||||
this.realElement.css({
|
||||
width: dimensions.width - paddingWidth,
|
||||
height: dimensions.height - paddingHeight
|
||||
});
|
||||
|
||||
this.horizontalScrollActive = this.realElement.prop('scrollWidth') > this.containerDimensions.width;
|
||||
this.verticalScrollActive = this.realElement.prop('scrollHeight') > this.containerDimensions.height;
|
||||
|
||||
this.restoreElementDimensions();
|
||||
resultDimensions = {
|
||||
innerWidth: dimensions.width - (this.verticalScrollActive ? fakeBarWidth : 0),
|
||||
innerHeight: dimensions.height - (this.horizontalScrollActive ? fakeBarHeight : 0)
|
||||
};
|
||||
}
|
||||
$.extend(resultDimensions, {
|
||||
width: resultDimensions.innerWidth - paddingWidth,
|
||||
height: resultDimensions.innerHeight - paddingHeight
|
||||
});
|
||||
return resultDimensions;
|
||||
},
|
||||
rebuildScrollbars: function() {
|
||||
// resize wrapper according to real element styles
|
||||
this.containerDimensions = this.getContainerDimensions();
|
||||
this.embeddedDimensions = this.getEmbeddedDimensions(this.containerDimensions);
|
||||
|
||||
// resize wrapper to desired dimensions
|
||||
this.scrollWrapper.css({
|
||||
width: this.containerDimensions.width,
|
||||
height: this.containerDimensions.height
|
||||
});
|
||||
|
||||
// resize element inside wrapper excluding scrollbar size
|
||||
this.realElement.css({
|
||||
overflow: 'hidden',
|
||||
width: this.embeddedDimensions.width,
|
||||
height: this.embeddedDimensions.height
|
||||
});
|
||||
|
||||
// redraw scrollbar offset
|
||||
this.redrawScrollbars();
|
||||
},
|
||||
redrawScrollbars: function() {
|
||||
var viewSize, maxScrollValue;
|
||||
|
||||
// redraw vertical scrollbar
|
||||
if (this.verticalScrollActive) {
|
||||
viewSize = this.vBarEdge ? this.containerDimensions.height - this.vBarEdge : this.embeddedDimensions.innerHeight;
|
||||
maxScrollValue = Math.max(this.realElement.prop('offsetHeight'), this.realElement.prop('scrollHeight')) - this.vBarEdge;
|
||||
|
||||
this.vBar.show().setMaxValue(maxScrollValue - viewSize).setRatio(viewSize / maxScrollValue).setSize(viewSize);
|
||||
this.vBar.setValue(this.realElement.scrollTop());
|
||||
} else {
|
||||
this.vBar.hide();
|
||||
}
|
||||
|
||||
// redraw horizontal scrollbar
|
||||
if (this.horizontalScrollActive) {
|
||||
viewSize = this.embeddedDimensions.innerWidth;
|
||||
maxScrollValue = this.realElement.prop('scrollWidth');
|
||||
|
||||
if (maxScrollValue === viewSize) {
|
||||
this.horizontalScrollActive = false;
|
||||
}
|
||||
this.hBar.show().setMaxValue(maxScrollValue - viewSize).setRatio(viewSize / maxScrollValue).setSize(viewSize);
|
||||
this.hBar.setValue(this.realElement.scrollLeft());
|
||||
} else {
|
||||
this.hBar.hide();
|
||||
}
|
||||
|
||||
// set "touch-action" style rule
|
||||
var touchAction = '';
|
||||
if (this.verticalScrollActive && this.horizontalScrollActive) {
|
||||
touchAction = 'none';
|
||||
} else if (this.verticalScrollActive) {
|
||||
touchAction = 'pan-x';
|
||||
} else if (this.horizontalScrollActive) {
|
||||
touchAction = 'pan-y';
|
||||
}
|
||||
this.realElement.css('touchAction', touchAction);
|
||||
},
|
||||
refresh: function() {
|
||||
this.rebuildScrollbars();
|
||||
},
|
||||
destroy: function() {
|
||||
// remove event listeners
|
||||
this.win.off('resize orientationchange load', this.onResize);
|
||||
this.realElement.off({
|
||||
'jcf-mousewheel': this.onMouseWheel,
|
||||
'jcf-pointerdown': this.onTouchBody
|
||||
});
|
||||
this.doc.off({
|
||||
'jcf-pointermove': this.onMoveBody,
|
||||
'jcf-pointerup': this.onReleaseBody
|
||||
});
|
||||
|
||||
// restore structure
|
||||
this.saveScrollOffsets();
|
||||
this.vBar.destroy();
|
||||
this.hBar.destroy();
|
||||
this.realElement.insertAfter(this.scrollWrapper).css({
|
||||
touchAction: '',
|
||||
overflow: '',
|
||||
width: '',
|
||||
height: ''
|
||||
});
|
||||
this.scrollWrapper.remove();
|
||||
this.restoreScrollOffsets();
|
||||
}
|
||||
});
|
||||
|
||||
// custom scrollbar
|
||||
function ScrollBar(options) {
|
||||
this.options = $.extend({
|
||||
holder: null,
|
||||
vertical: true,
|
||||
inactiveClass: 'jcf-inactive',
|
||||
verticalClass: 'jcf-scrollbar-vertical',
|
||||
horizontalClass: 'jcf-scrollbar-horizontal',
|
||||
scrollbarStructure: '<div class="jcf-scrollbar"><div class="jcf-scrollbar-dec jcf-inactive"></div><div class="jcf-scrollbar-slider"><div class="jcf-scrollbar-handle"></div></div><div class="jcf-scrollbar-inc"></div></div>',
|
||||
btnDecSelector: '.jcf-scrollbar-dec',
|
||||
btnIncSelector: '.jcf-scrollbar-inc',
|
||||
sliderSelector: '.jcf-scrollbar-slider',
|
||||
handleSelector: '.jcf-scrollbar-handle',
|
||||
scrollInterval: 300,
|
||||
scrollStep: 400 // px/sec
|
||||
}, options);
|
||||
this.init();
|
||||
}
|
||||
$.extend(ScrollBar.prototype, {
|
||||
init: function() {
|
||||
this.initStructure();
|
||||
this.attachEvents();
|
||||
},
|
||||
initStructure: function() {
|
||||
// define proporties
|
||||
this.doc = $(document);
|
||||
this.isVertical = !!this.options.vertical;
|
||||
this.sizeProperty = this.isVertical ? 'height' : 'width';
|
||||
this.fullSizeProperty = this.isVertical ? 'outerHeight' : 'outerWidth';
|
||||
this.invertedSizeProperty = this.isVertical ? 'width' : 'height';
|
||||
this.thicknessMeasureMethod = 'outer' + this.invertedSizeProperty.charAt(0).toUpperCase() + this.invertedSizeProperty.substr(1);
|
||||
this.offsetProperty = this.isVertical ? 'top' : 'left';
|
||||
this.offsetEventProperty = this.isVertical ? 'pageY' : 'pageX';
|
||||
|
||||
// initialize variables
|
||||
this.value = this.options.value || 0;
|
||||
this.maxValue = this.options.maxValue || 0;
|
||||
this.currentSliderSize = 0;
|
||||
this.handleSize = 0;
|
||||
|
||||
// find elements
|
||||
this.holder = $(this.options.holder);
|
||||
this.scrollbar = $(this.options.scrollbarStructure).appendTo(this.holder);
|
||||
this.btnDec = this.scrollbar.find(this.options.btnDecSelector);
|
||||
this.btnInc = this.scrollbar.find(this.options.btnIncSelector);
|
||||
this.slider = this.scrollbar.find(this.options.sliderSelector);
|
||||
this.handle = this.slider.find(this.options.handleSelector);
|
||||
|
||||
// set initial styles
|
||||
this.scrollbar.addClass(this.isVertical ? this.options.verticalClass : this.options.horizontalClass).css({
|
||||
touchAction: this.isVertical ? 'pan-x' : 'pan-y',
|
||||
position: 'absolute'
|
||||
});
|
||||
this.slider.css({
|
||||
position: 'relative'
|
||||
});
|
||||
this.handle.css({
|
||||
touchAction: 'none',
|
||||
position: 'absolute'
|
||||
});
|
||||
},
|
||||
attachEvents: function() {
|
||||
this.bindHandlers();
|
||||
this.handle.on('jcf-pointerdown', this.onHandlePress);
|
||||
this.slider.add(this.btnDec).add(this.btnInc).on('jcf-pointerdown', this.onButtonPress);
|
||||
},
|
||||
onHandlePress: function(e) {
|
||||
if (e.pointerType === 'mouse' && e.button > 1) {
|
||||
return;
|
||||
} else {
|
||||
e.preventDefault();
|
||||
this.handleDragActive = true;
|
||||
this.sliderOffset = this.slider.offset()[this.offsetProperty];
|
||||
this.innerHandleOffset = e[this.offsetEventProperty] - this.handle.offset()[this.offsetProperty];
|
||||
|
||||
this.doc.on('jcf-pointermove', this.onHandleDrag);
|
||||
this.doc.on('jcf-pointerup', this.onHandleRelease);
|
||||
}
|
||||
},
|
||||
onHandleDrag: function(e) {
|
||||
e.preventDefault();
|
||||
this.calcOffset = e[this.offsetEventProperty] - this.sliderOffset - this.innerHandleOffset;
|
||||
this.setValue(this.calcOffset / (this.currentSliderSize - this.handleSize) * this.maxValue);
|
||||
this.triggerScrollEvent(this.value);
|
||||
},
|
||||
onHandleRelease: function() {
|
||||
this.handleDragActive = false;
|
||||
this.doc.off('jcf-pointermove', this.onHandleDrag);
|
||||
this.doc.off('jcf-pointerup', this.onHandleRelease);
|
||||
},
|
||||
onButtonPress: function(e) {
|
||||
var direction, clickOffset;
|
||||
if (e.pointerType === 'mouse' && e.button > 1) {
|
||||
return;
|
||||
} else {
|
||||
e.preventDefault();
|
||||
if (!this.handleDragActive) {
|
||||
if (this.slider.is(e.currentTarget)) {
|
||||
// slider pressed
|
||||
direction = this.handle.offset()[this.offsetProperty] > e[this.offsetEventProperty] ? -1 : 1;
|
||||
clickOffset = e[this.offsetEventProperty] - this.slider.offset()[this.offsetProperty];
|
||||
this.startPageScrolling(direction, clickOffset);
|
||||
} else {
|
||||
// scrollbar buttons pressed
|
||||
direction = this.btnDec.is(e.currentTarget) ? -1 : 1;
|
||||
this.startSmoothScrolling(direction);
|
||||
}
|
||||
this.doc.on('jcf-pointerup', this.onButtonRelease);
|
||||
}
|
||||
}
|
||||
},
|
||||
onButtonRelease: function() {
|
||||
this.stopPageScrolling();
|
||||
this.stopSmoothScrolling();
|
||||
this.doc.off('jcf-pointerup', this.onButtonRelease);
|
||||
},
|
||||
startPageScrolling: function(direction, clickOffset) {
|
||||
var self = this,
|
||||
stepValue = direction * self.currentSize;
|
||||
|
||||
// limit checker
|
||||
var isFinishedScrolling = function() {
|
||||
var handleTop = (self.value / self.maxValue) * (self.currentSliderSize - self.handleSize);
|
||||
|
||||
if (direction > 0) {
|
||||
return handleTop + self.handleSize >= clickOffset;
|
||||
} else {
|
||||
return handleTop <= clickOffset;
|
||||
}
|
||||
};
|
||||
|
||||
// scroll by page when track is pressed
|
||||
var doPageScroll = function() {
|
||||
self.value += stepValue;
|
||||
self.setValue(self.value);
|
||||
self.triggerScrollEvent(self.value);
|
||||
|
||||
if (isFinishedScrolling()) {
|
||||
clearInterval(self.pageScrollTimer);
|
||||
}
|
||||
};
|
||||
|
||||
// start scrolling
|
||||
this.pageScrollTimer = setInterval(doPageScroll, this.options.scrollInterval);
|
||||
doPageScroll();
|
||||
},
|
||||
stopPageScrolling: function() {
|
||||
clearInterval(this.pageScrollTimer);
|
||||
},
|
||||
startSmoothScrolling: function(direction) {
|
||||
var self = this, dt;
|
||||
this.stopSmoothScrolling();
|
||||
|
||||
// simple animation functions
|
||||
var raf = window.requestAnimationFrame || function(func) {
|
||||
setTimeout(func, 16);
|
||||
};
|
||||
var getTimestamp = function() {
|
||||
return Date.now ? Date.now() : new Date().getTime();
|
||||
};
|
||||
|
||||
// set animation limit
|
||||
var isFinishedScrolling = function() {
|
||||
if (direction > 0) {
|
||||
return self.value >= self.maxValue;
|
||||
} else {
|
||||
return self.value <= 0;
|
||||
}
|
||||
};
|
||||
|
||||
// animation step
|
||||
var doScrollAnimation = function() {
|
||||
var stepValue = (getTimestamp() - dt) / 1000 * self.options.scrollStep;
|
||||
|
||||
if (self.smoothScrollActive) {
|
||||
self.value += stepValue * direction;
|
||||
self.setValue(self.value);
|
||||
self.triggerScrollEvent(self.value);
|
||||
|
||||
if (!isFinishedScrolling()) {
|
||||
dt = getTimestamp();
|
||||
raf(doScrollAnimation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// start animation
|
||||
self.smoothScrollActive = true;
|
||||
dt = getTimestamp();
|
||||
raf(doScrollAnimation);
|
||||
},
|
||||
stopSmoothScrolling: function() {
|
||||
this.smoothScrollActive = false;
|
||||
},
|
||||
triggerScrollEvent: function(scrollValue) {
|
||||
if (this.options.onScroll) {
|
||||
this.options.onScroll(scrollValue);
|
||||
}
|
||||
},
|
||||
getThickness: function() {
|
||||
return this.scrollbar[this.thicknessMeasureMethod]();
|
||||
},
|
||||
setSize: function(size) {
|
||||
// resize scrollbar
|
||||
var btnDecSize = this.btnDec[this.fullSizeProperty](),
|
||||
btnIncSize = this.btnInc[this.fullSizeProperty]();
|
||||
|
||||
// resize slider
|
||||
this.currentSize = size;
|
||||
this.currentSliderSize = size - btnDecSize - btnIncSize;
|
||||
this.scrollbar.css(this.sizeProperty, size);
|
||||
this.slider.css(this.sizeProperty, this.currentSliderSize);
|
||||
this.currentSliderSize = this.slider[this.sizeProperty]();
|
||||
|
||||
// resize handle
|
||||
this.handleSize = Math.round(this.currentSliderSize * this.ratio);
|
||||
this.handle.css(this.sizeProperty, this.handleSize);
|
||||
this.handleSize = this.handle[this.fullSizeProperty]();
|
||||
|
||||
return this;
|
||||
},
|
||||
setRatio: function(ratio) {
|
||||
this.ratio = ratio;
|
||||
return this;
|
||||
},
|
||||
setMaxValue: function(maxValue) {
|
||||
this.maxValue = maxValue;
|
||||
this.setValue(Math.min(this.value, this.maxValue));
|
||||
return this;
|
||||
},
|
||||
setValue: function(value) {
|
||||
this.value = value;
|
||||
if (this.value < 0) {
|
||||
this.value = 0;
|
||||
} else if (this.value > this.maxValue) {
|
||||
this.value = this.maxValue;
|
||||
}
|
||||
this.refresh();
|
||||
},
|
||||
setPosition: function(styles) {
|
||||
this.scrollbar.css(styles);
|
||||
return this;
|
||||
},
|
||||
hide: function() {
|
||||
this.scrollbar.detach();
|
||||
return this;
|
||||
},
|
||||
show: function() {
|
||||
this.scrollbar.appendTo(this.holder);
|
||||
return this;
|
||||
},
|
||||
refresh: function() {
|
||||
// recalculate handle position
|
||||
if (this.value === 0 || this.maxValue === 0) {
|
||||
this.calcOffset = 0;
|
||||
} else {
|
||||
this.calcOffset = (this.value / this.maxValue) * (this.currentSliderSize - this.handleSize);
|
||||
}
|
||||
this.handle.css(this.offsetProperty, this.calcOffset);
|
||||
|
||||
// toggle inactive classes
|
||||
this.btnDec.toggleClass(this.options.inactiveClass, this.value === 0);
|
||||
this.btnInc.toggleClass(this.options.inactiveClass, this.value === this.maxValue);
|
||||
this.scrollbar.toggleClass(this.options.inactiveClass, this.maxValue === 0);
|
||||
},
|
||||
destroy: function() {
|
||||
// remove event handlers and scrollbar block itself
|
||||
this.btnDec.add(this.btnInc).off('jcf-pointerdown', this.onButtonPress);
|
||||
this.handle.off('jcf-pointerdown', this.onHandlePress);
|
||||
this.doc.off('jcf-pointermove', this.onHandleDrag);
|
||||
this.doc.off('jcf-pointerup', this.onHandleRelease);
|
||||
this.doc.off('jcf-pointerup', this.onButtonRelease);
|
||||
this.stopSmoothScrolling();
|
||||
this.stopPageScrolling();
|
||||
this.scrollbar.remove();
|
||||
}
|
||||
});
|
||||
|
||||
}(jQuery, this));
|
||||
940
webroot/js/selectbox1/jcf.select.js
Normal file
940
webroot/js/selectbox1/jcf.select.js
Normal file
@@ -0,0 +1,940 @@
|
||||
/*!
|
||||
* JavaScript Custom Forms : Select Module
|
||||
*
|
||||
* Copyright 2014-2015 PSD2HTML - http://psd2html.com/jcf
|
||||
* Released under the MIT license (LICENSE.txt)
|
||||
*
|
||||
* Version: 1.1.3
|
||||
*/
|
||||
;(function($, window) {
|
||||
'use strict';
|
||||
|
||||
jcf.addModule({
|
||||
name: 'Select',
|
||||
selector: 'select',
|
||||
options: {
|
||||
element: null,
|
||||
multipleCompactStyle: false
|
||||
},
|
||||
plugins: {
|
||||
ListBox: ListBox,
|
||||
ComboBox: ComboBox,
|
||||
SelectList: SelectList
|
||||
},
|
||||
matchElement: function(element) {
|
||||
return element.is('select');
|
||||
},
|
||||
init: function() {
|
||||
this.element = $(this.options.element);
|
||||
this.createInstance();
|
||||
},
|
||||
isListBox: function() {
|
||||
return this.element.is('[size]:not([jcf-size]), [multiple]');
|
||||
},
|
||||
createInstance: function() {
|
||||
if (this.instance) {
|
||||
this.instance.destroy();
|
||||
}
|
||||
if (this.isListBox() && !this.options.multipleCompactStyle) {
|
||||
this.instance = new ListBox(this.options);
|
||||
} else {
|
||||
this.instance = new ComboBox(this.options);
|
||||
}
|
||||
},
|
||||
refresh: function() {
|
||||
var typeMismatch = (this.isListBox() && this.instance instanceof ComboBox) ||
|
||||
(!this.isListBox() && this.instance instanceof ListBox);
|
||||
|
||||
if (typeMismatch) {
|
||||
this.createInstance();
|
||||
} else {
|
||||
this.instance.refresh();
|
||||
}
|
||||
},
|
||||
destroy: function() {
|
||||
this.instance.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// combobox module
|
||||
function ComboBox(options) {
|
||||
this.options = $.extend({
|
||||
wrapNative: true,
|
||||
wrapNativeOnMobile: true,
|
||||
fakeDropInBody: true,
|
||||
useCustomScroll: true,
|
||||
flipDropToFit: true,
|
||||
maxVisibleItems: 4,
|
||||
fakeAreaStructure: '<span class="jcf-select"><span class="jcf-select-text"></span><span class="jcf-select-opener"></span></span>',
|
||||
fakeDropStructure: '<div class="jcf-select-drop"><div class="jcf-select-drop-content"></div></div>',
|
||||
optionClassPrefix: 'jcf-option-',
|
||||
selectClassPrefix: 'jcf-select-',
|
||||
dropContentSelector: '.jcf-select-drop-content',
|
||||
selectTextSelector: '.jcf-select-text',
|
||||
dropActiveClass: 'jcf-drop-active',
|
||||
flipDropClass: 'jcf-drop-flipped'
|
||||
}, options);
|
||||
this.init();
|
||||
}
|
||||
$.extend(ComboBox.prototype, {
|
||||
init: function() {
|
||||
this.initStructure();
|
||||
this.bindHandlers();
|
||||
this.attachEvents();
|
||||
this.refresh();
|
||||
},
|
||||
initStructure: function() {
|
||||
// prepare structure
|
||||
this.win = $(window);
|
||||
this.doc = $(document);
|
||||
this.realElement = $(this.options.element);
|
||||
this.fakeElement = $(this.options.fakeAreaStructure).insertAfter(this.realElement);
|
||||
this.selectTextContainer = this.fakeElement.find(this.options.selectTextSelector);
|
||||
this.selectText = $('<span></span>').appendTo(this.selectTextContainer);
|
||||
makeUnselectable(this.fakeElement);
|
||||
|
||||
// copy classes from original select
|
||||
this.fakeElement.addClass(getPrefixedClasses(this.realElement.prop('className'), this.options.selectClassPrefix));
|
||||
|
||||
// handle compact multiple style
|
||||
if (this.realElement.prop('multiple')) {
|
||||
this.fakeElement.addClass('jcf-compact-multiple');
|
||||
}
|
||||
|
||||
// detect device type and dropdown behavior
|
||||
if (this.options.isMobileDevice && this.options.wrapNativeOnMobile && !this.options.wrapNative) {
|
||||
this.options.wrapNative = true;
|
||||
}
|
||||
|
||||
if (this.options.wrapNative) {
|
||||
// wrap native select inside fake block
|
||||
this.realElement.prependTo(this.fakeElement).css({
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
width: '100%'
|
||||
}).addClass(this.options.resetAppearanceClass);
|
||||
} else {
|
||||
// just hide native select
|
||||
this.realElement.addClass(this.options.hiddenClass);
|
||||
this.fakeElement.attr('title', this.realElement.attr('title'));
|
||||
this.fakeDropTarget = this.options.fakeDropInBody ? $('body') : this.fakeElement;
|
||||
}
|
||||
},
|
||||
attachEvents: function() {
|
||||
// delayed refresh handler
|
||||
var self = this;
|
||||
this.delayedRefresh = function() {
|
||||
setTimeout(function() {
|
||||
self.refresh();
|
||||
if (self.list) {
|
||||
self.list.refresh();
|
||||
self.list.scrollToActiveOption();
|
||||
}
|
||||
}, 1);
|
||||
};
|
||||
|
||||
// native dropdown event handlers
|
||||
if (this.options.wrapNative) {
|
||||
this.realElement.on({
|
||||
focus: this.onFocus,
|
||||
change: this.onChange,
|
||||
click: this.onChange,
|
||||
keydown: this.onChange
|
||||
});
|
||||
} else {
|
||||
// custom dropdown event handlers
|
||||
this.realElement.on({
|
||||
focus: this.onFocus,
|
||||
change: this.onChange,
|
||||
keydown: this.onKeyDown
|
||||
});
|
||||
this.fakeElement.on({
|
||||
'jcf-pointerdown': this.onSelectAreaPress
|
||||
});
|
||||
}
|
||||
},
|
||||
onKeyDown: function(e) {
|
||||
if (e.which === 13) {
|
||||
this.toggleDropdown();
|
||||
} else if (this.dropActive) {
|
||||
this.delayedRefresh();
|
||||
}
|
||||
},
|
||||
onChange: function() {
|
||||
this.refresh();
|
||||
},
|
||||
onFocus: function() {
|
||||
if (!this.pressedFlag || !this.focusedFlag) {
|
||||
this.fakeElement.addClass(this.options.focusClass);
|
||||
this.realElement.on('blur', this.onBlur);
|
||||
this.toggleListMode(true);
|
||||
this.focusedFlag = true;
|
||||
}
|
||||
},
|
||||
onBlur: function() {
|
||||
if (!this.pressedFlag) {
|
||||
this.fakeElement.removeClass(this.options.focusClass);
|
||||
this.realElement.off('blur', this.onBlur);
|
||||
this.toggleListMode(false);
|
||||
this.focusedFlag = false;
|
||||
}
|
||||
},
|
||||
onResize: function() {
|
||||
if (this.dropActive) {
|
||||
this.hideDropdown();
|
||||
}
|
||||
},
|
||||
onSelectDropPress: function() {
|
||||
this.pressedFlag = true;
|
||||
},
|
||||
onSelectDropRelease: function(e, pointerEvent) {
|
||||
this.pressedFlag = false;
|
||||
if (pointerEvent.pointerType === 'mouse') {
|
||||
this.realElement.focus();
|
||||
}
|
||||
},
|
||||
onSelectAreaPress: function(e) {
|
||||
// skip click if drop inside fake element or real select is disabled
|
||||
var dropClickedInsideFakeElement = !this.options.fakeDropInBody && $(e.target).closest(this.dropdown).length;
|
||||
if (dropClickedInsideFakeElement || e.button > 1 || this.realElement.is(':disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// toggle dropdown visibility
|
||||
this.selectOpenedByEvent = e.pointerType;
|
||||
this.toggleDropdown();
|
||||
|
||||
// misc handlers
|
||||
if (!this.focusedFlag) {
|
||||
if (e.pointerType === 'mouse') {
|
||||
this.realElement.focus();
|
||||
} else {
|
||||
this.onFocus(e);
|
||||
}
|
||||
}
|
||||
this.pressedFlag = true;
|
||||
this.fakeElement.addClass(this.options.pressedClass);
|
||||
this.doc.on('jcf-pointerup', this.onSelectAreaRelease);
|
||||
},
|
||||
onSelectAreaRelease: function(e) {
|
||||
if (this.focusedFlag && e.pointerType === 'mouse') {
|
||||
this.realElement.focus();
|
||||
}
|
||||
this.pressedFlag = false;
|
||||
this.fakeElement.removeClass(this.options.pressedClass);
|
||||
this.doc.off('jcf-pointerup', this.onSelectAreaRelease);
|
||||
},
|
||||
onOutsideClick: function(e) {
|
||||
var target = $(e.target),
|
||||
clickedInsideSelect = target.closest(this.fakeElement).length || target.closest(this.dropdown).length;
|
||||
|
||||
if (!clickedInsideSelect) {
|
||||
this.hideDropdown();
|
||||
}
|
||||
},
|
||||
onSelect: function() {
|
||||
this.refresh();
|
||||
|
||||
if (this.realElement.prop('multiple')) {
|
||||
this.repositionDropdown();
|
||||
} else {
|
||||
this.hideDropdown();
|
||||
}
|
||||
|
||||
this.fireNativeEvent(this.realElement, 'change');
|
||||
},
|
||||
toggleListMode: function(state) {
|
||||
if (!this.options.wrapNative) {
|
||||
if (state) {
|
||||
// temporary change select to list to avoid appearing of native dropdown
|
||||
this.realElement.attr({
|
||||
size: 4,
|
||||
'jcf-size': ''
|
||||
});
|
||||
} else {
|
||||
// restore select from list mode to dropdown select
|
||||
if (!this.options.wrapNative) {
|
||||
this.realElement.removeAttr('size jcf-size');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
createDropdown: function() {
|
||||
// destroy previous dropdown if needed
|
||||
if (this.dropdown) {
|
||||
this.list.destroy();
|
||||
this.dropdown.remove();
|
||||
}
|
||||
|
||||
// create new drop container
|
||||
this.dropdown = $(this.options.fakeDropStructure).appendTo(this.fakeDropTarget);
|
||||
this.dropdown.addClass(getPrefixedClasses(this.realElement.prop('className'), this.options.selectClassPrefix));
|
||||
makeUnselectable(this.dropdown);
|
||||
|
||||
// handle compact multiple style
|
||||
if (this.realElement.prop('multiple')) {
|
||||
this.dropdown.addClass('jcf-compact-multiple');
|
||||
}
|
||||
|
||||
// set initial styles for dropdown in body
|
||||
if (this.options.fakeDropInBody) {
|
||||
this.dropdown.css({
|
||||
position: 'absolute',
|
||||
top: -9999
|
||||
});
|
||||
}
|
||||
|
||||
// create new select list instance
|
||||
this.list = new SelectList({
|
||||
useHoverClass: true,
|
||||
handleResize: false,
|
||||
alwaysPreventMouseWheel: true,
|
||||
maxVisibleItems: this.options.maxVisibleItems,
|
||||
useCustomScroll: this.options.useCustomScroll,
|
||||
holder: this.dropdown.find(this.options.dropContentSelector),
|
||||
multipleSelectWithoutKey: this.realElement.prop('multiple'),
|
||||
element: this.realElement
|
||||
});
|
||||
$(this.list).on({
|
||||
select: this.onSelect,
|
||||
press: this.onSelectDropPress,
|
||||
release: this.onSelectDropRelease
|
||||
});
|
||||
},
|
||||
repositionDropdown: function() {
|
||||
var selectOffset = this.fakeElement.offset(),
|
||||
selectWidth = this.fakeElement.outerWidth(),
|
||||
selectHeight = this.fakeElement.outerHeight(),
|
||||
dropHeight = this.dropdown.css('width', selectWidth).outerHeight(),
|
||||
winScrollTop = this.win.scrollTop(),
|
||||
winHeight = this.win.height(),
|
||||
calcTop, calcLeft, bodyOffset, needFlipDrop = false;
|
||||
|
||||
// check flip drop position
|
||||
if (selectOffset.top + selectHeight + dropHeight > winScrollTop + winHeight && selectOffset.top - dropHeight > winScrollTop) {
|
||||
needFlipDrop = true;
|
||||
}
|
||||
|
||||
if (this.options.fakeDropInBody) {
|
||||
bodyOffset = this.fakeDropTarget.css('position') !== 'static' ? this.fakeDropTarget.offset().top : 0;
|
||||
if (this.options.flipDropToFit && needFlipDrop) {
|
||||
// calculate flipped dropdown position
|
||||
calcLeft = selectOffset.left;
|
||||
calcTop = selectOffset.top - dropHeight - bodyOffset;
|
||||
} else {
|
||||
// calculate default drop position
|
||||
calcLeft = selectOffset.left;
|
||||
calcTop = selectOffset.top + selectHeight - bodyOffset;
|
||||
}
|
||||
|
||||
// update drop styles
|
||||
this.dropdown.css({
|
||||
width: selectWidth,
|
||||
left: calcLeft,
|
||||
top: calcTop
|
||||
});
|
||||
}
|
||||
|
||||
// refresh flipped class
|
||||
this.dropdown.add(this.fakeElement).toggleClass(this.options.flipDropClass, this.options.flipDropToFit && needFlipDrop);
|
||||
},
|
||||
showDropdown: function() {
|
||||
// do not show empty custom dropdown
|
||||
if (!this.realElement.prop('options').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// create options list if not created
|
||||
if (!this.dropdown) {
|
||||
this.createDropdown();
|
||||
}
|
||||
|
||||
// show dropdown
|
||||
this.dropActive = true;
|
||||
this.dropdown.appendTo(this.fakeDropTarget);
|
||||
this.fakeElement.addClass(this.options.dropActiveClass);
|
||||
this.refreshSelectedText();
|
||||
this.repositionDropdown();
|
||||
this.list.setScrollTop(this.savedScrollTop);
|
||||
this.list.refresh();
|
||||
|
||||
// add temporary event handlers
|
||||
this.win.on('resize', this.onResize);
|
||||
this.doc.on('jcf-pointerdown', this.onOutsideClick);
|
||||
},
|
||||
hideDropdown: function() {
|
||||
if (this.dropdown) {
|
||||
this.savedScrollTop = this.list.getScrollTop();
|
||||
this.fakeElement.removeClass(this.options.dropActiveClass + ' ' + this.options.flipDropClass);
|
||||
this.dropdown.removeClass(this.options.flipDropClass).detach();
|
||||
this.doc.off('jcf-pointerdown', this.onOutsideClick);
|
||||
this.win.off('resize', this.onResize);
|
||||
this.dropActive = false;
|
||||
if (this.selectOpenedByEvent === 'touch') {
|
||||
this.onBlur();
|
||||
}
|
||||
}
|
||||
},
|
||||
toggleDropdown: function() {
|
||||
if (this.dropActive) {
|
||||
this.hideDropdown();
|
||||
} else {
|
||||
this.showDropdown();
|
||||
}
|
||||
},
|
||||
refreshSelectedText: function() {
|
||||
// redraw selected area
|
||||
var selectedIndex = this.realElement.prop('selectedIndex'),
|
||||
selectedOption = this.realElement.prop('options')[selectedIndex],
|
||||
selectedOptionImage = selectedOption ? selectedOption.getAttribute('data-image') : null,
|
||||
selectedOptionText = '',
|
||||
selectedOptionClasses,
|
||||
self = this;
|
||||
|
||||
if (this.realElement.prop('multiple')) {
|
||||
$.each(this.realElement.prop('options'), function(index, option) {
|
||||
if (option.selected) {
|
||||
selectedOptionText += (selectedOptionText ? ', ' : '') + option.innerHTML;
|
||||
}
|
||||
});
|
||||
if (!selectedOptionText) {
|
||||
selectedOptionText = self.realElement.attr('placeholder') || '';
|
||||
}
|
||||
this.selectText.removeAttr('class').html(selectedOptionText);
|
||||
} else if (!selectedOption) {
|
||||
if (this.selectImage) {
|
||||
this.selectImage.hide();
|
||||
}
|
||||
this.selectText.removeAttr('class').empty();
|
||||
} else if (this.currentSelectedText !== selectedOption.innerHTML || this.currentSelectedImage !== selectedOptionImage) {
|
||||
selectedOptionClasses = getPrefixedClasses(selectedOption.className, this.options.optionClassPrefix);
|
||||
this.selectText.attr('class', selectedOptionClasses).html(selectedOption.innerHTML);
|
||||
|
||||
if (selectedOptionImage) {
|
||||
if (!this.selectImage) {
|
||||
this.selectImage = $('<img>').prependTo(this.selectTextContainer).hide();
|
||||
}
|
||||
this.selectImage.attr('src', selectedOptionImage).show();
|
||||
} else if (this.selectImage) {
|
||||
this.selectImage.hide();
|
||||
}
|
||||
|
||||
this.currentSelectedText = selectedOption.innerHTML;
|
||||
this.currentSelectedImage = selectedOptionImage;
|
||||
}
|
||||
},
|
||||
refresh: function() {
|
||||
// refresh fake select visibility
|
||||
if (this.realElement.prop('style').display === 'none') {
|
||||
this.fakeElement.hide();
|
||||
} else {
|
||||
this.fakeElement.show();
|
||||
}
|
||||
|
||||
// refresh selected text
|
||||
this.refreshSelectedText();
|
||||
|
||||
// handle disabled state
|
||||
this.fakeElement.toggleClass(this.options.disabledClass, this.realElement.is(':disabled'));
|
||||
},
|
||||
destroy: function() {
|
||||
// restore structure
|
||||
if (this.options.wrapNative) {
|
||||
this.realElement.insertBefore(this.fakeElement).css({
|
||||
position: '',
|
||||
height: '',
|
||||
width: ''
|
||||
}).removeClass(this.options.resetAppearanceClass);
|
||||
} else {
|
||||
this.realElement.removeClass(this.options.hiddenClass);
|
||||
if (this.realElement.is('[jcf-size]')) {
|
||||
this.realElement.removeAttr('size jcf-size');
|
||||
}
|
||||
}
|
||||
|
||||
// removing element will also remove its event handlers
|
||||
this.fakeElement.remove();
|
||||
|
||||
// remove other event handlers
|
||||
this.doc.off('jcf-pointerup', this.onSelectAreaRelease);
|
||||
this.realElement.off({
|
||||
focus: this.onFocus
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// listbox module
|
||||
function ListBox(options) {
|
||||
this.options = $.extend({
|
||||
wrapNative: true,
|
||||
useCustomScroll: true,
|
||||
fakeStructure: '<span class="jcf-list-box"><span class="jcf-list-wrapper"></span></span>',
|
||||
selectClassPrefix: 'jcf-select-',
|
||||
listHolder: '.jcf-list-wrapper'
|
||||
}, options);
|
||||
this.init();
|
||||
}
|
||||
$.extend(ListBox.prototype, {
|
||||
init: function() {
|
||||
this.bindHandlers();
|
||||
this.initStructure();
|
||||
this.attachEvents();
|
||||
},
|
||||
initStructure: function() {
|
||||
this.realElement = $(this.options.element);
|
||||
this.fakeElement = $(this.options.fakeStructure).insertAfter(this.realElement);
|
||||
this.listHolder = this.fakeElement.find(this.options.listHolder);
|
||||
makeUnselectable(this.fakeElement);
|
||||
|
||||
// copy classes from original select
|
||||
this.fakeElement.addClass(getPrefixedClasses(this.realElement.prop('className'), this.options.selectClassPrefix));
|
||||
this.realElement.addClass(this.options.hiddenClass);
|
||||
|
||||
this.list = new SelectList({
|
||||
useCustomScroll: this.options.useCustomScroll,
|
||||
holder: this.listHolder,
|
||||
selectOnClick: false,
|
||||
element: this.realElement
|
||||
});
|
||||
},
|
||||
attachEvents: function() {
|
||||
// delayed refresh handler
|
||||
var self = this;
|
||||
this.delayedRefresh = function(e) {
|
||||
if (e && e.which === 16) {
|
||||
// ignore SHIFT key
|
||||
return;
|
||||
} else {
|
||||
clearTimeout(self.refreshTimer);
|
||||
self.refreshTimer = setTimeout(function() {
|
||||
self.refresh();
|
||||
self.list.scrollToActiveOption();
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// other event handlers
|
||||
this.realElement.on({
|
||||
focus: this.onFocus,
|
||||
click: this.delayedRefresh,
|
||||
keydown: this.delayedRefresh
|
||||
});
|
||||
|
||||
// select list event handlers
|
||||
$(this.list).on({
|
||||
select: this.onSelect,
|
||||
press: this.onFakeOptionsPress,
|
||||
release: this.onFakeOptionsRelease
|
||||
});
|
||||
},
|
||||
onFakeOptionsPress: function(e, pointerEvent) {
|
||||
this.pressedFlag = true;
|
||||
if (pointerEvent.pointerType === 'mouse') {
|
||||
this.realElement.focus();
|
||||
}
|
||||
},
|
||||
onFakeOptionsRelease: function(e, pointerEvent) {
|
||||
this.pressedFlag = false;
|
||||
if (pointerEvent.pointerType === 'mouse') {
|
||||
this.realElement.focus();
|
||||
}
|
||||
},
|
||||
onSelect: function() {
|
||||
this.fireNativeEvent(this.realElement, 'change');
|
||||
this.fireNativeEvent(this.realElement, 'click');
|
||||
},
|
||||
onFocus: function() {
|
||||
if (!this.pressedFlag || !this.focusedFlag) {
|
||||
this.fakeElement.addClass(this.options.focusClass);
|
||||
this.realElement.on('blur', this.onBlur);
|
||||
this.focusedFlag = true;
|
||||
}
|
||||
},
|
||||
onBlur: function() {
|
||||
if (!this.pressedFlag) {
|
||||
this.fakeElement.removeClass(this.options.focusClass);
|
||||
this.realElement.off('blur', this.onBlur);
|
||||
this.focusedFlag = false;
|
||||
}
|
||||
},
|
||||
refresh: function() {
|
||||
this.fakeElement.toggleClass(this.options.disabledClass, this.realElement.is(':disabled'));
|
||||
this.list.refresh();
|
||||
},
|
||||
destroy: function() {
|
||||
this.list.destroy();
|
||||
this.realElement.insertBefore(this.fakeElement).removeClass(this.options.hiddenClass);
|
||||
this.fakeElement.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// options list module
|
||||
function SelectList(options) {
|
||||
this.options = $.extend({
|
||||
holder: null,
|
||||
maxVisibleItems: 10,
|
||||
selectOnClick: true,
|
||||
useHoverClass: false,
|
||||
useCustomScroll: false,
|
||||
handleResize: true,
|
||||
multipleSelectWithoutKey: false,
|
||||
alwaysPreventMouseWheel: false,
|
||||
indexAttribute: 'data-index',
|
||||
cloneClassPrefix: 'jcf-option-',
|
||||
containerStructure: '<span class="jcf-list"><span class="jcf-list-content"></span></span>',
|
||||
containerSelector: '.jcf-list-content',
|
||||
captionClass: 'jcf-optgroup-caption',
|
||||
disabledClass: 'jcf-disabled',
|
||||
optionClass: 'jcf-option',
|
||||
groupClass: 'jcf-optgroup',
|
||||
hoverClass: 'jcf-hover',
|
||||
selectedClass: 'jcf-selected',
|
||||
scrollClass: 'jcf-scroll-active'
|
||||
}, options);
|
||||
this.init();
|
||||
}
|
||||
$.extend(SelectList.prototype, {
|
||||
init: function() {
|
||||
this.initStructure();
|
||||
this.refreshSelectedClass();
|
||||
this.attachEvents();
|
||||
},
|
||||
initStructure: function() {
|
||||
this.element = $(this.options.element);
|
||||
this.indexSelector = '[' + this.options.indexAttribute + ']';
|
||||
this.container = $(this.options.containerStructure).appendTo(this.options.holder);
|
||||
this.listHolder = this.container.find(this.options.containerSelector);
|
||||
this.lastClickedIndex = this.element.prop('selectedIndex');
|
||||
this.rebuildList();
|
||||
},
|
||||
attachEvents: function() {
|
||||
this.bindHandlers();
|
||||
this.listHolder.on('jcf-pointerdown', this.indexSelector, this.onItemPress);
|
||||
this.listHolder.on('jcf-pointerdown', this.onPress);
|
||||
|
||||
if (this.options.useHoverClass) {
|
||||
this.listHolder.on('jcf-pointerover', this.indexSelector, this.onHoverItem);
|
||||
}
|
||||
},
|
||||
onPress: function(e) {
|
||||
$(this).trigger('press', e);
|
||||
this.listHolder.on('jcf-pointerup', this.onRelease);
|
||||
},
|
||||
onRelease: function(e) {
|
||||
$(this).trigger('release', e);
|
||||
this.listHolder.off('jcf-pointerup', this.onRelease);
|
||||
},
|
||||
onHoverItem: function(e) {
|
||||
var hoverIndex = parseFloat(e.currentTarget.getAttribute(this.options.indexAttribute));
|
||||
this.fakeOptions.removeClass(this.options.hoverClass).eq(hoverIndex).addClass(this.options.hoverClass);
|
||||
},
|
||||
onItemPress: function(e) {
|
||||
if (e.pointerType === 'touch' || this.options.selectOnClick) {
|
||||
// select option after "click"
|
||||
this.tmpListOffsetTop = this.list.offset().top;
|
||||
this.listHolder.on('jcf-pointerup', this.indexSelector, this.onItemRelease);
|
||||
} else {
|
||||
// select option immediately
|
||||
this.onSelectItem(e);
|
||||
}
|
||||
},
|
||||
onItemRelease: function(e) {
|
||||
// remove event handlers and temporary data
|
||||
this.listHolder.off('jcf-pointerup', this.indexSelector, this.onItemRelease);
|
||||
|
||||
// simulate item selection
|
||||
if (this.tmpListOffsetTop === this.list.offset().top) {
|
||||
this.listHolder.on('click', this.indexSelector, { savedPointerType: e.pointerType }, this.onSelectItem);
|
||||
}
|
||||
delete this.tmpListOffsetTop;
|
||||
},
|
||||
onSelectItem: function(e) {
|
||||
var clickedIndex = parseFloat(e.currentTarget.getAttribute(this.options.indexAttribute)),
|
||||
pointerType = e.data && e.data.savedPointerType || e.pointerType || 'mouse',
|
||||
range;
|
||||
|
||||
// remove click event handler
|
||||
this.listHolder.off('click', this.indexSelector, this.onSelectItem);
|
||||
|
||||
// ignore clicks on disabled options
|
||||
if (e.button > 1 || this.realOptions[clickedIndex].disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.element.prop('multiple')) {
|
||||
if (e.metaKey || e.ctrlKey || pointerType === 'touch' || this.options.multipleSelectWithoutKey) {
|
||||
// if CTRL/CMD pressed or touch devices - toggle selected option
|
||||
this.realOptions[clickedIndex].selected = !this.realOptions[clickedIndex].selected;
|
||||
} else if (e.shiftKey) {
|
||||
// if SHIFT pressed - update selection
|
||||
range = [this.lastClickedIndex, clickedIndex].sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
this.realOptions.each(function(index, option) {
|
||||
option.selected = (index >= range[0] && index <= range[1]);
|
||||
});
|
||||
} else {
|
||||
// set single selected index
|
||||
this.element.prop('selectedIndex', clickedIndex);
|
||||
}
|
||||
} else {
|
||||
this.element.prop('selectedIndex', clickedIndex);
|
||||
}
|
||||
|
||||
// save last clicked option
|
||||
if (!e.shiftKey) {
|
||||
this.lastClickedIndex = clickedIndex;
|
||||
}
|
||||
|
||||
// refresh classes
|
||||
this.refreshSelectedClass();
|
||||
|
||||
// scroll to active item in desktop browsers
|
||||
if (pointerType === 'mouse') {
|
||||
this.scrollToActiveOption();
|
||||
}
|
||||
|
||||
// make callback when item selected
|
||||
$(this).trigger('select');
|
||||
},
|
||||
rebuildList: function() {
|
||||
// rebuild options
|
||||
var self = this,
|
||||
rootElement = this.element[0];
|
||||
|
||||
// recursively create fake options
|
||||
this.storedSelectHTML = rootElement.innerHTML;
|
||||
this.optionIndex = 0;
|
||||
this.list = $(this.createOptionsList(rootElement));
|
||||
this.listHolder.empty().append(this.list);
|
||||
this.realOptions = this.element.find('option');
|
||||
this.fakeOptions = this.list.find(this.indexSelector);
|
||||
this.fakeListItems = this.list.find('.' + this.options.captionClass + ',' + this.indexSelector);
|
||||
delete this.optionIndex;
|
||||
|
||||
// detect max visible items
|
||||
var maxCount = this.options.maxVisibleItems,
|
||||
sizeValue = this.element.prop('size');
|
||||
if (sizeValue > 1 && !this.element.is('[jcf-size]')) {
|
||||
maxCount = sizeValue;
|
||||
}
|
||||
|
||||
// handle scrollbar
|
||||
var needScrollBar = this.fakeOptions.length > maxCount;
|
||||
this.container.toggleClass(this.options.scrollClass, needScrollBar);
|
||||
if (needScrollBar) {
|
||||
// change max-height
|
||||
this.listHolder.css({
|
||||
maxHeight: this.getOverflowHeight(maxCount),
|
||||
overflow: 'auto'
|
||||
});
|
||||
|
||||
if (this.options.useCustomScroll && jcf.modules.Scrollable) {
|
||||
// add custom scrollbar if specified in options
|
||||
jcf.replace(this.listHolder, 'Scrollable', {
|
||||
handleResize: this.options.handleResize,
|
||||
alwaysPreventMouseWheel: this.options.alwaysPreventMouseWheel
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// disable edge wheel scrolling
|
||||
if (this.options.alwaysPreventMouseWheel) {
|
||||
this.preventWheelHandler = function(e) {
|
||||
var currentScrollTop = self.listHolder.scrollTop(),
|
||||
maxScrollTop = self.listHolder.prop('scrollHeight') - self.listHolder.innerHeight();
|
||||
|
||||
// check edge cases
|
||||
if ((currentScrollTop <= 0 && e.deltaY < 0) || (currentScrollTop >= maxScrollTop && e.deltaY > 0)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
this.listHolder.on('jcf-mousewheel', this.preventWheelHandler);
|
||||
}
|
||||
},
|
||||
refreshSelectedClass: function() {
|
||||
var self = this,
|
||||
selectedItem,
|
||||
isMultiple = this.element.prop('multiple'),
|
||||
selectedIndex = this.element.prop('selectedIndex');
|
||||
|
||||
if (isMultiple) {
|
||||
this.realOptions.each(function(index, option) {
|
||||
self.fakeOptions.eq(index).toggleClass(self.options.selectedClass, !!option.selected);
|
||||
});
|
||||
} else {
|
||||
this.fakeOptions.removeClass(this.options.selectedClass + ' ' + this.options.hoverClass);
|
||||
selectedItem = this.fakeOptions.eq(selectedIndex).addClass(this.options.selectedClass);
|
||||
if (this.options.useHoverClass) {
|
||||
selectedItem.addClass(this.options.hoverClass);
|
||||
}
|
||||
}
|
||||
},
|
||||
scrollToActiveOption: function() {
|
||||
// scroll to target option
|
||||
var targetOffset = this.getActiveOptionOffset();
|
||||
if (typeof targetOffset === 'number') {
|
||||
this.listHolder.prop('scrollTop', targetOffset);
|
||||
}
|
||||
},
|
||||
getSelectedIndexRange: function() {
|
||||
var firstSelected = -1, lastSelected = -1;
|
||||
this.realOptions.each(function(index, option) {
|
||||
if (option.selected) {
|
||||
if (firstSelected < 0) {
|
||||
firstSelected = index;
|
||||
}
|
||||
lastSelected = index;
|
||||
}
|
||||
});
|
||||
return [firstSelected, lastSelected];
|
||||
},
|
||||
getChangedSelectedIndex: function() {
|
||||
var selectedIndex = this.element.prop('selectedIndex'),
|
||||
targetIndex;
|
||||
|
||||
if (this.element.prop('multiple')) {
|
||||
// multiple selects handling
|
||||
if (!this.previousRange) {
|
||||
this.previousRange = [selectedIndex, selectedIndex];
|
||||
}
|
||||
this.currentRange = this.getSelectedIndexRange();
|
||||
targetIndex = this.currentRange[this.currentRange[0] !== this.previousRange[0] ? 0 : 1];
|
||||
this.previousRange = this.currentRange;
|
||||
return targetIndex;
|
||||
} else {
|
||||
// single choice selects handling
|
||||
return selectedIndex;
|
||||
}
|
||||
},
|
||||
getActiveOptionOffset: function() {
|
||||
// calc values
|
||||
var dropHeight = this.listHolder.height(),
|
||||
dropScrollTop = this.listHolder.prop('scrollTop'),
|
||||
currentIndex = this.getChangedSelectedIndex(),
|
||||
fakeOption = this.fakeOptions.eq(currentIndex),
|
||||
fakeOptionOffset = fakeOption.offset().top - this.list.offset().top,
|
||||
fakeOptionHeight = fakeOption.innerHeight();
|
||||
|
||||
// scroll list
|
||||
if (fakeOptionOffset + fakeOptionHeight >= dropScrollTop + dropHeight) {
|
||||
// scroll down (always scroll to option)
|
||||
return fakeOptionOffset - dropHeight + fakeOptionHeight;
|
||||
} else if (fakeOptionOffset < dropScrollTop) {
|
||||
// scroll up to option
|
||||
return fakeOptionOffset;
|
||||
}
|
||||
},
|
||||
getOverflowHeight: function(sizeValue) {
|
||||
var item = this.fakeListItems.eq(sizeValue - 1),
|
||||
listOffset = this.list.offset().top,
|
||||
itemOffset = item.offset().top,
|
||||
itemHeight = item.innerHeight();
|
||||
|
||||
return itemOffset + itemHeight - listOffset;
|
||||
},
|
||||
getScrollTop: function() {
|
||||
return this.listHolder.scrollTop();
|
||||
},
|
||||
setScrollTop: function(value) {
|
||||
this.listHolder.scrollTop(value);
|
||||
},
|
||||
createOption: function(option) {
|
||||
var newOption = document.createElement('span');
|
||||
newOption.className = this.options.optionClass;
|
||||
newOption.innerHTML = option.innerHTML;
|
||||
newOption.setAttribute(this.options.indexAttribute, this.optionIndex++);
|
||||
|
||||
var optionImage, optionImageSrc = option.getAttribute('data-image');
|
||||
if (optionImageSrc) {
|
||||
optionImage = document.createElement('img');
|
||||
optionImage.src = optionImageSrc;
|
||||
newOption.insertBefore(optionImage, newOption.childNodes[0]);
|
||||
}
|
||||
if (option.disabled) {
|
||||
newOption.className += ' ' + this.options.disabledClass;
|
||||
}
|
||||
if (option.className) {
|
||||
newOption.className += ' ' + getPrefixedClasses(option.className, this.options.cloneClassPrefix);
|
||||
}
|
||||
return newOption;
|
||||
},
|
||||
createOptGroup: function(optgroup) {
|
||||
var optGroupContainer = document.createElement('span'),
|
||||
optGroupName = optgroup.getAttribute('label'),
|
||||
optGroupCaption, optGroupList;
|
||||
|
||||
// create caption
|
||||
optGroupCaption = document.createElement('span');
|
||||
optGroupCaption.className = this.options.captionClass;
|
||||
optGroupCaption.innerHTML = optGroupName;
|
||||
optGroupContainer.appendChild(optGroupCaption);
|
||||
|
||||
// create list of options
|
||||
if (optgroup.children.length) {
|
||||
optGroupList = this.createOptionsList(optgroup);
|
||||
optGroupContainer.appendChild(optGroupList);
|
||||
}
|
||||
|
||||
optGroupContainer.className = this.options.groupClass;
|
||||
return optGroupContainer;
|
||||
},
|
||||
createOptionContainer: function() {
|
||||
var optionContainer = document.createElement('li');
|
||||
return optionContainer;
|
||||
},
|
||||
createOptionsList: function(container) {
|
||||
var self = this,
|
||||
list = document.createElement('ul');
|
||||
|
||||
$.each(container.children, function(index, currentNode) {
|
||||
var item = self.createOptionContainer(currentNode),
|
||||
newNode;
|
||||
|
||||
switch (currentNode.tagName.toLowerCase()) {
|
||||
case 'option': newNode = self.createOption(currentNode); break;
|
||||
case 'optgroup': newNode = self.createOptGroup(currentNode); break;
|
||||
}
|
||||
list.appendChild(item).appendChild(newNode);
|
||||
});
|
||||
return list;
|
||||
},
|
||||
refresh: function() {
|
||||
// check for select innerHTML changes
|
||||
if (this.storedSelectHTML !== this.element.prop('innerHTML')) {
|
||||
this.rebuildList();
|
||||
}
|
||||
|
||||
// refresh custom scrollbar
|
||||
var scrollInstance = jcf.getInstance(this.listHolder);
|
||||
if (scrollInstance) {
|
||||
scrollInstance.refresh();
|
||||
}
|
||||
|
||||
// refresh selectes classes
|
||||
this.refreshSelectedClass();
|
||||
},
|
||||
destroy: function() {
|
||||
this.listHolder.off('jcf-mousewheel', this.preventWheelHandler);
|
||||
this.listHolder.off('jcf-pointerdown', this.indexSelector, this.onSelectItem);
|
||||
this.listHolder.off('jcf-pointerover', this.indexSelector, this.onHoverItem);
|
||||
this.listHolder.off('jcf-pointerdown', this.onPress);
|
||||
}
|
||||
});
|
||||
|
||||
// helper functions
|
||||
var getPrefixedClasses = function(className, prefixToAdd) {
|
||||
return className ? className.replace(/[\s]*([\S]+)+[\s]*/gi, prefixToAdd + '$1 ') : '';
|
||||
};
|
||||
var makeUnselectable = (function() {
|
||||
var unselectableClass = jcf.getOptions().unselectableClass;
|
||||
function preventHandler(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return function(node) {
|
||||
node.addClass(unselectableClass).on('selectstart', preventHandler);
|
||||
};
|
||||
}());
|
||||
|
||||
}(jQuery, this));
|
||||
10346
webroot/js/selectbox1/jquery.js
vendored
Normal file
10346
webroot/js/selectbox1/jquery.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user