2019-11-22 20:33:17 +08:00

416 lines
12 KiB
JavaScript

import { h, Component, render } from 'preact'
import { datas, childData } from '@/index.js';
import { checkUserAgent, isFunction, isArray, toNum, mergeArr } from '@/common/util'
import Label from '../label';
import General from '../plugin/general';
import Custom from '../plugin/custom';
import Tree from '../plugin/tree';
/**
* 框架渲染类, 渲染基础的外边框 + 属性变化监听
*/
class Framework extends Component{
constructor(options){
super(options);
//保留对象引用
childData[options.el] = this;
//初始化state数据
this.state = this.initState();
this.bodyView = null;
}
initState(){
return {
data: [],
dataObj: {},
flatData: [],
sels: [],
show: false,
tmpColor: '',
}
}
init(props, refresh){
let { data, prop, initValue } = props, sels;
//如果新数据和旧数据不同 或者 强制刷新 才进行数据处理
if(refresh){
let dataObj = {};
let flatData = [];
this.load(data, dataObj, flatData);
sels = initValue ? this.exchangeValue(initValue, true, dataObj) : Object.values(dataObj).filter(item => item[prop.selected] === true).filter(item => item[prop.optgroup] !== true)
this.setState({ sels, dataObj, flatData });
}
this.setState({ data });
return sels;
}
exchangeValue(arr, filterGroup = true, dataObj = this.state.dataObj){
let list = arr.map(sel => typeof sel === 'object' ? sel : dataObj[sel]).filter(a => a)
filterGroup && (list = list.filter(item => item[this.props.prop.optgroup] !== true))
return list;
}
value(sels, show, listenOn){
if(show !== false && show !== true){
show = this.state.show;
}
const { prop, tree } = this.props;
let changeData = this.exchangeValue(sels, !tree.show);
if(tree.show && tree.strict){
let data = this.state.data;
this.clearAndReset(data, changeData);
changeData = this.init({ data, prop }, true);
}
this.resetSelectValue(changeData, changeData, true, listenOn);
this.setState({ show })
}
clearAndReset(data, changeData){
const { selected, children, value } = this.props.prop;
data.forEach(item => {
item[selected] = changeData.findIndex(c => c[value] === item[value]) != -1;
let child = item[children];
child && isArray(child) && this.clearAndReset(child, changeData)
})
}
load(data, dataObj, flatData, parent){
const { prop, tree } = this.props;
const { children, optgroup, value, selected, disabled } = prop;
data.forEach(item => {
//数据提取/处理
item.__node = { parent, loading: item.__node && item.__node.loading }
dataObj[item[value]] = item;
flatData.push(item);
//遍历子级数据
let child = item[children];
if(child && isArray(child)){
let len = child.length;
if(len > 0){
this.load(child, dataObj, flatData, item);
//是否包含子节点
item[optgroup] = true;
//严格的父子结构
if(tree.strict){
if(item[selected] === true){
delete item[selected]
child.forEach(c => c[selected] = true)
}
if(item[disabled] === true){
delete item[disabled]
child.forEach(c => c[disabled] = true)
}
}
//检查子节点的数据是否都被选中
let slen = child.filter(i => i[selected] === true || i.__node.selected === true).length;
item.__node.selected = slen === len;
item.__node.half = slen > 0 && slen < len;
item.__node.disabled = child.filter(i => i[disabled] === true || i.__node.disabled === true).length === len;
}
}
});
}
//重置已选择数据
resetSelectValue(sels = [], change = [], isAdd, listenOn = true){
let on = this.props.on;
if(isFunction(on) && this.prepare && listenOn){
on({ arr: sels, change, isAdd });
}
this.setState({ sels });
}
updateBorderColor(tmpColor){
this.setState({ tmpColor });
}
treeHandler(sels, parent, change, type){
const { value, selected, disabled, children, optgroup } = this.props.prop;
let child = parent[children];
child.filter(item => !(item[disabled] || item.__node.disabled)).forEach(item => {
if(item[optgroup]){
this.treeHandler(sels, item, change, type);
}else{
let index = sels.findIndex(sel => sel[value] == item[value])
if(type === 'del'){
if(index != -1){
sels.splice(index, 1);
change.push(item);
}
}else if(type === 'half' || type === 'add'){
if(index == -1){
sels.push(item);
change.push(item);
}
}
}
})
let len = child.length;
let slen = child.filter(i => sels.findIndex(sel => sel[value] === i[value]) !== -1 || i.__node.selected === true).length;
parent.__node.selected = slen === len;
parent.__node.half = slen > 0 && slen < len;
}
//选项, 选中状态, 禁用状态, 是否强制删除:在label上点击删除
itemClick(item, itemSelected, itemDisabled, mandatoryDelete){
const { theme, prop, radio, repeat, clickClose, max, maxMethod, tree } = this.props
let { sels } = this.state
const { value, selected, disabled, children, optgroup } = prop
//如果是禁用状态, 不能进行操作
if(itemDisabled) return;
if(item[optgroup] && tree.strict){
let child = item[children], change = [], isAdd = true;
if(item.__node.selected){
this.treeHandler(sels, item, change, 'del');
isAdd = false;
}else if(item.__node.half){
this.treeHandler(sels, item, change, 'half');
//无法操作禁用状态, 变成取消操作
if(change.length === 0){
this.treeHandler(sels, item, change, 'del');
isAdd = false;
}
}else{
this.treeHandler(sels, item, change, 'add');
}
this.resetSelectValue(sels, change, isAdd);
this.setState({ data: this.state.data })
}else{
//如果现在是选中状态
if(itemSelected && (!repeat || mandatoryDelete)){
let index = sels.findIndex(sel => sel[value] == item[value])
if(index != -1){
sels.splice(index, 1);
this.resetSelectValue(sels, [item], !itemSelected);
}
}else{
//查看是否设置了多选上限
let maxCount = toNum(max);
if(maxCount > 0 && sels.length >= maxCount){
this.updateBorderColor(theme.maxColor);
//查看是否需要回调
maxMethod && isFunction(maxMethod) && maxMethod(sels, item);
return ;
}
//如果是单选模式
if(radio){
sels = [item];
}else{
sels = [...sels, item]
}
this.resetSelectValue(sels, [item], !itemSelected);
}
}
let parent = item.__node.parent;
if(parent){
while(parent){
let child = parent[children], len = child.length;
let slen = child.filter(i => sels.findIndex(sel => sel[value] === i[value]) !== -1 || i.__node.selected === true).length;
parent.__node.selected = slen === len;
parent.__node.half = (slen > 0 && slen < len) || child.filter(i => i.__node.half === true).length > 0;
parent = parent.__node.parent;
}
this.setState({ data: this.state.data })
}
//检查是否为选择即关闭状态, 强制删除情况下不做处理
clickClose && !mandatoryDelete && this.onClick();
};
//select框被点击
onClick(e){
if(this.props.disabled){
this.state.show !== false && this.setState({ show: false });
return ;
}
let show = !this.state.show;
if(show){
if(this.props.show && this.props.show() == false){
return;
}
//事件互斥原则, 打开一个多选, 关闭其他所有多选
Object.keys(datas).filter(key => key != this.props.el).forEach(el => datas[el].closed())
}else{
if(this.props.hide && this.props.hide() == false){
return;
}
//如果产生滚动条, 关闭下拉后回到顶部
this.bodyView.scroll && this.bodyView.scroll(0, 0);
}
this.setState({ show });
//阻止其他绑定事件的冒泡
e && e.stopPropagation();
}
onReset(data, type){
//重置数据
if(type === 'data'){
let changeData = data.filter(item => item[this.props.prop.selected] === true);
this.resetSelectValue(mergeArr(changeData, this.state.sels, this.props.prop), changeData, true);
let dataObj = {}, flatData = [];
this.load(data, dataObj, flatData);
this.setState({ data, flatData });
}else
//重置选中数据
if(type === 'sels'){
this.resetSelectValue(data, data, true);
}else
//追加数据
if(type === 'append'){
this.append(data);
}else
//清理数据
if(type === 'delete'){
this.del(data);
}else
//自动判断模式
if(type === 'auto'){
this.auto(data);
}else
//树状结构数据更新
if(type === 'treeData'){
this.value(this.state.sels, null, true)
}
}
append(arr){
let changeData = this.exchangeValue(arr);
this.resetSelectValue(mergeArr(changeData, this.state.sels, this.props.prop), changeData, true);
}
del(arr){
let value = this.props.prop.value;
let sels = this.state.sels;
arr = this.exchangeValue(arr);
arr.forEach(v => {
let index = sels.findIndex(item => item[value] === v[value]);
if(index != -1){
sels.splice(index, 1);
}
});
this.resetSelectValue(sels, arr, false);
}
auto(arr){
let value = this.props.prop.value;
let sels = arr.filter(v => this.state.sels.findIndex(item => item[value] === v[value]) != -1);
sels.length == arr.length ? this.del(arr) : this.append(arr);
}
//组件将要接收新属性
componentWillReceiveProps(props){
this.init(props, props.updateData);
}
//组件将要被挂载
componentWillMount(){
this.init(this.props, true);
}
render(config, state) {
const { theme, prop, radio, repeat, clickClose, on, max, maxMethod, content, disabled, tree } = config;
const borderStyle = { borderColor: theme.color };
let { data, dataObj, flatData, sels, show, tmpColor } = state;
//组件为禁用状态
if(disabled){
show = false;
}
//最外层边框的属性
const xmSelectProps = {
style: { ...config.style, ...(show ? borderStyle : {}) },
onClick: this.onClick.bind(this),
ua: checkUserAgent(),
size: config.size,
}
if(tmpColor){
xmSelectProps.style.borderColor = tmpColor;
setTimeout(() => {
xmSelectProps.style.borderColor = '';
this.updateBorderColor('');
}, 300);
}
//普通多选数据
const valueProp = prop.value;
const labelProps = { ...config, data, sels, ck: this.itemClick.bind(this), title: sels.map(sel => sel[prop.name]).join(',') }
const bodyProps = { ...config, data, dataObj, flatData, sels, ck: this.itemClick.bind(this), show, onReset: this.onReset.bind(this) }
//渲染组件
let Body = content ? <Custom { ...bodyProps } /> : tree.show ? <Tree { ...bodyProps } /> : <General { ...bodyProps } />;
return (
<xm-select { ...xmSelectProps } >
<input class="xm-select-default" name={ config.name } value={ sels.map(item => item[prop.value]).join(',') }></input>
<i class={ show ? 'xm-icon xm-icon-expand' : 'xm-icon' } />
{ sels.length === 0 && <div class="xm-tips">{ config.tips }</div> }
<Label { ...labelProps } />
<div class={ show ? 'xm-body' : 'xm-body dis' } ref={ ref => this.bodyView = ref}>
{ Body }
</div>
{ disabled && <div class="xm-select-disabled"></div> }
</xm-select>
);
}
//组件完成挂载
componentDidMount(){
this.prepare = true;
}
//此时页面又被重新渲染了
componentDidUpdate(){
let { direction } = this.props;
let rect = this.base.getBoundingClientRect();
if(direction === 'auto'){
//用于控制js获取下拉框的高度
this.bodyView.style.display = 'block';
this.bodyView.style.visibility = 'hidden';
//获取下拉元素的高度
let bodyViewRect = this.bodyView.getBoundingClientRect();
let bodyViewHeight = bodyViewRect.height;
//还原控制效果
this.bodyView.style.display = '';
this.bodyView.style.visibility = '';
//确定下拉框是朝上还是朝下
let y = rect.y || rect.top || 0;
let clientHeight = document.documentElement.clientHeight;
let diff = clientHeight - y - rect.height - 20;
direction = diff > bodyViewHeight || y < diff ? 'down' : 'up';
}
if(direction == 'down'){
this.bodyView.style.top = rect.height + 4 + 'px';
this.bodyView.style.bottom = 'auto';
}else{
this.bodyView.style.top = 'auto';
this.bodyView.style.bottom = rect.height + 4 + 'px';
}
}
}
export default Framework;