解决layui的table组件更新数据后每行toolbar工具栏不更新的问题

by img Microanswer Create at:Nov 1, 2019 5:29:03 PM 

Tags: layui layuitable table 表格更新 toolbar



layui是一套非常不错的后台管理系统UI框架,其简约并具有系统感的设计赢得了许多开发者的青睐。不过我在使用layui的动态表格组件时遇到了一个问题,更新一条数据时,对应的每行的toolbar(就是右边编辑/查看/删除这类按钮区)没有根据数据进行重新动态更新。我通过源码阅读,了解到了为什么没有更新了,并完美修复这个问题。下面将进行详细介绍。

一、问题重现

通常我们的表格右边都会有几个按钮,用来对某一条数据进行操作。而很大一种情况就是右边的操作按钮是根据数据状态显示对应操作功能的。那么这个时候,就可以使用其提供的 toolbar 字段来完成我们的需求。通过官方文档我们了解到,toolbar字段可以是一个模板id,而模板是支持动态渲染的。可在表格里刷新时,toolbar又不更新,这就很朴素迷离了啊。下面先看一个不会更新的示列:

上面的第二列按钮是根据用户身份显示不同的按钮的,工具条的模板定义如下:

<script id="data-tool-bar" type="text/html">
    <button class="layui-btn layui-btn-xs layui-btn-normal" lay-event="changeStatus">修改身份</button>
    {{# if(d.status === "vip"){ }}
        <button class="layui-btn layui-btn-xs">赠送礼包</button>
    {{# } else if (d.status === "poor") { }}
        <button class="layui-btn layui-btn-xs layui-btn-warm">催促充值</button>
    {{# } else if (d.status === "normal") { }}
        <button class="layui-btn layui-btn-xs layui-btn-primary">推送广告</button>
    {{# } }}
</script>

可以看到,这个第二个按钮是根据用户的不同身份,显示不同的按钮的。但是在更新数据修改用户身份时,后面的按钮并没有根据修改的状态显示对应的按钮,只是前面数据部分修改生效了。

二、追溯问题

为了修复这个问题,我们必须要进入源码一探究竟。我们知道,要监听这部分工具按钮的点击事件,可以通过给按钮指定一个 lay-event 来达到监听对应事件的效果。通过此事件可以获取到对应行的数据,并更具此数据进行进一步的业务操作。下面展示了上一节的【修改身份】按钮事件的代码:

// 监听按钮。这个 table 就是 layui.table 得到的全局table对象。
table.on('tool(test-table)', function (obj) {
    var event = obj.event;
    if ("changeStatus" === event) {
        var item = obj.data;
        // 根据当前状态确定下一个状态,这样来模拟依次变化身份。
        if (item.status === "vip") {
            item.status = "normal";
        } else if (item.status === "normal") {
            item.status = "poor";
        } else {
            item.status = "vip"
        }
        // 更新改变用户身份。
        obj.update(item);
    }
});

上述代码中,通过事件event可以拿到点击的这一行的data数据,然后我们将其status(身份)信息进行修改,并且通过其提供的update方法进行更新来完成界面上的更新。但发现这样做了之后,只有数据部分在界面上更新了,如果右边的toolbar是模板动态渲染的,就不会跟着新状态数据一起更新。

1、阅读源码找到位置

为了解决这个问题,询遍大街小巷,发现满到处都是与之相关的问题,而解决方案大多都是使用表格全局的reload方法将就用着。我可不是一个将就的人,必须死磕到底。

我们更新数据是通过update方法进行更新的,二话不说,找到layui的源代码打开目录./src/lay/modules/,找到其中的table.js组件文件。直接Ctrl+F进行搜索update方法名。还不算艰辛,直接可以定位到这块代码周围(下面的代码截取自layui源码提交版本号[5904e5b1344efa661b53f1cbd2a5d0e5b12ea4ef]的内容):

    //数据行中的事件监听返回的公共对象成员
    var commonMember = function(sets){
      var othis = $(this)
      ,index = othis.parents('tr').eq(0).data('index')
      ,tr = that.layBody.find('tr[data-index="'+ index +'"]')
      ,data = table.cache[that.key] || [];


      data = data[index] || {};

      return $.extend({
        tr: tr //行元素
        ,data: table.clearCacheKey(data) //当前行数据
        ,del: function(){ //删除行数据
          table.cache[that.key][index] = [];
          tr.remove();
          that.scrollPatch();
        }

        // 这里就是我们实际调用更新的代码位置。
        ,update: function(fields){ //修改行数据
          fields = fields || {};

          // 遍历传进来的每一个键值对(实际上就是我们的每一条数据)。
          layui.each(fields, function(key, value){
            // data 是通过服务器返回的实际的每一行数据
            if(key in data){

             // 这边定义一个templet用于确定下方是使用模板来更新数据还是
             // 直接使用字段的值来进行填充。
              var templet,
              td = tr.children('td[data-field="'+ key +'"]');
              data[key] = value;

              // 这个 eachCols 就是一个循环,i 是下标, item2 就是我们render函数
              // 传递进来的每一个col的配置项。
              that.eachCols(function(i, item2){

                // 判断指定字段是否使用模板进行数据展示。
                // 是的话就直接赋值模板。
                if(item2.field == key && item2.templet){
                  templet = item2.templet;
                }
              });

              // 然后这里就进行数据的模板渲染,然后更新到表格界面
              td.children(ELEM_CELL).html(parseTempData({
                templet: templet
              }, value, data));
              td.data('content', value);
            }
          });
        }
      }, sets);

经过上述代码分析后。我表示很惊讶。因为整个update函数里面根本就没有一点要去更新toolbar里面的视图的意思。 嘶~~。看到这里,我不禁倒吸一口冷气。要说layui开发团队不会写更新toolbar的方法是不可能的。可为什么不完成这个功能呢...难道是因为....😯我好像发现了什么。🤫别出声!

现在,既然找到了入口。不如就自己来实现这个功能吧!

2、实现更新

既然源码里没有更新toolbar的代码实现,那只好我们自己来实现这个功能了。我们的代码只需要在update方法后面继续追加实现就好了。所以在上述代码第52行下面继续追加在实现代码。实现之前,必须要先理清思路。

  • 第一步:先获取到配置的toolbar的模板id。
  • 第二步:使用模板id+数据来生成新的视图。
  • 第三步:找到toolbar应该放置的位置将新的视图放入。

第一步:获取到配置的toolbar的模板id

我们要为多次情况考虑,可能我们会为一行数据配置多个toolbar操作区域。所以我们需要使用一个数组来保存我们要更新的toolbar区域。然后通过已出现在更数据区域的eachCols方法获取到我们配置的toolbar的模板id。如下:

// 兼容一行中有好几个列使用toolbar的情况,所以使用数组。
var toolbarTemps = [];
// 在下面继续更新toolbar的内容。
that.eachCols(function (index, colOption) {

    // 筛选出配置了toolbar的条目。
    if (colOption.toolbar) {

        // 拿到了之后,除了需要模板id外,还需要一个key。这个key在初始化时内部已经生成
        // 要通过它来定位这个toolbar放在哪一个位置表格td下的位置,所以也获取下来。
        toolbarTemps.push({
            temp: colOption.toolbar,
            key: colOption.key
        });
    }
});

第二步:使用数据加模板生成新视图

现在我们拿到了模板id,已经保存在了toolbarTemps里面,只需要再拿到这一行的数据就可以进行生成了。通过原本更新数据的代码可以不难发现,update 方法里面的data就时对应的行的数据:

// 直接循环已经获取到的模板id。
layui.each(toolbarTemps, function (index, value) {
    var temp = value.temp; // 模板id。
    var key = value.key;   // 用于定位的key。

    // 用来保存新的渲染结果。
    var tempStr;
    if (temp.indexOf("#") > -1) {
        // 说明这是模板id。
        tempStr = $(temp).html();
    } else {
        // 否则认为这个字符串本身就是一个模板字符串。
        tempStr = temp;
    }
    // 使用数据重新渲染
    if (tempStr) {
        var result = laytpl(tempStr).render(data);
    }
});

这里的代码中使用了laytpl组件,不用担心,可以直接放心使用,table组件是引入了这个组件,并且table组件作用范围类时可以使用的。

第三步:将生成的结果放在对应的位置

通过阅读更新数据时的代码(第二章第一小节示例的代码),不难发现第4行,index = othis.parents('tr').eq(0).data('index')获取的是数据的下表,而且是通过预先设置的dom属性来获取的。而这一行代码中前部分我们需要用到,因此需要对这行代码进行小修改,将其拆分:

// 原来的代码
var commonMember = function(sets){
  var othis = $(this)
  ,index = othis.parents('tr').eq(0).data('index')
  ,tr = that.layBody.find('tr[data-index="'+ index +'"]')
  ,data = table.cache[that.key] || [];
  // 后面的省略...

// 将其修改为
var commonMember = function(sets){
  var othis = $(this)
  ,rowTr = othis.parents('tr').eq(0)
  ,index = rowTr.data('index')
  ,tr = that.layBody.find('tr[data-index="'+ index +'"]')
  ,data = table.cache[that.key] || [];
  // 后面的省略...

拆分完成后,继续完成上一步的代码,在获取到的result下面添加更新界面的逻辑。代码如下:

// 直接循环已经获取到的模板id。
layui.each(toolbarTemps, function (index, value) {
    var temp = value.temp; // 模板id。
    var key = value.key;   // 用于定位的key。

    // 用来保存新的渲染结果。
    var tempStr;
    if (temp.indexOf("#") > -1) {
        // 说明这是模板id。
        tempStr = $(temp).html();
    } else {
        // 否则认为这个字符串本身就是一个模板字符串。
        tempStr = temp;
    }
    // 使用数据重新渲染
    if (tempStr) {
        var result = laytpl(tempStr).render(data);

        // 要使用key来确定具体的选择器,才能正确的找到位置。然后将结果设置进去。
        rowTr.find("td[data-off='true'] div.laytable-cell-1-" + key).html(result);
    }
});

3、完整修复代码

现在,所有的工作都已经完成了。来看看最终我们现在把代码改成了什么样:

var commonMember = function(sets){
  var othis = $(this)
  ,rowTr = othis.parents('tr').eq(0)
  ,index = rowTr.data('index')
  ,tr = that.layBody.find('tr[data-index="'+ index +'"]')
  ,data = table.cache[that.key] || [];

  data = data[index] || {};

  return $.extend({
    tr: tr //行元素
    ,data: table.clearCacheKey(data) //当前行数据
    ,del: function(){ //删除行数据
      table.cache[that.key][index] = [];
      tr.remove();
      that.scrollPatch();
    }

    // 这里就是我们实际调用更新的代码位置。
    ,update: function(fields){ //修改行数据
      fields = fields || {};

      // 遍历传进来的每一个键值对(实际上就是我们的每一条数据)。
      layui.each(fields, function(key, value){
        // data 是通过服务器返回的实际的每一行数据
        if(key in data){

         // 这边定义一个templet用于确定下方是使用模板来更新数据还是
         // 直接使用字段的值来进行填充。
          var templet,
          td = tr.children('td[data-field="'+ key +'"]');
          data[key] = value;

          // 这个 eachCols 就是一个循环,i 是下标, item2 就是我们render函数
          // 传递进来的每一个col的配置项。
          that.eachCols(function(i, item2){

            // 判断指定字段是否使用模板进行数据展示。
            // 是的话就直接赋值模板。
            if(item2.field == key && item2.templet){
              templet = item2.templet;
            }
          });

          // 然后这里就进行数据的模板渲染,然后更新到表格界面
          td.children(ELEM_CELL).html(parseTempData({
            templet: templet
          }, value, data));
          td.data('content', value);
        }
      });
      // 兼容一行中有好几个列使用toolbar的情况,所以使用数组。
      var toolbarTemps = [];
      // 在下面继续更新toolbar的内容。
      that.eachCols(function (index, colOption) {

          // 筛选出配置了toolbar的条目。
          if (colOption.toolbar) {

              // 拿到了之后,除了需要模板id外,还需要一个key。这个key在初始化时内部已经生成
              // 要通过它来定位这个toolbar放在哪一个位置表格td下的位置,所以也获取下来。
              toolbarTemps.push({
                  temp: colOption.toolbar,
                  key: colOption.key
              });
          }
      });
      // 直接循环已经获取到的模板id。
      layui.each(toolbarTemps, function (index, value) {
          var temp = value.temp; // 模板id。
          var key = value.key;   // 用于定位的key。

          // 用来保存新的渲染结果。
          var tempStr;
          if (temp.indexOf("#") > -1) {
              // 说明这是模板id。
              tempStr = $(temp).html();
          } else {
              // 否则认为这个字符串本身就是一个模板字符串。
              tempStr = temp;
          }
          // 使用数据重新渲染
          if (tempStr) {
              var result = laytpl(tempStr).render(data);

              // 要使用key来确定具体的选择器,才能正确的找到位置。然后将结果设置进去。
              rowTr.find("td[data-off='true'] div.laytable-cell-1-" + key).html(result);
          }
      });
    }
  }, sets);

其实我们只是修改了一份源代码。而我们项目上用的代码肯定是经过了混淆之后的代码。其实可以把对应修改后的table.js组件文件覆盖下载的layui目录里面的经过混淆的table.js文件,这样,当我们再次刷新界面,也是可以达到效果的。

4、修复后效果

现在,让我们看看修复后的效果如何:

现在,可以愉快的调用obj的update方法来完成刷新了,同时右侧的工具栏也会随之刷新。

三、修复版table组件

如果你没有阅读以上内容,而是直接来到这一章节,建议你尽量不要使用这个修复版,毕竟它只是针对此问题进行功能性增强修复。使用了此组件你将失去此组件的原版更新支持。在你再三衡量了其重要性时,你可以下载此组件覆盖你之前使用的table组件。下载地址:

点击下载

如果你是使用的layui.all.js这样的全局一次性引入的话,此组件可能并不适用于你,因为此修复版组件只是单个table组件。不过如果你阅读了以上内容,相信即使是经过混淆了之后的代码、还是使用一份包含所有组件的代码,都可以很快修复并加入这个功能。

最后,如果对你有帮助。不妨点赞以资对我的鼓励。

Full text complete, Reproduction please indicate the source. Help you? Not as good as one:
Comment(Comments need to be logged in. You are not logged in.)
You need to log in before you can comment.

Comments (0 Comments)