new-ipicker
Version:
无任何依赖的轻量级省市区多级联动组件
1,134 lines (1,031 loc) • 57.5 kB
JavaScript
/* !
* iPicker v4.0.4
* Copyright (C) 2020-present, ZG
* Released under the MIT license.
*/
!( ( global, factory ) => {
typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() :
typeof define === "function" && define.amd ? define( factory ) :
( global = global || self, global.iPicker = factory() );
} )( typeof window !== "undefined" ? window : this, () => {
"use strict";
// 工具
const util = {
// 检测类型
type: obj => Object.prototype.toString.call( obj ).slice( 8, -1 ).toLowerCase(),
// 检测是否为不为空的纯对象
isNotEmptyPlainObject: obj => !!( util.type( obj ) === "object" && Object.keys( obj ).length ),
// 检测是否为大于或等于零的安全整数
isCorrectNumber: ( obj, zero ) => !!( Number.isSafeInteger( obj ) && ( !zero ? obj > 0 : obj >= 0 ) ),
// 检测是否为函数
isFunction: obj => util.type( obj ) === "function",
// 检测是否为 Promise
isPromise: obj => util.type( obj ) === "promise",
// 创建唯一标识符
uid ( useSymbol ) {
const random = Math.random().toString( 36 ).slice( 2, 10 );
return useSymbol ? Symbol( random ) : random;
},
// 定时器
delay ( delay = 0 ) {
return new Promise( resolve => {
let timer = window.setTimeout( () => {
window.clearTimeout( timer );
timer = null;
resolve();
}, delay )
} )
},
// 合并参数
mergeParam ( params = {}, defaultParams ) {
const result = {};
for ( const key in defaultParams ) {
const v = params[ key ];
if ( util.type( v ) === "object" ) {
result[ key ] = util.mergeParam( v, defaultParams[ key ] );
} else {
result[ key ] = v === 0 ? v : ( v || defaultParams[ key ] );
}
}
return result;
}
};
// DOM 系统
const $ = ( () => {
function buildDom ( domArray ) {
const length = domArray[ 0 ] ? domArray.length : 0;
this.length = length;
for ( let i = 0; i < length; i++ ) {
this[ i ] = domArray[ i ];
}
return this;
}
buildDom.prototype = {
each ( callback ) {
for ( let i = 0, j = this.length; i < j; i++ ) {
callback.call( this[ i ], i, this[ i ] );
}
return this;
},
get ( index = 0 ) {
return this[ index ];
},
click ( callback ) {
return this.each( function () {
this.addEventListener( "click", function ( event ) {
callback.call( this, event );
} )
} )
},
hasClass ( className ) {
return this[ 0 ].classList.contains( className );
},
addClass ( className ) {
return this.each( function () {
for ( const name of className.split( " " ) ) {
this.classList.add( name );
}
} )
},
removeClass ( className ) {
return this.each( function () {
for ( const name of className.split( " " ) ) {
this.classList.remove( name );
}
} )
},
toggleClass ( className ) {
return this.each( function () {
for ( const name of className.split( " " ) ) {
this.classList.toggle( name );
}
} )
},
css ( name, value ) {
function getStyle ( elem, prop ) {
return document.defaultView.getComputedStyle( elem, null ).getPropertyValue( prop );
}
function setStyle ( el, _name, _value ) {
el.style[ _name ] = _value;
}
return ( typeof name === "string" && !value ) ?
getStyle( this[ 0 ], name ) :
this.each( function () {
if ( name && value ) {
setStyle( this, name, value );
}
if ( util.isNotEmptyPlainObject( name ) && !value ) {
for ( const key in name ) {
setStyle( this, key, name[ key ] );
}
}
} )
},
html ( html ) {
return this.each( function () {
this.innerHTML = html;
} )
},
text () {
return this[ 0 ].textContent;
},
val ( val ) {
return this.each( function () {
this.value = val;
} )
},
eq ( index ) {
if ( typeof index === "number" ) {
const eqArr = [];
if ( index < this.length ) {
eqArr.push( this[ index ] );
}
return $( eqArr );
}
},
index () {
if ( this[ 0 ] ) {
let child = this[ 0 ];
let i = 0;
while ( ( child = child.previousSibling ) !== null ) {
child.nodeType === 1 && i++;
}
return i;
}
},
prev () {
const prevArr = [];
this.each( function () {
const p = this.previousElementSibling;
p && prevArr.push( p );
} )
return $( prevArr );
},
next () {
const nextArr = [];
this.each( function () {
const n = this.nextElementSibling;
n && nextArr.push( n );
} )
return $( nextArr );
},
nextAll () {
const nextAll = [];
this.each( function () {
let next = this.nextElementSibling;
function findNext () {
if ( next ) {
nextAll.push( next );
next = $( next ).get().nextElementSibling;
findNext();
}
}
findNext();
} );
return $( nextAll );
},
parent () {
const parentArr = [];
this.each( function () {
parentArr.push( this.parentNode );
} )
return $( parentArr );
},
find ( dom ) {
const findArr = [];
this.each( function () {
const node = this.querySelectorAll( dom );
for ( let i = 0, j = node.length; i < j; i++ ) {
( node[ i ].nodeType === 1 ) && findArr.push( node[ i ] );
}
} )
return $( findArr );
},
siblings: function () {
const sibling = [];
this.each( function () {
const child = this.parentNode.children;
for ( let i = 0, j = child.length; i < j; i++ ) {
if ( child[ i ] !== this ) {
sibling.push( child[ i ] );
}
}
} );
return $( sibling );
},
add ( elem ) {
let dom = this;
const addElem = $( elem );
for ( let i = 0, j = addElem.length; i < j; i++ ) {
dom[ dom.length ] = addElem[ i ];
dom.length++;
}
return dom;
},
data ( name, value ) {
return ( typeof name === "string" && !value ) ?
this[ 0 ].dataset[ name ] :
this.each( function () {
if ( name && value ) {
this.dataset[ name ] = value;
} else {
for ( const key in name ) {
this.dataset[ key ] = name[ key ];
}
}
} )
},
remove () {
return this.each( function () {
this.parentNode && this.parentNode.removeChild( this );
} )
},
show () {
return this.each( function () {
this.style.display = "block";
} )
},
hide () {
return this.each( function () {
this.style.display = "";
if ( $( this ).css( "display" ) !== "none" ) {
this.style.display = "none";
}
} )
}
};
return function ( selector ) {
let result = [];
if ( typeof selector === "string" ) {
result = document.querySelectorAll( selector );
}
if ( selector.nodeType || selector === document ) {
result.push( selector );
} else if ( selector.length > 0 && selector[ 0 ] && selector[ 0 ].nodeType ) {
for ( let i = 0, j = selector.length; i < j; i++ ) {
result.push( selector[ i ] );
}
}
return new buildDom( result );
}
} )();
// 默认配置
const defaults = {
theme: "select",
data: {
props: {
code: "code",
name: "name"
},
source: null,
when: null
},
level: 3,
radius: 2,
width: 200,
height: 34,
maxHeight: 300,
disabled: [],
disabledItem: [],
selected: [],
selectedCallback: () => {},
placeholder: [ "省", "市", "区" ],
separator: "/",
clearable: false,
strict: false,
onlyShowLastLevel: false,
icon: "arrow",
onClear: () => {},
onSelect: () => {}
};
// 存储组件数据
const cacheIPicker = {
// 原始目标元素 >> elem: "..."
originalElem: new WeakMap(),
// 组件配置信息 >> elem: opt
options: new WeakMap(),
// 组件选中结果 >> elem: [ [], [], [] ]
value: new WeakMap(),
// 组件唯一标识 >> elem: uid
id: new WeakMap(),
// 组件目标信息 >> uid: elem
target: new Map()
};
// 缓存数据源
const cacheCustomData = new Map();
// 样式 id
const styleId = "iPicker-default-style";
// 功能模块
const modules = {
createFrame ( $target, { theme, level, icon, clearable }, uid ) {
let frame = `
<div class="iPicker-container">
<div class="iPicker-result">
<input
type="text"
autocomplete="off"
spellcheck="false"
class="iPicker-input"
readonly
>
<i class="arrow-icon ${ icon === "arrow-outline" ? "arrow-outline" : "arrow-triangle" }"></i>
${ clearable ? "<i class=\"clear-icon\"></i>" : "" }
</div>
<div class="iPicker-list iPicker-${ theme }">___</div>
</div>
`;
switch ( theme ) {
case "select":
frame = frame.replace( "___", "<ul></ul>" ).repeat( level );
break;
case "cascader":
frame = frame.replace( "___", "<ul></ul>".repeat( level ) );
break;
case "panel":
frame = frame.replace( "___", `
<div class="iPicker-panel-tab">
<div class="iPicker-panel-tab-active">省份</div>
${ level > 1 ? "<div>城市</div>" : "" }
${ level > 2 ? "<div>区县</div>" : "" }
</div>
<div class="iPicker-panel-content">${ "<ul></ul>".repeat( level ) }</div>
` );
break;
}
$target.addClass( "iPicker-target" ).html( frame ).data( {
theme: theme,
id: uid.toString().replace( /(\(|\))/g, "" )
} );
},
createList ( data, opt, isInnerData ) {
return new Promise( resolve => {
let list = "";
const isCascader = opt.theme === "cascader";
if ( !isInnerData ) {
const { code, name } = opt.data.props || {};
data.forEach( obj => {
list += `
<li data-code="${ obj[ code ] }" data-name="${ obj[ name ] }">
<span>${ obj[ name ] }</span>
${ ( isCascader ? "<i></i>" : "" ) }
</li>
`;
} )
} else {
for ( const key in data ) {
list += `
<li data-code="${ key }" data-name="${ data[ key ] }">
<span>${ data[ key ] }</span>
${ ( isCascader ? "<i></i>" : "" ) }
</li>
`;
}
}
resolve( list );
} )
},
getData ( code, level, opt, isInnerData ) {
// 优先使用本地缓存的数据源
return new Promise( resolve => {
// 通过 opt.data.when 函数可对数据进行一次最后的处理
function when ( data, level ) {
if ( util.isFunction( opt.data.when ) ) {
return opt.data.when( data, level );
} else {
return data;
}
}
// 自定义数据源
if ( !isInnerData ) {
const hasCache = cacheCustomData.get( code );
if ( hasCache ) {
resolve( when( hasCache, level ) );
} else {
const dataSource = opt.data.source( code, level );
if ( util.type( dataSource ) === "object" && util.isFunction( dataSource.then ) ) {
dataSource.then( res => {
cacheCustomData.set( code, res );
resolve( when( res, level ) );
} )
}
}
} else {
// 内置数据源
opt.data.source.then( res => {
resolve( when( res[ code ], level ) );
} )
}
} )
},
getSelected ( $target ) {
// 根据被选中列表含有 "特征类" 来获取选中项
const $active = $target.find( ".iPicker-list-active" );
const activeSize = $active.length;
const [ code, name, map ] = [ [], [], [] ];
if ( activeSize ) {
$active.each( function () {
const dataCode = $( this ).data( "code" );
const dataName = $( this ).data( "name" );
code.push( dataCode );
name.push( dataName );
map.push( {
code: dataCode,
name: dataName
} );
} )
}
return [ code, name, map ];
},
cacheSelected ( target, value ) {
cacheIPicker.value.set( target, value );
}
};
// 核心程序
const iPicker = ( target, options ) => {
// 对必选参数进行校验
if (
!target ||
!options ||
typeof target !== "string" ||
!target.trim() ||
!util.isNotEmptyPlainObject( options ) ||
!util.isNotEmptyPlainObject( options.data ) ||
!options.data.source ||
( !util.isFunction( options.data.source ) && !util.isPromise( options.data.source ) )
) {
return;
}
const $target = $( target );
const _target = $target.get();
if ( !_target ) {
return;
}
// 合并参数
const opt = util.mergeParam( options, defaults );
// 检验并处理 level
if ( !util.isCorrectNumber( opt.level ) || opt.level < 1 || opt.level > 3 ) {
opt.level = 3;
}
// 缓存主题类型
const selectTheme = opt.theme === "select";
const cascaderTheme = opt.theme === "cascader";
const panelTheme = opt.theme === "panel";
// 缓存数据源类型
const isInnerData = util.isPromise( opt.data.source );
// 检测 onClear 和 onSelect 是否为函数
const onClearIsFunc = util.isFunction( opt.onClear );
const onSelectIsFunc = util.isFunction( opt.onSelect );
// 创建一个全局唯一标识符
const uid = _target.iPickerID || util.uid( true );
// 存储
cacheIPicker.originalElem.set( _target, target );
cacheIPicker.options.set( _target, opt );
cacheIPicker.target.set( uid, _target );
cacheIPicker.id.set( _target, uid );
_target.iPickerID = uid;
// 添加样式
if ( !document.getElementById( styleId ) ) {
document.head.insertAdjacentHTML( "afterbegin", `
<style id="${ styleId }">.iPicker-target{position:relative;height:34px}.iPicker-container,.iPicker-target *{box-sizing:border-box;font-size:14px;margin:0;padding:0}.iPicker-container{height:34px;float:left;position:relative}.iPicker-container:not(:first-of-type){margin-left:10px}.iPicker-result{display:flex;align-items:center;position:relative;background:#fff;border:#d6d6d6 solid 1px;min-width:100px;height:34px;border-radius:2px;cursor:pointer;user-select:none}.iPicker-result.iPicker-result-active:not(.iPicker-disabled),.iPicker-result.iPicker-result-active:not(.iPicker-disabled):hover{border:#00b8ff solid 1px}.iPicker-result.iPicker-result-active i::before{transform:scale(.55) rotate(180deg)}.iPicker-result.iPicker-result-active i.arrow-outline::before{transform:scale(.72) rotate(180deg)}.iPicker-result i{position:absolute;top:0;right:0;display:block;width:30px;height:34px}.iPicker-result i::before{position:absolute;top:0;right:2px;display:block;width:28px;height:100%;background-position:center;background-repeat:no-repeat;content:"";opacity:.5;transition:transform .2s;transform:scale(.55)}.iPicker-result i.arrow-icon::before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNTc2OTk1MjQ3Njc4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI2NTAiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PC9zdHlsZT48L2RlZnM+PHBhdGggZD0iTTUzNS40NjY2NjcgODEyLjhsNDUwLjEzMzMzMy01NjMuMmMxNC45MzMzMzMtMTkuMiAyLjEzMzMzMy00OS4wNjY2NjctMjMuNDY2NjY3LTQ5LjA2NjY2N0g2MS44NjY2NjdjLTI1LjYgMC0zOC40IDI5Ljg2NjY2Ny0yMy40NjY2NjcgNDkuMDY2NjY3bDQ1MC4xMzMzMzMgNTYzLjJjMTIuOCAxNC45MzMzMzMgMzQuMTMzMzMzIDE0LjkzMzMzMyA0Ni45MzMzMzQgMHoiIHAtaWQ9IjI2NTEiIGZpbGw9IiMwMDAwMDAiPjwvcGF0aD48L3N2Zz4=)}.iPicker-result i.clear-icon::before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjA3Njc2MTg3NDk0IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjMzNDIiIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PC9zdHlsZT48L2RlZnM+PHBhdGggZD0iTTUxMS45MTg2NDcgMTYzLjE1NDkxN2MtMTkzLjQzODY0MSAwLTM1MS42OTcwMzcgMTU4LjI1NDMwNC0zNTEuNjk3MDM3IDM1MS42OTkwODQgMCAxOTMuNDM5NjY0IDE1OC4yNTczNzQgMzUxLjY5NzAzNyAzNTEuNjk3MDM3IDM1MS42OTcwMzcgMTkzLjM5NDYzOCAwIDM1MS42NTMwMzUtMTU4LjI1NjM1IDM1MS42NTMwMzUtMzUxLjY5NzAzN0M4NjMuNTcwNjU5IDMyMS40MDkyMjEgNzA1LjMxMzI4NiAxNjMuMTU0OTE3IDUxMS45MTg2NDcgMTYzLjE1NDkxN002ODcuNzM2OTc4IDY0MS40NTIzMjdsLTQ5LjE5ODUxNSA0OS4yMjQwOTgtMTI2LjYxOTgxNi0xMjYuNjAwMzczLTEyNi41NzM3NjcgMTI2LjYwMDM3My00OS4zMDQ5MzktNDkuMjI0MDk4IDEyNi42MzYxODktMTI2LjU5ODMyNi0xMjYuNjM2MTg5LTEyNi42MDAzNzMgNDkuMzA0OTM5LTQ5LjIyNDA5OCAxMjYuNTczNzY3IDEyNi42MDAzNzMgMTI2LjYxOTgxNi0xMjYuNjAwMzczIDQ5LjE5ODUxNSA0OS4yMjQwOTgtMTI2LjU3Mzc2NyAxMjYuNjAwMzczTDY4Ny43MzY5NzggNjQxLjQ1MjMyN3oiIHAtaWQ9IjMzNDMiIGZpbGw9IiMwMDAwMDAiPjwvcGF0aD48L3N2Zz4=);transform:scale(1);z-index:10}.iPicker-result i.arrow-outline::before{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNjA1MDYzNzA0MzAzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjM0NDMiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PC9zdHlsZT48L2RlZnM+PHBhdGggZD0iTTUxMiA3MzBjLTYuNCAwLTEyLjgtMi40LTE3LjctNy4zbC0zODYtMzg2Yy05LjgtOS44LTkuOC0yNS42IDAtMzUuNCA5LjgtOS44IDI1LjYtOS44IDM1LjQgMEw1MTIgNjY5LjZsMzY4LjMtMzY4LjNjOS44LTkuOCAyNS42LTkuOCAzNS40IDAgOS44IDkuOCA5LjggMjUuNiAwIDM1LjRsLTM4NiAzODZjLTQuOSA0LjktMTEuMyA3LjMtMTcuNyA3LjN6IiBmaWxsPSIjMzMzMzMzIiBwLWlkPSIzNDQ0Ij48L3BhdGg+PC9zdmc+);transform:scale(.72);opacity:.9}.iPicker-result.iPicker-disabled{cursor:not-allowed;background:#f2f5fa;color:#898989;border-color:#dfdfdf}.iPicker-result.iPicker-disabled input{cursor:not-allowed;background:#f2f5fa;color:#898989}.iPicker-input{display:block;width:100%;width:calc(100% - 23px);height:32px;padding:0 10px;outline:0;border:0;cursor:pointer;user-select:none}.iPicker-result input::selection{background:#fff}.iPicker-list.iPicker-list-ontop{transform-origin:center bottom}.iPicker-list-show-temporary{display:block!important;opacity:0!important;pointer-events:none!important}.iPicker-input::-webkit-input-placeholder{color:#aaa}.iPicker-input::-moz-placeholder{color:#aaa}.iPicker-target[data-theme=panel] .iPicker-list{width:370px}.iPicker-list{position:relative;z-index:10;display:none;overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;user-select:none;background:#fff;border:#ddd solid 1px;box-shadow:rgba(0,0,0,.12) 0 2px 6px;transform-origin:center top;transform:scaleY(0);transition-property:transform,opacity;transition-duration:.2s}.iPicker-list ul{display:block;overflow:hidden}.iPicker-list li{cursor:pointer;transition:.2s}.iPicker-list li span{pointer-events:none}.iPicker-list li.iPicker-list-active,.iPicker-list li:hover{color:#00b8ff;background:rgba(0,184,255,.1)}.iPicker-list li.iPicker-list-disabled{cursor:not-allowed;background:#f2f5fa;color:#b6b6b6}.iPicker-panel-tab{display:flex;align-items:center;justify-content:flex-start;height:36px;background-color:#f5f5f5;border-bottom:#ddd solid 1px}.iPicker-panel-tab>div{cursor:pointer;padding:0 20px;height:36px;line-height:36px}.iPicker-panel-tab>div:last-child.iPicker-panel-tab-active,.iPicker-panel-tab>div:not(:last-child){border-right:#d3d3d3 solid 1px}.iPicker-panel-tab>div.iPicker-panel-tab-active{background:#fff;cursor:default;position:relative;height:37px;border-bottom:#fff solid 1px;color:#00b8ff}.iPicker-panel-content{padding:10px 0;min-height:50px;overflow-x:hidden;overflow-y:auto}.iPicker-panel-content ul{display:none}.iPicker-panel li{float:left;display:block;margin:2px 4px;padding:4px 10px;border-radius:2px}.iPicker-panel li span{font-size:13px}.iPicker-cascader li,.iPicker-select li{position:relative;display:block;padding:6px 12px;list-style:none;transition:.25s;overflow:hidden;clear:both;word-break:break-all}.iPicker-cascader li:first-child,.iPicker-select li:first-child{margin-top:8px}.iPicker-cascader li:last-child,.iPicker-select li:last-child{margin-bottom:8px}.iPicker-list.iPicker-cascader{overflow:hidden}.iPicker-cascader ul{position:relative;z-index:4;display:none;width:200px;float:left;overflow-y:auto;overscroll-behavior:contain}.iPicker-cascader ul:nth-child(2){z-index:3;margin-left:-2px}.iPicker-cascader ul:nth-child(3){z-index:2;margin-left:0}.iPicker-cascader ul:nth-child(4){z-index:1}.iPicker-cascader ul:not(:last-child){border-right:#dfdfdf solid 1px}.iPicker-cascader li i{display:block;position:absolute;top:50%;right:10px;width:8px;height:8px;margin-top:-4px;border-top:#6f6f6f solid 1px;border-right:#6f6f6f solid 1px;transform:scale(.8) rotate(45deg)}.iPicker-cascader li.iPicker-list-disabled i{opacity:.4}.iPicker-cascader li.iPicker-list-active i{border-top-color:#00b8ff;border-right-color:#00b8ff}.iPicker-cascader ul:last-child li i{display:none}.iPicker-list.iPicker-list-show{display:block;transform:scaleY(1)}.iPicker-list.iPicker-list-hide{transform:scaleY(0);opacity:0}</style>
` );
}
// 生成组件结构
modules.createFrame( $target, opt, uid );
// 缓存 dom
const $container = $target.find( ".iPicker-container" );
const $result = $target.find( ".iPicker-result" );
const $input = $target.find( ".iPicker-input" );
const $list = $target.find( ".iPicker-list" );
const $ul = $list.find( "ul" );
// 添加索引标记
$ul.each( function ( i ) {
$( this ).data( "level", ++i );
} )
// 设置列表最大高度
// 最小有效值是 100,如果设置了小于 100 的值则按照默认配置进行处理
if ( util.isCorrectNumber( opt.maxHeight ) && opt.maxHeight >= 100 ) {
$list.css( "maxHeight", `${ opt.maxHeight }px` );
if ( cascaderTheme ) {
$ul.css( "maxHeight", `${ opt.maxHeight }px` );
}
if ( panelTheme ) {
$list.find( ".iPicker-panel-content" ).css( "height", `${ opt.maxHeight - 38 }px` );
}
}
// 设置结果展示框宽度
// 当宽度是数字时最小有效值是 100,如果设置了小于 100 的值则按照默认配置进行处理
// 当宽度是字符串时必须是百分比形式
if ( util.isCorrectNumber( opt.width ) && opt.width >= 100 ) {
$result.css( "width", `${ opt.width }px` );
if ( selectTheme ) {
$list.css( "width", `${ opt.width }px` );
}
}
if ( typeof opt.width === "string" && opt.width.trim().endsWith( "%" ) ) {
$result.css( "width", opt.width );
if ( selectTheme ) {
$list.css( "width", opt.width );
} else {
$container.css( "width", opt.width );
}
}
// 设置结果展示框高度
// 最小有效值是 20,如果设置了小于 20 的值则按照默认配置进行处理
if ( util.isCorrectNumber( opt.height ) && opt.height >= 20 ) {
$result.css( "height", `${ opt.height }px` );
$input.css( "height", `${ opt.height - 2 }px` );
$input.next().css( "height", `${ opt.height - 2 }px` );
}
// 禁用指定地区
if ( opt.disabledItem === true ) {
new MutationObserver( () => {
$target.find( "li" ).addClass( "iPicker-list-disabled" );
} ).observe( _target, {
childList: true,
subtree: true
} )
}
if ( Array.isArray( opt.disabledItem ) && opt.disabledItem.length ) {
for ( const code of [ ...new Set( opt.disabledItem ) ] ) {
new MutationObserver( () => {
const li = _target.querySelector( `[data-code="${ code }"]:not(.iPicker-list-disabled)` );
if ( li ) {
li.classList.add( "iPicker-list-disabled" );
}
} ).observe( _target, {
childList: true,
subtree: true
} )
}
}
// 禁用指定的结果展示框
if ( opt.disabled === true ) {
opt.disabled = [ 1, 2, 3 ].slice( 0, opt.level );
}
if ( util.isCorrectNumber( opt.disabled ) ) {
opt.disabled = [ opt.disabled ];
}
if ( Array.isArray( opt.disabled ) && opt.disabled.length ) {
for ( const level of [ ...new Set( opt.disabled ) ] ) {
if ( util.isCorrectNumber( level ) && level >= 1 && level <= 3 ) {
$result.eq( level - 1 ).addClass( "iPicker-disabled" );
}
}
}
// 设置 placeholder
if ( selectTheme && Array.isArray( opt.placeholder ) ) {
opt.placeholder.forEach( ( v, i ) => {
const input = $input.eq( i ).get();
if ( input ) {
input.setAttribute( "placeholder", opt.placeholder[ i ] || defaults.placeholder[ i ] );
}
} )
}
if ( cascaderTheme || panelTheme ) {
if ( typeof opt.placeholder !== "string" || !opt.placeholder.trim() ) {
opt.placeholder = "请选择地区";
}
$input.eq( 0 ).get().setAttribute( "placeholder", opt.placeholder );
}
// 设置结果展示框的圆角值
if ( util.isCorrectNumber( opt.radius, true ) ) {
$result.add( $input ).css( "borderRadius", `${ opt.radius }px` );
}
// 设置清空按钮
$result.find( ".clear-icon" ).hide();
$result.each( function () {
// 鼠标滑入滑出显示或隐藏清空按钮
const el = $( this ).get();
el.addEventListener( "mouseenter", () => {
const input = el.querySelector( "input" );
if ( input ) {
if ( input.value && !el.classList.contains( "iPicker-disabled" ) ) {
$( this ).find( ".clear-icon" ).show().prev().hide();
}
}
} )
el.addEventListener( "mouseleave", () => {
$( this ).find( ".clear-icon" ).hide().prev().show();
} )
} )
const $clear = $target.find( ".clear-icon" );
if ( !selectTheme ) {
$clear.click( () => {
$clear.hide().prev().show();
iPicker.clear( uid );
if ( onClearIsFunc ) {
opt.onClear();
}
} )
} else {
$clear.each( function () {
const $this = $( this );
$this.click( function () {
$this.hide().prev().show();
const $parent = $this.parent();
const $ul = $parent.next().find( "ul" );
const index = +$ul.data( "index" );
$parent.find( "input" ).val( "" );
$ul.find( ".iPicker-list-active" ).removeClass( "iPicker-list-active" );
$parent
.parent()
.nextAll()
.find( "input" )
.val( "" )
.parent()
.next()
.find( "ul" )
.html( "" );
getCacheShow();
closeList( $parent.next() );
if ( onClearIsFunc ) {
opt.onClear();
}
} )
} )
}
// 通过点击展示框来展开或关闭列表
$result.each( function () {
$( this ).find( "input, .arrow-icon" ).click( function () {
const $this = $( this ).parent();
const $next = $this.next();
const id = $this.parent().parent().data( "id" );
// 关闭其它列表
const $otherList = $( `.iPicker-target:not([data-id="${ id }"]) .iPicker-list` );
if ( $otherList.length ) {
closeList( $otherList );
}
// 组件必须在 "启用" 状态下才有效
if ( !$this.hasClass( "iPicker-disabled" ) ) {
// 列表中必须有数据
if ( !$next.find( "li" ).length ) {
return;
}
$this.toggleClass( "iPicker-result-active" );
if ( $next.hasClass( "iPicker-list-show" ) ) {
closeList( $next );
} else {
if ( panelTheme ) {
$target
.find( ".iPicker-panel-tab > div:first-child" )
.addClass( "iPicker-panel-tab-active" )
.siblings()
.removeClass( "iPicker-panel-tab-active" );
$target.find( ".iPicker-panel-content > ul:first-child" ).show().siblings().hide();
}
/*
# 自动检测展示框的位置
# 实时调整下拉列表的位置
*/
let flag = false;
const resultHeight = parseInt( $this.css( "height" ) );
const positionListener = () => {
if ( flag ) {
return;
}
flag = true;
$next.addClass( "iPicker-list-show-temporary" );
const bottom = document.documentElement.clientHeight - $this.get().getBoundingClientRect().bottom;
const height = parseInt( $next.css( "height" ) );
if ( bottom < height ) {
$next.css( "marginTop", `-${ height + resultHeight }px` ).addClass( "iPicker-list-ontop" );
} else {
$next.css( "marginTop", "0px" ).removeClass( "iPicker-list-ontop" );
}
flag = false;
$next.removeClass( "iPicker-list-show-temporary" );
}
positionListener();
window.addEventListener( "scroll", positionListener );
window.addEventListener( "resize", positionListener );
$next.addClass( "iPicker-list-show" ).removeClass( "iPicker-list-hide" );
}
}
} )
} )
// 关闭列表函数
function closeList ( $list ) {
if ( $list.hasClass( "iPicker-list-show" ) ) {
$list
.addClass( "iPicker-list-hide" )
.removeClass( "iPicker-list-show" )
.prev()
.removeClass( "iPicker-result-active" );
$list.show();
util.delay( 200 ).then( () => {
$list.get().style.removeProperty( "display" );
} )
if ( !selectTheme ) {
getCacheShow();
}
// 检测 strict 模式
const $target = $list.parent().parent();
const opt = cacheIPicker.options.get( $target.get() );
if ( opt.strict ) {
// 利用定时器进行延时处理
// 在列表关闭动画结束后执行相关程序
util.delay( 200 ).then( () => {
const [ code ] = cacheIPicker.value.get( $target.get() );
const codeLen = code.length;
// 只有在至少选择了一个层级
// 但又没有完整选择全部指定的层级的情况下
// 才能执行后续程序
if ( codeLen && codeLen !== opt.level ) {
new Promise( resolve => {
if ( codeLen === 1 ) {
if ( opt.level === 2 ) {
resolve();
} else {
addList( $ul.eq( 1 ).find( "li:first-child" ).data( "code" ), 3 ).then( () => {
resolve();
} );
}
} else {
resolve();
}
} ).then( () => {
$ul.each( function () {
if ( !$( this ).find( ".iPicker-list-active" ).length ) {
$( this ).find( "li:first-child" ).addClass( "iPicker-list-active" );
}
} )
getCacheShow();
} )
}
} )
}
}
}
// 添加列表
function addList ( code, level ) {
return new Promise( resolve => {
modules.getData( code, !selectTheme ? level : level > 3 ? 3 : level, opt, isInnerData ).then( res => {
modules.createList( res, opt, isInnerData ).then( content => {
const $targetUL = $ul.eq( level - 1 );
$targetUL.html( content ).nextAll().html( "" );
if ( selectTheme ) {
$targetUL.parent().parent().nextAll().find( "ul" ).html( "" );
}
if ( cascaderTheme ) {
let size = 0;
$ul.each( function () {
if ( this.innerHTML ) {
size++;
}
} )
$list.css( "width", `${ 200 * size }px` );
$ul.eq( level - 1 ).show().nextAll().hide();
}
if ( panelTheme ) {
$ul.eq( level - 1 ).show().siblings().hide();
$target
.find( `.iPicker-panel-tab > div:nth-child(${ level })` )
.addClass( "iPicker-panel-tab-active" )
.siblings()
.removeClass( "iPicker-panel-tab-active" );
}
selectTheme ? getCacheShow() : level <= opt.level && getCacheShow();
resolve();
} );
} )
} )
}
// 获取,存储,显示结果
function getCacheShow () {
util.delay( 10 ).then( () => {
// 获取并存储选中结果
const getSelected = modules.getSelected( $target );
modules.cacheSelected( _target, getSelected );
// 显示选中结果
const separator = opt.separator.trim().charAt( 0 );
function showResult ( result ) {
if ( result ) {
if ( cascaderTheme || panelTheme ) {
if ( opt.onlyShowLastLevel ) {
result = result.split( separator ).slice( -1 )[ 0 ].trim();
}
}
}
return result;
}
// 显示选中结果
if ( selectTheme ) {
getSelected[ 1 ].forEach( ( item, index ) => {
$input.eq( index ).val( showResult( item ) );
} )
} else {
const name = getSelected[ 1 ].join( ` ${ separator } ` );
$input.eq( 0 ).val( showResult( name ) );
}
// 执行 onSelect 事件
if ( onSelectIsFunc ) {
if ( getSelected[ 1 ].length ) {
opt.onSelect( ...cacheIPicker.value.get( _target ) );
}
}
} )
}
// 自动获取第一层级的数据
// 含有对 "默认选中项" 的处理
addList( ( isInnerData ? "86" : null ), 1 ).then( () => {
_target.dataset.promise = "true";
// 设置默认选中项
// selected <Array>
// 默认选中项中不能含有已被禁用的选项,即:
// selected 数组中的值不能在 disabledItem 中也存在,否则无效
if ( Array.isArray( opt.selected ) && opt.selected.length ) {
opt.selected = [ ...new Set( opt.selected ) ]
for ( const code of opt.selected ) {
if ( opt.disabledItem.includes( code ) ) {
return;
}
}
!( function selected ( i ) {
addList( opt.selected[ i - 1 ], i + 1 ).then( () => {
i++;
if ( i < opt.level ) {
selected( i );
} else {
opt.selected.forEach( item => {
$target.find( `li[data-code="${ item }"]` ).addClass( "iPicker-list-active" );
} )
getCacheShow();
// 执行 selectedCallback 函数
if ( util.isFunction( opt.selectedCallback ) ) {
opt.selectedCallback();
}
}
} )
} )( 1 );
}
} );
// 点击选择事件
$target.click( event => {
if ( event.target.nodeName.toLowerCase() !== "li" ) {
return;
}
const $li = $( event.target );
const $ul = $li.parent();
if ( $li.hasClass( "iPicker-list-disabled" ) ) {
return;
}
$li.addClass( "iPicker-list-active" ).siblings().removeClass( "iPicker-list-active" );
addList( $li.data( "code" ), +$ul.data( "level" ) + 1 );
// select 主题下,点击选择后自动关闭列表
if ( selectTheme ) {
closeList( $ul.parent() );
$ul.parent().parent().nextAll().find( ".iPicker-result input" ).val( "" );
}
// cascader 和 panel 模式下,如果点击选择的是最后一级的数据
// 则自动关闭列表
if ( $ul.index() === opt.level - 1 ) {
if ( cascaderTheme ) {
closeList( $ul.parent() );
}
if ( panelTheme ) {
closeList( $ul.parent().parent() );
}
}
} )
// cascader 主题下需要强制设置高度
if ( cascaderTheme ) {
$ul.css( {
minHeight: `${ opt.maxHeight }px`,
maxHeight: `${ opt.maxHeight }px`
} );
}
// panel 主题下的切换
if ( panelTheme ) {
$target.find( ".iPicker-panel-tab > div" ).click( function () {
const index = $( this ).index();
if ( !$( this ).parent().next().find( "ul" ).eq( index ).find( "li" ).length ) {
return;
}
$( this )
.addClass( "iPicker-panel-tab-active" )
.siblings()
.removeClass( "iPicker-panel-tab-active" );
$target
.find( ".iPicker-panel-content ul" )
.eq( $( this ).index() )
.show()
.siblings()
.hide();
} )
}
// 点击空白处隐藏列表
$( document ).click( function ( event ) {
$container.each( function ( i ) {
if ( event.target !== this && !this.contains( event.target ) ) {
closeList( $list.eq( i ) );
}
} )
} )
return uid;
}
// 创建组件
// 等同于 iPicker 函数
iPicker.create = ( target, options ) => iPicker( target, options )
// 设置值
// value: <Array>
iPicker.set = ( id, value ) => {
const _target = cacheIPicker.target.get( id );
if ( !id || !_target || !value || !Array.isArray( value ) || !value.length ) {
return;
}
// 清除已选项
iPicker.clear( id );
// 如果目标元素已经设置了 data-promise 属性
// 说明已经获取到了第一层级的数据
// 可直接执行 fn 函数
if ( _target.dataset.promise ) {
fn();
} else {
// 尚未获取到数据时
// 调用 MutationObserver 方法监听 data-promise 属性的变化
// 以此来判断是否已经获取到了第一层级的数据
// 当获取到数据时执行 fn 函数
new MutationObserver( () => {
fn();
} ).observe( _target, {
attributes: true
} );
}
function fn () {
const ul = _target.querySelectorAll( "ul" );
!( function set ( i ) {
const li = _target.querySelector( `[data-code="${ value[ i ] }"]` );
if ( ul[ i + 1 ] ) {
// 监听 ul 子元素的变化
// 一旦已经生成了列表
// 就可以执行后续操作
new MutationObserver( () => {
++i;
if ( i < value.length ) {
set( i );
}
} ).observe( ul[ i + 1 ], {
childList: true
} );
li.click();
} else {
li && li.click();
}
} )( 0 );
}
}
// 获取值
iPicker.get = ( id, type ) => {
const _target = cacheIPicker.target.get( id );
if ( !id || !_target ) {
return;
}
const result = cacheIPicker.value.get( _target );
// 获取地区行政编码
if ( type === "code" || type === undefined ) {
return result[ 0 ];
}
// 获取地区名称
if ( type === "name" ) {
return result[ 1 ];
}
// 获取地区行政编码和名称
if ( type === "all" ) {
return result[ 2 ];
}
}
// 清空值
iPicker.clear = id => {
const _target = cacheIPicker.target.get( id );
if ( !id || !_target ) {
return;
}
const $target = $( _target );
const opt = cacheIPicker.options.get( _target );
// 清空值
cacheIPicker.value.set( _target, [ [], [], [] ] );
$target.find( "input" ).val( "" );
$target.find( "li" ).removeClass( "iPicker-list-active" );
$target.find( "ul" ).each( function ( i ) {
const $this = $( this );
// 移除第一级以外的其它层级的内容
if ( i ) {
$this.html( "" );
if ( opt.theme === "cascader" ) {
$this.parent().css( "width", "200px" );
$this.get().style.removeProperty( "display" );
}
}
} )
if ( opt.theme === "panel" ) {
$target
.find( ".iPicker-panel-tab > div" )
.eq( 0 )
.addClass( "iPicker-panel-tab-active" )
.siblings()
.removeClass( "iPicker-panel-tab-active" );
$target.find( ".iPicker-panel-content > ul" ).eq( 0 ).show().siblings().hide();
}
// 滚动条回顶
$target.find( ".iPicker-list" ).get().scrollTop = 0;
$target.find( "ul" ).get().scrollTop = 0;