UNPKG

isu-elements

Version:

Polymer components for building web apps.

501 lines (482 loc) 15.6 kB
import { html, PolymerElement } from '@polymer/polymer' import '@webcomponents/shadycss/entrypoints/apply-shim.js' import './behaviors/isu-tree-shared-styles.js' import './isu-tree-node.js' import './isu-input.js' import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class' import { BaseBehavior } from './behaviors/base-behavior' /** * * `isu-tree` * * Example: * ```html * <div> * <h4>Basic</h4> * <isu-tree id="tree"></isu-tree> * <h4>部分禁用</h4> * <isu-tree id="tree4" show-radio></isu-tree> * <h4>多选</h4> * <isu-tree id="tree2" multi></isu-tree> * <h4>单选</h4> * <isu-tree id="tree3" show-radio></isu-tree> * <h4>可以进行搜索</h4> * <isu-tree id="tree5" multi show-search-input></isu-tree> * <h4>手风琴模式,一次只展开一个同级列表</h4> * <isu-tree id="tree6" multi accordion></isu-tree> * <h4>展开所有节点</h4> * <isu-tree id="tree7" multi default-expand-all></isu-tree> * <h4>默认选中部分值</h4> * <isu-tree id="tree9" multi></isu-tree> * </div> * ``` * * ### Styling * * `<isu-button>` provides the following custom properties and mixins * for styling: * * Custom property | Description | Default * ----------------|-------------|---------- * `--paper-checkbox` | Mixin applied to the paper-checkbox | {} * `--paper-checkbox-half-choose` | Mixin applied to the paper-checkbox with half selected status | {} * `--paper-checkbox-half-after` | Mixin applied to the paper-checkbox with half selected shape| {} * `--paper-checkbox-unchecked-background-color` | Checkbox background color when the input is not checked | `transparent` * `--paper-checkbox-unchecked-color` | Checkbox border color when the input is not checked | `--primary-text-color` * `--paper-checkbox-unchecked-ink-color` | Selected/focus ripple color when the input is not checked | `--primary-text-color` * `--paper-checkbox-checked-color` | Checkbox color when the input is checked | `--primary-color` * `--paper-checkbox-checked-ink-color` | Selected/focus ripple color when the input is checked | `--primary-color` * `--paper-checkbox-checkmark-color` | Checkmark color | `white` * `--paper-checkbox-label-color` | Label color | `--primary-text-color` * `--paper-checkbox-label-checked-color` | Label color when the input is checked | `--paper-checkbox-label-color` * `--paper-checkbox-label-spacing` | Spacing between the label and the checkbox | `8px` * `--paper-checkbox-label` | Mixin applied to the label | `{}` * `--paper-checkbox-label-checked` | Mixin applied to the label when the input is checked | `{}` * `--paper-checkbox-error-color` | Checkbox color when invalid | `--error-color` * `--paper-checkbox-size` | Size of the checkbox | `18px` * `--paper-checkbox-ink-size` | Size of the ripple | `48px` * `--paper-checkbox-margin` | Margin around the checkbox container | `initial` * `--paper-checkbox-vertical-align` | Vertical alignment of the checkbox container | `middle` * * @customElement * @polymer * @demo demo/isu-tree/index.html */ class IsuTree extends mixinBehaviors([BaseBehavior], PolymerElement) { static get template () { return html` <style include="isu-tree-shared-styles"> #tree-search-input { position: -webkit-sticky; /* safari 浏览器 */ position: sticky; /* 其他浏览器 */ top: 0px; z-index: 100; @apply --tree-search-input } </style> <template is="dom-if" if="[[showSearchInput]]"> <isu-input id="tree-search-input" type="text" value="{{searchWord}}"></isu-input> </template> <template is="dom-if" if="[[isRenderNodes]]"> <template is="dom-repeat" items="[[data]]" index-as="index" initial-count="5"> <isu-tree-node data="{{item}}" multi="[[multi]]" show-radio="[[showRadio]]" search-word="[[searchWord]]" is-first="[[_isFirst(index)]]" accordion="[[accordion]]" default-expand-all="[[defaultExpandAll]]" indent="[[indent]]" attr-for-value="[[attrForValue]]" attr-for-label="[[attrForLabel]]" selected-items="[[selectedItems]]" icon="[[icon]]" > <div slot="before-label"> <slot name="before-label"> </slot> </div> </isu-tree-node> </template> </template> ` } static get properties () { return { /** * 原据源,为树结构的数组 * @demo * [ * { id: 1, label: '第1个', children: []}, * { id: 2, label: '第2个', children: [{ id: 11, label: '二级第1个', children: [] }]}, * ] * * @type {array} */ data: { type: Array }, /** * 存储树转Array后的数据集合 */ dataSet: { type: Array }, /** * 树结构数据子节点的属性,默认为children * @type {String} * @default 'children' */ attrForChild: { type: String }, /** * 节点作为value值的属性,默认取id作为value值 * @type {String} * @default 'children' */ attrForValue: { type: String, value: 'id' }, attrForLabel: { type: String, value: 'label' }, /** * Whether to require a query input box or not * * @type {boolean} * @default false */ showSearchInput: { type: Boolean, value: false }, /** * The selected value of this select tree */ value: { type: String, notify: true }, /** * 选择的树节点集合 * * @type {array} * */ selectedItems: { type: Array, notify: true }, /** * 对选中数据结果进行处理的函数 */ filterFn: { type: Function }, /** * 过滤属性,对选中的结果集进行处理,过滤选中结果集只为指定层级的数据,与filterFn并行使用且优先于filterFn,eg: '2,3' */ onlySelectLevel: { type: String }, /** * 经过onlySelectLevel、filterFn处理过后的结果集合 */ filterSelectedItems: { type: Array, notify: true, computed: '__filterSelectedItemsComputed(selectedItems, onlySelectLevel, filterFn)' }, filterValue: { type: String, notify: true, computed: '__filterValueComputed(filterSelectedItems)' }, /** * 仅做初始化数据使用 * 应用场景,通过filterValue回显数据 */ initFilterValue: { type: String }, /** * 是否多选 * * @type {boolean} * @default false * */ multi: { type: Boolean, value: false }, /** * Whether to show radio or not * * @type {boolean} * @default false * */ showRadio: { type: Boolean, value: false }, /** * Whether to expand all nodes by default * * @type {boolean} * @default false * */ defaultExpandAll: { type: Boolean, value: false }, /** * Accordion mode, showing only one sibling node at a time * * @type {boolean} * @default false * */ accordion: { type: Boolean, value: false }, /** * How many pixels are indented per layer. * * @type {number} * @default 18 * */ indent: { // 每一层缩进多少像素 type: Number, value: 18 }, isTree: { type: Boolean, value: true }, props: { type: Object, value () { return { children: 'children', label: 'label', disabled: 'disabled' } } }, /** * The search word * * @type {string} * */ searchWord: { type: String, notify: true }, /** * 是否渲染树节点,默认为true * 设置为false时候,树节点不进行渲染, * isu-select-tree-new 通过该属性,控制树的渲染 */ isRenderNodes: { type: Boolean, value: true }, icon: { type: String, value: 'icons:arrow-drop-down' } } } static get observers () { return [ '__valueChanged(value)', '__selectedItemsChanged(selectedItems)', '__initData(data)', '_dataSetChanged(dataSet)', '_initValue(initFilterValue, dataSet)' ] } // __attrForChildChanged() { // console.log('__attrForChildChanged') // this.__initData(this.metadata) // } __initData (data) { if (!data) { this.set('dataSet', []) return } const attrForChild = this.attrForChild || 'children' const attrForValue = this.attrForValue || 'id' const dataSet = [] const selectedItems = [] const __init = (arr, level = 1, parent) => { arr.forEach(item => { item.children = item[attrForChild] item.parent = parent item.level = level item.visible = true if (item.checked) { selectedItems.push(item) } else { item.checked = false } dataSet.push(item) __init(item[attrForChild] || [], level + 1, item[attrForValue]) }) } __init(data) this.set('dataSet', dataSet) if (selectedItems.length > 0) { this.set('selectedItems', selectedItems) } } __valueChanged (value) { if (!this.dataSet || this.dataSet.length === 0) { return } value = (value && `${value}`) || '' // 将value强制转为string const attrForValue = this.attrForValue || 'id' const valueItems = value.split(',') const selectedItems = this.selectedItems || [] const flag = valueItems.length === selectedItems.length && valueItems.every(item => selectedItems.findIndex(i => `${item[attrForValue]}` === item) > -1) if (!flag) { const newSelectedItems = this.dataSet.filter(item => valueItems.includes(`${item[attrForValue]}`)) this.set('selectedItems', newSelectedItems) } } _dataSetChanged (dataSet) { if (this.value) { this.__valueChanged(this.value) } } __selectedItemsChanged (selectedItems) { const attrForValue = this.attrForValue || 'id' const value = (selectedItems || []).map(item => item[attrForValue]).join(',') this.set('value', value) } __filterSelectedItemsComputed (selectedItems, onlySelectLevel, filterFn) { selectedItems = selectedItems || [] if (onlySelectLevel) { const onlySelectLevelItems = (onlySelectLevel || '').split(',') selectedItems = selectedItems.filter(item => onlySelectLevelItems.includes(`${item.level}`)) } if (filterFn && this.isFunction(filterFn)) { selectedItems = filterFn(selectedItems) } return selectedItems } __filterValueComputed (filterSelectedItems) { const attrForValue = this.attrForValue || 'id' if (this.dataSet) { return filterSelectedItems && filterSelectedItems.map(item => item[attrForValue]).join(',') } } connectedCallback () { super.connectedCallback() this.addEventListener('multiple-checked-changed', this._multipleCheckChangedHandle) this.addEventListener('multiple-click-checked-changed', this._multipleClickCheckChangedHandle) this.addEventListener('single-checked-changed', this._singleCheckedChangedHandle) } /** * 多选,多选框变化处理器 * @param e * @private */ _multipleCheckChangedHandle (e) { const { data, isChecked } = e.detail const selectedItems = this.selectedItems || [] const attrForValue = this.attrForValue || 'id' if (isChecked) { // 选中 if (selectedItems.findIndex(item => item[attrForValue] === data[attrForValue]) === -1) { const newSelectedItems = [...selectedItems, data] this.set('selectedItems', newSelectedItems) } } else { // 非选中 const newSelectedItems = selectedItems.filter(item => item[attrForValue] !== data[attrForValue]) this.set('selectedItems', newSelectedItems) } } /** * 多选,多选框点击处理器 * @param e * @private */ _multipleClickCheckChangedHandle (e) { let { data, isChecked } = e.detail const { indeterminate } = data if (indeterminate && isChecked) { // 中间态当作已经选中取反 isChecked = false } const targetItems = [] const disabledItems = [] const __initTargetItems = (d) => { if (d.disabled) { disabledItems.push(d) } else { targetItems.push(d) d.children && d.children.forEach(item => __initTargetItems(item)) } } __initTargetItems(data) const selectedItems = this.selectedItems || [] const attrForValue = this.attrForValue || 'id' const newSelectedItems = selectedItems.filter(item => targetItems.findIndex(target => target[attrForValue] === item[attrForValue]) === -1) if (isChecked) { this.set('selectedItems', [...newSelectedItems, ...targetItems]) } else { this.set('selectedItems', newSelectedItems) } } _singleCheckedChangedHandle (e) { if (this.multi) return // 如果是多选,不做处理 const { data } = e.detail const attrForValue = this.attrForValue || 'id' const selectedItems = this.selectedItems || [] if (selectedItems.length > 0 && selectedItems[0][attrForValue] === data[attrForValue]) { this.set('selectedItems', []) } else { const newSelectedItems = [data] this.set('selectedItems', newSelectedItems) } } _isFirst (index) { return index === 0 } /** * 初始化value * 初始化条件: * 前置条件:dataSet存在且dataSet.length>0 * 1、initFilterValue 存在且value 不存在,需要初始化value值 * 2、value、initFilterValue(空字符串也算)同时存在,且initFilterValue 与 filterValue 不一致,需要重置value * * 通过initFilterValue 重置value为空,需要将initFilterValue设置为'' * @param initFilterValue * @param dataSet * @private */ _initValue (initFilterValue, dataSet) { if (!dataSet || dataSet.length === 0) return const flag1 = initFilterValue && !this.value const flag2 = initFilterValue !== undefined && this.value && initFilterValue !== this.filterValue if (!flag1 && !flag2) return const attrForValue = this.attrForValue || 'id' const valueItems = [] const _initValueItems = (value) => { const target = dataSet.find(item => `${item[attrForValue]}` === `${value}`) if (target) { !valueItems.includes(target) && valueItems.push(target) if (target.children && target.children.length > 0) { target.children.forEach(child => _initValueItems(child[attrForValue])) } } } `${initFilterValue}`.split(',').forEach(value => _initValueItems(value)) const value = valueItems.map(item => item[attrForValue]).join(',') this.set('value', value) } } window.customElements.define('isu-tree', IsuTree)