小程序树形组件单选实现

javascript/jquery

浏览数:69

2020-6-12

AD:资源代下载服务

树形组件,常规思想是递归实现,主要是不知道有多少层。在react中,支持jsx写法,即HTML中加入js代码,就可以使用函数递归来生成树,而小程序里面的写法,我现在还没搞太懂,个人感觉不支持。就使用组件递归来生成树,即在组件中调用自身。
WXML代码

<view>
<block wx:for="{{tree}}" wx:key="{{item.id}}">
<view style="display:flex;align-items:center;margin-left:{{depth*50}}rpx;height:80rpx;font-size:36rpx;">
  <van-icon size="22rpx" class="tree_icon" wx:if="{{item.children}}" name="{{item.open?'../../../assets/common/icon/decline.png':'../../../assets/common/icon/addition.png'}}" data-id="{{item.id}}" bindtap="onchange"></van-icon>
  <view class="{{item.children?'parent':'node'}} {{item.selected?'selected':''}}" bindtap="handleClick" data-id="{{item.id}}">{{item.title}}</view>
</view>
<block wx:if="{{item.children}}">
  <view hidden="{{!item.open}}">
    <treeSelect treeList="{{item.children}}" id="treeSelect" bind:change="change" bind:onclick="click" depth="{{depth+1}}"></treeSelect>
  </view>
</block>
</block>
</view>

js代码:

    const tranverse=(e)=>{
    for(let i in e){
    if (e[i].children) {
      e[i].open = false;
      tranverse(e[i].children)
    }
    e[i].selected=false;
  }
}
Component({
  properties:{
    treeList:{
      type:Array,
      value: [{
        title: 'Node1',
        id:0,
        children: [
          {
            title: 'Child Node1',
            id:2,
            children:[{
              title:'grandChild Node1',
              id:4
            }]
          },
          {
            title: 'Child Node2',
            id:3
          },
        ],
      },
      {
        title: 'Node2',
        id:1
      },
    ]},
    depth:{
    type:Number,
    value:0
    }
  },
  data:{
  },
  ready(){
    const {treeList}=this.properties; 
    for(let i in treeList){
      if(treeList[i].children){
        treeList[i].open=false;
        tranverse(treeList[i].children);
      }
      treeList[i].selected=false;
    }  
    this.setData({
      tree:treeList
    })
  },
  methods:{
    onchange(e){
      const {treeList}=this.data;
      const {id}=e.currentTarget.dataset;
      this.changeOpen(treeList,id);
      this.triggerEvent('change',id,{bubbles:true,composed:true});
      this.setData({
        tree:treeList
      }) 
    },
    //修改折叠状态
    changeOpen(tree,id){ 
      for(let i=0;i<tree.length;i+=1){
        if(tree[i].id===id){
          tree[i].open=!tree[i].open;
        }
        if(tree[i].children){
          this.changeOpen(tree[i].children,id);
        }
      }
      return;
    },
    change(e){
      const id = e.detail;
      const { treeList } = this.data;
      this.changeOpen(treeList, id);
      this.setData({
        tree: treeList
      })
    },
    click(e){
      const t = this.selectAllComponents('#treeSelect');
      t.forEach(item => {
        item.click(e);
      })
      let id='';
      if(e.detail){
        id = e.detail.id;
      }
      const { treeList } = this.data;
      this.setStatus(treeList, id);
      this.setData({
        tree: treeList
      })
    },
    handleClick(e){
      const t = this.selectAllComponents('#treeSelect');
      t.forEach(item => {
        item.click(e);
      })
      const {id}=e.currentTarget.dataset;
      const {treeList}=this.data;
      const value = this.getData(treeList,id)
      this.setStatus(treeList,id);
      this.triggerEvent('onclick',{id,value},{composed:true,bubbles:true});
      this.setData({
        tree:treeList
      })
    },
    //切换选中状态
    setStatus(tree,id){
     for(let i=0;i<tree.length;i+=1){
       if(tree[i].id==id){
         tree[i].selected=true;
       }else{
         tree[i].selected=false;
       }
       if (tree[i].children) {
         this.setStatus(tree[i].children, id);
     }
    }
    return ;
  },
  //获取选中项信息
    getData(tree, id) {
      for (let i = 0; i < tree.length; i += 1) {
        if (tree[i].id === id) {
          return tree[i].title;
        }
        if (tree[i].children) {
           this.getData(tree[i].children, id);
        }
      }
      return '';
    },
  }
})

调用时:

<treeSelect treeList="{{pageData}}" id="treeSelect" bind:onclick="lclick" depth="{{0}}"></treeSelect>

js代码:

lclick(e) {
const t = this.selectAllComponents('#treeSelect');
t.forEach(item => {
  item.click(e);
})
const {id,value} = e.detail;
const { pageData } = this.data;
if(pageData){
  this.setStatus(pageData, id);
}
this.setData({
  tree: pageData,
  choose:{id,value}
})
 },
 setStatus(tree, id) {   
for (let i = 0; i < tree.length; i += 1) {
  if (tree[i].id == id) {
    tree[i].selected = true;
  } else {
    tree[i].selected = false;
  }
  if (tree[i].children) {
    this.setStatus(tree[i].children, id);
  }
}
return;
 },

后续将继续优化
代码讲解:
上半部分就是渲染本层的树节点,其中包含对是否是父节点的判断,父节点前会加一个展开/关闭图标,若有子节点,就调用自身渲染本层节点,depth主要用来做缩进,每加一层,后面的树就会向后移动。
接下来就是重头戏,单选是怎样实现的。首先,当选中某一节点时,会拿到对应节点的id,然后拿着id去遍历该层数据将对应id的节点的selected设为true,其余设为false,并向下传递,同样更改下层的节点为false,关键到了,怎么修改更高层的状态呢,通过this.triggerEvent()将id传到高层节点,做同样的操作,配置triggerEvent时需配置{ bubbles: true, composed: true },允许冒泡,否则数据传不到上层。然后就可以实现单选了。具体见代码。我已经说不清了,就这样吧。

组件递归很简单,也很实用。但现实是残酷的,我需要实现单选功能,本来想的很简单,通过回调将选中项的selected属性设为true,其余项的selected属性设为false即可,但是事实是由于是递归生成的,每一层之间没有关联,每层的treelist只是根节点的一部分,就会有一个问题是每一层可以实现单选,但是并不满足条件。只能另想办法。

思路很简单,但点击某个节点时,一方面向下传递,将下级的节点的selected设为false,另一方面向上传递,将父节点的其余节点selected设为false,实现整体的统一,即实现单选。具体详见代码。
其实还想过用小程序的模板即template,这样就能很容易实现单选,但是小程序不允许用template递归,就只能放弃了。

作者:tbao