selectmenu.js 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597
  1. /**
  2. * @summary SelectMenu
  3. * @desc Simple, easily and diversity menu solution
  4. * @file selectmenu.js
  5. * @version 2.1
  6. * @author TerryZeng
  7. * @contact https://terryz.github.io/
  8. * @license MIT License
  9. *
  10. * depend on:
  11. * jQuery1.x
  12. *
  13. */
  14. ;(function($){
  15. "use strict";
  16. /**
  17. * Version: v3.6.1
  18. * The MIT License: Copyright (c) 2010-2017 LiosK.
  19. * Link: https://github.com/LiosK/UUID.js
  20. */
  21. var UUID;
  22. 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&&
  23. (module.exports=a);return a}(UUID);
  24. /**
  25. * Default options
  26. */
  27. var defaults = {
  28. /**
  29. * Menu data source
  30. * @type array | function
  31. * @example
  32. * array:[{a:1,b:2,c:3},{...}]
  33. * function: function(){ return [{...}];}
  34. * return data format is same to array
  35. */
  36. data: undefined,
  37. /**
  38. * Show quick search input element, work on advance menu mode
  39. * @type boolean
  40. * @default true
  41. */
  42. search : true,
  43. /**
  44. * Title bar text, set false to close title bar
  45. * @type string | boolean
  46. * @default 'SelectMenu'
  47. */
  48. title : 'SelectMenu',
  49. /**
  50. * Regular menu mode
  51. * @type boolean
  52. * @default false
  53. */
  54. regular : false,
  55. /**
  56. * Mouse right click to show menu
  57. * @type boolean
  58. * @default false
  59. */
  60. rightClick : false,
  61. /**
  62. * Menu show arrow, look like a bubble
  63. * @type boolean
  64. * @default false
  65. */
  66. arrow : false,
  67. /**
  68. * Alignment direction
  69. * @type string
  70. * @enum
  71. * 'left'
  72. * 'center'
  73. * 'right'
  74. * @default 'left'
  75. */
  76. position : 'left',
  77. /**
  78. * Embedded to page
  79. * @type boolean
  80. * @default false
  81. */
  82. embed : false,
  83. /**
  84. * Language ('cn', 'ja', 'en', 'es', 'pt-br')
  85. * @type string
  86. * @default 'cn'
  87. */
  88. lang: 'cn',
  89. /**
  90. * Multiple select mode(tags)
  91. * @type boolean
  92. * @default false
  93. */
  94. multiple: false,
  95. /**
  96. * Menu result list size, the number mean is visible menu item amount
  97. * @type number
  98. * @default 10
  99. */
  100. listSize : 10,
  101. /**
  102. * Maximum selected item limit in multiple select mode, set 0 to unlimited
  103. * @type number
  104. * @default 0 (unlimited)
  105. */
  106. maxSelectLimit: 0,
  107. /**
  108. * Close result list after menu item selected, work on multiple select mode
  109. * @type boolean
  110. * @default true
  111. */
  112. selectToCloseList: false,
  113. /**
  114. * Set a key to selected menu item when plugin init complete, work on multiple select mode
  115. * the key will match to keyField
  116. * @type string
  117. */
  118. initSelected: undefined,
  119. /**
  120. * Key field to return data
  121. * @type string
  122. * @default 'id'
  123. */
  124. keyField: 'id',
  125. /**
  126. * Show content field
  127. * @type string
  128. * @default 'name'
  129. */
  130. showField: 'name',
  131. /**
  132. * Data field in search, not set default used showField
  133. * @type string
  134. */
  135. searchField : undefined,
  136. /**
  137. * Filter type ('AND' or 'OR')
  138. * @type string default: 'AND'
  139. */
  140. andOr: 'AND',
  141. /**
  142. * Sort order, not set default used showField
  143. * @type array
  144. * @example
  145. * orderBy : ['id desc']//order by id desc
  146. */
  147. orderBy: undefined,
  148. /**
  149. * Max item size
  150. * @type number
  151. */
  152. pageSize: 100,
  153. /**
  154. * Menu item result format
  155. * @type function
  156. * @param data {object} menu item data
  157. * @return string
  158. */
  159. formatItem : undefined,
  160. /**
  161. * -----------------------------------------Event--------------------------------------------
  162. */
  163. /**
  164. * Menu item select callback
  165. * @type function
  166. * @param data {array[Object]} selected items data
  167. */
  168. eSelect : undefined,
  169. /**
  170. * Multiple group data type tab switch callback
  171. * @type function
  172. * @param index {number}
  173. */
  174. eTabSwitch : undefined,
  175. /**
  176. * Menu hide callback
  177. * @type function
  178. * @param data {array[Object]} selected items data
  179. */
  180. eHidden : undefined
  181. };
  182. /**
  183. * @constructor
  184. * @param {Object} input - menu caller
  185. * @param {Object} option - menu init option
  186. */
  187. var SelectMenu = function(input, option) {
  188. this.target = input;
  189. this.setOption(option);
  190. if(this.option.embed && !$(input).is('div')){
  191. console.warn('SelectMenu embed mode need a "div" container element!');
  192. return;
  193. }
  194. this.setLanguage();
  195. this.setCssClass();
  196. this.setProp();
  197. if(option.regular) this.setRegularMenu();
  198. else this.setElem();
  199. if(!option.rightClick) this.populate();
  200. this.eInput();
  201. if(!option.embed) this.eWhole();
  202. this.atLast();
  203. };
  204. /**
  205. * Plugin version number
  206. */
  207. SelectMenu.version = '2.1';
  208. /**
  209. * Plugin object cache key
  210. */
  211. SelectMenu.dataKey = 'selectMenuObject';
  212. /**
  213. * Data source type
  214. * List type
  215. */
  216. SelectMenu.dataTypeList = 'SelectMenuList';
  217. /**
  218. * Group type
  219. */
  220. SelectMenu.dataTypeGroup = 'SelectMenuGroup';
  221. /**
  222. * Regular menu type
  223. */
  224. SelectMenu.dataTypeMenu = 'SelectMenuMenu';
  225. /**
  226. * Initial plugin option
  227. * @param {Object} option
  228. */
  229. SelectMenu.prototype.setOption = function(option) {
  230. //if not set, default used showField set field
  231. option.searchField = option.searchField || option.showField;
  232. if(option.regular && option.title === defaults.title) option.title = false;
  233. //Close arrow in embed and mouse right click mode
  234. if(option.embed || option.rightClick) option.arrow = false;
  235. option.andOr = option.andOr.toUpperCase();
  236. if(option.andOr!=='AND' && option.andOr!=='OR') option.andOr = 'AND';
  237. option.orderBy = (option.orderBy === undefined) ? option.showField : option.orderBy;
  238. //Multiple field sort
  239. //Example: [ ['id', 'ASC'], ['name', 'DESC'] ]
  240. option.orderBy = this.setOrderbyOption(option.orderBy, option.showField);
  241. if($.type(option.data) === 'string'){
  242. option.autoSelectFirst = false;
  243. }
  244. if($.type(option.listSize) !== 'number' || option.listSize < 0) option.listSize = 12;
  245. this.option = option;
  246. };
  247. /**
  248. * Initial order
  249. * @param {Array} arg_order
  250. * @param {string} arg_field
  251. * @return {Array}
  252. */
  253. SelectMenu.prototype.setOrderbyOption = function(arg_order, arg_field) {
  254. var arr = [],orders = [];
  255. if (typeof arg_order == 'object') {
  256. for (var i = 0; i < arg_order.length; i++) {
  257. orders = $.trim(arg_order[i]).split(' ');
  258. arr[i] = (orders.length == 2) ? orders: [orders[0], 'ASC'];
  259. }
  260. } else {
  261. orders = $.trim(arg_order).split(' ');
  262. arr[0] = (orders.length == 2) ? orders: (orders[0].match(/^(ASC|DESC)$/i)) ? [arg_field, orders[0]] : [orders[0], 'ASC'];
  263. }
  264. return arr;
  265. };
  266. /**
  267. * i18n
  268. */
  269. SelectMenu.prototype.setLanguage = function() {
  270. var message;
  271. switch (this.option.lang) {
  272. // 中文
  273. case 'cn':
  274. message = {
  275. select_all_btn: '选择所有 (或当前页签) 项目',
  276. remove_all_btn: '清除所有选中的项目',
  277. close_btn: '关闭菜单 (Esc键)',
  278. loading: '读取中...',
  279. select_ng: '请注意:请从列表中选择.',
  280. select_ok: 'OK : 已经选择.',
  281. not_found: '无查询结果',
  282. ajax_error: '连接到服务器时发生错误!',
  283. max_selected: '最多只能选择 max_selected_limit 个项目'
  284. };
  285. break;
  286. // English
  287. case 'en':
  288. message = {
  289. select_all_btn: 'Select All (Tabs) items',
  290. remove_all_btn: 'Clear all selected items',
  291. close_btn: 'Close Menu (Esc key)',
  292. loading: 'loading...',
  293. select_ng: 'Attention : Please choose from among the list.',
  294. select_ok: 'OK : Correctly selected.',
  295. not_found: 'not found',
  296. ajax_error: 'An error occurred while connecting to server.',
  297. max_selected: 'You can only select up to max_selected_limit items'
  298. };
  299. break;
  300. // Japanese
  301. case 'ja':
  302. message = {
  303. select_all_btn: 'すべての (または現在のタブ) 項目を選択',
  304. remove_all_btn: '選択したすべての項目をクリアする',
  305. close_btn: '閉じる (Tabキー)',
  306. loading: '読み込み中...',
  307. select_ng: '注意 : リストの中から選択してください',
  308. select_ok: 'OK : 正しく選択されました。',
  309. not_found: '(0 件)',
  310. ajax_error: 'サーバとの通信でエラーが発生しました。',
  311. max_selected: '最多で max_selected_limit のプロジェクトを選ぶことしかできません'
  312. };
  313. break;
  314. // German
  315. case 'de':
  316. message = {
  317. select_all_btn: 'Wählen Sie alle (oder aktuellen Registerkarten) aus',
  318. remove_all_btn: 'Alle ausgewählten Elemente löschen',
  319. close_btn: 'Schließen (Tab)',
  320. loading: 'lade...',
  321. select_ng: 'Achtung: Bitte wählen Sie aus der Liste aus.',
  322. select_ok: 'OK : Richtig ausgewählt.',
  323. not_found: 'nicht gefunden',
  324. ajax_error: 'Bei der Verbindung zum Server ist ein Fehler aufgetreten.',
  325. max_selected: 'Sie können nur bis zu max_selected_limit Elemente auswählen'
  326. };
  327. break;
  328. // Spanish
  329. case 'es':
  330. message = {
  331. select_all_btn: 'Seleccionar todos los elementos (o la pestaña actual)',
  332. remove_all_btn: 'Borrar todos los elementos seleccionados',
  333. close_btn: 'Cerrar (tecla TAB)',
  334. loading: 'Cargando...',
  335. select_ng: 'Atencion: Elija una opcion de la lista.',
  336. select_ok: 'OK: Correctamente seleccionado.',
  337. not_found: 'no encuentre',
  338. ajax_error: 'Un error ocurrió mientras conectando al servidor.',
  339. max_selected: 'Solo puedes seleccionar hasta max_selected_limit elementos'
  340. };
  341. break;
  342. // Brazilian Portuguese
  343. case 'pt-br':
  344. message = {
  345. select_all_btn: 'Selecione todos os itens (ou guia atual)',
  346. remove_all_btn: 'Limpe todos os itens selecionados',
  347. close_btn: 'Fechar (tecla TAB)',
  348. loading: 'Carregando...',
  349. select_ng: 'Atenção: Escolha uma opção da lista.',
  350. select_ok: 'OK: Selecionado Corretamente.',
  351. not_found: 'não encontrado',
  352. ajax_error: 'Um erro aconteceu enquanto conectando a servidor.',
  353. max_selected: 'Você só pode selecionar até max_selected_limit itens'
  354. };
  355. break;
  356. }
  357. this.message = message;
  358. };
  359. /**
  360. * CSS classname set
  361. */
  362. SelectMenu.prototype.setCssClass = function() {
  363. var css_class = {
  364. target_clicked : 'sm_target_clicked',
  365. container: 'sm_container',
  366. container_open: 'sm_container_open',
  367. container_embed: 'sm_embed',
  368. header: 'sm_header',
  369. re_area: 'sm_result_area',
  370. re_tabs: 'sm_result_tabs',
  371. re_list: 'sm_list_mode',
  372. control_box: 'sm_control_box',
  373. two_btn: 'sm_two_btn',
  374. element_box: 'sm_element_box',
  375. results: 'sm_results',
  376. re_off: 'sm_results_off',
  377. select: 'sm_over',
  378. selected_icon: 'sm_selected_icon',
  379. item_text: 'sm_item_text',
  380. select_ok: 'sm_select_ok',
  381. select_ng: 'sm_select_ng',
  382. selected: 'sm_selected',
  383. input_off: 'sm_input_off',
  384. message_box: 'sm_message_box',
  385. btn_close: 'sm_close_button',
  386. btn_selectall: 'sm_selectall_button',
  387. btn_removeall: 'sm_removeall_button',
  388. btn_on: 'sm_btn_on',
  389. btn_out: 'sm_btn_out',
  390. btn_back: 'sm_sub_back',
  391. input: 'sm_input',
  392. input_area: 'sm_input_area',
  393. clear_btn: 'sm_clear_btn',
  394. menu_root: 'sm_menu_root',
  395. menu_divider: 'sm_divider',
  396. menu_regular: 'sm_regular',
  397. menu_arrow: 'sm_arrow',
  398. menu_arrow_have_title : 'sm_have_title',
  399. menu_disabled: 'sm_disabled',
  400. menu_header: 'sm_header',
  401. menu_caret: 'sm_caret',
  402. menu_sub_menu: 'sm_sub_menu',
  403. menu_sub_item: 'sm_sub_item',
  404. menu_sub_header: 'sm_sub_header',
  405. direction_top : 'sm_arrow_top',
  406. direction_bottom : 'sm_arrow_bottom'
  407. };
  408. this.css_class = css_class;
  409. this.template = {
  410. msg :{
  411. maxSelectLimit: 'max_selected_limit'
  412. }
  413. };
  414. };
  415. /**
  416. * Internal variable initial
  417. */
  418. SelectMenu.prototype.setProp = function() {
  419. this.prop = {
  420. //selected menu item keys
  421. values : [],
  422. data : undefined,
  423. //multiple group data current data index
  424. data_index : 0,
  425. key_select: false,
  426. prev_value: '',
  427. selected_text : '',
  428. last_input_time: undefined,
  429. //menu data type
  430. data_type : SelectMenu.dataTypeList,
  431. //id prefix
  432. menu_tab_id_prefix : 'selectmenu_tab_',
  433. menu_code_prefix: 'selectmenu_',
  434. //mouse x point
  435. x : undefined,
  436. //mouse y point
  437. y : undefined
  438. };
  439. };
  440. /**
  441. * Data source type check
  442. */
  443. SelectMenu.prototype.checkDataType = function(d){
  444. var self = this,p = this.option;
  445. if(d && $.isArray(d) && d.length){
  446. if(p.regular) return SelectMenu.dataTypeMenu;
  447. else{
  448. var row = d[0];
  449. if(row.hasOwnProperty('title') && row.hasOwnProperty('list') && $.isArray(row.list)){
  450. return SelectMenu.dataTypeGroup;
  451. }else return SelectMenu.dataTypeList;
  452. }
  453. }else return null;
  454. };
  455. /**
  456. * Menu structure build
  457. */
  458. SelectMenu.prototype.setElem = function() {
  459. var self = this, p = this.option, css = this.css_class;
  460. // 1. build dom element
  461. var elem = {};
  462. elem.container = p.embed ? $(self.target).addClass(css.container_embed) : $('<div>');
  463. elem.container.addClass(css.container).addClass(css.direction_bottom);
  464. if(p.title){
  465. elem.header = $('<div>').addClass(css.header);
  466. elem.header.append('<h3>' + p.title + '</h3>');
  467. if(p.multiple){
  468. elem.selectAllButton = $('<button type="button"><i class="iconfont icon-selectall"></i></button>')
  469. .attr('title',this.message.select_all_btn)
  470. .addClass(css.btn_selectall);
  471. elem.removeAllButton = $('<button type="button"><i class="iconfont icon-removeall"></i></button>')
  472. .attr('title',this.message.remove_all_btn)
  473. .addClass(css.btn_removeall);
  474. elem.header.append(elem.selectAllButton);
  475. elem.header.append(elem.removeAllButton);
  476. }
  477. if(!p.embed){
  478. elem.closeButton = $('<button type="button">×</button>')
  479. .attr('title',self.message.close_btn)
  480. .addClass(css.btn_close);
  481. elem.header.append(elem.closeButton);
  482. }
  483. }
  484. elem.inputArea = $('<div>').addClass(css.input_area);
  485. elem.input = $('<input type="text" autocomplete="off">').addClass(css.input);
  486. //Result list
  487. elem.resultArea = $('<div>').addClass(css.re_area);
  488. elem.resultTabs = $('<div>').addClass(css.re_tabs);
  489. elem.results = $('<ul>').addClass(css.results);
  490. elem.selectedIcon = $('<i class="iconfont icon-selected">');
  491. // 2. DOM element put
  492. if(p.arrow){
  493. elem.arrow = $('<div>').addClass(css.menu_arrow);
  494. if(p.title) elem.arrow.addClass(css.menu_arrow_have_title);
  495. elem.container.append(elem.arrow);
  496. }
  497. if(p.title) elem.container.append(elem.header);
  498. if(p.search){
  499. elem.container.append(elem.inputArea);
  500. elem.inputArea.append(elem.input);
  501. }
  502. elem.container.append(elem.resultTabs).append(elem.resultArea);
  503. elem.resultArea.append(elem.results);
  504. if(!p.embed) $(document.body).append(elem.container);
  505. this.elem = elem;
  506. };
  507. /**
  508. * Initial regular menu frame
  509. */
  510. SelectMenu.prototype.setRegularMenu = function(){
  511. var p = this.option, self = this, css = this.css_class;
  512. var elem = {};
  513. elem.container = p.embed ? $(self.target).addClass(css.container_embed) : $('<div>');
  514. elem.container.addClass(css.container)
  515. .addClass(css.direction_bottom)
  516. .addClass(css.menu_regular);
  517. if(p.title){
  518. elem.header = $('<div>').addClass(css.header);
  519. elem.header.append('<h3>' + p.title + '</h3>');
  520. if(!p.embed)
  521. elem.closeButton = $('<button type="button">×</button>')
  522. .attr('title',self.message.close_btn)
  523. .addClass(css.btn_close);
  524. }
  525. elem.resultArea = $('<div>').addClass(css.re_area);
  526. elem.results = $('<ul>').addClass(css.results).addClass(css.menu_root);
  527. if(p.arrow){
  528. elem.arrow = $('<div>').addClass(css.menu_arrow);
  529. if(p.title) elem.arrow.addClass(css.menu_arrow_have_title);
  530. elem.container.append(elem.arrow);
  531. }
  532. if(p.title){
  533. elem.container.append(elem.header);
  534. if(!p.embed) elem.header.append(elem.closeButton);
  535. }
  536. elem.container.append(elem.resultArea);
  537. elem.resultArea.append(elem.results);
  538. if(!p.embed) $(document.body).append(elem.container);
  539. this.elem = elem;
  540. };
  541. /**
  542. * Regular menu item render
  543. */
  544. SelectMenu.prototype.regularMenuInit = function(){
  545. var d = this.prop.data, p = this.option, self = this, css = this.css_class, el = self.elem;
  546. var showMenu = function(){
  547. if(!p.embed){
  548. this.calcResultsSize(this);
  549. el.container.addClass(css.container_open);
  550. }
  551. };
  552. if(el.results.find('li').size() && !$.isFunction(p.data)){
  553. showMenu.call(self);
  554. return;
  555. }
  556. if(d && $.isArray(d) && d.length){
  557. var buildMenu = function(menudata, ul){
  558. if(ul.hasClass(css.menu_root)) ul.empty().hide();
  559. $.each(menudata,function(i,row){
  560. if(!row.content ||
  561. (!row.header &&
  562. !row.url &&
  563. !row.callback &&
  564. !row.menus &&
  565. row.content !== css.menu_divider))
  566. return true;
  567. var li = $('<li>');
  568. if(row.content === css.menu_divider){
  569. li.addClass(css.menu_divider);
  570. }else{
  571. var a = $('<a>').html(row.content).attr('href',
  572. (row.url && !row.disabled)?row.url:'javascript:void(0);');
  573. if(row.callback && $.isFunction(row.callback) && !row.url){
  574. a.on('click.selectMenu',function(e){
  575. e.stopPropagation();
  576. if(row.disabled) return;
  577. row.callback();
  578. self.hideResults(self);
  579. });
  580. }
  581. //build sub menus
  582. if(row.menus && $.isArray(row.menus) && row.menus.length){
  583. var itemCode = self.prop.menu_code_prefix + UUID.generate();
  584. a.attr({
  585. 'href': 'javascript:void(0);',
  586. 'item_code': itemCode
  587. }).append($('<span>').addClass(css.menu_caret)).addClass(css.menu_sub_item);
  588. var subMenu = $('<ul>').attr('id', itemCode).addClass(css.results).addClass(css.menu_sub_menu);
  589. //build sub menu header bar
  590. var backBtn = $('<button type="button">').addClass(css.btn_back).append('<i class="iconfont icon-back"></i>');
  591. var header = $('<li>').append(backBtn).append($('<p>').html(row.content)).addClass(css.menu_sub_header);
  592. subMenu.append(header).append($('<li>').addClass(css.menu_divider));
  593. el.resultArea.append(subMenu);
  594. buildMenu(row.menus, subMenu);
  595. }
  596. li.prepend(a);
  597. if(row.disabled) li.addClass(css.menu_disabled);
  598. if(row.header) li.addClass(css.menu_header);
  599. }
  600. ul.append(li);
  601. });
  602. if(!ul.hasClass(css.menu_sub_menu)) ul.show();
  603. };
  604. el.resultArea.find('ul.'+css.results+':not(.'+css.menu_root+')').remove();
  605. buildMenu(d, el.results);
  606. //sub menus event bind
  607. el.resultArea.find('a.'+css.menu_sub_item).off('click.SelectMenu').on('click.SelectMenu', function(e){
  608. e.preventDefault();
  609. e.stopPropagation();
  610. var $this = $(this),
  611. $menu = $this.closest('ul.'+css.results),
  612. $subMenu = $('#'+$this.attr('item_code'));
  613. if($subMenu.size()){
  614. $menu.hide();
  615. /*
  616. $subMenu.css({ marginLeft: 60 }).show().animate({
  617. marginLeft: 0
  618. },100);
  619. */
  620. $subMenu.addClass('vivify fadeInRight').show();
  621. }
  622. });
  623. //back button
  624. el.resultArea.find('button.'+css.btn_back).off('click.SelectMenu').on('click.SelectMenu', function(e){
  625. var $btn = $(this),
  626. $menu = $btn.closest('ul'),
  627. $parentMenu = $('a[item_code="'+$menu.attr('id')+'"]').closest('ul');
  628. $menu.hide();
  629. $parentMenu.addClass('vivify fadeInLeft').show();
  630. });
  631. showMenu.call(self);
  632. }
  633. };
  634. /**
  635. * Show menu
  636. * @param self
  637. */
  638. SelectMenu.prototype.showMenu = function(self){
  639. self.populate();
  640. if($(self.target).is('button'))
  641. $(self.target).addClass(self.css_class.target_clicked);
  642. };
  643. /**
  644. * Set menu item to selected
  645. * @param self
  646. * @param list - datasource
  647. */
  648. SelectMenu.prototype.setInitSelected = function(self, list){
  649. var p = self.option;
  650. if($.type(p.initSelected) !== 'undefined' &&
  651. !p.regular && list && $.isArray(list) && list.length){
  652. var str = String(p.initSelected),arr = str.split(',');
  653. var matchItem = function(dataList){
  654. $.each(dataList, function(i,row){
  655. var id = String(row[p.keyField]);
  656. if(id && $.inArray(id,arr) !== -1) self.prop.values.push(row);
  657. });
  658. };
  659. if(self.prop.data_type === SelectMenu.dataTypeList){
  660. matchItem(list);
  661. }else if(self.prop.data_type === SelectMenu.dataTypeGroup){
  662. $.each(list, function(i,group){
  663. group && group.list && group.list.length && matchItem(group.list);
  664. });
  665. }
  666. p.initSelected = undefined;
  667. }
  668. };
  669. /**
  670. * Menu frame event handle
  671. */
  672. SelectMenu.prototype.eInput = function() {
  673. var self = this,p = this.option,el = self.elem;
  674. if(!p.regular && p.search){
  675. el.input.keyup(function(e) {
  676. self.processKey(self, e);
  677. }).keydown(function(e){
  678. self.processControl(self, e);
  679. });
  680. }
  681. if(p.title){
  682. if(!p.embed){
  683. el.closeButton.click(function(e){
  684. self.hideResults(self);
  685. });
  686. }
  687. if(!p.regular){
  688. if(p.multiple){
  689. el.selectAllButton.click(function(e){
  690. e.stopPropagation();
  691. self.selectAllLine(self);
  692. });
  693. el.removeAllButton.click(function(e){
  694. e.stopPropagation();
  695. self.clearAll(self);
  696. });
  697. }
  698. }
  699. }
  700. if(!p.regular && self.prop.data_type === SelectMenu.dataTypeGroup){
  701. el.resultTabs.off('click.selectMenu').on('click.selectMenu', 'a', function(e){
  702. e.stopPropagation();
  703. if(!$(this).hasClass('active')){
  704. var li = $(this).closest('li');
  705. li.siblings().children('a').removeClass('active');
  706. $(this).addClass('active');
  707. self.prop.data_index = parseInt($(this).attr('data_index'));
  708. self.populate();
  709. if(p.eTabSwitch && $.isFunction(p.eTabSwitch)){
  710. var currentGroup = $.extend({}, self.prop.data[self.prop.data_index]);
  711. //cut the list item
  712. delete currentGroup.list;
  713. p.eTabSwitch.call(this, self.prop.data_index, currentGroup);
  714. }
  715. }
  716. });
  717. }
  718. if(p.rightClick){
  719. $(self.target).on('contextmenu',function(e){
  720. e.preventDefault();
  721. e.stopPropagation();
  722. e.cancelBubble = true;
  723. e.returnValue = false;
  724. var scrollX = document.documentElement.scrollLeft || document.body.scrollLeft,
  725. scrollY = document.documentElement.scrollTop || document.body.scrollTop;
  726. self.prop.x = e.pageX || e.clientX + scrollX;
  727. self.prop.y = e.pageY || e.clientY + scrollY;
  728. if(!self.isVisible(self)) self.populate();
  729. else self.calcResultsSize(self);
  730. return false;
  731. }).mouseup(function(e){
  732. if(e.button != 2) self.hideResults(self);
  733. });
  734. self.hideResults(self);
  735. }
  736. };
  737. /**
  738. * Out of menu event bind
  739. */
  740. SelectMenu.prototype.eWhole = function() {
  741. var self = this, css = this.css_class;
  742. $(document).off('mouseup.selectMenu').on('mouseup.selectMenu',function(e) {
  743. var srcEl = e.target || e.srcElement,
  744. sm = $(srcEl).closest('div.' + css.container);
  745. //out of menu area click, when menu is opened , hide it
  746. $('div.' + css.container + '.' + css.container_open).each(function(){
  747. var d = $(this).data(SelectMenu.dataKey);
  748. if(this == sm[0] || d.target == srcEl || $(srcEl).closest(d.target).size()) return;
  749. d.hideResults(d);
  750. });
  751. });
  752. };
  753. /**
  754. * Menu item event bind
  755. */
  756. SelectMenu.prototype.eResultList = function() {
  757. var self = this,p = this.option,el = self.elem;
  758. self.elem.results.children('li').mouseenter(function() {
  759. if (self.prop.key_select) {
  760. self.prop.key_select = false;
  761. return;
  762. }
  763. if(!$(this).hasClass('sm_message_box')) $(this).addClass(self.css_class.select);
  764. }).mouseleave(function(){
  765. $(this).removeClass(self.css_class.select);
  766. }).click(function(e) {
  767. if (self.prop.key_select) {
  768. self.prop.key_select = false;
  769. return;
  770. }
  771. e.preventDefault();
  772. e.stopPropagation();
  773. self.selectCurrentLine(self, false);
  774. });
  775. };
  776. /**
  777. * Reposition result list when list beyond the visible area
  778. */
  779. SelectMenu.prototype.eScroll = function(){
  780. var self = this, css = this.css_class;
  781. $(window).on('scroll.SelectMenu',function(e){
  782. $('div.' + css.container + '.' + css.container_open).each(function(){
  783. var d = $(this).data(SelectMenu.dataKey),
  784. offset = d.elem.container.offset(),
  785. screenScrollTop = $(window).scrollTop(),
  786. docHeight = $(document).height(),//the document full height
  787. viewHeight = $(window).height(),//browser visible area height
  788. menuHeight = d.elem.container.outerHeight(),
  789. menuBottom = offset.top + menuHeight,
  790. hasOverflow = docHeight > viewHeight,
  791. down = d.elem.container.hasClass(css.direction_bottom);
  792. if(hasOverflow){
  793. if(down){//show down
  794. if(menuBottom > (viewHeight + screenScrollTop)) d.calcResultsSize(d);
  795. }else{//show up
  796. if(offset.top < screenScrollTop) d.calcResultsSize(d);
  797. }
  798. }
  799. });
  800. });
  801. };
  802. /**
  803. * Closing work
  804. * @param {Object} self
  805. */
  806. SelectMenu.prototype.atLast = function(self){
  807. if(!self) self = this;
  808. var p = self.option;
  809. if(p.search && !p.regular && !p.embed && !p.rightClick) self.elem.input.focus();
  810. self.elem.container.data(SelectMenu.dataKey,self);
  811. if($(self.target).is('button,.btn') && !p.embed && !p.rightClick)
  812. $(self.target).addClass(self.css_class.target_clicked);
  813. }
  814. /**
  815. * Ajax request fail
  816. * @param {Object} self
  817. * @param {string} errorThrown
  818. */
  819. SelectMenu.prototype.ajaxErrorNotify = function(self, errorThrown) {
  820. self.showMessage(self.message.ajax_error);
  821. };
  822. /**
  823. * Show some message
  824. * @param {Object} self
  825. * @param msg {string}
  826. */
  827. SelectMenu.prototype.showMessage = function(self,msg){
  828. if(!msg) return;
  829. var msgLi = '<li class="sm_message_box"><i class="iconfont icon-warn"></i> '+msg+'</li>';
  830. self.elem.results.empty().append(msgLi);
  831. self.calcResultsSize(self);
  832. self.elem.container.addClass(self.css_class.container_open);
  833. self.elem.control.hide();
  834. };
  835. /**
  836. * Check input to search
  837. * @param {Object} self
  838. */
  839. SelectMenu.prototype.checkValue = function(self) {
  840. var now_value = self.elem.input.val();
  841. if (now_value != self.prop.prev_value) {
  842. self.prop.prev_value = now_value;
  843. self.suggest(self);
  844. }
  845. };
  846. /**
  847. * Input element event handle( regular letter )
  848. * @param {Object} self
  849. * @param {Object} e - event
  850. */
  851. SelectMenu.prototype.processKey = function(self, e){
  852. if($.inArray(e.keyCode, [38, 40, 27, 9, 13]) === -1){
  853. //if(e.keyCode != 16) self.setCssFocusedInput(self); // except Shift(16)
  854. if($.type(self.option.data) === 'string'){
  855. self.prop.last_input_time = e.timeStamp;
  856. setTimeout(function(){
  857. if((e.timeStamp - self.prop.last_input_time) === 0)
  858. self.checkValue(self);
  859. },self.option.inputDelay * 1000);
  860. }else{
  861. self.checkValue(self);
  862. }
  863. }
  864. }
  865. /**
  866. * Input element event handle( control key )
  867. * @param {Object} self
  868. * @param {Object} e - event
  869. */
  870. SelectMenu.prototype.processControl = function(self, e) {
  871. if (($.inArray(e.keyCode, [38, 40, 27, 9]) > -1 && self.elem.container.is(':visible')) ||
  872. ($.inArray(e.keyCode, [13, 9]) > -1 && self.getCurrentLine(self))) {
  873. e.preventDefault();
  874. e.stopPropagation();
  875. e.cancelBubble = true;
  876. e.returnValue = false;
  877. switch (e.keyCode) {
  878. case 38:// up
  879. self.prop.key_select = true;
  880. self.prevLine(self);
  881. break;
  882. case 40:// down
  883. if (self.elem.results.children('li').length) {
  884. self.prop.key_select = true;
  885. self.nextLine(self);
  886. } else self.suggest(self);
  887. break;
  888. case 9: // tab
  889. self.selectCurrentLine(self, true);
  890. //self.hideResults(self);
  891. break;
  892. case 13:// return
  893. self.selectCurrentLine(self, true);
  894. break;
  895. case 27:// escape
  896. self.hideResults(self);
  897. break;
  898. }
  899. }
  900. };
  901. /**
  902. * Populate menu data
  903. */
  904. SelectMenu.prototype.populate = function() {
  905. var self = this, p = this.option;
  906. if(!p.regular) self.elem.input.val('');
  907. /**
  908. * 1.Process data source
  909. */
  910. if(p.data){
  911. if($.type(p.data) === 'array'){
  912. self.prop.data = p.data;
  913. }else if($.type(p.data) === 'function'){
  914. self.prop.data = p.data();
  915. }
  916. }
  917. //Check data type
  918. if($.type(self.prop.data) === 'array')
  919. this.prop.data_type = this.checkDataType(self.prop.data);
  920. /**
  921. * 2.Set menu init selected
  922. */
  923. if($.type(p.data) !== 'string') self.setInitSelected(self, self.prop.data);
  924. /**
  925. * 3.Show data
  926. */
  927. if(p.regular) self.regularMenuInit();
  928. else self.suggest(self);
  929. //scrolling listen
  930. if(!p.embed) self.eScroll();
  931. };
  932. /**
  933. * Search suggest
  934. * @param {Object} self
  935. */
  936. SelectMenu.prototype.suggest = function(self) {
  937. var q_word, p = self.option,
  938. val = $.trim(self.elem.input.val());
  939. if(p.multiple) q_word = val;
  940. else{
  941. if(val && val === self.prop.selected_text) q_word = '';
  942. else q_word = val;
  943. }
  944. q_word = q_word.split(/[\s ]+/);
  945. self.setLoading(self);
  946. if ($.type(p.data) === 'array' || $.type(p.data) === 'function') self.search(self, q_word);
  947. };
  948. /**
  949. * Loading
  950. * @param {Object} self
  951. */
  952. SelectMenu.prototype.setLoading = function(self) {
  953. if (self.elem.results.html() === '') {
  954. //self.calcResultsSize(self);
  955. if(!self.option.embed) self.elem.container.addClass(self.css_class.container_open);
  956. }
  957. };
  958. /**
  959. * Search / load menu data
  960. * @param {Object} self
  961. * @param {Array} q_word - query keywords
  962. */
  963. SelectMenu.prototype.search = function(self, q_word) {
  964. var p = self.option, innerData = self.prop.data,
  965. matched = [], esc_q = [], sorted = [], json = {}, i = 0, arr_reg = [];
  966. do {
  967. //'/\W/g'正则代表全部不是字母,数字,下划线,汉字的字符
  968. //将非法字符进行转义
  969. esc_q[i] = q_word[i].replace(/\W/g, '\\$&').toString();
  970. arr_reg[i] = new RegExp(esc_q[i], 'gi');
  971. i++;
  972. } while ( i < q_word.length );
  973. var d = [];
  974. if(self.prop.data_index > (innerData.length-1) || self.prop.data_index < 0) self.prop.data_index = 0;
  975. if(self.prop.data_type === SelectMenu.dataTypeGroup){
  976. d = innerData[self.prop.data_index].list;
  977. }else d = innerData;
  978. // SELECT * FROM data WHERE field LIKE q_word;
  979. for (i = 0; i < d.length; i++) {
  980. var flag = false;
  981. var row = d[i];
  982. for (var j = 0; j < arr_reg.length; j++) {
  983. var itemText = row[p.searchField];
  984. if(p.formatItem && $.isFunction(p.formatItem))
  985. itemText = p.formatItem(row);
  986. if (itemText.match(arr_reg[j])) {
  987. flag = true;
  988. if (p.andOr == 'OR') break;
  989. } else {
  990. flag = false;
  991. if (p.andOr == 'AND') break;
  992. }
  993. }
  994. if (flag) matched.push(row);
  995. }
  996. // (CASE WHEN ...) then く order some field
  997. var reg1 = new RegExp('^' + esc_q[0] + '$', 'gi'),
  998. reg2 = new RegExp('^' + esc_q[0], 'gi'),
  999. matched1 = [], matched2 = [], matched3 = [];
  1000. for (i = 0; i < matched.length; i++) {
  1001. var orderField = p.orderBy[0][0],
  1002. orderValue = String(matched[i][orderField]);
  1003. if (orderValue.match(reg1)) {
  1004. matched1.push(matched[i]);
  1005. } else if (orderValue.match(reg2)) {
  1006. matched2.push(matched[i]);
  1007. } else {
  1008. matched3.push(matched[i]);
  1009. }
  1010. }
  1011. if (p.orderBy[0][1].match(/^asc$/i)) {
  1012. matched1 = self.sortAsc(self, matched1);
  1013. matched2 = self.sortAsc(self, matched2);
  1014. matched3 = self.sortAsc(self, matched3);
  1015. } else {
  1016. matched1 = self.sortDesc(self, matched1);
  1017. matched2 = self.sortDesc(self, matched2);
  1018. matched3 = self.sortDesc(self, matched3);
  1019. }
  1020. sorted = sorted.concat(matched1).concat(matched2).concat(matched3);
  1021. /*
  1022. if (sorted.length === undefined || sorted.length === 0 ) {
  1023. self.notFoundSearch(self);
  1024. return;
  1025. }
  1026. */
  1027. //json.cnt_whole = sorted.length;
  1028. //Cache original row data
  1029. json.originalResult = [];
  1030. if(json.keyField === undefined) json.keyField = [];
  1031. if(json.candidate === undefined) json.candidate = [];
  1032. $.each(sorted, function(i,row){
  1033. if(row === undefined || $.type(row) !== 'object') return true;
  1034. json.originalResult.push(row);
  1035. if(row.hasOwnProperty(p.keyField) && row.hasOwnProperty(p.showField)){
  1036. json.keyField.push(row[p.keyField]);
  1037. json.candidate.push(row[p.showField]);
  1038. }
  1039. });
  1040. //json.cnt_page = json.candidate.length;
  1041. self.prepareResults(self, json, q_word);
  1042. };
  1043. /**
  1044. * Sort ascending
  1045. * @param {Object} self
  1046. * @param {Array} arr
  1047. */
  1048. SelectMenu.prototype.sortAsc = function(self, arr) {
  1049. arr.sort(function(a, b) {
  1050. var valA = a[self.option.orderBy[0][0]],
  1051. valB = b[self.option.orderBy[0][0]];
  1052. return $.type(valA) === 'number' ? valA - valB : String(valA).localeCompare(String(valB));
  1053. });
  1054. return arr;
  1055. };
  1056. /**
  1057. * Sort descending
  1058. * @param {Object} self
  1059. * @param {Array} arr
  1060. */
  1061. SelectMenu.prototype.sortDesc = function(self, arr) {
  1062. arr.sort(function(a, b) {
  1063. var valA = a[self.option.orderBy[0][0]],
  1064. valB = b[self.option.orderBy[0][0]];
  1065. return $.type(valA) === 'number' ? valB - valA : String(valB).localeCompare(String(valA));
  1066. });
  1067. return arr;
  1068. };
  1069. /**
  1070. * No result handle
  1071. * @param {Object} self
  1072. */
  1073. SelectMenu.prototype.notFoundSearch = function(self) {
  1074. self.elem.results.empty();
  1075. self.calcResultsSize(self);
  1076. self.elem.container.addClass(self.css_class.container_open);
  1077. self.setCssFocusedInput(self);
  1078. };
  1079. /**
  1080. * Prepare data to render menu item
  1081. * @param {Object} self
  1082. * @param {Object} json
  1083. * @param {Array} q_word - query keywords
  1084. */
  1085. SelectMenu.prototype.prepareResults = function(self, json, q_word) {
  1086. if (!json.keyField) json.keyField = false;
  1087. if (self.option.selectOnly &&
  1088. json.candidate.length === 1 &&
  1089. json.candidate[0] == q_word[0]) {
  1090. self.elem.hidden.val(json.keyField[0]);
  1091. this.setButtonAttrDefault();
  1092. }
  1093. var is_query = false;
  1094. if (q_word && q_word.length && q_word[0]) is_query = true;
  1095. //self.setInitSelected(self,json.originalResult);
  1096. self.displayResults(self, json, is_query);
  1097. };
  1098. /**
  1099. * Render menu item
  1100. * @param {Object} self
  1101. * @param {Object} json
  1102. * @param {boolean} is_query
  1103. */
  1104. SelectMenu.prototype.displayResults = function(self, json, is_query) {
  1105. var p = self.option, el = self.elem, css = self.css_class;
  1106. el.results.hide().empty();
  1107. // build tabs
  1108. if(self.prop.data_type === SelectMenu.dataTypeGroup) {
  1109. var ul = $('<ul>');
  1110. $.each(self.prop.data,function(i,row){
  1111. var a = $('<a href="javascript:void(0);">').html(row.title).attr({
  1112. 'tab_id' : self.prop.menu_tab_id_prefix + (i+1),
  1113. 'data_index' : i
  1114. });
  1115. if(i === self.prop.data_index) a.addClass('active');
  1116. var li = $('<li>').append(a);
  1117. ul.append(li);
  1118. });
  1119. el.resultTabs.empty().append(ul);
  1120. }else{
  1121. el.resultTabs.hide();
  1122. if(p.title || p.search) el.resultArea.addClass(this.css_class.re_list);
  1123. }
  1124. if(p.multiple && $.type(p.maxSelectLimit) === 'number' && p.maxSelectLimit){
  1125. var selectedSize = self.prop.results.length;
  1126. if(selectedSize && selectedSize >= p.maxSelectLimit){
  1127. var msg = self.message.max_selected;
  1128. self.showMessage(self, msg.replace(self.template.msg.maxSelectLimit, p.maxSelectLimit));
  1129. return;
  1130. }
  1131. }
  1132. if(json.candidate.length){
  1133. var arr_candidate = json.candidate, arr_primary_key = json.keyField;
  1134. for (var i = 0; i < arr_candidate.length; i++) {
  1135. var itemText = '', custom = false, row = json.originalResult[i];
  1136. if(p.formatItem && $.isFunction(p.formatItem)){
  1137. try {
  1138. itemText = p.formatItem(row);
  1139. custom = true;
  1140. } catch (e) {
  1141. console.error('formatItem 内容格式化函数内容设置不正确!');
  1142. itemText = arr_candidate[i];
  1143. }
  1144. }else itemText = arr_candidate[i];
  1145. var icon = $('<div>').html('<i class="iconfont icon-selected">').addClass(css.selected_icon),
  1146. text = $('<div>').html(itemText).addClass(css.item_text),
  1147. li = $('<li>').append(icon).append(text).attr('pkey' , arr_primary_key[i]);
  1148. if(!custom) li.attr('title',itemText);
  1149. //set selected item to highlight
  1150. if ($.inArray(row,self.prop.values) !== -1) {
  1151. li.addClass(css.selected);
  1152. }
  1153. //cache item data
  1154. li.data('dataObj',row);
  1155. el.results.append(li);
  1156. }
  1157. }else{
  1158. var li = '<li class="sm_message_box"><i class="iconfont icon-warn"></i> ' + self.message.not_found + '</li>';
  1159. el.results.append(li);
  1160. }
  1161. el.results.show();
  1162. self.calcResultsSize(self);
  1163. if(!p.embed) el.container.addClass(css.container_open);
  1164. //menu item event bind
  1165. self.eResultList();
  1166. //auto highlight first item in search, in have result and set autoSelectFirst to true situation
  1167. //if (is_query && json.candidate.length > 0 && p.autoSelectFirst) self.nextLine(self);
  1168. self.atLast(self);
  1169. };
  1170. /**
  1171. * Calculate menu position and size
  1172. * @param {Object} self
  1173. */
  1174. SelectMenu.prototype.calcResultsSize = function(self) {
  1175. var p = self.option, el = self.elem, css = self.css_class,
  1176. hasScroll = function(){
  1177. return $(document).height() > $(window).height();
  1178. };
  1179. var setListHeight = function(){
  1180. if(!p.regular){
  1181. //result list height
  1182. var itemHeight = el.results.find('li:first').outerHeight(),
  1183. listHeight = itemHeight * p.listSize;
  1184. el.results.css({
  1185. 'max-height':listHeight
  1186. });
  1187. }
  1188. };
  1189. var scrollFlag = hasScroll();
  1190. var rePosition = function(){
  1191. var menuHeight = el.container.outerHeight(),
  1192. screenScrollTop = $(window).scrollTop(),
  1193. viewHeight = $(window).height();
  1194. if(p.rightClick){
  1195. var top = self.prop.y;
  1196. if((self.prop.y + menuHeight) > (screenScrollTop + viewHeight))
  1197. top = self.prop.y - menuHeight;
  1198. return {top : top, left : self.prop.x};
  1199. }
  1200. var boxoffset = $(self.target).offset(),
  1201. t = boxoffset.top,
  1202. menuWidth = el.container.outerWidth(),
  1203. targetWidth = Math.round($(self.target)[0].getBoundingClientRect().width),
  1204. dist = 5;
  1205. t += $(self.target).outerHeight() + dist;
  1206. if(p.arrow && !p.embed) t += el.arrow.outerHeight(true);
  1207. if((t + menuHeight) > (screenScrollTop + viewHeight)){
  1208. t = boxoffset.top - dist - menuHeight;
  1209. if(p.arrow && !p.embed) t -= el.arrow.outerHeight(true);
  1210. el.container.removeClass(css.direction_bottom).addClass(css.direction_top);
  1211. }else{
  1212. if(el.container.hasClass(css.direction_top))
  1213. el.container.removeClass(css.direction_top).addClass(css.direction_bottom);
  1214. }
  1215. var l = boxoffset.left;
  1216. switch (p.position){
  1217. case 'right':
  1218. l = l + targetWidth - menuWidth;
  1219. if(p.arrow) el.arrow.css('left',menuWidth - (targetWidth / 2));
  1220. break;
  1221. case 'center':
  1222. l = l + (targetWidth / 2) - (menuWidth / 2);
  1223. break;
  1224. case 'left':
  1225. default:
  1226. if(p.arrow) el.arrow.css('left',targetWidth / 2);
  1227. break;
  1228. }
  1229. return {top : t,left : l};
  1230. }
  1231. if(el.container.is(':visible')){
  1232. setListHeight();
  1233. if(!p.embed) el.container.offset(rePosition());
  1234. }else{
  1235. el.container.show(1,function(){
  1236. setListHeight();
  1237. $(this).offset(rePosition());
  1238. });
  1239. }
  1240. if(scrollFlag !== hasScroll()) el.container.offset(rePosition());
  1241. };
  1242. /**
  1243. *
  1244. */
  1245. SelectMenu.prototype.subMenuPosition = function(parent, menu){
  1246. var pOffset = $(parent).offset();
  1247. var t = pOffset.top,l = pOffset.left + $(parent).outerWidth() + 5;
  1248. };
  1249. /**
  1250. * Hide menu
  1251. * @param {Object} self
  1252. */
  1253. SelectMenu.prototype.hideResults = function(self) {
  1254. var p = self.option;
  1255. if (p.autoFillResult) {
  1256. //self.selectCurrentLine(self, true);
  1257. }
  1258. if(!p.regular) self.elem.results.empty();
  1259. if(!p.embed){
  1260. self.elem.container.removeClass(self.css_class.container_open).hide();
  1261. if($(self.target).is('button,.btn')) $(self.target).removeClass(self.css_class.target_clicked);
  1262. }
  1263. self.elem.resultArea.find('ul.'+self.css_class.results).not('.'+self.css_class.menu_root).hide();
  1264. //remove animate class
  1265. self.elem.results.removeClass('vivify').removeClass('fadeInLeft').show();
  1266. $(window).off('scroll.SelectMenu');
  1267. if(!p.regular && p.eHidden && $.isFunction(p.eHidden)) p.eHidden.call(self, self.prop.values.concat());
  1268. };
  1269. /**
  1270. * do something after select/unSelect action
  1271. * @param {Object} self
  1272. */
  1273. SelectMenu.prototype.afterAction = function(self){
  1274. //$(self.elem.input).change();
  1275. if(self.option.multiple){
  1276. if(self.option.selectToCloseList){
  1277. self.hideResults(self);
  1278. self.elem.input.blur();
  1279. }else{
  1280. //self.suggest(self);
  1281. self.elem.input.focus();
  1282. }
  1283. }else{
  1284. self.hideResults(self);
  1285. self.elem.input.blur();
  1286. }
  1287. };
  1288. /**
  1289. * Get current menu item
  1290. * @param {Object} self
  1291. */
  1292. SelectMenu.prototype.getCurrentLine = function(self) {
  1293. if (self.elem.container.is(':hidden')) return false;
  1294. var obj = self.elem.results.find('li.' + self.css_class.select);
  1295. if (obj.size()) return obj;
  1296. else return false;
  1297. };
  1298. /**
  1299. * Get selected menu item
  1300. * @param self
  1301. * @returns {*}
  1302. */
  1303. SelectMenu.prototype.getSelectedLine = function(self) {
  1304. if (self.elem.container.is(':hidden')) return false;
  1305. var obj = self.elem.results.find('li.' + self.css_class.selected);
  1306. if (obj.size()) return obj;
  1307. else return false;
  1308. };
  1309. /**
  1310. * Selected menu item and trigger select callback
  1311. * @param {Object} self
  1312. * @param {boolean} is_enter_key
  1313. */
  1314. SelectMenu.prototype.selectCurrentLine = function(self, is_enter_key) {
  1315. var current = self.getCurrentLine(self), p = self.option;
  1316. if (current) {
  1317. var rowData = current.data('dataObj'),
  1318. id = String(rowData[p.keyField]);
  1319. if($.inArray(rowData,self.prop.values) === -1){
  1320. if(!p.multiple) self.prop.values.splice(0,self.prop.values.length);
  1321. self.prop.values.push(rowData);
  1322. if(!p.multiple)
  1323. self.elem.results.find('li.' + self.css_class.selected).removeClass(self.css_class.selected);
  1324. current.addClass(self.css_class.selected);
  1325. } else{
  1326. self.prop.values.splice($.inArray(rowData,self.prop.values),1);
  1327. current.removeClass(self.css_class.selected);
  1328. }
  1329. //trigger callback
  1330. if(p.eSelect && $.isFunction(p.eSelect)){
  1331. if(p.multiple){
  1332. p.eSelect.call(self, self.prop.values.concat());
  1333. }else p.eSelect.call(self, [rowData]);
  1334. }
  1335. self.prop.prev_value = self.elem.input.val();
  1336. self.prop.selected_text = self.elem.input.val();
  1337. }
  1338. if(!p.embed) self.afterAction(self);
  1339. };
  1340. /**
  1341. * Select all menu item
  1342. * @param {Object} self
  1343. */
  1344. SelectMenu.prototype.selectAllLine = function(self){
  1345. self.elem.results.find('li').each(function(i,row){
  1346. var d = $(row).data('dataObj');
  1347. if($.inArray(d,self.prop.values) === -1) self.prop.values.push(d);
  1348. $(this).addClass(self.css_class.selected);
  1349. //limited max select items
  1350. /*
  1351. if($.type(self.option.maxSelectLimit) === 'number' &&
  1352. self.option.maxSelectLimit > 0 &&
  1353. self.option.maxSelectLimit === $('li.selected_tag',self.elem.element_box).size()){
  1354. return false;
  1355. }
  1356. */
  1357. });
  1358. if(self.option.eSelect && $.isFunction(self.option.eSelect))
  1359. self.option.eSelect.call(self, self.prop.values.concat());
  1360. self.afterAction(self);
  1361. };
  1362. /**
  1363. * Clear all selected menu items
  1364. * @param {Object} self
  1365. */
  1366. SelectMenu.prototype.clearAll = function(self){
  1367. var p = self.option, el = self.elem;
  1368. el.input.val('');
  1369. el.results.find('li').each(function(i,row){
  1370. $(this).removeClass(self.css_class.selected);
  1371. });
  1372. self.prop.values.splice(0,self.prop.values.length);
  1373. self.afterAction(self);
  1374. if (p.eSelect && $.isFunction(p.eSelect)) p.eSelect.call(self, []);
  1375. };
  1376. /**
  1377. * Select next menu item
  1378. * @param {Object} self
  1379. */
  1380. SelectMenu.prototype.nextLine = function(self) {
  1381. var obj = self.getCurrentLine(self), el = self.elem, idx;
  1382. if (!obj) idx = -1;
  1383. else {
  1384. idx = el.results.children('li').index(obj);
  1385. obj.removeClass(self.css_class.select);
  1386. }
  1387. idx++;
  1388. var size = el.results.find('li').size();
  1389. if(idx === size) idx = size - 1;
  1390. if (idx < size) {
  1391. var next = el.results.children('li').eq(idx);
  1392. next.addClass(self.css_class.select);
  1393. var itemHeight = el.results.find('li:first').outerHeight(),
  1394. curTop = next.position().top,
  1395. curScrollTop = el.resultArea.scrollTop(),
  1396. listHeight = el.resultArea.outerHeight(),
  1397. dist = curTop + itemHeight - listHeight;
  1398. if((curTop + itemHeight) > listHeight)
  1399. el.resultArea.scrollTop(curScrollTop + dist);
  1400. }
  1401. };
  1402. /**
  1403. * Select previous menu item
  1404. * @param {Object} self
  1405. */
  1406. SelectMenu.prototype.prevLine = function(self) {
  1407. var el = self.elem, idx, obj = self.getCurrentLine(self);
  1408. if (!obj) idx = el.results.children('li').length;
  1409. else {
  1410. idx = el.results.children('li').index(obj);
  1411. obj.removeClass(self.css_class.select);
  1412. }
  1413. idx--;
  1414. if(idx < 0) idx = 0;
  1415. if (idx > -1) {
  1416. var prev = el.results.children('li').eq(idx),
  1417. itemHeight = el.results.find('li:first').outerHeight(),
  1418. curTop = prev.position().top,
  1419. curScrollTop = el.resultArea.scrollTop(),
  1420. listHeight = el.resultArea.outerHeight();
  1421. prev.addClass(self.css_class.select);
  1422. if((curTop > (curScrollTop + listHeight)) || (curTop < curScrollTop))
  1423. el.resultArea.scrollTop(curScrollTop - (0 - curTop));
  1424. }
  1425. };
  1426. /**
  1427. * Check menu visible
  1428. * @param self
  1429. */
  1430. SelectMenu.prototype.isVisible = function(self){
  1431. return self.elem.container.hasClass(self.css_class.container_open);
  1432. }
  1433. /**
  1434. * Init plugin entrance
  1435. * @global
  1436. * @memberof jQuery
  1437. * @param option {Object} init parameters
  1438. */
  1439. function Plugin(option) {
  1440. return this.each(function(){
  1441. var $this = $(this),
  1442. data = $this.data(SelectMenu.dataKey),
  1443. params = $.extend({}, defaults, $this.data(), data && data.option ,typeof option === 'object' && option);
  1444. if(!data) $this.data(SelectMenu.dataKey,(data = new SelectMenu(this,params)));
  1445. else{
  1446. if(data.isVisible(data)) data.hideResults(data);
  1447. else data.showMenu(data);
  1448. }
  1449. });
  1450. }
  1451. /**
  1452. * Hide menu
  1453. */
  1454. function HideMenu(){
  1455. return this.each(function(){
  1456. var $this = $(this),
  1457. data = $this.data(SelectMenu.dataKey);
  1458. if(data) data.hideResults(data);
  1459. });
  1460. }
  1461. /**
  1462. * Clear all menu selected item
  1463. */
  1464. function ClearSelected(){
  1465. return this.each(function(){
  1466. var $this = $(this),
  1467. data = $this.data(SelectMenu.dataKey);
  1468. if(data) data.clearAll(data);
  1469. });
  1470. }
  1471. /**
  1472. * Get selected item data
  1473. */
  1474. function GetSelected(){
  1475. var results = new Array();
  1476. this.each(function(){
  1477. var $this =$(this),
  1478. data = $this.data(SelectMenu.dataKey);
  1479. if(data) results = data.prop.values.concat();
  1480. });
  1481. return results;
  1482. }
  1483. var old = $.fn.selectMenu;
  1484. $.fn.selectMenu = Plugin;
  1485. $.fn.selectMenu.Constructor = SelectMenu;
  1486. $.fn.selectMenuHide = HideMenu;
  1487. $.fn.selectMenuClear = ClearSelected;
  1488. $.fn.selectMenuValues = GetSelected;
  1489. // SelectMenu no conflict
  1490. // =================
  1491. $.fn.selectMenu.noConflict = function () {
  1492. $.fn.selectMenu = old;
  1493. return this;
  1494. };
  1495. })(window.jQuery);