UNPKG

selectmenu

Version:

Simple, easily and diversity menu solution

1,411 lines (1,324 loc) 56.3 kB
/** * @summary SelectMenu * @desc Simple, easily and diversity menu solution * @file selectmenu.js * @version 2.1 * @author TerryZeng * @contact https://terryz.github.io/ * @license MIT License * * depend on: * jQuery1.x * */ ;(function($){ "use strict"; /** * Version: v3.6.1 * The MIT License: Copyright (c) 2010-2017 LiosK. * Link: https://github.com/LiosK/UUID.js */ var UUID; UUID=function(f){function a(){}a.generate=function(){var b=a._getRandomInt,c=a._hexAligner;return c(b(32),8)+"-"+c(b(16),4)+"-"+c(16384|b(12),4)+"-"+c(32768|b(14),4)+"-"+c(b(48),12)};a._getRandomInt=function(b){if(0>b||53<b)return NaN;var c=0|1073741824*Math.random();return 30<b?c+1073741824*(0|Math.random()*(1<<b-30)):c>>>30-b};a._hexAligner=function(b,c){for(var a=b.toString(16),d=c-a.length,e="0";0<d;d>>>=1,e+=e)d&1&&(a=e+a);return a};a.overwrittenUUID=f;"undefined"!==typeof module&&module&&module.exports&& (module.exports=a);return a}(UUID); /** * Default options */ var defaults = { /** * Menu data source * @type array | function * @example * array:[{a:1,b:2,c:3},{...}] * function: function(){ return [{...}];} * return data format is same to array */ data: undefined, /** * Show quick search input element, work on advance menu mode * @type boolean * @default true */ search : true, /** * Title bar text, set false to close title bar * @type string | boolean * @default 'SelectMenu' */ title : 'SelectMenu', /** * Regular menu mode * @type boolean * @default false */ regular : false, /** * Mouse right click to show menu * @type boolean * @default false */ rightClick : false, /** * Menu show arrow, look like a bubble * @type boolean * @default false */ arrow : false, /** * Alignment direction * @type string * @enum * 'left' * 'center' * 'right' * @default 'left' */ position : 'left', /** * Embedded to page * @type boolean * @default false */ embed : false, /** * Language ('cn', 'ja', 'en', 'es', 'pt-br') * @type string * @default 'cn' */ lang: 'cn', /** * Multiple select mode(tags) * @type boolean * @default false */ multiple: false, /** * Menu result list size, the number mean is visible menu item amount * @type number * @default 10 */ listSize : 10, /** * Maximum selected item limit in multiple select mode, set 0 to unlimited * @type number * @default 0 (unlimited) */ maxSelectLimit: 0, /** * Close result list after menu item selected, work on multiple select mode * @type boolean * @default true */ selectToCloseList: false, /** * Set a key to selected menu item when plugin init complete, work on multiple select mode * the key will match to keyField * @type string */ initSelected: undefined, /** * Key field to return data * @type string * @default 'id' */ keyField: 'id', /** * Show content field * @type string * @default 'name' */ showField: 'name', /** * Data field in search, not set default used showField * @type string */ searchField : undefined, /** * Filter type ('AND' or 'OR') * @type string default: 'AND' */ andOr: 'AND', /** * Sort order, not set default used showField * @type array * @example * orderBy : ['id desc']//order by id desc */ orderBy: undefined, /** * Max item size * @type number */ pageSize: 100, /** * Menu item result format * @type function * @param data {object} menu item data * @return string */ formatItem : undefined, /** * -----------------------------------------Event-------------------------------------------- */ /** * Menu item select callback * @type function * @param data {array[Object]} selected items data */ eSelect : undefined, /** * Multiple group data type tab switch callback * @type function * @param index {number} */ eTabSwitch : undefined, /** * Menu hide callback * @type function * @param data {array[Object]} selected items data */ eHidden : undefined }; /** * @constructor * @param {Object} input - menu caller * @param {Object} option - menu init option */ var SelectMenu = function(input, option) { this.target = input; this.setOption(option); if(this.option.embed && !$(input).is('div')){ console.warn('SelectMenu embed mode need a "div" container element!'); return; } this.setLanguage(); this.setCssClass(); this.setProp(); if(option.regular) this.setRegularMenu(); else this.setElem(); if(!option.rightClick) this.populate(); this.eInput(); if(!option.embed) this.eWhole(); this.atLast(); }; /** * Plugin version number */ SelectMenu.version = '2.1'; /** * Plugin object cache key */ SelectMenu.dataKey = 'selectMenuObject'; /** * Data source type * List type */ SelectMenu.dataTypeList = 'SelectMenuList'; /** * Group type */ SelectMenu.dataTypeGroup = 'SelectMenuGroup'; /** * Regular menu type */ SelectMenu.dataTypeMenu = 'SelectMenuMenu'; /** * Initial plugin option * @param {Object} option */ SelectMenu.prototype.setOption = function(option) { //if not set, default used showField set field option.searchField = option.searchField || option.showField; if(option.regular && option.title === defaults.title) option.title = false; //Close arrow in embed and mouse right click mode if(option.embed || option.rightClick) option.arrow = false; option.andOr = option.andOr.toUpperCase(); if(option.andOr!=='AND' && option.andOr!=='OR') option.andOr = 'AND'; option.orderBy = (option.orderBy === undefined) ? option.showField : option.orderBy; //Multiple field sort //Example: [ ['id', 'ASC'], ['name', 'DESC'] ] option.orderBy = this.setOrderbyOption(option.orderBy, option.showField); if($.type(option.data) === 'string'){ option.autoSelectFirst = false; } if($.type(option.listSize) !== 'number' || option.listSize < 0) option.listSize = 12; this.option = option; }; /** * Initial order * @param {Array} arg_order * @param {string} arg_field * @return {Array} */ SelectMenu.prototype.setOrderbyOption = function(arg_order, arg_field) { var arr = [],orders = []; if (typeof arg_order == 'object') { for (var i = 0; i < arg_order.length; i++) { orders = $.trim(arg_order[i]).split(' '); arr[i] = (orders.length == 2) ? orders: [orders[0], 'ASC']; } } else { orders = $.trim(arg_order).split(' '); arr[0] = (orders.length == 2) ? orders: (orders[0].match(/^(ASC|DESC)$/i)) ? [arg_field, orders[0]] : [orders[0], 'ASC']; } return arr; }; /** * i18n */ SelectMenu.prototype.setLanguage = function() { var message; switch (this.option.lang) { // 中文 case 'cn': message = { select_all_btn: '选择所有 (或当前页签) 项目', remove_all_btn: '清除所有选中的项目', close_btn: '关闭菜单 (Esc键)', loading: '读取中...', select_ng: '请注意:请从列表中选择.', select_ok: 'OK : 已经选择.', not_found: '无查询结果', ajax_error: '连接到服务器时发生错误!', max_selected: '最多只能选择 max_selected_limit 个项目' }; break; // English case 'en': message = { select_all_btn: 'Select All (Tabs) items', remove_all_btn: 'Clear all selected items', close_btn: 'Close Menu (Esc key)', loading: 'loading...', select_ng: 'Attention : Please choose from among the list.', select_ok: 'OK : Correctly selected.', not_found: 'not found', ajax_error: 'An error occurred while connecting to server.', max_selected: 'You can only select up to max_selected_limit items' }; break; // Japanese case 'ja': message = { select_all_btn: 'すべての (または現在のタブ) 項目を選択', remove_all_btn: '選択したすべての項目をクリアする', close_btn: '閉じる (Tabキー)', loading: '読み込み中...', select_ng: '注意 : リストの中から選択してください', select_ok: 'OK : 正しく選択されました。', not_found: '(0 件)', ajax_error: 'サーバとの通信でエラーが発生しました。', max_selected: '最多で max_selected_limit のプロジェクトを選ぶことしかできません' }; break; // German case 'de': message = { select_all_btn: 'Wählen Sie alle (oder aktuellen Registerkarten) aus', remove_all_btn: 'Alle ausgewählten Elemente löschen', close_btn: 'Schließen (Tab)', loading: 'lade...', select_ng: 'Achtung: Bitte wählen Sie aus der Liste aus.', select_ok: 'OK : Richtig ausgewählt.', not_found: 'nicht gefunden', ajax_error: 'Bei der Verbindung zum Server ist ein Fehler aufgetreten.', max_selected: 'Sie können nur bis zu max_selected_limit Elemente auswählen' }; break; // Spanish case 'es': message = { select_all_btn: 'Seleccionar todos los elementos (o la pestaña actual)', remove_all_btn: 'Borrar todos los elementos seleccionados', close_btn: 'Cerrar (tecla TAB)', loading: 'Cargando...', select_ng: 'Atencion: Elija una opcion de la lista.', select_ok: 'OK: Correctamente seleccionado.', not_found: 'no encuentre', ajax_error: 'Un error ocurrió mientras conectando al servidor.', max_selected: 'Solo puedes seleccionar hasta max_selected_limit elementos' }; break; // Brazilian Portuguese case 'pt-br': message = { select_all_btn: 'Selecione todos os itens (ou guia atual)', remove_all_btn: 'Limpe todos os itens selecionados', close_btn: 'Fechar (tecla TAB)', loading: 'Carregando...', select_ng: 'Atenção: Escolha uma opção da lista.', select_ok: 'OK: Selecionado Corretamente.', not_found: 'não encontrado', ajax_error: 'Um erro aconteceu enquanto conectando a servidor.', max_selected: 'Você só pode selecionar até max_selected_limit itens' }; break; } this.message = message; }; /** * CSS classname set */ SelectMenu.prototype.setCssClass = function() { var css_class = { target_clicked : 'sm_target_clicked', container: 'sm_container', container_open: 'sm_container_open', container_embed: 'sm_embed', header: 'sm_header', re_area: 'sm_result_area', re_tabs: 'sm_result_tabs', re_list: 'sm_list_mode', control_box: 'sm_control_box', two_btn: 'sm_two_btn', element_box: 'sm_element_box', results: 'sm_results', re_off: 'sm_results_off', select: 'sm_over', selected_icon: 'sm_selected_icon', item_text: 'sm_item_text', select_ok: 'sm_select_ok', select_ng: 'sm_select_ng', selected: 'sm_selected', input_off: 'sm_input_off', message_box: 'sm_message_box', btn_close: 'sm_close_button', btn_selectall: 'sm_selectall_button', btn_removeall: 'sm_removeall_button', btn_on: 'sm_btn_on', btn_out: 'sm_btn_out', btn_back: 'sm_sub_back', input: 'sm_input', input_area: 'sm_input_area', clear_btn: 'sm_clear_btn', menu_root: 'sm_menu_root', menu_divider: 'sm_divider', menu_regular: 'sm_regular', menu_arrow: 'sm_arrow', menu_arrow_have_title : 'sm_have_title', menu_disabled: 'sm_disabled', menu_header: 'sm_header', menu_caret: 'sm_caret', menu_sub_menu: 'sm_sub_menu', menu_sub_item: 'sm_sub_item', menu_sub_header: 'sm_sub_header', direction_top : 'sm_arrow_top', direction_bottom : 'sm_arrow_bottom' }; this.css_class = css_class; this.template = { msg :{ maxSelectLimit: 'max_selected_limit' } }; }; /** * Internal variable initial */ SelectMenu.prototype.setProp = function() { this.prop = { //selected menu item keys values : [], data : undefined, //multiple group data current data index data_index : 0, key_select: false, prev_value: '', selected_text : '', last_input_time: undefined, //menu data type data_type : SelectMenu.dataTypeList, //id prefix menu_tab_id_prefix : 'selectmenu_tab_', menu_code_prefix: 'selectmenu_', //mouse x point x : undefined, //mouse y point y : undefined }; }; /** * Data source type check */ SelectMenu.prototype.checkDataType = function(d){ var self = this,p = this.option; if(d && $.isArray(d) && d.length){ if(p.regular) return SelectMenu.dataTypeMenu; else{ var row = d[0]; if(row.hasOwnProperty('title') && row.hasOwnProperty('list') && $.isArray(row.list)){ return SelectMenu.dataTypeGroup; }else return SelectMenu.dataTypeList; } }else return null; }; /** * Menu structure build */ SelectMenu.prototype.setElem = function() { var self = this, p = this.option, css = this.css_class; // 1. build dom element var elem = {}; elem.container = p.embed ? $(self.target).addClass(css.container_embed) : $('<div>'); elem.container.addClass(css.container).addClass(css.direction_bottom); if(p.title){ elem.header = $('<div>').addClass(css.header); elem.header.append('<h3>' + p.title + '</h3>'); if(p.multiple){ elem.selectAllButton = $('<button type="button"><i class="iconfont icon-selectall"></i></button>') .attr('title',this.message.select_all_btn) .addClass(css.btn_selectall); elem.removeAllButton = $('<button type="button"><i class="iconfont icon-removeall"></i></button>') .attr('title',this.message.remove_all_btn) .addClass(css.btn_removeall); elem.header.append(elem.selectAllButton); elem.header.append(elem.removeAllButton); } if(!p.embed){ elem.closeButton = $('<button type="button">×</button>') .attr('title',self.message.close_btn) .addClass(css.btn_close); elem.header.append(elem.closeButton); } } elem.inputArea = $('<div>').addClass(css.input_area); elem.input = $('<input type="text" autocomplete="off">').addClass(css.input); //Result list elem.resultArea = $('<div>').addClass(css.re_area); elem.resultTabs = $('<div>').addClass(css.re_tabs); elem.results = $('<ul>').addClass(css.results); elem.selectedIcon = $('<i class="iconfont icon-selected">'); // 2. DOM element put if(p.arrow){ elem.arrow = $('<div>').addClass(css.menu_arrow); if(p.title) elem.arrow.addClass(css.menu_arrow_have_title); elem.container.append(elem.arrow); } if(p.title) elem.container.append(elem.header); if(p.search){ elem.container.append(elem.inputArea); elem.inputArea.append(elem.input); } elem.container.append(elem.resultTabs).append(elem.resultArea); elem.resultArea.append(elem.results); if(!p.embed) $(document.body).append(elem.container); this.elem = elem; }; /** * Initial regular menu frame */ SelectMenu.prototype.setRegularMenu = function(){ var p = this.option, self = this, css = this.css_class; var elem = {}; elem.container = p.embed ? $(self.target).addClass(css.container_embed) : $('<div>'); elem.container.addClass(css.container) .addClass(css.direction_bottom) .addClass(css.menu_regular); if(p.title){ elem.header = $('<div>').addClass(css.header); elem.header.append('<h3>' + p.title + '</h3>'); if(!p.embed) elem.closeButton = $('<button type="button">×</button>') .attr('title',self.message.close_btn) .addClass(css.btn_close); } elem.resultArea = $('<div>').addClass(css.re_area); elem.results = $('<ul>').addClass(css.results).addClass(css.menu_root); if(p.arrow){ elem.arrow = $('<div>').addClass(css.menu_arrow); if(p.title) elem.arrow.addClass(css.menu_arrow_have_title); elem.container.append(elem.arrow); } if(p.title){ elem.container.append(elem.header); if(!p.embed) elem.header.append(elem.closeButton); } elem.container.append(elem.resultArea); elem.resultArea.append(elem.results); if(!p.embed) $(document.body).append(elem.container); this.elem = elem; }; /** * Regular menu item render */ SelectMenu.prototype.regularMenuInit = function(){ var d = this.prop.data, p = this.option, self = this, css = this.css_class, el = self.elem; var showMenu = function(){ if(!p.embed){ this.calcResultsSize(this); el.container.addClass(css.container_open); } }; if(el.results.find('li').size() && !$.isFunction(p.data)){ showMenu.call(self); return; } if(d && $.isArray(d) && d.length){ var buildMenu = function(menudata, ul){ if(ul.hasClass(css.menu_root)) ul.empty().hide(); $.each(menudata,function(i,row){ if(!row.content || (!row.header && !row.url && !row.callback && !row.menus && row.content !== css.menu_divider)) return true; var li = $('<li>'); if(row.content === css.menu_divider){ li.addClass(css.menu_divider); }else{ var a = $('<a>').html(row.content).attr('href', (row.url && !row.disabled)?row.url:'javascript:void(0);'); if(row.callback && $.isFunction(row.callback) && !row.url){ a.on('click.selectMenu',function(e){ e.stopPropagation(); if(row.disabled) return; row.callback(); self.hideResults(self); }); } //build sub menus if(row.menus && $.isArray(row.menus) && row.menus.length){ var itemCode = self.prop.menu_code_prefix + UUID.generate(); a.attr({ 'href': 'javascript:void(0);', 'item_code': itemCode }).append($('<span>').addClass(css.menu_caret)).addClass(css.menu_sub_item); var subMenu = $('<ul>').attr('id', itemCode).addClass(css.results).addClass(css.menu_sub_menu); //build sub menu header bar var backBtn = $('<button type="button">').addClass(css.btn_back).append('<i class="iconfont icon-back"></i>'); var header = $('<li>').append(backBtn).append($('<p>').text(row.content)).addClass(css.menu_sub_header); subMenu.append(header).append($('<li>').addClass(css.menu_divider)); el.resultArea.append(subMenu); buildMenu(row.menus, subMenu); } li.prepend(a); if(row.disabled) li.addClass(css.menu_disabled); if(row.header) li.addClass(css.menu_header); } ul.append(li); }); if(!ul.hasClass(css.menu_sub_menu)) ul.show(); }; el.resultArea.find('ul.'+css.results+':not(.'+css.menu_root+')').remove(); buildMenu(d, el.results); //sub menus event bind el.resultArea.find('a.'+css.menu_sub_item).off('click.SelectMenu').on('click.SelectMenu', function(e){ e.preventDefault(); e.stopPropagation(); var $this = $(this), $menu = $this.closest('ul.'+css.results), $subMenu = $('#'+$this.attr('item_code')); if($subMenu.size()){ $menu.hide(); /* $subMenu.css({ marginLeft: 60 }).show().animate({ marginLeft: 0 },100); */ $subMenu.addClass('vivify fadeInRight').show(); } }); //back button el.resultArea.find('button.'+css.btn_back).off('click.SelectMenu').on('click.SelectMenu', function(e){ var $btn = $(this), $menu = $btn.closest('ul'), $parentMenu = $('a[item_code="'+$menu.attr('id')+'"]').closest('ul'); $menu.hide(); $parentMenu.addClass('vivify fadeInLeft').show(); }); showMenu.call(self); } }; /** * Show menu * @param self */ SelectMenu.prototype.showMenu = function(self){ self.populate(); if($(self.target).is('button')) $(self.target).addClass(self.css_class.target_clicked); }; /** * Set menu item to selected * @param self * @param list - datasource */ SelectMenu.prototype.setInitSelected = function(self, list){ var p = self.option; if($.type(p.initSelected) !== 'undefined' && !p.regular && list && $.isArray(list) && list.length){ var str = String(p.initSelected),arr = str.split(','); var matchItem = function(dataList){ $.each(dataList, function(i,row){ var id = String(row[p.keyField]); if(id && $.inArray(id,arr) !== -1) self.prop.values.push(row); }); }; if(self.prop.data_type === SelectMenu.dataTypeList){ matchItem(list); }else if(self.prop.data_type === SelectMenu.dataTypeGroup){ $.each(list, function(i,group){ group && group.list && group.list.length && matchItem(group.list); }); } p.initSelected = undefined; } }; /** * Menu frame event handle */ SelectMenu.prototype.eInput = function() { var self = this,p = this.option,el = self.elem; if(!p.regular && p.search){ el.input.keyup(function(e) { self.processKey(self, e); }).keydown(function(e){ self.processControl(self, e); }); } if(p.title){ if(!p.embed){ el.closeButton.click(function(e){ self.hideResults(self); }); } if(!p.regular){ if(p.multiple){ el.selectAllButton.click(function(e){ e.stopPropagation(); self.selectAllLine(self); }); el.removeAllButton.click(function(e){ e.stopPropagation(); self.clearAll(self); }); } } } if(!p.regular && self.prop.data_type === SelectMenu.dataTypeGroup){ el.resultTabs.off('click.selectMenu').on('click.selectMenu', 'a', function(e){ e.stopPropagation(); if(!$(this).hasClass('active')){ var li = $(this).closest('li'); li.siblings().children('a').removeClass('active'); $(this).addClass('active'); self.prop.data_index = parseInt($(this).attr('data_index')); self.populate(); if(p.eTabSwitch && $.isFunction(p.eTabSwitch)){ var currentGroup = $.extend({}, self.prop.data[self.prop.data_index]); //cut the list item delete currentGroup.list; p.eTabSwitch.call(this, self.prop.data_index, currentGroup); } } }); } if(p.rightClick){ $(self.target).on('contextmenu',function(e){ e.preventDefault(); e.stopPropagation(); e.cancelBubble = true; e.returnValue = false; var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft, scrollY = document.documentElement.scrollTop || document.body.scrollTop; self.prop.x = e.pageX || e.clientX + scrollX; self.prop.y = e.pageY || e.clientY + scrollY; if(!self.isVisible(self)) self.populate(); else self.calcResultsSize(self); return false; }).mouseup(function(e){ if(e.button != 2) self.hideResults(self); }); self.hideResults(self); } }; /** * Out of menu event bind */ SelectMenu.prototype.eWhole = function() { var self = this, css = this.css_class; $(document).off('mouseup.selectMenu').on('mouseup.selectMenu',function(e) { var srcEl = e.target || e.srcElement, sm = $(srcEl).closest('div.' + css.container); //out of menu area click, when menu is opened , hide it $('div.' + css.container + '.' + css.container_open).each(function(){ var d = $(this).data(SelectMenu.dataKey); if(this == sm[0] || d.target == srcEl || $(srcEl).closest(d.target).size()) return; d.hideResults(d); }); }); }; /** * Menu item event bind */ SelectMenu.prototype.eResultList = function() { var self = this,p = this.option,el = self.elem; self.elem.results.children('li').mouseenter(function() { if (self.prop.key_select) { self.prop.key_select = false; return; } if(!$(this).hasClass('sm_message_box')) $(this).addClass(self.css_class.select); }).mouseleave(function(){ $(this).removeClass(self.css_class.select); }).click(function(e) { if (self.prop.key_select) { self.prop.key_select = false; return; } e.preventDefault(); e.stopPropagation(); self.selectCurrentLine(self, false); }); }; /** * Reposition result list when list beyond the visible area */ SelectMenu.prototype.eScroll = function(){ var self = this, css = this.css_class; $(window).on('scroll.SelectMenu',function(e){ $('div.' + css.container + '.' + css.container_open).each(function(){ var d = $(this).data(SelectMenu.dataKey), offset = d.elem.container.offset(), screenScrollTop = $(window).scrollTop(), docHeight = $(document).height(),//the document full height viewHeight = $(window).height(),//browser visible area height menuHeight = d.elem.container.outerHeight(), menuBottom = offset.top + menuHeight, hasOverflow = docHeight > viewHeight, down = d.elem.container.hasClass(css.direction_bottom); if(hasOverflow){ if(down){//show down if(menuBottom > (viewHeight + screenScrollTop)) d.calcResultsSize(d); }else{//show up if(offset.top < screenScrollTop) d.calcResultsSize(d); } } }); }); }; /** * Closing work * @param {Object} self */ SelectMenu.prototype.atLast = function(self){ if(!self) self = this; var p = self.option; if(p.search && !p.regular && !p.embed && !p.rightClick) self.elem.input.focus(); self.elem.container.data(SelectMenu.dataKey,self); if($(self.target).is('button,.btn') && !p.embed && !p.rightClick) $(self.target).addClass(self.css_class.target_clicked); } /** * Ajax request fail * @param {Object} self * @param {string} errorThrown */ SelectMenu.prototype.ajaxErrorNotify = function(self, errorThrown) { self.showMessage(self.message.ajax_error); }; /** * Show some message * @param {Object} self * @param msg {string} */ SelectMenu.prototype.showMessage = function(self,msg){ if(!msg) return; var msgLi = '<li class="sm_message_box"><i class="iconfont icon-warn"></i> '+msg+'</li>'; self.elem.results.empty().append(msgLi); self.calcResultsSize(self); self.elem.container.addClass(self.css_class.container_open); self.elem.control.hide(); }; /** * Check input to search * @param {Object} self */ SelectMenu.prototype.checkValue = function(self) { var now_value = self.elem.input.val(); if (now_value != self.prop.prev_value) { self.prop.prev_value = now_value; self.suggest(self); } }; /** * Input element event handle( regular letter ) * @param {Object} self * @param {Object} e - event */ SelectMenu.prototype.processKey = function(self, e){ if($.inArray(e.keyCode, [38, 40, 27, 9, 13]) === -1){ //if(e.keyCode != 16) self.setCssFocusedInput(self); // except Shift(16) if($.type(self.option.data) === 'string'){ self.prop.last_input_time = e.timeStamp; setTimeout(function(){ if((e.timeStamp - self.prop.last_input_time) === 0) self.checkValue(self); },self.option.inputDelay * 1000); }else{ self.checkValue(self); } } } /** * Input element event handle( control key ) * @param {Object} self * @param {Object} e - event */ SelectMenu.prototype.processControl = function(self, e) { if (($.inArray(e.keyCode, [38, 40, 27, 9]) > -1 && self.elem.container.is(':visible')) || ($.inArray(e.keyCode, [13, 9]) > -1 && self.getCurrentLine(self))) { e.preventDefault(); e.stopPropagation(); e.cancelBubble = true; e.returnValue = false; switch (e.keyCode) { case 38:// up self.prop.key_select = true; self.prevLine(self); break; case 40:// down if (self.elem.results.children('li').length) { self.prop.key_select = true; self.nextLine(self); } else self.suggest(self); break; case 9: // tab self.selectCurrentLine(self, true); //self.hideResults(self); break; case 13:// return self.selectCurrentLine(self, true); break; case 27:// escape self.hideResults(self); break; } } }; /** * Populate menu data */ SelectMenu.prototype.populate = function() { var self = this, p = this.option; if(!p.regular) self.elem.input.val(''); /** * 1.Process data source */ if(p.data){ if($.type(p.data) === 'array'){ self.prop.data = p.data; }else if($.type(p.data) === 'function'){ self.prop.data = p.data(); } } //Check data type if($.type(self.prop.data) === 'array') this.prop.data_type = this.checkDataType(self.prop.data); /** * 2.Set menu init selected */ if($.type(p.data) !== 'string') self.setInitSelected(self, self.prop.data); /** * 3.Show data */ if(p.regular) self.regularMenuInit(); else self.suggest(self); //scrolling listen if(!p.embed) self.eScroll(); }; /** * Search suggest * @param {Object} self */ SelectMenu.prototype.suggest = function(self) { var q_word, p = self.option, val = $.trim(self.elem.input.val()); if(p.multiple) q_word = val; else{ if(val && val === self.prop.selected_text) q_word = ''; else q_word = val; } q_word = q_word.split(/[\s ]+/); self.setLoading(self); if ($.type(p.data) === 'array' || $.type(p.data) === 'function') self.search(self, q_word); }; /** * Loading * @param {Object} self */ SelectMenu.prototype.setLoading = function(self) { if (self.elem.results.html() === '') { //self.calcResultsSize(self); if(!self.option.embed) self.elem.container.addClass(self.css_class.container_open); } }; /** * Search / load menu data * @param {Object} self * @param {Array} q_word - query keywords */ SelectMenu.prototype.search = function(self, q_word) { var p = self.option, innerData = self.prop.data, matched = [], esc_q = [], sorted = [], json = {}, i = 0, arr_reg = []; do { //'/\W/g'正则代表全部不是字母,数字,下划线,汉字的字符 //将非法字符进行转义 esc_q[i] = q_word[i].replace(/\W/g, '\\$&').toString(); arr_reg[i] = new RegExp(esc_q[i], 'gi'); i++; } while ( i < q_word.length ); var d = []; if(self.prop.data_index > (innerData.length-1) || self.prop.data_index < 0) self.prop.data_index = 0; if(self.prop.data_type === SelectMenu.dataTypeGroup){ d = innerData[self.prop.data_index].list; }else d = innerData; // SELECT * FROM data WHERE field LIKE q_word; for (i = 0; i < d.length; i++) { var flag = false; var row = d[i]; for (var j = 0; j < arr_reg.length; j++) { var itemText = row[p.searchField]; if(p.formatItem && $.isFunction(p.formatItem)) itemText = p.formatItem(row); if (itemText.match(arr_reg[j])) { flag = true; if (p.andOr == 'OR') break; } else { flag = false; if (p.andOr == 'AND') break; } } if (flag) matched.push(row); } // (CASE WHEN ...) then く order some field var reg1 = new RegExp('^' + esc_q[0] + '$', 'gi'), reg2 = new RegExp('^' + esc_q[0], 'gi'), matched1 = [], matched2 = [], matched3 = []; for (i = 0; i < matched.length; i++) { var orderField = p.orderBy[0][0], orderValue = String(matched[i][orderField]); if (orderValue.match(reg1)) { matched1.push(matched[i]); } else if (orderValue.match(reg2)) { matched2.push(matched[i]); } else { matched3.push(matched[i]); } } if (p.orderBy[0][1].match(/^asc$/i)) { matched1 = self.sortAsc(self, matched1); matched2 = self.sortAsc(self, matched2); matched3 = self.sortAsc(self, matched3); } else { matched1 = self.sortDesc(self, matched1); matched2 = self.sortDesc(self, matched2); matched3 = self.sortDesc(self, matched3); } sorted = sorted.concat(matched1).concat(matched2).concat(matched3); /* if (sorted.length === undefined || sorted.length === 0 ) { self.notFoundSearch(self); return; } */ //json.cnt_whole = sorted.length; //Cache original row data json.originalResult = []; if(json.keyField === undefined) json.keyField = []; if(json.candidate === undefined) json.candidate = []; $.each(sorted, function(i,row){ if(row === undefined || $.type(row) !== 'object') return true; json.originalResult.push(row); if(row.hasOwnProperty(p.keyField) && row.hasOwnProperty(p.showField)){ json.keyField.push(row[p.keyField]); json.candidate.push(row[p.showField]); } }); //json.cnt_page = json.candidate.length; self.prepareResults(self, json, q_word); }; /** * Sort ascending * @param {Object} self * @param {Array} arr */ SelectMenu.prototype.sortAsc = function(self, arr) { arr.sort(function(a, b) { var valA = a[self.option.orderBy[0][0]], valB = b[self.option.orderBy[0][0]]; return $.type(valA) === 'number' ? valA - valB : String(valA).localeCompare(String(valB)); }); return arr; }; /** * Sort descending * @param {Object} self * @param {Array} arr */ SelectMenu.prototype.sortDesc = function(self, arr) { arr.sort(function(a, b) { var valA = a[self.option.orderBy[0][0]], valB = b[self.option.orderBy[0][0]]; return $.type(valA) === 'number' ? valB - valA : String(valB).localeCompare(String(valA)); }); return arr; }; /** * No result handle * @param {Object} self */ SelectMenu.prototype.notFoundSearch = function(self) { self.elem.results.empty(); self.calcResultsSize(self); self.elem.container.addClass(self.css_class.container_open); self.setCssFocusedInput(self); }; /** * Prepare data to render menu item * @param {Object} self * @param {Object} json * @param {Array} q_word - query keywords */ SelectMenu.prototype.prepareResults = function(self, json, q_word) { if (!json.keyField) json.keyField = false; if (self.option.selectOnly && json.candidate.length === 1 && json.candidate[0] == q_word[0]) { self.elem.hidden.val(json.keyField[0]); this.setButtonAttrDefault(); } var is_query = false; if (q_word && q_word.length && q_word[0]) is_query = true; //self.setInitSelected(self,json.originalResult); self.displayResults(self, json, is_query); }; /** * Render menu item * @param {Object} self * @param {Object} json * @param {boolean} is_query */ SelectMenu.prototype.displayResults = function(self, json, is_query) { var p = self.option, el = self.elem, css = self.css_class; el.results.hide().empty(); // build tabs if(self.prop.data_type === SelectMenu.dataTypeGroup) { var ul = $('<ul>'); $.each(self.prop.data,function(i,row){ var a = $('<a href="javascript:void(0);">').html(row.title).attr({ 'tab_id' : self.prop.menu_tab_id_prefix + (i+1), 'data_index' : i }); if(i === self.prop.data_index) a.addClass('active'); var li = $('<li>').append(a); ul.append(li); }); el.resultTabs.empty().append(ul); }else{ el.resultTabs.hide(); if(p.title || p.search) el.resultArea.addClass(this.css_class.re_list); } if(p.multiple && $.type(p.maxSelectLimit) === 'number' && p.maxSelectLimit){ var selectedSize = self.prop.results.length; if(selectedSize && selectedSize >= p.maxSelectLimit){ var msg = self.message.max_selected; self.showMessage(self, msg.replace(self.template.msg.maxSelectLimit, p.maxSelectLimit)); return; } } if(json.candidate.length){ var arr_candidate = json.candidate, arr_primary_key = json.keyField; for (var i = 0; i < arr_candidate.length; i++) { var itemText = '', custom = false, row = json.originalResult[i]; if(p.formatItem && $.isFunction(p.formatItem)){ try { itemText = p.formatItem(row); custom = true; } catch (e) { console.error('formatItem 内容格式化函数内容设置不正确!'); itemText = arr_candidate[i]; } }else itemText = arr_candidate[i]; var icon = $('<div>').html('<i class="iconfont icon-selected">').addClass(css.selected_icon), text = $('<div>').html(itemText).addClass(css.item_text), li = $('<li>').append(icon).append(text).attr('pkey' , arr_primary_key[i]); if(!custom) li.attr('title',itemText); //set selected item to highlight if ($.inArray(row,self.prop.values) !== -1) { li.addClass(css.selected); } //cache item data li.data('dataObj',row); el.results.append(li); } }else{ var li = '<li class="sm_message_box"><i class="iconfont icon-warn"></i> ' + self.message.not_found + '</li>'; el.results.append(li); } el.results.show(); self.calcResultsSize(self); if(!p.embed) el.container.addClass(css.container_open); //menu item event bind self.eResultList(); //auto highlight first item in search, in have result and set autoSelectFirst to true situation //if (is_query && json.candidate.length > 0 && p.autoSelectFirst) self.nextLine(self); self.atLast(self); }; /** * Calculate menu position and size * @param {Object} self */ SelectMenu.prototype.calcResultsSize = function(self) { var p = self.option, el = self.elem, css = self.css_class, hasScroll = function(){ return $(document).height() > $(window).height(); }; var setListHeight = function(){ if(!p.regular){ //result list height var itemHeight = el.results.find('li:first').outerHeight(), listHeight = itemHeight * p.listSize; el.results.css({ 'max-height':listHeight }); } }; var scrollFlag = hasScroll(); var rePosition = function(){ var menuHeight = el.container.outerHeight(), screenScrollTop = $(window).scrollTop(), viewHeight = $(window).height(); if(p.rightClick){ var top = self.prop.y; if((self.prop.y + menuHeight) > (screenScrollTop + viewHeight)) top = self.prop.y - menuHeight; return {top : top, left : self.prop.x}; } var boxoffset = $(self.target).offset(), t = boxoffset.top, menuWidth = el.container.outerWidth(), targetWidth = Math.round($(self.target)[0].getBoundingClientRect().width), dist = 5; t += $(self.target).outerHeight() + dist; if(p.arrow && !p.embed) t += el.arrow.outerHeight(true); if((t + menuHeight) > (screenScrollTop + viewHeight)){ t = boxoffset.top - dist - menuHeight; if(p.arrow && !p.embed) t -= el.arrow.outerHeight(true); el.container.removeClass(css.direction_bottom).addClass(css.direction_top); }else{ if(el.container.hasClass(css.direction_top)) el.container.removeClass(css.direction_top).addClass(css.direction_bottom); } var l = boxoffset.left; switch (p.position){ case 'right': l = l + targetWidth - menuWidth; if(p.arrow) el.arrow.css('left',menuWidth - (targetWidth / 2)); break; case 'center': l = l + (targetWidth / 2) - (menuWidth / 2); break; case 'left': default: if(p.arrow) el.arrow.css('left',targetWidth / 2); break; } return {top : t,left : l}; } if(el.container.is(':visible')){ setListHeight(); if(!p.embed) el.container.offset(rePosition()); }else{ el.container.show(1,function(){ setListHeight(); $(this).offset(rePosition()); }); } if(scrollFlag !== hasScroll()) el.container.offset(rePosition()); }; /** * */ SelectMenu.prototype.subMenuPosition = function(parent, menu){ var pOffset = $(parent).offset(); var t = pOffset.top,l = pOffset.left + $(parent).outerWidth() + 5; }; /** * Hide menu * @param {Object} self */ SelectMenu.prototype.hideResults = function(self) { var p = self.option; if (p.autoFillResult) { //self.selectCurrentLine(self, true); } if(!p.regular) self.elem.results.empty(); if(!p.embed){ self.elem.container.removeClass(self.css_class.container_open).hide(); if($(self.target).is('button,.btn')) $(self.target).removeClass(self.css_class.target_clicked); } self.elem.resultArea.find('ul.'+self.css_class.results).not('.'+self.css_class.menu_root).hide(); //remove animate class self.elem.results.removeClass('vivify').removeClass('fadeInLeft').show(); $(window).off('scroll.SelectMenu'); if(!p.regular && p.eHidden && $.isFunction(p.eHidden)) p.eHidden.call(self, self.prop.values.concat()); }; /** * do something after select/unSelect action * @param {Object} self */ SelectMenu.prototype.afterAction = function(self){ //$(self.elem.input).change(); if(self.option.multiple){ if(self.option.selectToCloseList){ self.hideResults(self); self.elem.input.blur(); }else{ //self.suggest(self); self.elem.input.focus(); } }else{ self.hideResults(self); self.elem.input.blur(); } }; /** * Get current menu item * @param {Object} self */ SelectMenu.prototype.getCurrentLine = function(self) { if (self.elem.container.is(':hidden')) return false; var obj = self.elem.results.find('li.' + self.css_class.select); if (obj.size()) return obj; else return false; }; /** * Get selected menu item * @param self * @returns {*} */ SelectMenu.prototype.getSelectedLine = function(self) { if (self.elem.container.is(':hidden')) return false; var obj = self.elem.results.find('li.' + self.css_class.selected); if (obj.size()) return obj; else return false; }; /** * Selected menu item and trigger select callback * @param {Object} self * @param {boolean} is_enter_key */ SelectMenu.prototype.selectCurrentLine = function(self, is_enter_key) { var current = self.getCurrentLine(self), p = self.option; if (current) { var rowData = current.data('dataObj'), id = String(rowData[p.keyField]); if($.inArray(rowData,self.prop.values) === -1){ if(!p.multiple) self.prop.values.splice(0,self.prop.values.length); self.prop.values.push(rowData); if(!p.multiple) self.elem.results.find('li.' + self.css_class.selected).removeClass(self.css_class.selected); current.addClass(self