gplx/mediawiki/extensions/SemanticMediaWiki/res/jquery/jquery.selectmenu.js

1597 lines
53 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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 modetags
* @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').length && !$.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.length){
$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).length) 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.length) 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.length) 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.css_class.selected);
} else{
self.prop.values.splice($.inArray(rowData,self.prop.values),1);
current.removeClass(self.css_class.selected);
}
//trigger callback
if(p.eSelect && $.isFunction(p.eSelect)){
if(p.multiple){
p.eSelect.call(self, self.prop.values.concat());
}else p.eSelect.call(self, [rowData]);
}
self.prop.prev_value = self.elem.input.val();
self.prop.selected_text = self.elem.input.val();
}
if(!p.embed) self.afterAction(self);
};
/**
* Select all menu item
* @param {Object} self
*/
SelectMenu.prototype.selectAllLine = function(self){
self.elem.results.find('li').each(function(i,row){
var d = $(row).data('dataObj');
if($.inArray(d,self.prop.values) === -1) self.prop.values.push(d);
$(this).addClass(self.css_class.selected);
//limited max select items
/*
if($.type(self.option.maxSelectLimit) === 'number' &&
self.option.maxSelectLimit > 0 &&
self.option.maxSelectLimit === $('li.selected_tag',self.elem.element_box).length){
return false;
}
*/
});
if(self.option.eSelect && $.isFunction(self.option.eSelect))
self.option.eSelect.call(self, self.prop.values.concat());
self.afterAction(self);
};
/**
* Clear all selected menu items
* @param {Object} self
*/
SelectMenu.prototype.clearAll = function(self){
var p = self.option, el = self.elem;
el.input.val('');
el.results.find('li').each(function(i,row){
$(this).removeClass(self.css_class.selected);
});
self.prop.values.splice(0,self.prop.values.length);
self.afterAction(self);
if (p.eSelect && $.isFunction(p.eSelect)) p.eSelect.call(self, []);
};
/**
* Select next menu item
* @param {Object} self
*/
SelectMenu.prototype.nextLine = function(self) {
var obj = self.getCurrentLine(self), el = self.elem, idx;
if (!obj) idx = -1;
else {
idx = el.results.children('li').index(obj);
obj.removeClass(self.css_class.select);
}
idx++;
var size = el.results.find('li').length;
if(idx === size) idx = size - 1;
if (idx < size) {
var next = el.results.children('li').eq(idx);
next.addClass(self.css_class.select);
var itemHeight = el.results.find('li:first').outerHeight(),
curTop = next.position().top,
curScrollTop = el.resultArea.scrollTop(),
listHeight = el.resultArea.outerHeight(),
dist = curTop + itemHeight - listHeight;
if((curTop + itemHeight) > listHeight)
el.resultArea.scrollTop(curScrollTop + dist);
}
};
/**
* Select previous menu item
* @param {Object} self
*/
SelectMenu.prototype.prevLine = function(self) {
var el = self.elem, idx, obj = self.getCurrentLine(self);
if (!obj) idx = el.results.children('li').length;
else {
idx = el.results.children('li').index(obj);
obj.removeClass(self.css_class.select);
}
idx--;
if(idx < 0) idx = 0;
if (idx > -1) {
var prev = el.results.children('li').eq(idx),
itemHeight = el.results.find('li:first').outerHeight(),
curTop = prev.position().top,
curScrollTop = el.resultArea.scrollTop(),
listHeight = el.resultArea.outerHeight();
prev.addClass(self.css_class.select);
if((curTop > (curScrollTop + listHeight)) || (curTop < curScrollTop))
el.resultArea.scrollTop(curScrollTop - (0 - curTop));
}
};
/**
* Check menu visible
* @param self
*/
SelectMenu.prototype.isVisible = function(self){
return self.elem.container.hasClass(self.css_class.container_open);
}
/**
* Init plugin entrance
* @global
* @memberof jQuery
* @param option {Object} init parameters
*/
function Plugin(option) {
return this.each(function(){
var $this = $(this),
data = $this.data(SelectMenu.dataKey),
params = $.extend({}, defaults, $this.data(), data && data.option ,typeof option === 'object' && option);
if(!data) $this.data(SelectMenu.dataKey,(data = new SelectMenu(this,params)));
else{
if(data.isVisible(data)) data.hideResults(data);
else data.showMenu(data);
}
});
}
/**
* Hide menu
*/
function HideMenu(){
return this.each(function(){
var $this = $(this),
data = $this.data(SelectMenu.dataKey);
if(data) data.hideResults(data);
});
}
/**
* Clear all menu selected item
*/
function ClearSelected(){
return this.each(function(){
var $this = $(this),
data = $this.data(SelectMenu.dataKey);
if(data) data.clearAll(data);
});
}
/**
* Get selected item data
*/
function GetSelected(){
var results = new Array();
this.each(function(){
var $this =$(this),
data = $this.data(SelectMenu.dataKey);
if(data) results = data.prop.values.concat();
});
return results;
}
var old = $.fn.selectMenu;
$.fn.selectMenu = Plugin;
$.fn.selectMenu.Constructor = SelectMenu;
$.fn.selectMenuHide = HideMenu;
$.fn.selectMenuClear = ClearSelected;
$.fn.selectMenuValues = GetSelected;
// SelectMenu no conflict
// =================
$.fn.selectMenu.noConflict = function () {
$.fn.selectMenu = old;
return this;
};
})(window.jQuery);