isu-elements
Version:
Polymer components for building web apps.
468 lines (438 loc) • 13.7 kB
JavaScript
import { html, PolymerElement } from '@polymer/polymer'
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class'
import '@webcomponents/shadycss/entrypoints/apply-shim.js'
import '@polymer/iron-icon/iron-icon'
import '@polymer/iron-icons/iron-icons'
import '@polymer/iron-icons/social-icons'
import { BaseBehavior } from './behaviors/base-behavior'
import { AjaxBehavior } from './behaviors/ajax-behavior'
import './behaviors/isu-elements-shared-styles.js'
import './isu-tree.js'
import './isu-iron-fit.js'
import { dom } from '@polymer/polymer/lib/legacy/polymer.dom'
/**
Example:
```html
```
## Styling
The following custom properties and mixins are available for styling:
|Custom property | Description | Default|
|----------------|-------------|----------|
|`--isu-ui-font-family` | The font family of the picker | Microsoft YaHei
|`--isu-ui-font-size` | The font size of the picker | 14px
|`--isu-select-tree-new-collapse` | Mixin applied to the collapse tree | {}
|`--isu-select-tree-new-input` | Mixin applied to the input div | {}
|`--isu-label` | Mixin applied to the label | {}
* @customElement
* @polymer
* @demo demo/isu-select-tree/index.html
*/
class IsuSelectTreeNew extends mixinBehaviors([BaseBehavior, AjaxBehavior], PolymerElement) {
static get template () {
return html`
<style include="isu-elements-shared-styles">
:host {
display: flex;
line-height: var(--isu-select-tree-new-height, var(--isu-default-line-height, 34px));
font-family: var(--isu-ui-font-family), sans-serif;
font-size: var(--isu-ui-font-size);
position: relative;
box-sizing: border-box;
width: var(--isu-select-tree-new-width, 320px);
}
#newtree-collapse {
position: absolute;
z-index: 999;
min-width: 225px;
background: white;
border: 1px solid lightgray;
border-radius: 5px;
height: 420px;
overflow-y: auto;
@apply --isu-select-tree-new-collapse
}
#newtree-collapse[hidden] {
visibility: hidden;
height: 0;
opacity: 0;
}
#select__container {
flex: 1;
position: relative;
display: flex;
min-width: 0px;
}
#select__container[hidden] {
display: none;
}
.input-div {
line-height: calc(var(--isu-select-tree-new-height, var(--isu-default-line-height, 34px)) - 2px - 4px);
height: 24px;
flex: 1;
font-family: 'Microsoft Yahei', sans-serif;
font-size: inherit;
padding: 2px 8px;
min-width: inherit;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
@apply --isu-select-tree-new-input
}
.placeholder {
color: #999;
}
#tag-content {
flex: 1;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
overflow-y: auto;
}
#tag-content::-webkit-scrollbar, #select-collapse::-webkit-scrollbar {
display: none;
}
.tag {
max-width: calc(100% - 14px);
color: #fff;
background: var(--isu-ui-bg);
border-radius: 4px;
margin: 3px 2px;
padding: 0 4px;
min-height: 22px;
line-height: 22px;
display: flex;
word-break: break-all;
cursor: default;
@apply --isu-select-tree-new-tag;
}
.tag-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
@apply --isu-select-tag-name;
}
.tag-deleter {
margin-left: 2px;
width: 18px;
color: #fff;
cursor: pointer;
display: inline-block;
--iron-icon-height: auto;
@apply --isu-select-tree-tag-deleter;
}
.tag-deleter:hover {
color: var(--isu-ui-red);
}
:host([show-all]) #tag-content {
height: auto;
min-height: 24px
@apply --isu-select-tag-content;
}
.view-text {
@apply --isu-view-text
}
.input-div.true {
border: 1px solid #D9001B;
}
</style>
<template is="dom-if" if="[[ toBoolean(label) ]]">
<div class="isu-label-div"><span class$="isu-label [[fontSize]]">[[label]]</span><span class="isu-label-after-extension"></span></div>
</template>
<div id="select__container" hidden="[[isAllTrue(isView, readonly)]]" class$="[[fontSize]]">
<div id="tag-content" tabindex="0" on-click="_inputFocus" class$="input-div [[_isRequired(required, value)]]">
<template is="dom-if" if="[[isArrayEmpty(filterSelectedItems)]]">
<span class="placeholder">[[placeholder]]</span>
</template>
<template is="dom-if" if="[[!isArrayEmpty(filterSelectedItems)]]">
<template is="dom-repeat" items="[[ filterSelectedItems ]]">
<div class="tag">
<div class="tag-name" title="[[getValueByKey(item, attrForLabel)]]">
<span>[[getValueByKey(item, attrForLabel)]]</span>
<span>
<iron-icon class="tag-deleter" icon="icons:clear" data-args="[[ getValueByKey(item, attrForValue) ]]" on-click="_deleteTag"></iron-icon>
</span>
</div>
</div>
</template>
</template>
</div>
<isu-iron-fit id="newtree-collapse" auto-fit-on-attach vertical-align="auto" horizontal-align="auto" no-overlap dynamic-align hidden="[[!isShowCollapse]]">
<isu-tree id="tree" data="{{data}}" data-set="{{dataSet}}" selected-items="{{selectedItems}}" value="{{value}}"
filter-selected-items="{{filterSelectedItems}}" filter-value="{{filterValue}}" init-filter-value="[[initFilterValue]]"
attr-for-value="[[attrForValue]]" attr-for-label="[[attrForLabel]]"
only-select-level="[[onlySelectLevel]]" filter-fn="[[filterFn]]"
multi="[[multi]]" show-search-input="[[showSearchInput]]" default-expand-all search-word="{{searchWord}}"
is-render-nodes="[[isShowCollapse]]"
></isu-tree>
</isu-iron-fit>
<div class="prompt-tip__container" data-prompt$="[[prompt]]">
<div class="prompt-tip">
<iron-icon class="prompt-tip-icon" icon="social:sentiment-very-dissatisfied"></iron-icon>
[[prompt]]
</div>
</div>
</div>
<template is="dom-if" if="[[isAllTrue(isView, readonly)]]">
<div class$="view-text [[fontSize]]">
<span>[[textValue]]</span>
</div>
</template>
`
}
static get properties () {
return {
/**
* A url for fetching local data, the response data of the request should be json.
* @type {string}
*/
src: {
type: String
},
/**
* The data of the tree
* @type {array}
* @default []
*/
data: {
type: Array,
value: [],
notify: true
},
/**
* The label of the select tree.
* @type {string}
*/
label: {
type: String
},
attrForLabel: {
type: String,
value: 'label'
},
/**
* Attribute name for value.
* @type {string}
* @default 'id'
*/
attrForValue: {
type: String,
value: 'id'
},
/**
* The placeholder of the select.
* @type {String}
* @default '请选择'
*/
placeholder: {
type: String,
value: '请选择'
},
/**
*
* The selected value of this select tree
* @type {string}
*/
value: {
type: String,
notify: true
},
/**
* An array of the selected items
* @type {array}
*/
selectedItems: {
type: Array,
notify: true
},
/**
* 对选中数据结果进行处理的函数
*/
filterFn: {
type: Function
},
/**
* 过滤属性,对选中的结果集进行处理,过滤选中结果集只为指定层级的数据,与filterFn并行使用且优先于filterFn,eg: '2,3'
*/
onlySelectLevel: {
type: String
},
filterSelectedItems: {
type: Array,
notify: true
},
filterValue: {
type: String,
notify: true
},
/**
* 仅做初始化数据使用
* 应用场景,通过filterValue回显数据
*/
initFilterValue: {
type: String
},
/**
* Set to true, if the selection is required.
* @type {boolean}
* @default false
*/
required: {
type: Boolean,
value: false,
reflectToAttribute: true
},
/**
* Set to true, if the select is readonly.
* @type {boolean}
* @default false
*/
readonly: {
type: Boolean,
value: false,
reflectToAttribute: true
},
/**
* The prompt tip to show when input is invalid.
* @type {string}
*/
prompt: {
type: String
},
/**
* The prompt tip's position. top/bottom
* @type String
* @default ''
*/
promptPosition: {
type: String,
value: ''
},
/**
* The text mode display requires readonly=true to take effect
* @type {boolean}
* @default false
* */
isView: {
type: Boolean,
value: false
},
/**
* The connector to connect labels when the isView=true, eg: "苹果,香蕉,梨"
* @type {string}
* @default ','
* */
joinConnector: {
type: String,
value: ','
},
/**
* multiple options or not,default not
* @type {boolean}
* @default false
*/
multi: {
type: Boolean,
value: false
},
/**
* Whether to display the search box
* @type {boolean}
* @default false
*/
showSearchInput: {
type: Boolean,
value: false
},
/**
* 输入框是否因为显示数量太多被撑开展示全部,默认展示全部
* */
showAll: {
type: Boolean,
value: true,
reflectToAttribute: true
},
textValue: {
type: String,
notify: true,
computed: '_textValueComputed(selectedItems, filterSelectedItems)'
},
/**
* 是否显示选择树面板,默认不显示
*/
isShowCollapse: {
type: Boolean,
value: false
}
}
}
static get is () {
return 'isu-select-tree-new'
}
static get observers () {
return [
'_srcChanged(src)'
]
}
connectedCallback () {
super.connectedCallback()
const target = dom(this.$['newtree-collapse']).rootTarget
const myFit = this.$['newtree-collapse']
myFit.positionTarget = target || this.$['tag-content']
const self = this
document.addEventListener('click', e => {
e.stopPropagation()
// 点击除了组织树以外的其他地方,组织树都消失
const composedPath = e.composedPath()
if (!composedPath.some(item => item.tagName === 'ISU-SELECT-TREE-NEW')) {
self.set('searchWord', null)
self._displayCollapse(false)
}
})
this.addEventListener('select-tree-close-collapse', e => {
self._displayCollapse(false)
})
}
_inputFocus () {
if (!this.readonly) {
this._displayCollapse(true)
}
}
/**
* Whether to display the selection panel
* @param display
* @private
*/
_displayCollapse (display) {
this.set('isShowCollapse', display)
}
/**
* Validate, true if the select is set to be required and this.selectedValues.length > 0, or else false.
* @return {boolean}
*/
validate () {
return this.required ? !!this.value.trim() : true
}
async _srcChanged (src) {
if (!src) return
const data = await this.query({ url: src, data: {} }, { showLoading: false })
if (Array.isArray(data)) {
this.set('data', data)
} else {
this.set('data', [data])
}
}
_textValueComputed (selectedItems, filterSelectedItems) {
return (this.filterFn && this.isFunction(this.filterFn)) || !!this.onlySelectLevel ? (filterSelectedItems || []).map(item => item[this.attrForLabel]).join(this.joinConnector) : (selectedItems || []).map(item => item[this.attrForLabel]).join(this.joinConnector)
}
_deleteTag (e) {
e.stopPropagation()
const curItem = e.model.item
const newSelectedItems = this.selectedItems.filter(item => item !== curItem)
this.set('selectedItems', newSelectedItems)
}
_isRequired (required, value) {
return required ? (!value) : false
}
}
window.customElements.define(IsuSelectTreeNew.is, IsuSelectTreeNew)