/*! * ZUI: 下拉选择器 - v1.10.0 - 2021-11-04 * http://openzui.com * GitHub: https://github.com/easysoft/zui.git * Copyright (c) 2021 cnezsoft.com; Licensed MIT */ /* ======================================================================== * ZUI: picker.js * http://openzui.com * ======================================================================== * Copyright (c) 2020 cnezsoft.com; Licensed MIT * ======================================================================== */ /** * TODO: * * 优化展开时滚动到选中项体验 * * 不要在 picker 兼容模式移除 option * * picker-search 和 input 都会触发 change 事件,希望搜索框作为辅助控件不要触发通用的 change 事件 */ (function($, window, document) { 'use strict'; var NAME = 'zui.picker'; // model name var SHOWS = {}; var DEFAULTS = { lang: null, remote: null, // {url: '/picker/fetch?search={search}', method: 'GET'} or '/picker/fetch?search={search}&limit={limit}' or function({search, limit}, callback), remoteConverter: null, // function(responseData, textStatus, jqXHR, picker) remoteOnly: false, onRemoteError: null, // function() disableEmptySearch: false, textKey: 'text', valueKey: 'value', keysKey: 'keys', multi: 'auto', // true, false, 'auto' formItem: 'auto', list: null, // [{text: 'Apple', value: 'apple', keys: 'fruit foods'}, {text: 'Banana', value: 'banana', keys: 'fruit foods'}] or 'Apple,Banana' or [['Apple', 'apple', 'fruit foods'], ['Banana', 'banana', 'fruit foods']] or function({search, limit}) allowSingleDeselect: null, showMultiSelectedOptions: false, autoSelectFirst: false, // optionItemFormatter: null, // function($item, picker); Not supported yet maxSelectedCount: 0, // 0 = Infinity maxListCount: 50, // 0 = Infinity hideEmptyTextOption: true, searchValueKey: true, emptyResultHint: null, hideOnWindowScroll: true, inheritFormItemClasses: false, emptySearchResultHint: null, accurateSearchHint: null, remoteErrorHint: null, deleteByBackspace: true, disableScrollOnShow: true, // placeholder: undefined, maxDropHeight: 250, dropDirection: 'auto', dropWidth: '100%', // 'auto', '100%', '500px', maxAutoDropWidth: 450, multiValueSplitter: ',', searchDelay: 200, autoClearDrop: 6e4, // 60 * 1000 fixLabelFor: true, hotkey: true, // sortValuesByDnd: false, // defaultValue: null, onSelect: null, // function({value, picker}), onDeselect: null, // function({value, picker}), onBeforeChange: null, // function(newValue, oldValue), onChange: null, // function(newValue, oldValue), onReady: null, // function(picker), onNoResults: null, // function(search), onShowingDrop: null, // function onHidingDrop: null, // function onShowedDrop: null, // function onHiddenDrop: null, // function valueMustInList: true, }; var LANG = { zh_cn: { emptyResultHint: '没有可选项', emptySearchResultHint: '没有找到 “{0}”', accurateSearchHint: '请提供更多关键词缩小匹配范围', remoteErrorHint: '无法从服务器获取结果 - {0}', }, zh_tw: {             emptyResultHint: '沒有可選項',             emptySearchResultHint: '沒有找到 “{0}”',             accurateSearchHint: '請提供更多關鍵詞縮小匹配範圍',             remoteErrorHint: '無法從服務器獲取結果 - {0}', }, en: {             emptyResultHint: 'No options',             emptySearchResultHint: 'Cannot found "{0}"',             accurateSearchHint: 'Suggest to provide more keywords',             remoteErrorHint: 'Unable to get result from server: {0}', } }; // The picker model class var Picker = function(element, options) { var that = this; that.name = NAME; that.$ = $(element); that.id = 'pk_' + (that.$.attr('id') || $.zui.uuid()); // Options options = that.options = $.extend({}, Picker.DEFAULTS, this.$.data(), options); // Lang var defaultLang = $.zui.clientLang ? $.zui.clientLang() : 'en'; var lang = options.lang || defaultLang; that.lang = $.zui.getLangData ? $.zui.getLangData(NAME, lang, LANG) : (LANG[lang] || LANG[defaultLang]); // Form var $formItem; var formItem = options.formItem; var formType; var supportFormItems = '.form-item,input[type="hidden"],select,input[type="text"]'; if (formItem === 'self') { $formItem = that.$; } else if (formItem === 'auto' || !formItem) { if(that.$.is(supportFormItems)) { $formItem = that.$; } else { $formItem = that.$.find(supportFormItems).first(); } } else { $formItem = that.$.find(formItem); } if (!$formItem.length) { return console.error && console.error('Cannot found form item for picker.'); } if ($formItem.is('input[type="hidden"]')) formType = 'hidden'; else if ($formItem.is('select')) formType = 'select'; else if ($formItem.is('input[type="text"]')) formType = 'text'; else { return console.error && console.error('Unknown form type for picker.'); } if (options.inheritFormItemClasses) { $container.addClass($formItem.attr('class')); } that.formType = formType; that.$formItem = $formItem.removeClass('picker').hide(); that.selfFormItem = $formItem.is(that.$); // Multi or single var multi = options.multi; if(!multi || multi === 'auto') { multi = formType === 'select' && $formItem.attr('multiple') === 'multiple'; } multi = !!multi; that.multi = multi; // Init list var list = options.list; if (list) { that.setList(typeof list === 'function' ? list({search: that.search, limit: options.maxListCount}) : list, true); } else if (formType === 'select') { that.updateFromSelect(); } else { that.setList([], true); } // Create dom elements var $container; if (!that.selfFormItem && that.$.hasClass('picker')) { $container = that.$; } else { $container = $('
').insertAfter(that.$); } $container.addClass('picker').toggleClass('picker-multi', multi).toggleClass('picker-single', !multi); var $selections = $container.children('.picker-selections'); if ($selections.length) { $selections.empty(); } else { $selections = $('
'); } var searchID = that.id + '-search'; var $search = $('').appendTo($selections); if (!multi) { var $singleSelection = $('
'); if (options.allowSingleDeselect) { $singleSelection.append(''); } $singleSelection.appendTo($selections); that.$singleSelection = $singleSelection; } $container.toggleClass('picker-input-empty', !$search.val().length).append($selections); that.$container = $container; that.$selections = $selections; that.$search = $search; that.search = ''; // Init placeholder var placeholder = options.placeholder; if (placeholder === undefined) { placeholder = $formItem.attr('placeholder'); } if (typeof placeholder === 'string' && placeholder.length) { $selections.append($('
').text(placeholder)); } options.placeholder = placeholder; // Fix label for attribute if (options.fixLabelFor) { var formItemID = $formItem.attr('id'); if (formItemID) { $('label[for="' + formItemID + '"]').attr('for', searchID); } } // Init values and sync default value var defaultValue = options.defaultValue !== undefined ? options.defaultValue : $formItem.val(); that.setValue(defaultValue, true); // Bind events $search.on('focus', function() { $container.addClass('picker-focus'); that.showDropList(); }).on('blur', function() { $container.removeClass('picker-focus'); }).on('input change', function() { var searchValue = $search.val(); if (multi) { $search.width(searchValue.length * 14); } $container.toggleClass('picker-input-empty', !searchValue.length); that.tryUpdateList(searchValue); }); if (options.hotkey) { $search.on('keydown', function(e) { var key = e.key || e.which; if (!that.dropListShowed) { return; } var activeValue = that.activeValue; var hasActiveValue = typeof activeValue === 'string'; if (key === 'Enter' || key === 13) { if (hasActiveValue) { that.select(activeValue, multi); if (multi) { that.$search.val(''); that.tryUpdateList(''); } else { $search.blur(); } e.preventDefault(); } } else if (key === 'ArrowDown' || key === 40) { var $activeOption = that.$activeOption; var $nextOption; if ($activeOption) { $nextOption = $activeOption.next('.picker-option'); if (multi) { while ($nextOption.length && $nextOption.hasClass('picker-option-selected')) { $nextOption = $nextOption.next('.picker-option'); } } } if (!$nextOption || !$nextOption.length) { $nextOption = that.$optionsList.children(multi ? '.picker-option:not(.picker-option-selected)' : '.picker-option').first(); } if ($nextOption.length) { that.activeOption($nextOption); } e.preventDefault(); } else if (key === 'ArrowUp' || key === 30) { var $activeOption = that.$activeOption; var $prevOption; if ($activeOption) { $prevOption = $activeOption.prev('.picker-option'); if (multi) { while ($prevOption.length && $prevOption.hasClass('picker-option-selected')) { $prevOption = $prevOption.prev('.picker-option'); } } } if (!$prevOption || !$prevOption.length) { $prevOption = that.$optionsList.children(multi ? '.picker-option:not(.picker-option-selected)' : '.picker-option').last(); } if ($prevOption.length) { that.activeOption($prevOption); } e.preventDefault(); } else if (options.deleteByBackspace && multi && (key === 'Backspace' || key === 8) && that.value && that.value.length && !$search.val().length) { that.deselect(that.value[that.value.length - 1]); } }); } if (multi) { $selections.on('mousedown', function(e) { if (that.dropListShowed) { e.preventDefault(); e.stopPropagation(); return; } }).on('mouseup', function(e) { if (!$selections.hasClass('sortable-sorting') && !$(e.target).closest('.picker-selection-remove').length && !that.dropListShowed) { that.focus(); } }); var sortValuesByDnd = options.sortValuesByDnd; if (sortValuesByDnd && $.fn.sortable) { $container.addClass('picker-sortable'); var sortableOptions = { selector: '.picker-selection', stopPropagation: true, start: function() { that.hideDropList(); }, finish: function(e) { var values = []; $.each(e.list, function(_, ele) { values.push(ele.item.data('value')); }); that.setValue(values.slice(), false, true); } }; if (typeof sortValuesByDnd === 'object') { $.extend(sortableOptions, sortValuesByDnd); } $selections.sortable(sortableOptions); } } $selections.on('click', '.picker-selection-remove', function(e) { if (that.multi) { var $selection = $(this).closest('.picker-selection'); that.deselect($selection.data('value')); } else { that.deselect(); } e.stopPropagation(); }); // Compatible with Chosen $formItem.on('chosen:updated', function() { that.updateFromSelect(); that.setValue($formItem.val(), true); that.updateList(); }) .on('chosen:activate', that.focus) .on('chosen:open', that.showDropList) .on('chosen:close', that.hideDropList); $container.addClass('picker-ready'); setTimeout(function() { that.triggerEvent('ready', {picker: that}, '', 'chosen:ready'); }, 0); }; Picker.prototype.destroy = function() { var that = this; var options = that.options; that.hideDropList(); var $search = that.$search; $search.off('focus blur input change'); if (options.hotkey) { $search.off('keydown'); } $search.remove(); var $selections = that.$selections; $selections.off('click'); if (that.multi) { $selections.off('mousedown mouseup'); } $selections.remove(); var $formItem = that.$formItem; if (that.selectOptionsBackup) { $formItem.empty(); $.each(that.selectOptionsBackup, function(_, item) { var attrs = {value: item[options.valueKey]}; var keys = item[options.keysKey]; if (keys !== undefined) { attrs['data-' + options.keysKey] = keys; } $formItem.append($('