UNPKG

isu-element

Version:

Polymer components for building web apps.

505 lines (463 loc) 14.2 kB
import {html, PolymerElement} from "@polymer/polymer"; import './behaviors/isu-tree-shared-styles.js'; import '@polymer/paper-styles/default-theme.js'; import '@polymer/paper-checkbox'; import {BaseBehavior} from "./behaviors/base-behavior"; import {mixinBehaviors} from "@polymer/polymer/lib/legacy/class"; import '@polymer/paper-radio-button'; import Node from "./utils/tree/node"; /** * * `isu-tree-node` * * Example: * ```html * * ``` * * @customElement * @polymer * @demo demo/isu-tree/index.html */ class IsuTreeNode extends mixinBehaviors([BaseBehavior], PolymerElement) { static get template() { return html` <style include="isu-tree-shared-styles"> .black-span { display: inline-block; width: 24px; height: 24px; } paper-checkbox { --paper-checkbox-unchecked-color: #B1B6BF; position: relative; @apply --paper-checkbox } paper-checkbox.half{ --paper-checkbox-unchecked-background-color: #3F51B5; --paper-checkbox-unchecked-color: #3F51B5; @apply --paper-checkbox-half-choose } paper-checkbox.half::after { content: '-'; display:inline-block; font-size: 30px; font-weight: bold; width: 24px; height: 24px; color: white; position: absolute; top: 8px; left: 4px; @apply --paper-checkbox-half-after } .trigger__icon { color: #B1B6BF; } </style> <template is="dom-if" if="{{node.visible}}"> <div class="dht-tree-twig-one"> <div class="dht-tree-node-content" style$="{{_getIndentStyle(level, indent)}}" on-click="showNode"> <!--箭头--> <template is="dom-if" if="[[node.childNodes.length]]"> <iron-icon class="trigger__icon" icon="icons:arrow-drop-down" style$="{{_getRotateStyle(rotate)}}"></iron-icon> </template> <!--没有子元素的时候箭头用空span代替--> <template is="dom-if" if="[[!node.childNodes.length]]"> <span class="black-span"></span> </template> <!--多选框--> <template is="dom-if" if="[[showCheckbox]]"> <paper-checkbox class="checkbox-item half" class$="[[getHalfClass(node.indeterminate)]]" checked="{{ node.checked }}" disabled="{{ node.disabled }}" on-change="__checkedChangeHandler" on-click="__checkedClickedHandler" value="[[ getValueByKey(item, attrForValue) ]]"> [[ getValueByKey(item, attrForLabel) ]] </paper-checkbox> </template> <!--单选框--> <template is="dom-if" if="[[showRadio]]"> <paper-radio-button class="checkbox-item" name="radio" checked="{{ node.checked }}" disabled="{{ node.disabled }}" on-change="__checkedRadioChangeHandler" on-click="__checkedRadioClickedHandler" value="[[ getValueByKey(item, attrForValue) ]]"> [[ getValueByKey(item, attrForLabel) ]] </paper-radio-button> </template> <!--可自定义部分--> <slot name="before-label"></slot> <span>[[node.label]]</span> <!--可自定义部分--> <slot name="after-label"></slot> </div> <!--<transition-group name="dht-tree-node">--> <template is="dom-if" if="{{isShow}}"> <template is="dom-repeat" items="{{node.childNodes}}" index-as="index"> <isu-tree-node show-checkbox="[[showCheckbox]]" show-radio="[[showRadio]]" is-checked="{{isChecked}}" search-word="[[searchWord]]" is-show="{{isShow}}" key="[[getNodeKey(item, index)]]" node="{{item}}" id="{{item.nodeId}}" default-expand-all="[[defaultExpandAll]]" level="[[_getNextLevel(level)]]" data-location="[[_getDataLocation(index)]]" indent="[[indent]]" > <slot name="before-label"></slot> </isu-tree-node> </template> </template> <!--</transition-group>--> </div> </template> `; } static get properties() { return { tree: Object, rotate: { type: Number, value: -90 }, // 三角形标记 /** * 是否显示多选框 * */ showCheckbox: { type: Boolean, value: false }, /** * 是否显示单选框 * */ showRadio: { type: Boolean, value: false }, /** * 手风琴模式,一次只展示一个 * */ accordion: { type: Boolean, value: false }, /** * 是否默认展开所有节点 * */ defaultExpandAll: { type: Boolean, value: false }, /** * 操作子元素关闭 * */ isShow: { type: Boolean, value: false, observer: '__isShowChanged' }, /** * 缩进 * */ indent: { type: Number, value: 18 }, dataLocation: Array, // 数据定位,表示层级和数据位置 level: Number, // 当前层级 node: {// 子节点数据 type: Node, notify: true, reflectToAttribute: true }, isChecked: { type: Boolean, value: false }, isFirst: { type: Boolean, value: false }, visible: { type: Boolean, value: true }, id: { type: String }, searchWord: { type: String }, isIndeterminate: { type: Boolean, value: false } } } static get is() { return "isu-tree-node"; } static get observers() { return [ '_defaultExpandAllChanged(defaultExpandAll)', '_searchWordChanged(searchWord)', '_childNodesChanged(node.childNodes.*)', '_isIndeterminateChanged(isIndeterminate)', '_notifyDataChanged(isChecked)', '_notifyDataChanged(node.checked)', '_isFirst(isFirst)' ] } connectedCallback() { super.connectedCallback(); const parent = this.parentNode.host || this.parentNode if (parent.isTree) { this.tree = parent } else { this.tree = parent.parentNode.host.tree } } _isFirst(isFirst) { if(!this.defaultExpandAll) { this.set('isShow', isFirst) this.set('rotate', isFirst ? 0 : -90) } } getHalfClass(isIndeterminate) { return isIndeterminate === true ? 'half' : '' } _isIndeterminateChanged(isIndeterminate) { if (!this.node.disabled && isIndeterminate) { this.set('isIndeterminate', isIndeterminate) this.set('node.indeterminate', isIndeterminate) } } _childNodesChanged (target) { this._showNodeFilter() const {path, value} = target if(path.includes('checked')) { this.__setNodeChecked(this) } // this._notifyDataChanged(this.isChecked) } _searchWordChanged(searchWord) { this._showNodeFilter() } __isShowChanged(isShow) { const self = this // 这里是从父节点对下面的所有子孙节点手动添加监听,防止节点折叠的时候选择父节点,子孙节点选择错误的问题。后面需要再优化 if(isShow) { const recursionNotifyNodeChecked = (prefixKey, curNode) => { self.notifyPath(`${prefixKey}.childNodes.splices`) self.notifyPath(`${prefixKey}.checked`) const childNodes = curNode.childNodes childNodes.forEach((node, index) => { recursionNotifyNodeChecked(`${prefixKey}.childNodes.${index}`, node) }) } recursionNotifyNodeChecked('node', this.node) } } _showNodeFilter () { const self = this const visible = self.filterNode(self.searchWord, self.node) self.set('node.visible', visible) } filterNode(searchWord, node) { if (!node) { return false } if (!searchWord) { return true } const self = this const {data, childNodes} = node const selfVisible = data.label.includes(searchWord) const childVisilbe = childNodes.some(childNode => { return self.filterNode(searchWord, childNode) }) return selfVisible || childVisilbe } _defaultExpandAllChanged(defaultExpandAll) { if (defaultExpandAll) { this.isShow = true this.rotate = 0 } } _getNextLevel(level) { return level + 1 } showNode () { if (this.node.childNodes.length <= 0) return false const parent = this.parentNode.host || this.parentNode if (this.isShow) { this.isShow = false this.rotate = -90 return } //如果开启了手风琴模式,一次展示一个层级 if (this.accordion) { parent.shadowRoot.querySelectorAll('isu-tree-node').forEach(el => { el.isShow = false el.rotate = -90 }) } //操作子元素方式开启关闭 this.isShow = true this.rotate = 0 } _getDataLocation(index) { return [this.level + 1, index] } _getIndentStyle(level, indent) { return `padding-left: ${ level * indent }px` } _getRotateStyle(rotate) { return `transform: rotate(${ rotate }deg)` } getNodeKey (node, index) { return node.id ? node.id : index } __checkedRadioChangeHandler(e) { e.stopPropagation() const isChecked = e.target.checked const allPaperRadioButton = this.__getAllPaperRadioButton() allPaperRadioButton.forEach(item => item.checked = false ) this.isChecked = isChecked this.set('node.checked', isChecked) const store = this.tree.store let param = { checkedNodes: store.getCheckedNodes(), checkedKeys: store.getCheckedKeys(), halfCheckedNodes: store.getHalfCheckedNodes(), halfCheckedKeys: store.getHalfCheckedKeys() } this.dispatchEvent(new CustomEvent("check", {detail: {data: this.node.data, ...param}})); } __checkedChangeHandler(e) { e.stopPropagation() const isChecked = e.target.checked if(this.isIndeterminate && isChecked) { this.__setNodeChecked(this) } this._notifyDataChanged(isChecked) } _notifyDataChanged(isChecked) { const self = this if(isChecked === undefined || isChecked === null) { return } if(this.tree) { const store = this.tree.store let param = { checkedNodes: store.getCheckedNodes(), checkedKeys: store.getCheckedKeys(), halfCheckedNodes: store.getHalfCheckedNodes(), halfCheckedKeys: store.getHalfCheckedKeys() } this.dispatchEvent(new CustomEvent("check-button", {detail: {data: this.node.data, ...param}, bubbles: true, composed: true})) } } __notifyDataChangedByHalfHandler (isChecked, isHalfCheck) { if(!isHalfCheck) { // 全选递归选中/取消选中子节点 this._notifyDataChanged(isChecked) } } __getChecked(node) { if (!node) return false const checked = node.childNodes.every(childNode => childNode.checked == true) const childrenChecked = node.childNodes.every(childNode => { return this.__getChecked(childNode) }) return checked && childrenChecked } __getAllPaperRadioButton() { // 拿到所有的paper-radio-button按钮 let allCustomElements = []; const findAllPaperRadioButton = (nodes) =>{ for (let i = 0, el; el = nodes[i]; ++i) { if (el.localName === 'paper-radio-button') { allCustomElements.push(el); } // 如果元素有shadow DOM, 那么就继续深入 if (el.shadowRoot) { findAllPaperRadioButton(el.shadowRoot.querySelectorAll('*')); } } } findAllPaperRadioButton(document.querySelectorAll('*')); return allCustomElements } __setNodeChecked(ele) { const node = ele.node const noneChecked = this.__noneCheckedFn(node) const allChecked = this.__allCheckedFn(node) const halfChecked = this.__CheckHalfCheckededFn(node) if (noneChecked) { ele.set('isIndeterminate', false) ele.set('isChecked', false) ele.set('node.checked', ele.isChecked) ele.set('node.indeterminate', ele.isIndeterminate) } else if (halfChecked) { ele.set('isIndeterminate', true) ele.set('isChecked', false) ele.set('node.checked', ele.isChecked) ele.set('node.indeterminate', ele.isIndeterminate) } else if (allChecked) { ele.set('isIndeterminate', false) ele.set('isChecked', true) ele.set('node.checked', ele.isChecked) ele.set('node.indeterminate', ele.isIndeterminate) } } __checkedRadioClickedHandler(e) { e.stopPropagation() } __checkedClickedHandler(e) { e.stopPropagation() const self = this const isChecked = e.target.checked // this.set('isShow', true) // this.set('rotate', 0) // this.shadowRoot.querySelectorAll('isu-tree-node').forEach(el => { // el.isShow = true // el.rotate = 0 // }) const recursionCheckedChildNode = (prefixKey, childNodes) => { childNodes.forEach((node, index) => { if(!node.disabled) { const key = `${prefixKey}.${index}.checked` self.set(key, isChecked) recursionCheckedChildNode(`${prefixKey}.${index}.childNodes`, node.childNodes) } }) } const prefixKey = `node.childNodes` const childNodes = this.node.childNodes recursionCheckedChildNode(prefixKey, childNodes) this._notifyDataChanged(isChecked) } __noneCheckedFn(node) { const self = this return node.childNodes.every(childNode => !childNode.checked && self.__noneCheckedFn(childNode)) } __allCheckedFn(node) { const self = this if(node.childNodes.length === 0) { return true } return node.childNodes.every(childNode => childNode.checked && self.__allCheckedFn(childNode)) } __CheckHalfCheckededFn(node) { return !this.__noneCheckedFn(node) && !this.__allCheckedFn(node) } } window.customElements.define(IsuTreeNode.is, IsuTreeNode);