HandsonTable封装构思

来源:互联网 发布:大数据工程师 年龄要求 编辑:程序博客网 时间:2024/06/07 14:11

HandsonTable是一个很牛逼的仿Excel的jQuery表格插件。
在做现在这个项目的时候,用原生的HandsonTable可以实现80%左右的需求。然而,第三方的插件毕竟不是为自己的项目定制的,所以在实现某些功能或者效果的时候,感觉比较费劲;而插件所提供的一系列强大的功能又没有派上用场。
当下的考虑是抽空把HandsonTable梳理一下,然后包装一层给我们的项目使用。这样一方面可以踩在巨人的肩膀上,起点较高地往上爬;另一方面,又能因地制宜地把重心集中于我们自己的业务需求上。

当前项目主要业务需求的分析:

1.层级结构展示,以及展开折叠;
2.Dropdown形式的下拉选择编辑;
2.联动编辑(包括纵向联动,横向联动);
3.“展开到”功能实现;
4.在存在折叠的情况下的联动编辑;
5.去除为展现而增加的html元素后向服务器做请求;
6.拖选控制(精准到列,以及跳过有关联控制的行);

层级结构展示,展开折叠:

现有实现:

  • 在向HandsonTable添加数据的时候,直接将html元素内嵌到表格值中去;
  • 向嵌入的html元素添加level之类的class属性(level,level1,level2……,lastLevel等),通过css进行样式控制(主要是margin属性的左边距);
  • 为附加的html元素添加事件,用来做展开折叠;
  • 在折叠的时候是通过将折叠数据临时存放到一个以rowNo做索引的二位数组中来实现的,这样可以保证在HandsonTable做滚动加载的时候不会出现留白的现象;
var sectionLevel = 'level' + section.level;            if ((section.sectionNo == undefined) || (section.sectionNo == '')) {                sectionObj.code = '<a class="level ' + sectionLevel + '">' + '<i class="iconfont icon-reduce"></i></a>' + section.sequenceNo;            } else {                sectionObj.code = '<a class="level ' + sectionLevel + '">' + '<i class="iconfont icon-reduce"></i></a>' + section.sectionNo;            }

考虑封装:

  • 将用来做展开折叠的列封装起来,不在每个包含HandsonTable的页面写这些嵌入html元素的代码,而是由封装的Table控件统一处理;
  • 在进行编辑时,移除html元素的处理封装到组件中,并考虑是否能够在显示的时候不闪现html代码;
  • 展开折叠使用的hideData全局变量考虑重构,现有实现对于同一页面包含多个Table的情况下有问题;
  • 将展开折叠的实现封装到Table控件中,hideData作为Table属性存在;

现有实现:

  • 使用HandsonTable的钩子回调事件afterBeginEditing,也就是在一个表格进入编辑状态的时候,在HandsonTable的InputHolder中嵌入一个下拉列表html串,然后将其呈现出来;
  • html串自行组装,因此可以按照自己的业务要求来做控制;
  • 在下拉列表点击完成之后,将InputHolder自带的Editor销毁,然后移除下拉列表内容;
relationTable.addHook('afterBeginEditing', function (row, col) {        var colProp = this.colToProp(col);        var cellVal = this.getDataAtCell(row, col);        var colIndex = indexCols.indexOf(colProp);        if ((colProp == 'tradeType') || (colIndex >= 0)) {            if (cellVal) {                var iconIndex = cellVal.indexOf(getRelationTableSelectIconContent());                var realVal = cellVal.substring(0, iconIndex);                this.setDataAtCell(row, col, realVal);            }            if (colProp == 'tradeType') {                showFloatingList(buildTradeTypeListHtml());            } else {                var currTradeTypeId = relationTable.getSourceDataAtRow(row).tradeTypeId;                showFloatingList(buildRelationIndexListHtml(currTradeTypeId, getIndexInfosByColProp(colProp)));            }        }    });
var $selectModal = $(".handsontableInputHolder .selectModal");    if ($selectModal) {        $(".handsontableInputHolder .selectModal").remove();    }    var $holder = $('.handsontableInputHolder');    $holder.append(listHtml);    $('.handsontableInputHolder .selectModal').show();
$("body").on("click", ".handsontableInputHolder .selectModal li", function() {        var selectedVal = $(this).html();        var selectedKey = $(this).attr('accesskey');        var highlight = relationTable.getSelectedRange().highlight;        var col = highlight.col;        var prop = relationTable.colToProp(col);        if (prop == 'tradeType') {            tradeTypeCallback(selectedVal, selectedKey);        } else {            indexCallback(prop, selectedVal, selectedKey);        }        relationTable.destroyEditor(true);        $(".handsontableInputHolder .selectModal").remove();        return false;    });

考虑封装:

  • 首先,针对column的编辑方式type为dropdown的列,将html元素(下拉箭头div)嵌入的逻辑放到封装的Table组件中;
  • 将afterBeginEditing事件的去除html元素的处理封装到组件中;
  • 将呈现下拉列表的处理提供回调事件列表进行处理,业务逻辑中仅仅提供下拉列表纯数据,html组装由组件完成;
  • 点击响应事件提供回调进行处理,此处需要与联动编辑共同考虑;可能要用到HandsonTable的beforeChange钩子回调事件;

联动编辑:

现有实现:

  • 在HandsonTable的afterChange或者beforeChange或者下拉列表框的click事件中进行业务逻辑处理;
  • 业务逻辑代码中查找需要联动的子记录,并判断是否符合业务逻辑(可否联动),然后进行服务器的请求,请求成功后进行表格填充;
$("body").on("click", ".handsontableInputHolder .selectModal li", function() {        var selectedVal = $(this).html();        var selectedKey = $(this).attr('accesskey');        var highlight = relationTable.getSelectedRange().highlight;        var col = highlight.col;        var prop = relationTable.colToProp(col);        if (prop == 'tradeType') {            tradeTypeCallback(selectedVal, selectedKey);        } else {            indexCallback(prop, selectedVal, selectedKey);        }        relationTable.destroyEditor(true);        $(".handsontableInputHolder .selectModal").remove();        return false;    });
layer.load(1, {shade : [0.3, '#ccc']});    do {        if (relationData.type == 'section') {            currTypeIds.push(relationData.id);        } else if (relationData.type == 'normitem') {            childTypeIds.push(relationData.id);        }        validRows.push(row);        relationData = normInfoTable.getSourceDataAtRow(++row);    } while ((relationData != undefined) && (relationData.level > currLevel));    collectRelationInfoIdsFromHideData(currRow, row, 'section', 'normitem', currTypeIds, childTypeIds);    updateSectionTradeType(currTypeIds, childTypeIds, selectedKey, function () {        $.each(validRows, function (rowIndex, rowValue) {            validCells.push([rowValue, currCol, cellVal]);            normInfoTable.setDataAtRowProp(rowValue, 'tradeTypeId', selectedKey);            resetDisplayNormInfoIndex(rowValue);        });        setHideNormInfoTradeType(currRow, row, cellVal, selectedKey);        normInfoTable.setDataAtCell(validCells);        layer.closeAll();    }, function () {        normInfoTable.setDataAtCell([            [currRow, currCol, getCellValueWithSelectIcon(normInfoTable.getDataAtCell(currRow, currCol))]        ]);        layer.closeAll();    });

封装考虑:

  • 根据层级关系,提供默认联动机制;
  • 联动机制中提供allow回调机制,由业务层提供回调判断逻辑;
  • allow回调机制是一个Hash,键为列的prop属性,值为回调方法;

展开到:

现有实现:

封装考虑:

  • 使用封装到组件中的hideData属性来实现(展开折叠中曾用到);
  • 不影响原有的展开折叠,原先展开的仍然展开,原先折叠的仍然折叠;
  • 根据行元素的level属性来做折叠的功能实现;
  • 将实现封装到组件内部,外部直接调用expand或者collapse,并传入一个level参数即可;

在存在折叠的情况下的联动编辑:

现有实现:

  • 业务层自行实现,在处理了目前呈现的记录之后,再行考虑折叠隐藏的记录(即hideData中的记录);
$.each(hideData, function (index, data) {        if ((parseInt(index) >= minRow) && (parseInt(index) < maxRow)) {            $.each(data, function (arrayIndex, arrayData) {                if (arrayData.type == currType) {                    if (currTradeTypeId == undefined) {                        currTypeIds.push(arrayData.id);                    } else if (arrayData.tradeTypeId == currTradeTypeId) {                        currTypeIds.push(arrayData.id);                    }                } else if (arrayData.type == childType) {                    if (currTradeTypeId == undefined) {                        childTypeIds.push(arrayData.id);                    } else if (arrayData.tradeTypeId == currTradeTypeId) {                        childTypeIds.push(arrayData.id);                    }                }            });        }    });
updateSectionTradeType(currTypeIds, childTypeIds, selectedKey, function () {        $.each(validRows, function (rowIndex, rowValue) {            validCells.push([rowValue, currCol, cellVal]);            normInfoTable.setDataAtRowProp(rowValue, 'tradeTypeId', selectedKey);            resetDisplayNormInfoIndex(rowValue);        });        **setHideNormInfoTradeType(currRow, row, cellVal, selectedKey);**        normInfoTable.setDataAtCell(validCells);        layer.closeAll();    }, function () {        normInfoTable.setDataAtCell([            [currRow, currCol, getCellValueWithSelectIcon(normInfoTable.getDataAtCell(currRow, currCol))]        ]);        layer.closeAll();    });
$.each(hideData, function (index, data) {        if ((parseInt(index) >= minRow) && (parseInt(index) < maxRow)) {            $.each(data, function (arrayIndex, arrayData) {                arrayData.tradeType = cellValue;                arrayData.tradeTypeId = selectedKey;                arrayData.normEconomicIndex = getRelationTableSelectIconContent();                arrayData.normEconomicIndexId = undefined;                arrayData.normQuantityIndex = getRelationTableSelectIconContent();                arrayData.normQuantityIndexId = undefined;                arrayData.quantityIndexUnit = undefined;                arrayData.quantityIndexFactor = undefined;            });        }    });

封装考虑:

  • 将针对hideData的处理封装到组件层,而不是业务层;
  • 业务层仅提供相关回调判断,是否需要联动编辑等;

去除为展现而增加的html元素后向服务器做请求

现有实现

  • 业务层在做上传请求之前,对获得的数据进行组装和拆除,同时将为呈现嵌入的html元素移除掉;
if (oldVal != newVal) {                        var indexData = indexTable.getSourceDataAtRow(row);                        var formatIndex = formatIndexJson(indexData);                        updateCallback(formatIndex);                    }
function formatIndexJson(indexJson) {    var formatIndex = {};    formatIndex['tradeType\.id'] = tradeId;    $.each(indexJson, function (key, val) {        if (key == 'sequenceNo') {            return true;        } else if (key == 'description') {            formatIndex.description = getIndexRealDescription(indexJson.description);            return true;        } else if (key == 'parentIndex\.id') {            if (val != undefined) {                formatIndex['parentIndex\.id'] = indexJson['parentIndex\.id'];            }            return true;        }        formatIndex[key] = val;    });    return formatIndex;}

封装考虑

  • 考虑两种情况,单值请求与单记录请求;
  • 如果在某一列prop上面挂接了单值请求的回调,则不会重复调用单记录请求;否则整行记录进行请求;
  • 考虑上下级联动的情况下进行多记录请求(同样分值级别与记录级别);
  • 在请求时,组件自动进行拆装箱(对每一条记录的每个属性,分为两种情况,请求带有的,呈现附加的),将为呈现附加的prop去掉,将嵌入了html元素的prop的值进行重组;然后再做上传请求处理;

拖选控制(精准到列,以及跳过有关联控制的行)

现有实现

封装考虑

待定,需要看一下HandsonTable的实现再做定夺!

原创粉丝点击