|
@@ -0,0 +1,1174 @@
|
|
|
+!function($){
|
|
|
+ 'use strict';
|
|
|
+
|
|
|
+ /* LIST CLASS DEFINITION
|
|
|
+ * ========================= */
|
|
|
+
|
|
|
+ var Megalist = function(element, $parent) {
|
|
|
+ var html;
|
|
|
+
|
|
|
+ if ($parent === undefined){
|
|
|
+ //if there's no $parent then we are creating one
|
|
|
+ this.$el = element;
|
|
|
+ this.setOptions(this.$el.options);
|
|
|
+ // build HTML
|
|
|
+ html = this.buildParentDOM();
|
|
|
+ //source list - data to choose from
|
|
|
+ this.$el.sourceList = new Megalist(html.srcElement, this.$el);
|
|
|
+ //destination list - data chosen by user
|
|
|
+ this.$el.destinationList = new Megalist(html.dstElement, this.$el);
|
|
|
+
|
|
|
+ } else {
|
|
|
+ //else just init one of the megalistSide children
|
|
|
+ this.init(element, $parent);
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ Megalist.prototype = {
|
|
|
+
|
|
|
+ constructor: Megalist,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * megalistSide constructor - initializes one side of megalist
|
|
|
+ *
|
|
|
+ * @param {object} element - jQuery object on witch megalist is initialized
|
|
|
+ * @param {object} $parent - optional jQuery object with parent for
|
|
|
+ * megalistSide initialization only
|
|
|
+ * @return {object} - returns self
|
|
|
+ */
|
|
|
+ init: function(element, $parent) {
|
|
|
+ this.$el = element;
|
|
|
+ this.$parent = $parent;
|
|
|
+
|
|
|
+ //defaults
|
|
|
+ this.processedItems = {};
|
|
|
+ this.totalItems = [];
|
|
|
+ this.itemHeight = -1;
|
|
|
+ this.listItems = $();
|
|
|
+ this.suffix = undefined;
|
|
|
+ this.yPosition = 0;
|
|
|
+ this.filteredData = [];
|
|
|
+ this.pageHeight = 0;
|
|
|
+ this.scrollingActive = false;
|
|
|
+
|
|
|
+ //init widget
|
|
|
+ this.setOptions(this.$parent.options);
|
|
|
+ this.getSuffix();
|
|
|
+ this.buildDOM();
|
|
|
+ this.bindEvents();
|
|
|
+ this.bindData();
|
|
|
+ this.updateLayout();
|
|
|
+ this.generatePOST(this.conf.BUILD_FULL_POST);
|
|
|
+
|
|
|
+ return this;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets default options and extends them if configuration was provided on
|
|
|
+ * megalist initialization
|
|
|
+ *
|
|
|
+ * @param {object} options - object containing options for megalist
|
|
|
+ */
|
|
|
+ setOptions: function(options){
|
|
|
+ var conf = {};
|
|
|
+
|
|
|
+ // mimimum scrollbar height in pixels
|
|
|
+ conf.SCROLLBAR_MIN_SIZE = 12;
|
|
|
+ // inertial delay for megalist ui update after resize event occurs
|
|
|
+ conf.RESIZE_TIMEOUT_DELAY = 100;
|
|
|
+ // minimum characters to trigger quicksearch filtering
|
|
|
+ conf.MINIMUM_SEARCH_QUERY_SIZE = 3;
|
|
|
+ // build full or simple (comma separated ids) post
|
|
|
+ conf.BUILD_FULL_POST = false;
|
|
|
+ // move action event name to trigger
|
|
|
+ conf.MOVE_ACTION_NAME = 'move';
|
|
|
+ //functional suffixes for multiselect: destination list suffix
|
|
|
+ conf.DESTINATION_SUFFIX = 'dst';
|
|
|
+ // functional suffixes for multiselect: source list suffix
|
|
|
+ conf.SOURCE_SUFFIX = 'src';
|
|
|
+ // text to display as search input placeholder
|
|
|
+ conf.PLACEHOLDER_TEXT = 'Search';
|
|
|
+ // time to wait for first continous scrolling
|
|
|
+ conf.CONTINOUS_SCROLLING_FIRST_INTERVAL = 500;
|
|
|
+ // time to wait for every next continous scrolling
|
|
|
+ conf.CONTINOUS_SCROLLING_INTERVAL = 60;
|
|
|
+
|
|
|
+ if (typeof options === 'object'){
|
|
|
+ conf = $.extend(conf, options);
|
|
|
+ }
|
|
|
+ this.conf = conf;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Builds required html elements for both source and destination
|
|
|
+ * megalistSide and append them to parent element
|
|
|
+ */
|
|
|
+ buildParentDOM: function() {
|
|
|
+ var srcElement, dstElement;
|
|
|
+
|
|
|
+ this.$el.html('');
|
|
|
+ this.$el.addClass('megalist-mutliselect');
|
|
|
+
|
|
|
+ //create 2 containers for megalists and buttons between then append
|
|
|
+ srcElement = $( '<div/>', {
|
|
|
+ 'id': this.$el.attr('id') + '_' + this.conf.SOURCE_SUFFIX,
|
|
|
+ 'class': 'megalist-inner'
|
|
|
+ });
|
|
|
+ dstElement = $( '<div/>', {
|
|
|
+ 'id': this.$el.attr('id') + '_' + this.conf.DESTINATION_SUFFIX,
|
|
|
+ 'class': 'megalist-inner'
|
|
|
+ });
|
|
|
+ this.$el.$moveButtons = $( '<div/>', {
|
|
|
+ 'class': 'move-buttons'
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$el.append(srcElement, this.$el.$moveButtons, dstElement);
|
|
|
+
|
|
|
+ return {srcElement:srcElement, dstElement:dstElement};
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Builds required html elements for megalistSide:
|
|
|
+ * searchbox, scrollbar, move button and hidden result input
|
|
|
+ */
|
|
|
+ buildDOM: function() {
|
|
|
+ var arrowIcon = 'arrow-left';
|
|
|
+
|
|
|
+ if (this.suffix === this.conf.SOURCE_SUFFIX) {
|
|
|
+ arrowIcon = 'arrow-right';
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$el.wrap('<div class="megalist"></div>"');
|
|
|
+
|
|
|
+ this.$search = $('<input/>', {
|
|
|
+ 'id': this.$el.attr('id') + '_search',
|
|
|
+ 'placeholder': this.conf.PLACEHOLDER_TEXT,
|
|
|
+ 'type': 'text'
|
|
|
+ });
|
|
|
+ this.$scrollbar = $('<div/>', {
|
|
|
+ 'id': this.$el.attr('id') + '_scrollbar',
|
|
|
+ 'class': 'scrollbar'
|
|
|
+ });
|
|
|
+ this.$scrollbarBackground = $('<div/>', {
|
|
|
+ 'class': 'scrollbar-background'
|
|
|
+ });
|
|
|
+ this.$moveall = $('<div/>', {
|
|
|
+ 'class': 'move-button ' + arrowIcon
|
|
|
+ });
|
|
|
+
|
|
|
+ if (Modernizr.svg) {
|
|
|
+ this.$moveall.append($(
|
|
|
+ '<svg width="32" height="32" viewBox="0 0 64 64">' +
|
|
|
+ '<use xlink:href="#' + arrowIcon + '"></svg>'
|
|
|
+ ));
|
|
|
+ } else {
|
|
|
+ this.$moveall.addClass('no-svg');
|
|
|
+ this.$moveall.append($('<div class="svg" />'));
|
|
|
+ }
|
|
|
+
|
|
|
+ //attach to container in parent
|
|
|
+ this.$parent.$moveButtons.append(this.$moveall);
|
|
|
+
|
|
|
+ this.$input = $('<input/>', {
|
|
|
+ 'name': this.name,
|
|
|
+ 'type': 'hidden'
|
|
|
+ });
|
|
|
+ this.$ul = $('<ul />');
|
|
|
+
|
|
|
+ this.$el.before(this.$search);
|
|
|
+
|
|
|
+ // Set tabindex, so the element can be in focus
|
|
|
+ this.$el.attr('tabindex', '-1');
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Resolves suffix for megalistSide so that it know if it's source or
|
|
|
+ * destination side. Resolving is based on id of the container
|
|
|
+ */
|
|
|
+ getSuffix: function() {
|
|
|
+ var id_tokens, lastToken;
|
|
|
+
|
|
|
+ id_tokens = this.$el.attr('id').split('_');
|
|
|
+ lastToken = id_tokens[id_tokens.length - 1];
|
|
|
+ this.name = id_tokens.splice(id_tokens, id_tokens.length-1).join('_');
|
|
|
+
|
|
|
+ if (lastToken === this.conf.SOURCE_SUFFIX) {
|
|
|
+ this.suffix = this.conf.SOURCE_SUFFIX;
|
|
|
+ } else if (lastToken === this.conf.DESTINATION_SUFFIX) {
|
|
|
+ this.suffix = this.conf.DESTINATION_SUFFIX;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns targetList for current megalistSide. In not defined, gets proper
|
|
|
+ * one from $parent first and stores it for later use
|
|
|
+ *
|
|
|
+ * @return {object} - returns target list
|
|
|
+ */
|
|
|
+ getTargetList: function() {
|
|
|
+ if (!(this.targetList instanceof Object)){
|
|
|
+ if ( this.suffix === this.conf.SOURCE_SUFFIX) {
|
|
|
+ this.targetList = this.$parent.destinationList;
|
|
|
+ } else if ( this.suffix === this.conf.DESTINATION_SUFFIX) {
|
|
|
+ this.targetList = this.$parent.sourceList;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this.targetList;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Binds all events need by the widget
|
|
|
+ */
|
|
|
+ bindEvents: function() {
|
|
|
+ var self = this,
|
|
|
+ filterEvent;
|
|
|
+
|
|
|
+ $(window).resize(function(event){
|
|
|
+ return self.onResize(event);
|
|
|
+ });
|
|
|
+
|
|
|
+ $(window).bind('keydown', function(event) {
|
|
|
+ return self.onKeydown(event);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$el.mousedown(function() {
|
|
|
+ setTimeout(function(){
|
|
|
+ this.focus();
|
|
|
+ }, 1);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$el.bind('mousewheel DOMMouseScroll', function(event) {
|
|
|
+ event.preventDefault();
|
|
|
+ return self.onMouseWheel(event);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$el.click(function(event) {
|
|
|
+ self.processListClick(event);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$scrollbar.bind('mousedown', function(event) {
|
|
|
+ self.onScrollbarStart(event);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$scrollbarBackground.mousedown(function(event) {
|
|
|
+ self.scrollingActive = true;
|
|
|
+ self.scrollDir = undefined;
|
|
|
+ self.onScrollbarBackgroundClick(event);
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$scrollbarBackground.mouseleave(function(event) {
|
|
|
+ self.scrollingActive = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$scrollbarBackground.mouseup(function(event) {
|
|
|
+ self.scrollingActive = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ this.$moveall.click(function(event) {
|
|
|
+ self.onMoveAll(event);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (Modernizr.hasEvent('input', this.$search)) {
|
|
|
+ filterEvent = 'input';
|
|
|
+ } else {
|
|
|
+ filterEvent = 'keyup';
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$search.on(filterEvent, function() {
|
|
|
+ self.yPosition = 0;
|
|
|
+ self.filterList();
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Extracts the supplied data for megalistSide from data-provider-src or
|
|
|
+ * data-provider-dst attributes depending which side is being loaded.
|
|
|
+ * The attributes must be set on megalist container.
|
|
|
+ */
|
|
|
+ bindData: function() {
|
|
|
+ this.origData = this.$parent.attr('data-provider-' + this.suffix);
|
|
|
+ if (this.origData.length){
|
|
|
+ this.dataProviderOrig = this.parseData(this.origData);
|
|
|
+ this.$parent.attr('data-provider-' + this.suffix, '');
|
|
|
+ } else {
|
|
|
+ this.dataProviderOrig = {};
|
|
|
+ }
|
|
|
+
|
|
|
+ this.dataProvider = this.dataProviderOrig;
|
|
|
+
|
|
|
+ this.clearSelectedIndex();
|
|
|
+
|
|
|
+ this.$ul.find('li').each(function() {
|
|
|
+ $(this).remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ this.yPosition = 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parses the data extracted from container attribues. Currently two
|
|
|
+ * formats are supported: JSON and passing old <select> element that
|
|
|
+ * is being replaced by this widget
|
|
|
+ *
|
|
|
+ * @param {string} origData - string extracted from attribute
|
|
|
+ * (JSON or old select html)
|
|
|
+ * @return {Array} parsed - parsed data array
|
|
|
+ */
|
|
|
+ parseData: function(origData){
|
|
|
+ var parsed = [], item = {};
|
|
|
+ var selected = ':not(:selected)';
|
|
|
+
|
|
|
+ //first see if it's JSON
|
|
|
+ try {
|
|
|
+ parsed = $.parseJSON(origData);
|
|
|
+ } catch(e) {
|
|
|
+ //not JSON
|
|
|
+ }
|
|
|
+ //ok, maybe it's being fed <option>s from an old select?
|
|
|
+ if (origData.substr(0, 7) == '<select'){
|
|
|
+ if (this.suffix === this.conf.DESTINATION_SUFFIX) {
|
|
|
+ selected = ':selected';
|
|
|
+ }
|
|
|
+ $.map($('option', origData).filter(selected), function(opt){
|
|
|
+ item.listValue = opt.value;
|
|
|
+ item.label = opt.text;
|
|
|
+ parsed.push(item);
|
|
|
+ item = {};
|
|
|
+ });
|
|
|
+ } else if ((origData.indexOf('<select') > -1)){
|
|
|
+ console.log('ERROR: the supplied string MUST start with <select');
|
|
|
+ }
|
|
|
+
|
|
|
+ return parsed;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Updates responsive mutliselect on window resize by recalculating new
|
|
|
+ * sizing and redrawing megalistSide widgets. Updating has some inertia
|
|
|
+ * added resizing only after RESIZE_TIMEOUT_DELAY is reached
|
|
|
+ */
|
|
|
+ onResize: function() {
|
|
|
+ clearTimeout(this.reizeTimeout);
|
|
|
+ var self = this,
|
|
|
+ totalHeight = this.dataProvider.length * this.itemHeight,
|
|
|
+ maxPosition = totalHeight - this.$el.height();
|
|
|
+
|
|
|
+ maxPosition = Math.max(0, maxPosition);
|
|
|
+ this.yPosition = Math.min(this.yPosition, maxPosition);
|
|
|
+ this.reizeTimeout = setTimeout(function() {
|
|
|
+ self.updateLayout();
|
|
|
+ }, this.conf.RESIZE_TIMEOUT_DELAY);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @TODO - @FIXME
|
|
|
+ * @param {event} event - user key press event
|
|
|
+ */
|
|
|
+ onKeydown: function (event) {
|
|
|
+ var delta = 0,
|
|
|
+ action = this.conf.MOVE_ACTION_NAME,
|
|
|
+ self = this,
|
|
|
+ oldindex = this.getSelectedIndex(),
|
|
|
+ index = oldindex + delta;
|
|
|
+
|
|
|
+ if (!this.$el.is(':focus')) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (event.which) {
|
|
|
+ case 33: // Page up
|
|
|
+ delta = -1 * Math.floor(this.$el.height() / this.itemHeight);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 34: // Page down
|
|
|
+ delta = Math.floor(this.$el.height() / this.itemHeight);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 38: // Up
|
|
|
+ delta = -1;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 40: // Down
|
|
|
+ delta = 1;
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (index > this.dataProvider.length -1) {
|
|
|
+ index = this.dataProvider.length;
|
|
|
+ }
|
|
|
+ if (index < 0) {
|
|
|
+ index = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (index === oldindex) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.setSelectedIndex(index);
|
|
|
+
|
|
|
+ if (this.yPosition > (index * this.itemHeight)) {
|
|
|
+ this.yPosition = (index*this.itemHeight);
|
|
|
+ }
|
|
|
+ if (this.yPosition < ((index+1) * this.itemHeight) - this.$el.height()) {
|
|
|
+ this.yPosition = ((index+1)*this.itemHeight) - this.$el.height();
|
|
|
+ }
|
|
|
+
|
|
|
+ this.updateLayout();
|
|
|
+ this.cleanupTimeout = setTimeout(function() {
|
|
|
+ self.cleanupListItems();
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ var target = this.$ul.find('.megalistSelected');
|
|
|
+
|
|
|
+ setTimeout(function() {
|
|
|
+ var event = $.Event(action, data),
|
|
|
+ data = {
|
|
|
+ selectedIndex: index,
|
|
|
+ srcElement: $(target),
|
|
|
+ item: self.dataProvider[index],
|
|
|
+ destination: self.$el.attr('id')
|
|
|
+ };
|
|
|
+ self.$el.trigger(event);
|
|
|
+ }, 150);
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Updates megalistSide widget on mouse scroll event
|
|
|
+ * only concerned about vertical scroll
|
|
|
+ *
|
|
|
+ * @param {event} event - mouse wheel event
|
|
|
+ */
|
|
|
+ onMouseWheel: function (event) {
|
|
|
+ clearTimeout(this.cleanupTimeout);
|
|
|
+
|
|
|
+ var self = this,
|
|
|
+ orgEvent = event.originalEvent,
|
|
|
+ delta = 0,
|
|
|
+ totalHeight = this.dataProvider.length * this.itemHeight,
|
|
|
+ maxPosition = totalHeight - this.$el.height();
|
|
|
+
|
|
|
+ // Old school scrollwheel delta
|
|
|
+ if (orgEvent.wheelDelta) {
|
|
|
+ delta = orgEvent.wheelDelta / 120;
|
|
|
+ }
|
|
|
+ if (orgEvent.detail) {
|
|
|
+ delta = -orgEvent.detail / 3;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Webkit
|
|
|
+ if ( orgEvent.wheelDeltaY !== undefined ) {
|
|
|
+ delta = orgEvent.wheelDeltaY / 120;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.yPosition -= delta * this.itemHeight;
|
|
|
+
|
|
|
+ //limit the mouse wheel scroll area
|
|
|
+ if (this.yPosition > maxPosition) {
|
|
|
+ this.yPosition = maxPosition;
|
|
|
+ }
|
|
|
+ if (this.yPosition < 0) {
|
|
|
+ this.yPosition = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.updateLayout();
|
|
|
+ this.cleanupTimeout = setTimeout(function() {
|
|
|
+ self.cleanupListItems();
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles click event on megalist element
|
|
|
+ *
|
|
|
+ * @param {event} event - mouse wheel event
|
|
|
+ */
|
|
|
+ processListClick: function(event) {
|
|
|
+ var self = this,
|
|
|
+ target = event.target,
|
|
|
+ index = $(target).attr('list-index'),
|
|
|
+ out_data = this.dataProvider[index],
|
|
|
+ clicked_value = this.dataProvider[index];
|
|
|
+
|
|
|
+ while (target.parentNode !== null) {
|
|
|
+ if (target.nodeName === 'LI') {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ target = target.parentNode;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (target.nodeName !== 'LI') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (index === this.selectedIndex) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.setSelectedIndex(index);
|
|
|
+
|
|
|
+ this.getTargetList().updateDataProvider(out_data);
|
|
|
+
|
|
|
+ self.clearSelectedIndex();
|
|
|
+
|
|
|
+ self.dataProviderOrig.splice(
|
|
|
+ self.dataProviderOrig.indexOf(clicked_value), 1
|
|
|
+ );
|
|
|
+
|
|
|
+ if (this.yPosition > this.getMaxPosition()) {
|
|
|
+ this.yPosition -= this.itemHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ self.filterList();
|
|
|
+ this.$parent.destinationList.generatePOST(this.conf.BUILD_FULL_POST);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles click on "move all" button, move all items from one
|
|
|
+ * megalistSide to the other, renders POST result into hidden input field
|
|
|
+ * after the action is performed
|
|
|
+ *
|
|
|
+ */
|
|
|
+ onMoveAll: function(){
|
|
|
+ var out_data = this.dataProvider,
|
|
|
+ i;
|
|
|
+
|
|
|
+ this.getTargetList().updateDataProvider(out_data);
|
|
|
+
|
|
|
+ this.clearSelectedIndex();
|
|
|
+ this.dataProvider = [];
|
|
|
+ if (this.filteredData.length > 0) {
|
|
|
+ for (i = this.filteredData.length - 1; i >= 0; i--) {
|
|
|
+ this.dataProviderOrig.splice(this.filteredData[i], 1);
|
|
|
+ }
|
|
|
+ } else if (!this.searchingIsActive()) {
|
|
|
+ this.dataProviderOrig = [];
|
|
|
+ }
|
|
|
+ this.$parent.destinationList.generatePOST(this.conf.BUILD_FULL_POST);
|
|
|
+ this.updateLayout();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles drag event on scrollbar - binds events appropriate to user
|
|
|
+ * action and delgates event to correct function
|
|
|
+ *
|
|
|
+ * @param {event} event - mouse event on scrollbar
|
|
|
+ */
|
|
|
+ onScrollbarStart: function(event) {
|
|
|
+ var self = this;
|
|
|
+
|
|
|
+ this.unbindScrollbarEvents();
|
|
|
+ this.scrollbarInputCoordinates = this.getInputCoordinates(event);
|
|
|
+
|
|
|
+ $(document).bind('mousemove', function(event) {
|
|
|
+ self.onScrollbarMove(event);
|
|
|
+ });
|
|
|
+
|
|
|
+ $(document).bind('mouseup', function() {
|
|
|
+ self.unbindScrollbarEvents();
|
|
|
+ });
|
|
|
+
|
|
|
+ event.preventDefault();
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles drag event on scroll bar and recalculates what items should be
|
|
|
+ * rendered in the viewport
|
|
|
+ *
|
|
|
+ * @param {event} event - scrollbar drag event to get coordinates from
|
|
|
+ */
|
|
|
+ onScrollbarMove: function(event) {
|
|
|
+ var newCoordinates = this.getInputCoordinates(event),
|
|
|
+ height = this.$el.height(),
|
|
|
+ totalHeight = this.dataProvider.length * this.itemHeight,
|
|
|
+ scrollbarHeight = this.$scrollbar.height(),
|
|
|
+ yDelta = this.scrollbarInputCoordinates.y - newCoordinates.y,
|
|
|
+ yPosition = parseInt(this.$scrollbar.css('top'), 10),
|
|
|
+ usingMinSize = scrollbarHeight === this.conf.SCROLLBAR_MIN_SIZE,
|
|
|
+ heightOffset = usingMinSize ? scrollbarHeight : 0,
|
|
|
+ newYPosition;
|
|
|
+
|
|
|
+ // valid move occurs only when pressing left mouse button
|
|
|
+ if (event.which !== 1) {
|
|
|
+ this.unbindScrollbarEvents();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ yPosition -= yDelta;
|
|
|
+
|
|
|
+ yPosition = Math.max(yPosition, 0);
|
|
|
+ yPosition = Math.min(yPosition, height - scrollbarHeight);
|
|
|
+ yPosition = Math.min(yPosition, height);
|
|
|
+
|
|
|
+ this.$scrollbar.css('top', yPosition);
|
|
|
+ this.scrollbarInputCoordinates = newCoordinates;
|
|
|
+
|
|
|
+ newYPosition = (
|
|
|
+ yPosition / (height - heightOffset) *
|
|
|
+ (this.itemHeight * this.dataProvider.length - 1)
|
|
|
+ );
|
|
|
+ newYPosition = Math.max(0, newYPosition);
|
|
|
+ newYPosition = Math.min(
|
|
|
+ newYPosition, totalHeight - height
|
|
|
+ );
|
|
|
+
|
|
|
+ this.yPosition = newYPosition;
|
|
|
+ this.updateLayout(true);
|
|
|
+
|
|
|
+ event.preventDefault();
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Utility function to remove events bound to the scrollbar
|
|
|
+ *
|
|
|
+ */
|
|
|
+ unbindScrollbarEvents: function() {
|
|
|
+ $(document).unbind('mousemove');
|
|
|
+ $(document).unbind('mouseup');
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Handles click event on scrollbar background - a click on scrollbar
|
|
|
+ * background should cause pageUp/PageDown action on the viewport
|
|
|
+ *
|
|
|
+ * @param {event} event - scrollbar click event to get coordinates from
|
|
|
+ */
|
|
|
+ onScrollbarBackgroundClick: function(event, repeatTimeout) {
|
|
|
+ var self = this,
|
|
|
+ // firefox uses originalEvent.layerY instead of offsetY
|
|
|
+ yOffset = event.offsetY !== undefined ? event.offsetY : event.originalEvent.layerY,
|
|
|
+ scrollbarBackgroundHeight = $(event.target).height(),
|
|
|
+ clickPos = yOffset / scrollbarBackgroundHeight,
|
|
|
+ listTotalHeight = this.dataProvider.length * this.itemHeight,
|
|
|
+ scrollbarHeightFraction = this.$scrollbar.height() / scrollbarBackgroundHeight,
|
|
|
+ currentPos = this.yPosition / listTotalHeight,
|
|
|
+ offsetToMove = this.pageHeight,
|
|
|
+ shouldMoveUp = clickPos > currentPos + scrollbarHeightFraction,
|
|
|
+ shouldMoveDown = clickPos < currentPos;
|
|
|
+
|
|
|
+ if (!this.scrollingActive) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.scrollDir == undefined) {
|
|
|
+ if (shouldMoveUp) {
|
|
|
+ this.scrollDir = 1;
|
|
|
+ } else if (shouldMoveDown) {
|
|
|
+ this.scrollDir = -1;
|
|
|
+ } else {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shouldMoveUp && this.scrollDir === 1) {
|
|
|
+ this.yPosition += offsetToMove;
|
|
|
+ } else if (shouldMoveDown && this.scrollDir === -1) {
|
|
|
+ this.yPosition -= offsetToMove;
|
|
|
+ } else {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.yPosition > listTotalHeight - this.pageHeight) {
|
|
|
+ this.yPosition = listTotalHeight - this.pageHeight;
|
|
|
+ } else if (this.yPosition < 0) {
|
|
|
+ this.yPosition = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.updateLayout();
|
|
|
+
|
|
|
+ if (this.scrollingActive) {
|
|
|
+ if (repeatTimeout === undefined) {
|
|
|
+ repeatTimeout = this.conf.CONTINOUS_SCROLLING_FIRST_INTERVAL;
|
|
|
+ }
|
|
|
+ setTimeout(function() {
|
|
|
+ self.onScrollbarBackgroundClick(
|
|
|
+ event, self.conf.CONTINOUS_SCROLLING_INTERVAL
|
|
|
+ );
|
|
|
+ }, repeatTimeout);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Removes items rendered in megalist that no longer fit into the viewport
|
|
|
+ * and removes them from processed items cache
|
|
|
+ */
|
|
|
+ cleanupListItems: function() {
|
|
|
+ //remove any remaining LI elements hanging out on the dom
|
|
|
+ var temp = [],
|
|
|
+ item, index, x;
|
|
|
+
|
|
|
+ for (x = 0; x < this.totalItems.length; x++ ) {
|
|
|
+ item = this.totalItems[x];
|
|
|
+ index = item.attr('list-index');
|
|
|
+ if (this.processedItems[index] === undefined) {
|
|
|
+ item.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //cleanup processedItems array
|
|
|
+ if (this.processedItems) {
|
|
|
+ for (index in this.processedItems) {
|
|
|
+ temp.push(this.processedItems[index]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.totalItems = temp;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Extracts input coordinates from the event
|
|
|
+ *
|
|
|
+ * @param {event} event - event to get coordinates from
|
|
|
+ * @return {object} result - object containge x and y coordinates
|
|
|
+ */
|
|
|
+ getInputCoordinates: function (event) {
|
|
|
+ var targetEvent = event,
|
|
|
+ result = {
|
|
|
+ x: Math.round(targetEvent.pageX),
|
|
|
+ y: Math.round(targetEvent.pageY)
|
|
|
+ };
|
|
|
+ return result;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Main rendering function for megalist: redraws the list based on data
|
|
|
+ * fed to megalist and scrollbar position. Iterates over visible items
|
|
|
+ * and renders them then calls update on the scrollbar if ignoreScrollbar
|
|
|
+ * not set to true
|
|
|
+ *
|
|
|
+ * @param {boolean} ignoreScrollbar - a flag allowing scrollbar to not be
|
|
|
+ * redrawn if not necessary
|
|
|
+ */
|
|
|
+ updateLayout: function(ignoreScrollbar) {
|
|
|
+ var height = this.$el.height(),
|
|
|
+ i = -1,
|
|
|
+ startPosition = Math.ceil(this.yPosition / this.itemHeight),
|
|
|
+ maxHeight = 2 * (height + (2 * this.itemHeight)),
|
|
|
+ index, item, currentPosition, parentLength;
|
|
|
+
|
|
|
+ if (this.dataProvider.length > 0) {
|
|
|
+ this.$ul.detach();
|
|
|
+ this.processedItems = {};
|
|
|
+
|
|
|
+ while (i * this.itemHeight < maxHeight) {
|
|
|
+ index = Math.min(
|
|
|
+ Math.max(startPosition + i, 0),
|
|
|
+ this.dataProvider.length
|
|
|
+ );
|
|
|
+
|
|
|
+ item = this.getItemAtIndex(index);
|
|
|
+ this.totalItems.push(item);
|
|
|
+
|
|
|
+ this.processedItems[index.toString()] = item;
|
|
|
+ currentPosition = i * this.itemHeight;
|
|
|
+ this.setItemPosition(item, 0, currentPosition);
|
|
|
+
|
|
|
+ if (item.parent().length <= 0) {
|
|
|
+ this.$ul.append(item);
|
|
|
+
|
|
|
+ if (this.itemHeight <= 0) {
|
|
|
+ this.prepareLayout(item);
|
|
|
+ this.updateLayout();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ i++;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.cleanupListItems();
|
|
|
+ if (ignoreScrollbar !== true) {
|
|
|
+ this.updateScrollBar();
|
|
|
+ }
|
|
|
+ if (this.$scrollbar.parent().length > 0){
|
|
|
+ this.$scrollbar.before(this.$ul);
|
|
|
+ } else {
|
|
|
+ this.$el.append(this.$ul);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (this.$ul.children().length > 0) {
|
|
|
+ this.$ul.empty();
|
|
|
+ this.cleanupListItems();
|
|
|
+ parentLength = this.$scrollbar.parent().length > 0;
|
|
|
+ if (ignoreScrollbar !== true && parentLength > 0) {
|
|
|
+ this.updateScrollBar();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ this.hideScrollbar();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Prepares layout by appending list to DOM, and calculating site of
|
|
|
+ * element and size of single page
|
|
|
+ *
|
|
|
+ * @param {object} item jQuery object representing single item
|
|
|
+ */
|
|
|
+ prepareLayout: function(item) {
|
|
|
+ var itemsPerPage;
|
|
|
+
|
|
|
+ // make sure item have proper height by filling it with content
|
|
|
+ item.html('&nsbp;');
|
|
|
+ this.$el.append(this.$ul);
|
|
|
+
|
|
|
+ // calculate height of item and height of single page
|
|
|
+ this.itemHeight = item.outerHeight();
|
|
|
+ itemsPerPage = Math.floor(
|
|
|
+ this.$ul.parent().height() / this.itemHeight
|
|
|
+ );
|
|
|
+ this.pageHeight = this.itemHeight * itemsPerPage;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Shows scrollbar
|
|
|
+ */
|
|
|
+ showScrollbar: function() {
|
|
|
+ this.$el.append(this.$scrollbar, this.$scrollbarBackground);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Hides scrollbar
|
|
|
+ */
|
|
|
+ hideScrollbar: function() {
|
|
|
+ this.$scrollbar.detach();
|
|
|
+ this.$scrollbarBackground.detach();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Renders the scrollbar as a part of UI update when list is scrolled or
|
|
|
+ * modified
|
|
|
+ */
|
|
|
+ updateScrollBar: function() {
|
|
|
+ var height = this.$el.height(),
|
|
|
+ maxScrollbarHeight = height,
|
|
|
+ maxItemsHeight = this.dataProvider.length * this.itemHeight,
|
|
|
+ targetHeight = maxScrollbarHeight * Math.min(
|
|
|
+ maxScrollbarHeight / maxItemsHeight, 1
|
|
|
+ ),
|
|
|
+ actualHeight = Math.floor(
|
|
|
+ Math.max(targetHeight, this.conf.SCROLLBAR_MIN_SIZE)
|
|
|
+ ),
|
|
|
+ scrollPosition = (
|
|
|
+ this.yPosition / (maxItemsHeight - height) *
|
|
|
+ (maxScrollbarHeight - actualHeight)
|
|
|
+ ),
|
|
|
+ parent = this.$scrollbar.parent();
|
|
|
+
|
|
|
+ if (scrollPosition < 0) {
|
|
|
+ actualHeight = Math.max(actualHeight + scrollPosition, 0);
|
|
|
+ scrollPosition = 0;
|
|
|
+ } else if (scrollPosition > (height - actualHeight)) {
|
|
|
+ actualHeight = Math.min(actualHeight, height - scrollPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ this.$scrollbar.height(actualHeight);
|
|
|
+
|
|
|
+ if ((this.dataProvider.length * this.itemHeight) <= height) {
|
|
|
+ if (parent.length > 0) {
|
|
|
+ this.hideScrollbar();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (parent.length <= 0) {
|
|
|
+ this.showScrollbar();
|
|
|
+ }
|
|
|
+ this.$scrollbar.css('top', scrollPosition);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Utility function to set offset css on an item
|
|
|
+ *
|
|
|
+ * @param {object} item - megalist element
|
|
|
+ * @param {int} x - x offset in pixels
|
|
|
+ * @param {int} y - y offset in pixels
|
|
|
+ */
|
|
|
+ setItemPosition: function(item, x, y) {
|
|
|
+ item.css('left', x);
|
|
|
+ item.css('top', y);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Gets megalist item at given index. Parses it to <li> item if necessary
|
|
|
+ *
|
|
|
+ * @param {int} i - object index
|
|
|
+ * @return {object} - jQuery object containing selected <li> element
|
|
|
+ */
|
|
|
+ getItemAtIndex: function(i) {
|
|
|
+ var item, iString, data;
|
|
|
+ if (this.dataProvider === this.listItems) {
|
|
|
+ item = $(this.listItems[i]);
|
|
|
+ }
|
|
|
+ else if (i !== undefined){
|
|
|
+ iString = i.toString();
|
|
|
+
|
|
|
+ if (this.listItems[iString] === null ||
|
|
|
+ this.listItems[iString] === undefined
|
|
|
+ ) {
|
|
|
+ item = $('<li />');
|
|
|
+ this.listItems[iString] = item;
|
|
|
+ } else {
|
|
|
+ item = $(this.listItems[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (i >= 0 && i < this.dataProvider.length){
|
|
|
+ data = this.dataProvider[i];
|
|
|
+ item.html(data.label);
|
|
|
+ item.attr('list-value', data.listValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (item !== null && item !== undefined) {
|
|
|
+ item.attr('list-index', i);
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns index of currently selected item
|
|
|
+ *
|
|
|
+ * @return {int} - index of item that was selected
|
|
|
+ */
|
|
|
+ getSelectedIndex: function() {
|
|
|
+ return parseInt(this.selectedIndex, 10);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets item at given index as selected and adds appropriate styling to it
|
|
|
+ *
|
|
|
+ * @param {int} index = index of item that was selected
|
|
|
+ */
|
|
|
+ setSelectedIndex: function(index) {
|
|
|
+ var item = this.getItemAtIndex(this.selectedIndex);
|
|
|
+
|
|
|
+ if (item !== undefined) {
|
|
|
+ item.removeClass('megalistSelected');
|
|
|
+ }
|
|
|
+
|
|
|
+ this.selectedIndex = index;
|
|
|
+ this.getItemAtIndex(index).addClass('megalistSelected');
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Clears currently selected object by removing styling and setting
|
|
|
+ * internal variable pointing to currently selected item to -1
|
|
|
+ *
|
|
|
+ */
|
|
|
+ clearSelectedIndex: function() {
|
|
|
+ var item = this.getItemAtIndex(this.selectedIndex);
|
|
|
+
|
|
|
+ if (item !== undefined) {
|
|
|
+ item.removeClass('megalistSelected');
|
|
|
+ }
|
|
|
+ this.selectedIndex = -1;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Sets initial data for megalist and updates layout with it
|
|
|
+ *
|
|
|
+ * @param {Array} dataProvider - object array to initially feed megalist
|
|
|
+ */
|
|
|
+ setDataProvider: function(dataProvider) {
|
|
|
+ this.clearSelectedIndex();
|
|
|
+ this.dataProviderOrig = dataProvider;
|
|
|
+ this.dataProvider = dataProvider;
|
|
|
+
|
|
|
+ this.$ul.find('li').each(function() {
|
|
|
+ $(this).remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ this.yPosition = 0;
|
|
|
+ this.updateLayout();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Updates megalist with new data. Accepts either a single object or
|
|
|
+ * an Array of objects and updates layout with new data
|
|
|
+ *
|
|
|
+ * @param {object|Array} newElement - new object / array of objects
|
|
|
+ * to be inserted into the list
|
|
|
+ */
|
|
|
+ updateDataProvider: function(newElement) {
|
|
|
+ this.clearSelectedIndex();
|
|
|
+
|
|
|
+ if ($.isArray(newElement)) {
|
|
|
+ $.merge(this.dataProviderOrig, newElement);
|
|
|
+ } else {
|
|
|
+ this.dataProviderOrig.push(newElement);
|
|
|
+ }
|
|
|
+ this.filterList();
|
|
|
+
|
|
|
+ this.$ul.find('li').each(function() {
|
|
|
+ $(this).remove();
|
|
|
+ });
|
|
|
+
|
|
|
+ this.yPosition = this.getMaxPosition();
|
|
|
+ this.updateLayout();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns current objects in megalist
|
|
|
+ *
|
|
|
+ * @return {Array} - list of objects in megalist
|
|
|
+ *
|
|
|
+ */
|
|
|
+ getDataProvider: function() {
|
|
|
+ return this.dataProvider;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get maximum value of yPosition
|
|
|
+ *
|
|
|
+ * @return {int} maximum value of yPosition
|
|
|
+ */
|
|
|
+ getMaxPosition: function() {
|
|
|
+ var height = this.$el.height(),
|
|
|
+ totalHeight = this.dataProvider.length * this.itemHeight;
|
|
|
+
|
|
|
+ return totalHeight > height ? totalHeight - height : 0;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks if search input has minimal length and therefor searching is
|
|
|
+ * active.
|
|
|
+ * @return {bool} true when searching is active, false otherwise
|
|
|
+ */
|
|
|
+ searchingIsActive: function() {
|
|
|
+ var querySize = $.trim(this.$search.val()).length;
|
|
|
+ return querySize >= this.conf.MINIMUM_SEARCH_QUERY_SIZE;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Parses search input and performs filtering of list. The algorithm
|
|
|
+ * splits the search query to tokens and seeks for all subsequent
|
|
|
+ * tokens in the data. If not all tokens are found in the data then this
|
|
|
+ * record is excluded from the results.
|
|
|
+ *
|
|
|
+ */
|
|
|
+ filterList: function() {
|
|
|
+ var self = this,
|
|
|
+ searchQuery = $.trim(this.$search.val().toLowerCase()),
|
|
|
+ searchTokens = searchQuery.split(' '),
|
|
|
+ i;
|
|
|
+
|
|
|
+ this.filteredData = [];
|
|
|
+
|
|
|
+ for (i = searchTokens.length - 1; i >= 0; i--) {
|
|
|
+ searchTokens[i] = $.trim(searchTokens[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.searchingIsActive()) {
|
|
|
+ this.dataProvider = this.dataProviderOrig;
|
|
|
+
|
|
|
+ } else {
|
|
|
+ this.dataProvider = $.grep(
|
|
|
+ this.dataProviderOrig,
|
|
|
+ function(val, index) {
|
|
|
+ return self.testListElement(val, searchTokens, index);
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ this.updateLayout();
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Tests if element of list data meets the query search criterias.
|
|
|
+ *
|
|
|
+ * @param {string} val value of the element to test
|
|
|
+ * @param {array} searchTokens query search tokens
|
|
|
+ * @param {int} index the index of element in original data
|
|
|
+ *
|
|
|
+ * @return {boolean} whether element meets the criteria on not
|
|
|
+ */
|
|
|
+ testListElement: function(val, searchTokens, index) {
|
|
|
+ var tokenIndex = 0,
|
|
|
+ valI = 0,
|
|
|
+ tokenDetected = true,
|
|
|
+ i;
|
|
|
+ val = val.label.toLowerCase();
|
|
|
+ while (valI < val.length) {
|
|
|
+ if (val[valI++] === searchTokens[tokenIndex][0]) {
|
|
|
+ tokenDetected = true;
|
|
|
+ for (i = 1; i < searchTokens[tokenIndex].length; i++) {
|
|
|
+ if (val[valI] === searchTokens[tokenIndex][i]) {
|
|
|
+ valI++;
|
|
|
+ } else {
|
|
|
+ tokenDetected = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (tokenDetected && ++tokenIndex === searchTokens.length) {
|
|
|
+ this.filteredData[this.filteredData.length] = index;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generates string result of what is currently selected and populates
|
|
|
+ * this.$input value, adding it to DOM id necessary. Only does it for
|
|
|
+ * destination list. Result can be in 2 formats: POST-like (full) or comma
|
|
|
+ * separated
|
|
|
+ *
|
|
|
+ * @param {boolean} full - wherever to generate full POST-like data
|
|
|
+ * @return {string} result - string result of what is currently selected
|
|
|
+ */
|
|
|
+ generatePOST: function(full) {
|
|
|
+ var i,
|
|
|
+ postData = [],
|
|
|
+ result = {},
|
|
|
+ name = this.name;
|
|
|
+
|
|
|
+ if (this.suffix === this.conf.DESTINATION_SUFFIX){
|
|
|
+ for (i = 0; i < this.dataProviderOrig.length; i++) {
|
|
|
+ postData[i] = this.dataProviderOrig[i].listValue;
|
|
|
+ }
|
|
|
+ if (full === true){
|
|
|
+ result[name] = postData;
|
|
|
+ result = decodeURIComponent($.param(result, true ));
|
|
|
+ //cut out first name so that the post will not contain repetition
|
|
|
+ result = result.slice(this.name.length + 1, result.length);
|
|
|
+ this.$input.val(result);
|
|
|
+ } else {
|
|
|
+ result = postData.join(',');
|
|
|
+ this.$input.val(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.$el.has(this.$input).length < 1) {
|
|
|
+ this.$el.append(this.$input);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ } else {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ /* LIST PLUGIN DEFINITION
|
|
|
+ * ========================== */
|
|
|
+
|
|
|
+ $.fn.megalist = function (option, params) {
|
|
|
+ if (typeof option === 'object') { this.options = option;}
|
|
|
+ var multiselect = new Megalist(this);
|
|
|
+ if (typeof option === 'string') { this.result = multiselect[option](params); }
|
|
|
+ return this;
|
|
|
+ };
|
|
|
+
|
|
|
+ // injects svg arrow icons into dom
|
|
|
+ $(document).ready(function(){
|
|
|
+ $('body').append('<svg style="display: none" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"> <path id="arrow-left" d="M48 10.667q1.104 0 1.885 0.781t0.781 1.885-0.792 1.896l-16.771 16.771 16.771 16.771q0.792 0.792 0.792 1.896t-0.781 1.885-1.885 0.781q-1.125 0-1.896-0.771l-18.667-18.667q-0.771-0.771-0.771-1.896t0.771-1.896l18.667-18.667q0.771-0.771 1.896-0.771zM32 10.667q1.104 0 1.885 0.781t0.781 1.885-0.792 1.896l-16.771 16.771 16.771 16.771q0.792 0.792 0.792 1.896t-0.781 1.885-1.885 0.781q-1.125 0-1.896-0.771l-18.667-18.667q-0.771-0.771-0.771-1.896t0.771-1.896l18.667-18.667q0.771-0.771 1.896-0.771z"></path> <path id="arrow-right" d="M29.333 10.667q1.104 0 1.875 0.771l18.667 18.667q0.792 0.792 0.792 1.896t-0.792 1.896l-18.667 18.667q-0.771 0.771-1.875 0.771t-1.885-0.781-0.781-1.885q0-1.125 0.771-1.896l16.771-16.771-16.771-16.771q-0.771-0.771-0.771-1.896 0-1.146 0.76-1.906t1.906-0.76zM13.333 10.667q1.104 0 1.875 0.771l18.667 18.667q0.792 0.792 0.792 1.896t-0.792 1.896l-18.667 18.667q-0.771 0.771-1.875 0.771t-1.885-0.781-0.781-1.885q0-1.125 0.771-1.896l16.771-16.771-16.771-16.771q-0.771-0.771-0.771-1.896 0-1.146 0.76-1.906t1.906-0.76z"></path></svg>');});
|
|
|
+
|
|
|
+ //adds indexOf to arry prototype for ie8
|
|
|
+ if(!Array.prototype.indexOf){Array.prototype.indexOf=function(e){var t=this.length>>>0;var n=Number(arguments[1])||0;n=n<0?Math.ceil(n):Math.floor(n);if(n<0)n+=t;for(;n<t;n++){if(n in this&&this[n]===e)return n}return-1}}
|
|
|
+
|
|
|
+} (window.jQuery);
|