Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Optimizing Element-UI Transfer Component for Large Datasets

Tech 1

When the Element-UI Transfer component handles extensive datasets, performance dgeradation occurs during rendering, searching, selection, and data transfer operations.

A custom component can be created by modifying the original Element-UI Transfer source code. The necessary files are located in the packages directory, specifically transfer-panel.vue, main.vue, and index.js.

In transfer-panel.vue, improve the updateAllChecked method to reduce time complexity from O(n²) to O(n) by using an object for lookups.

updateAllChecked() {
    const checkedSet = {};
    this.checked.forEach(item => {
        checkedSet[item] = true;
    });
    this.allChecked = this.checkableData.length > 0 &&
        this.checked.length > 0 &&
        this.checkableData.every(item => checkedSet[item[this.keyProp]]);
}

Modify the checked watcher to optimize single selection handling.

watch: {
    checked(newVal, oldVal) {
        this.updateAllChecked();
        const newSet = {};
        newVal.forEach(item => { newSet[item] = true; });
        const oldSet = {};
        oldVal.forEach(item => { oldSet[item] = true; });
        if (this.checkChangeByUser) {
            const movedKeys = newVal.concat(oldVal)
                .filter(key => newSet[key] || oldSet[key]);
            this.$emit('checked-change', newVal, movedKeys);
        } else {
            this.$emit('checked-change', newVal);
            this.checkChangeByUser = true;
        }
    }
}

In main.vue, refactor the addToRight method to enhance data movement efficiency.

addToRight() {
    let currentValue = this.value.slice();
    const itemsToMove = [];
    const keyProp = this.props.key;
    const leftCheckedSet = {};
    this.leftChecked.forEach(item => { leftCheckedSet[item] = true; });
    const valueSet = {};
    this.value.forEach(item => { valueSet[item] = true; });
    this.data.forEach(item => {
        const itemKey = item[keyProp];
        if (leftCheckedSet[itemKey] && !valueSet[itemKey]) {
            itemsToMove.push(itemKey);
        }
    });
    currentValue = this.targetOrder === 'unshift' ?
        itemsToMove.concat(currentValue) :
        currentValue.concat(itemsToMove);
    this.$emit('input', currentValue);
    this.$emit('change', currentValue, 'right', this.leftChecked);
}

Update the sourceData and targetData computed properties.

computed: {
    sourceData() {
        const valueSet = {};
        this.value.forEach(item => { valueSet[item] = true; });
        return this.data.filter(item => !valueSet[item[this.props.key]]);
    },
    targetData() {
        if (this.targetOrder === 'original') {
            const valueSet = {};
            this.value.forEach(item => { valueSet[item] = true; });
            return this.data.filter(item => valueSet[item[this.props.key]]);
        } else {
            return this.value.reduce((acc, cur) => {
                const val = this.dataObj[cur];
                if (val) acc.push(val);
                return acc;
            }, []);
        }
    }
}

Implement virtual scrolling using vue-virtual-scroll-list. First, install the package with npm install vue-virtual-scroll-list.

Create a TransferCheckboxItem.vue component.

<template>
  <el-checkbox
    class="el-transfer-panel__item"
    :label="source[keyProp]"
    :disabled="source[disabledProp]">
    <option-content :option="source"></option-content>
  </el-checkbox>
</template>

<script>
import ElCheckbox from 'element-ui/packages/checkbox';
export default {
  name: 'TransferCheckboxItem',
  props: {
    index: Number,
    source: {
      type: Object,
      default: () => ({})
    },
    keyProp: String,
    disabledProp: String
  },
  components: { ElCheckbox, OptionContent: {
    props: { option: Object },
    render(h) {
      const findPanel = vm => {
        if (vm.$options.componentName === 'ElTransferPanel') return vm;
        return vm.$parent ? findPanel(vm.$parent) : vm;
      };
      const panel = findPanel(this);
      const transfer = panel.$parent || panel;
      return panel.renderContent
        ? panel.renderContent(h, this.option)
        : transfer.$scopedSlots.default
          ? transfer.$scopedSlots.default({ option: this.option })
          : <span>{ this.option[panel.labelProp] || this.option[panel.keyProp] }</span>;
    }
  } }
};
</script>

In transfer-panel.vue, import the components and modify the template.

import TransferCheckboxItem from './TransferCheckboxItem.vue';
import VirtualList from 'vue-virtual-scroll-list';

export default {
  components: { 'virtual-list': VirtualList },
  data() {
    return {
      itemComponent: TransferCheckboxItem,
      virtualListConfig: {}
    };
  },
  computed: {
    virtualScroll() {
      return this.$parent.virtualScroll;
    },
    keyProp() {
      this.virtualListConfig.keyProp = this.props.key || 'key';
      return this.props.key || 'key';
    },
    disabledProp() {
      this.virtualListConfig.disabledProp = this.props.disabled || 'disabled';
      return this.props.disabled || 'disabled';
    }
  }
};

Replace the checkbox rendering section.

<el-checkbox-group
  v-model="checked"
  v-show="!hasNoMatch && data.length > 0"
  :class="{ 'is-filterable': filterable }"
  class="el-transfer-panel__list">
  <virtual-list
    v-if="virtualScroll"
    style="height:100%;overflow-y: auto;"
    :data-key="keyProp"
    :data-sources="filteredData"
    :data-component="itemComponent"
    :extra-props="virtualListConfig"
  />
  <template v-else>
    <el-checkbox
      class="el-transfer-panel__item"
      :label="item[keyProp]"
      :disabled="item[disabledProp]"
      :key="item[keyProp]"
      v-for="item in filteredData">
      <option-content :option="item"></option-content>
    </el-checkbox>
  </template>
</el-checkbox-group>

Add a virtualScroll prop to main.vue.

props: {
  virtualScroll: {
    type: Boolean,
    default: false
  }
}

Enable the feature in the parent component.

<custom-transfer v-model="selectedValues" :data="items" :virtual-scroll="true"></custom-transfer>

These modifications allow the Transfer component to effficiently manage datasets of up to 100,000 items.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.