Create dual list in EJ2 TypeScript ListView Control

28 Jan 202520 minutes to read

The dual list configuration involves two ListView controls, allowing for list items to be transferred between lists using client-side events. This section explains how to integrate ListView controls to achieve a dual list.

Use cases

Dual lists can be utilized in scenarios such as:

  • Stock exchanges of two different countries.
  • Job applications (skill sets).

Integration of Dual List

To implement a dual list, two ListView controls are used to display items. The interface uses an ej2-button for transferring data between lists and a textbox for filtering support.

Key features of the dual list include:

  • Transferring all items from one list to another.
  • Transferring selected items from one list to another.
  • Filtering lists using input from a textbox.

In the ListView control, sorting is enabled using the sortOrder property, and the select event is triggered when an item is selected. The select event manages the enabling and disabling of buttons based on the item selection.

Manipulating data

Moving entire data from the first list to the second list (>>)

All items can be transferred from the first ListView to the second by clicking the designated button. When clicked, all items from the first list are concatenated with the second ListView’s data source. This button is active only when the first ListView has items.

Moving entire data From the second list to the first list (<<)

This button performs the reverse action, transferring all items from the second ListView to the first. It is active only when the second ListView has items.

Transferring selected items (> and <)

The Select event facilitates transferring selected items between lists. These buttons are enabled when an item is selected in either list.

Filtering method

A filtering method allows you to filter list items by typing characters into a textbox. This feature utilizes the dataManager to fetch and display filtered data in the ListView.

Sorting

Using the dual list, list items can be sorted in the ListView using the sortOrder property. Enabling sorting in one ListView allows the data to be transferred in the same order to the other ListView.

import { ListView, SelectedItem } from '@syncfusion/ej2-lists';
import { Button } from '@syncfusion/ej2-buttons';
import { enableRipple } from '@syncfusion/ej2-base';
import { DataManager, Query } from '@syncfusion/ej2-data';
enableRipple(true);
//Define an array of JSON data
let firstListData: { [key: string]: Object }[] = [
    { text: 'Hennessey Venom', id: 'list-01' },
    { text: 'Bugatti Chiron', id: 'list-02' },
    { text: 'Bugatti Veyron Super Sport', id: 'list-03' },
    { text: 'SSC Ultimate Aero', id: 'list-04' },
    { text: 'Koenigsegg CCR', id: 'list-05' },
    { text: 'McLaren F1', id: 'list-06' },

];

let secondListData: { [key: string]: Object }[] = [
    { text: 'Aston Martin One- 77', id: 'list-07' },
    { text: 'Jaguar XJ220', id: 'list-08' },
    { text: 'McLaren P1', id: 'list-09' },
    { text: 'Ferrari LaFerrari', id: 'list-10' },
];


// Initialize the ListView control
let listObj1: ListView = new ListView({

    //Set the defined data to the dataSource property
    dataSource: firstListData.slice(),

    //Map the appropriate columns to the fields property
    fields: { text: 'text', id: 'id' },
    sortOrder: 'Ascending',
    select: onFirstListSelect

});

//Render the initialized ListView control
listObj1.appendTo('#list-1');

let listObj2: ListView = new ListView({

    //Set the defined data to the dataSource property
    dataSource: secondListData.slice(),

    //Map the appropriate columns to the fields property
    fields: { text: 'text', id: 'id' },
    sortOrder: 'Ascending',
    select: onSeconListSelect

});

//Render the initialized ListView control
listObj2.appendTo('#list-2');

let btnobj1: Button = new Button();
btnobj1.appendTo('#firstBtn');
let btnobj2: Button = new Button({
    disabled: true
});
btnobj2.appendTo('#secondBtn');
let btnobj3: Button = new Button({
    disabled: true
});
btnobj3.appendTo('#thirdBtn');
let btnobj4: Button = new Button();
btnobj4.appendTo('#fourthBtn');


//Here, all list items are moved to the second list on clicking move all button
btnobj1.element.addEventListener('click', () => {
    listObj2.dataSource = Array.prototype.concat.call(listObj1.dataSource, listObj2.dataSource);
    listObj2.dataBind();
    updateFirstListData();
    listObj1.removeMultipleItems(Array.prototype.slice.call(listObj1.element.querySelectorAll('.e-list-item')));
    firstListData = firstListData.concat(listObj1.dataSource as { [key: string]: Object }[]);
    secondListData = (listObj2.dataSource as { [key: string]: Object }[]).slice();
    btnobj1.disabled = true;
    onFirstKeyUp();
    setButtonState();
});

//Here, the selected list items are moved to the second list on clicking move button
btnobj2.element.addEventListener('click', () => {
    let selectedItem: SelectedItem = listObj1.getSelectedItems() as SelectedItem;
    listObj2.dataSource = Array.prototype.concat.call(listObj2.dataSource, selectedItem.data);
    listObj2.dataBind();
    updateFirstListData();
    listObj1.removeItem(selectedItem.item);
    firstListData = firstListData.concat(listObj1.dataSource as { [key: string]: Object }[]);
    secondListData = (listObj2.dataSource as { [key: string]: Object }[]).slice();
    onFirstKeyUp();
    btnobj2.disabled = true;
    setButtonState();
});

//Here, the selected list items are moved to the first list on clicking move button
btnobj3.element.addEventListener('click', () => {
    let selectedItem: SelectedItem = listObj2.getSelectedItems() as SelectedItem;
    listObj1.dataSource = Array.prototype.concat.call(listObj1.dataSource, selectedItem.data);
    listObj1.dataBind();
    updateSecondListData();
    listObj2.removeItem(selectedItem.item);
    secondListData = secondListData.concat(listObj2.dataSource as { [key: string]: Object }[]);
    firstListData = (listObj1.dataSource as { [key: string]: Object }[]).slice();
    onSecondKeyUp();
    btnobj3.disabled = true;
    setButtonState();

});

//Here, all list items are moved to the first list on clicking move all button
btnobj4.element.addEventListener('click', () => {
    listObj1.dataSource = Array.prototype.concat.call(listObj1.dataSource, listObj2.dataSource);
    listObj1.dataBind();
    updateSecondListData();
    listObj2.removeMultipleItems(Array.prototype.slice.call(listObj2.element.querySelectorAll('.e-list-item')));
    secondListData = secondListData.concat(listObj2.dataSource as { [key: string]: Object }[]);
    firstListData = (listObj1.dataSource as { [key: string]: Object }[]).slice();
    onSecondKeyUp();
    setButtonState();

});

//Here, the ListView data source is updated to the first list
function updateFirstListData() {
    Array.prototype.forEach.call(listObj1.element.querySelectorAll('.e-list-item'), (list: HTMLLIElement) => {
        firstListData.forEach((data, index) => {
            if (list.innerText.trim() === data.text) {
                firstListData.splice(index, 1)
            }
        });
    });
    (document.getElementById("firstInput") as HTMLInputElement).value = '';
    let ds: { [key: string]: Object }[] = [];
    firstListData.forEach((data) => {
        ds.push(data);
    })
    firstListData = ds;

}

//Here, the ListView dataSource is updated for the second list
function updateSecondListData() {
    Array.prototype.forEach.call(listObj2.element.querySelectorAll('.e-list-item'), (list: HTMLLIElement) => {
        secondListData.forEach((data, index) => {
            if (list.innerText.trim() === data.text) {
                secondListData.splice(index, 1)
            }
        });

    });
    (document.getElementById("secondInput") as HTMLInputElement).value = '';
    let ds: { [key: string]: Object }[] = [];
    secondListData.forEach((data) => {
        ds.push(data);
    })
    secondListData = ds;

}
function onFirstListSelect() {
    btnobj2.disabled = false;
}
function onSeconListSelect() {
    btnobj3.disabled = false;
}
(document.getElementById('firstInput') as HTMLInputElement).addEventListener('keyup', onFirstKeyUp);
//Here, filtering is handled using the dataManager for the first list
function onFirstKeyUp() {
    let value: string = (document.getElementById("firstInput") as HTMLInputElement).value;
    var data: Object[] = new DataManager(firstListData).executeLocal(new Query().where('text', 'startswith', value, true));
    if (!value) {
        listObj1.dataSource = firstListData.slice();
    } else {
        listObj1.dataSource = data as { [key: string]: Object }[];
    }
    listObj1.dataBind();

}
(document.getElementById('secondInput') as HTMLInputElement).addEventListener('keyup', onSecondKeyUp);
//Here, filtering is handled using the dataManager for the second list
function onSecondKeyUp() {
    let value: string = (document.getElementById("secondInput") as HTMLInputElement).value;
    var data: Object[] = new DataManager(secondListData).executeLocal(new Query().where('text', 'startswith', value, true));
    if (!value) {
        listObj2.dataSource = secondListData.slice();
    } else {
        listObj2.dataSource = data as { [key: string]: Object }[];
    }
    listObj2.dataBind();
}

//Here, the state of the button is changed
function setButtonState() {
    if ((listObj1.dataSource as { [key: string]: Object }[]).length) {
        btnobj1.disabled = false;
    } else {
        btnobj1.disabled = true;
        btnobj2.disabled = true;
    }

    if ((listObj2.dataSource as { [key: string]: Object }[]).length) {
        btnobj4.disabled = false;
    } else {
        btnobj4.disabled = true;
        btnobj3.disabled = true;
    }

}
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Essential JS 2 for ListView </title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Essential JS 2 for ListView UI Control" />
    <meta name="author" content="Syncfusion" />
    <link href="index.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/28.2.3/ej2-base/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/28.2.3/ej2-lists/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/28.2.3/ej2-buttons/styles/material.css" rel="stylesheet" />
    <link href="https://cdn.syncfusion.com/ej2/28.2.3/ej2-inputs/styles/material.css" rel="stylesheet" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.38/system.js"></script>
    <script src="systemjs.config.js"></script>
<script src="https://cdn.syncfusion.com/ej2/syncfusion-helper.js" type ="text/javascript"></script>
</head>

<body>
    <div id='loader'>Loading....</div>
    <div id="container">
        <h3>Dual List</h3>
        <div class="list_container">
            <div id="list_container_1">
                <input class="e-input" type="text" id="firstInput" placeholder="  Filter" title="Type in a name">
                <div id="list-1"></div>
            </div>
            <div id="btn">
                <button id="firstBtn"> >> </button>
                <button id="secondBtn"> > </button>
                <button id="thirdBtn">
                    < </button>
                        <button id="fourthBtn">
                            << </button>
            </div>
            <div id="list_container_2">
                <input class="e-input" type="text" id="secondInput" placeholder="  Filter" title="Type in a name">
                <div id="list-2"></div>
            </div>
        </div>
    </div>
    <style>
        .list_container {
            height: 398px;
            max-width: 485px;
            margin: auto;
        }

        #list_container_1,
        #list_container_2 {
            width: 200px;
        }

        #list-1,
        #list-2 {
            height: 362px;
            border: 1px solid #dddddd;
            border-radius: 3px;
        }

        #list_container_1,
        #list_container_2 {
            display: inline-block;
        }

        .e-btn {
            margin-bottom: 15px;
            width: 40px;
            height: 40px;
        }

        #btn {
            display: inline-block;
            width: 41px;
            margin: 0 15px;
            position: relative;
            top: -53%;
            transform: translateY(50%);
        }
    </style>
</body>

</html>
#container {
    visibility: hidden;
}

#loader {
  color: #008cff;
  height: 40px;
  width: 30%;
  position: absolute;
  font-family: 'Helvetica Neue','calibiri';
  font-size: 14px;
  top: 45%;
  left: 45%;
}