introduce

Because the work needs to complete an unlimited level of catalog display and check the problem, and there is no relevant framework available for Alipay applet, so I sorted out a set of scheme to deal with this kind of problem. This article is mainly about the processing method of recording tree structure data, of course, it can also be used on the Web side, the idea is the same. The content includes the transformation of one-dimensional array into tree structure data, the rendering of tree structure data, the linkage selection of father and son nodes, the default checking of history nodes, etc. Here’s some parsing of critical code.

The source code

Simple and crude, directly on the source code.

Class methods that process data

*/ const REG_CHAR_NUM = /[a-za-z0-9]/ // / class catalogSort {/** @Param {Object} ResData * @Param {Boolean} isSingleChecked = true- false- multiple * @Param {Array} HistoryChecked = IDS  */ constructor(resData, isSingleChecked, HistoryChecked = []) {this.treeMap = null this.tree = null this.currentChecked = false // This. isSingleChecked = isSingleChecked this.checkedList = historyChecked[0] === 'All'? [] : [... HistoryChecked] this.checkedNameList = [] this.checkedAll = historyChecked[0] === 'all' // This. totalCount = 0 // this.sortInit(resData)} sortInit(resData) {this.sortInit(resData) {this.sortInit(resData); this.sortInit(resData); this.sortInit(resData); this.sortInit(resData); this.sortInit(resData); This. sortByNumLetter(this.tree)} /** ** * @Param {Array} ResData */ SolveTree (ResData) {// Build parent-child mapping this.treeMap = ResData. reduce((ACC, Cur) = > {cur. PictureCategoryName = cur. PictureCategoryName. The trim () cur. Children = [] the if (this. CheckedAll) {/ / if the selection, Checked = true this.checkedList.push(cur. picturecategoryID)} else {// Otherwise set checked to the default option when initialized cur.checked = this.checkedList.includes(cur.pictureCategoryId) } acc[cur.pictureCategoryId] = cur this.totalCount ++ Return acc}, {}) // Initialize the list of selected directory names instead of building it directly in the loop above, This.checkedNameList = this.checkedList.reduce((acc,)); this.checkedNameList = this.checkedList.reduce((acc,)); this.checkedNameList = this.checkedList. id) => { return acc.concat(this.treeMap[id].pictureCategoryName) }, Filter (item => {this.treeMap[item.parentid] &&) {this.treeMap[item.parentid] && This.treeMap [item.parentid].child.push (item) return item.parentid == ""})} ** * @param {Array} SortByNumLetter (tree) {tree. Sort (item1, item1); item2) => { if (REG_CHAR_NUM.test(item1.pictureCategoryName) || REG_CHAR_NUM.test(item2.pictureCategoryName)) { // If it's numbers or letters, start with numbers or letters from 1 to 10... a-z... A-Z... Sort the if (item1. PictureCategoryName > item2. PictureCategoryName) {1} return else if (item1. PictureCategoryName < Item2. PictureCategoryName) {return 1} else {return 0}} else {/ / if it is Chinese, In accordance with the rules of the sort (according to taobao directory Settings) return item1. PictureCategoryName. LocaleCompare (item2 pictureCategoryName, If (item.children.length) {this.sortByNumLetter(item.children.length)}}) selectNode(id) { let item = this.treeMap[id] item.checked = ! Item. Checked = item.Checked if (this.isSingleChecked) { Set the last value is set to false const checkPrev = this. CheckedList. The shift () enclosing checkedNameList. The shift () if (checkPrev) { this.treeMap[checkPrev].checked = false } this.setCheckedList(item.pictureCategoryId) return } this.setCheckedList(item.pictureCategoryId) this.checkChilds(item.children, Item. Checked) this.CheckParents (item.parentid)} /** * Directory selection */ /*selectNodeAnother(Tree,) id) { for (let item of tree) { if (item.pictureCategoryId === id) { item.checked = ! item.checked this.currentChecked = item.checked if (this.isSingleChecked) { return true } this.setCheckedList(item.pictureCategoryId) this.checkChilds(item.children, item.checked) this.checkParents(item.parentId) return true } if (item.children.length) { const res = this.selectNode(item.children, Id) if (res) return res}} return null}*/ ** * @Param {Array} childItems * @Param {Boolean} checked */ checkChilds(childItems, childItems, childItems); Checked) {if (childItems. Length) {childItems. Foreach (item =>) { If (item.checked == this.currentchecked) return item.checked = checked this.setCheckedList(item.pictureCategoryId) this.checkChilds(item.children, Checked)})}} /** * @Param {String} parentId * @Param {Object} TreeMap Parentid */ checkParents(parentId) { if (this.treeMap[parentId] && this.treeMap[parentId].children.length) { const parentChecked = this.treeMap[parentId].children.every(item => item.checked) if (this.treeMap[parentId].checked === parentChecked) return // If the parent node is the same as the state that needs to be selected, exit the loop. This.treeMap [parentId]. Checked = parentChecked this.setCheckedList(this.treeMap[parentId].pictureCategoryId) This.treeMap [parentId]. ParentId && this.checkParents(this.treeMap[parentId]. ParentId)}} /** * Set to select IDS * @Param {String} id is set properties checked node id * / setCheckedList (id) {const checkedIndex = this. CheckedList. FindIndex (item = > item = = = Id) if (this.Checked && CheckedIndex === -1) { And node id is not selected in the array is populated enclosing checkedList. Push (id) enclosing checkedNameList. Push (enclosing treeMap [id]. PictureCategoryName)} else if (! This.currentChecked &&CheckedIndex >-1) {this.currentChecked &&CheckedIndex >-1) {this.currentChecked &&CheckedIndex >-1) {this.currentChecked Delete this.checkedList.splice(checkedIndex); delete this.checkedList.splice(checkedIndex); 1) / / this. CheckedNameList. FindIndex (name = > name = = = this. TreeMap [id]. PictureCategoryName) don't use this method is to prevent the nuptial lead to delete the error / / Control insert name and id insert is consistent, can be directly to delete this. According to common index checkedNameList. Splice (checkedIndex, 1)}}} export {CatalogSort}

The parent component

  • acss

    /* popup */. SelectCatalog_ContentStyle {var: 1; width: 750rpx; BACKGROUND-COLOR: rgba(0, 0, 0, 0.6); display: flex; position: fixed; top: 0; left: 0; z-index: 66; flex-direction: column; height: 100vh; } .selectCatalogDialog_Btn { height: 114rpx; background-color: #ffffff; border-radius: 24rpx; margin: 20rpx 25rpx 25rpx 25rpx; justify-content: center; display: flex; align-items: center; } .selectCatalogDialog_Btn_txt { font-size: 32rpx; color: #3089dc; text-align: center; flex: 1; line-height: 114rpx; } .comfirm-btn { border-left: 1rpx solid #ddd; } .selectCatalogDialog_body { position: relative; border-radius: 24rpx; background-color: #ffffff; margin: 25rpx 25rpx 0 25rpx; flex: 1; overflow: hidden; display: flex; } .catalog_list_row_refresh_block { position: absolute; top: 26rpx; right: -30rpx; z-index: 1; flex: 1; flex-direction: row; justify-content:flex-end; align-items: center; display: flex; } .catalog_list_row_refresh_touch { width: 150rpx; flex-direction: row; align-items: center; display: flex; } .catalog_list_row_refresh_touch_text { color: #3089dc; }
  • axml

    <view class="selectCatalogDialog_contentStyle" style="left:{{show? '0rpx':'-750rpx'}}"> <scroll-view class="selectCatalogDialog_body" scroll-y="{{true}}"> <block a:if="{{cataloglist}}"> <view class="catalog_list_row_refresh_block"> <view class="catalog_list_row_refresh_touch" onTap="refreshCategory"> <image src="{{imgUrl+'/imageOff/common/refresh.png'}}" style="width:35rpx; Height :35rpx"/> <view class="catalog_list_row_refresh_touch_text"> refresh </view> </view> </view> <tree-child a:for="{{cataloglist}}" catalog="{{item}}" level="{{0}}" onSelectCatalog="onSelectCatalog"></tree-child> </block> </scroll > view> <view class="selectCatalogDialog_Btn"> <text onTap="hide" class="selectCatalogDialog_Btn_txt"> </text> <text a:if="{{! IsSingleChecked}" onTap="confirm" class=" selectCatalog_Btn_Txt comfirm-btn" </text> </view> </view> </view>
  • js

    import PictureCatalogService from '/pages/pictureSpace/public/PictureCatalogService.js'; import { RyUrlConfigure } from '.. /.. /js/together/RyUrlConfigure.js' import { CatalogSort } from '.. /.. /js/together/catalogSort.js' const app = getApp(); Component({ mixins: [], data: { imgUrl: RyUrlConfigure.getUrlImg(), cataloglist: null, }, props: { show: False, isSingleChecked: false, // check false- check true- check historyChecked: [], // check list onSelect: () => {}, onClose: () => {},}, didMount() {this.loadData() this.treeInstance = null}, didUpdate() {}, didUnmount() {}, didUnmount() {}, didMount() {this.loadData() this.treeInstance = null methods: { loadData(){ PictureCatalogService.getCatalogTreeData().then((data) => { this.treeInstance = new CatalogSort(data, this.props.isSingleChecked, this.props.historyChecked) this.setData({ cataloglist: this.treeInstance.tree }) }).catch(err => { my.alert({ content: err }) }); }, / images directory choose * * * * / onSelectCatalog (id) {this. TreeInstance. SelectNode (id) enclosing setData ({cataloglist: this.treeInstance.tree }, () = > {/ / radio chosen directly to the selected value passed to the parent component if (this. Props. IsSingleChecked) { this.props.onSelect(this.treeInstance.treeMap[this.treeInstance.checkedList[0]]) } }) }, Hide (){this.props. OnClose ()}, /** * Click OK */ confirm() {// pass the IDS to the parent const {checkedList, checkedNameList, checkedNameList, checkedNameList, checkedNameList, checkedNameList, checkedNameList, checkedNameList. TotalCount} = this.treeInstance if (checkedList.length === 0) {app.toast(' Please select directory first ') return const values = {TotalCount} const values = { checkedList, checkedNameList, totalCount, } this.props.onSelect(values) }, RefreshCategory () {PictureCatalogService. GetCatalogTreeData (true). Then (res = > {app. Toast (' refresh 'success) enclosing the loadData () }).catch(err => { my.alert({ content: err }) }) } }, });
  • json

    {
    "component": true,
    "usingComponents": {
      "check-box": "/common/components/ryCheckbox/index",
      "am-checkbox": "mini-ali-ui/es/am-checkbox/index",
      "tree-child": "/common/components/picCategoryTree/tree-child/tree-child"
    }
    }

    Subcomponent (tree recursive render node)

  • acss

    .catalog_list_row {
    border-bottom: 1rpx solid #dddddd;
    background-color: #ffffff;
    height: 89rpx;
    flex-direction: row;
    align-items: center;
    position: relative;
    display: flex;
    padding-left: 20rpx;
    }
    
    .catalog_list_row_select_block {
    flex: 1;
    flex-direction: row;
    align-items: center;
    display: flex;
    }
    
    .catalog_list_row_title {
    font-size: 28rpx;
    color: #333333;
    margin-left: 20rpx;
    }
    
    .child_toggle_btn {
    position: absolute;
    height: 88rpx;
    width: 78rpx;
    justify-content: center;
    align-items: center;
    right: 0rpx;
    top: 0rpx;
    display: flex;
    }
    
    .child_toggle_btn_icon {
    width: 38rpx;
    height: 38rpx;
    }
  • axml

    <view style="width: 100%;" > <view class="catalog_list_row"> <view onTap="checkNode" data-id="{{catalog.pictureCategoryId}}" class="catalog_list_row_select_block" style="padding-left:{{level * 50}}rpx;" > <check-box checked="{{catalog.checked}}" /> <view class="catalog_list_row_title">{{catalog.pictureCategoryName}}</view> </view> <view onTap="toggleChild" class="child_toggle_btn" a:if="{{isBranch && catalog.pictureCategoryId ! == '0'}}"> <image class="child_toggle_btn_icon" src="{{ImgUrlPrefix + (open ? 'icon-fold.png' : 'icon-unfold.png')}}" /> </view> </view> <view hidden="{{! open}}"> <tree-child a:for="{{catalog.children}}" catalog="{{item}}" level="{{level + 1}}" onSelectCatalog="selectCatalog"></tree-child> </view> </view>
  • js

    import { RyUrlConfigure } from '.. /.. /.. /js/together/RyUrlConfigure.js' Component({ mixins: [], data: { imgUrl: RyUrlConfigure.getUrlImg(), ImgUrlPrefix: Rurlconfigure. getUrlimg () + '/imageOff/picture-space/', open: false, // expand isBranch: false, // parent node}, props: props: // OnSelectCatalog: () => {},}, didMount() {this.setData({isBranch: :); // OnSelectCatalog: () => {},}, didMount(); this.props.catalog.children.length > 0, open: this.props.level === 0, }) }, didUpdate() {}, didUnmount() {}, methods: { toggleChild() { this.setData({ open: ! this.data.open, }) }, checkNode({ target: { dataset: { id } } } = e) { this.selectCatalog(id) }, selectCatalog(id) { this.props.onSelectCatalog(id) }, }, });
  • json

    {
    "component": true,
    "usingComponents": {
      "check-box": "/common/components/ryCheckbox/index",
      "tree-child": "/common/components/picCategoryTree/tree-child/tree-child"
    }
    }

    Steps to resolve

    A one-dimensional array is converted to a tree structure

    Since the data retrieved from the interface is not a constructed tree data, but a one-dimensional array, we need to convert it to a tree structure first. The key point of the content here is to build the parent-child node mapping relationship, which is convenient for the search of subsequent nodes and greatly reduces the recursive call. When initializing the source array and building the parent-child relationship, the default checked item is added. Because the selection may not pass all nodes but just pass a field that represents the selection, there will be a temporary judgment of whether the selection is all during initialization to add checked (checked) status item to each child item. The totalCount value and the checked array length value are used to determine whether all are selected. Formally build the tree structure, traverse the source array, and according to the parent node ID of each item, fill the corresponding child node items for the corresponding parent node of each item by using the parent-child mapping object just constructed previously. Finally, filter is used to return the first layer of data, namely the parentless node relationship (empty here).

    Directory sorted

    Directory sorting uses recursion to sort each layer of data by numbers, letters and Chinese. LocaleCompare has different compatibility in different browsers, so it is necessary to determine whether the directory name is alphanumeric first. If yes, use sort first, otherwise use LocaleCompare.

    Tree structure rendering

    The tree rendering uses a recursive component, so it needs to be split into two components for rendering. The child component renders each single item of the catalog, and the recursive child component renders the whole tree. When a parent component introduces a child component, it declares a property with a level of 0 to represent the hierarchy, and then uses the hierarchy to specify the padding-left value of the child component to indent to build the parent-child visual relationship. The child component is unfolded and closed with two attribute value operations, isBrand means whether there are child nodes, open means whether to expand, and level controls the default expansion level. The unfurl status icon is displayed by whether there are children (that is, the isBrand attribute). Since the first layer in my business is a parent node and does not fold, the first layer does not render the unfold icon. The isOpen status value is used to determine whether to hide child node content. These two properties are only for the node itself, and do not need to initialize each node to add similar checked property, very convenient event operation, without recursive tree structure array.

    Tree node selection

    The node selection includes radio selection and multiple selection. The tree component instance is initialized to pass IsSingleChecked to determine whether it is radio selection, and historyChecked to indicate the default selected item. When a node is selected, the selectNode method finds the selected node through the parent-child node mapping. For radio selection, store the last checked value (used to set checked to false for each radio selection) and the current selection status (checked or unchecked). At the same time, the selected ID is stored in the checkedList array (there is an extra name array here, for business needs). If it is selected and not in the selected array, push; if it is unchecked and in the array, delete the ID. When multiple selections are possible, the parent-child selection is more complicated. In addition to changing the selected state, the child nodes and the parent nodes should be traversed and selected. In the process of recursion of parent-child node, it can break out of the loop by judging whether it is consistent with the current node state and reduce the recursion times. Select the children node and recurse directly to the children subarray. The parent node selects to search the parent relationship through the parent-child mapping list, and then evaluates the checked property of the child node list of the parent relationship. If all are true, the checked property is selected; otherwise, the checked state is unchecked.

    A component receives

    The component receives values mainly from the selected IDS array of the node, the selected node name array, and the total length. If there are other businesses in the later period, they can be extended here.

    conclusion

    So that’s all I have to say about the tree structure business. For the convenience of later viewing, record here. If one of them can help others, very happy.