How to enable reordering, dragging, and dropping in a ListView

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

Learn how to add reordering, dragging, and dropping items feature to a ListView control. (Windows only)

What you need to know

Technologies

Prerequisites

  • You should be able to create a basic Windows app using JavaScript that uses WinJS controls. For instructions about how to get started with WinJS controls, see the Quickstart: adding WinJS controls and styles.

  • Before adding additional functionality, you should know how to create a basic ListView control. For a quick overview of how to create a simple ListView, see Quickstart: Adding a ListView or review the ListView control reference.

Instructions

Step 1: Set up the example

This example demonstrates how to create a ListView control and an ItemContainer for displaying info about an item in the list.

Use the following HTML markup as the foundation for the ListView control. You can copy and paste the code into the default.html file within a Blank app in Microsoft Visual Studio 2013.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>List_View_demo</title>

    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>

    <!-- List_View_demo references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
    <script src="js/data.js"></script>
</head>
<body>
    <div id="listViewTemplate">
        <div id="listTemplate" 
            data-win-control="WinJS.Binding.Template">
            <div class="listTemplate">
                <div>
                    <h4 data-win-bind="innerText: title"></h4>
                    <h6 data-win-bind="innerText: text"></h6>
                </div>
            </div>
        </div>
        <div id="listView" 
             data-win-control="WinJS.UI.ListView"
             data-win-options="{
                itemDataSource : DataExample.itemList.dataSource,
                itemTemplate: select('#listTemplate'),
                itemsDraggable: true,
                itemsReorderable: true,
                layout: { type: WinJS.UI.GridLayout }
             }">
        </div>
    </div>
    <div id="listViewDetail" >
        <h2>Details</h2><br/><br/>
        <div id="listViewDetailInfo" 
             draggable="true"
             data-win-control="WinJS.UI.ItemContainer">
            <h4>Cherry chocolate swirl</h4>
            <h6>Ice cream</h6>
            <p>Description: 
                <span>A sumptious blending of cherry 
                and dark chocolate.</span>
            </p>
        </div>
    </div>
</body>
</html>

The example also uses CSS styling to adjust the ListView control and ItemContainer on the HTML page. Add the following CSS code to the style sheet associated with the ListView control (css/default.css in the Blank app template).


/* Layout the app page as a grid. */
body {
    display: -ms-grid;
    -ms-grid-columns: 600px 1fr;
    -ms-grid-rows: 1fr;
}

/* Style the template for the ListView control.
.listTemplate {
    width: 282px;
    height: 70px;
    padding: 5px;
    overflow: hidden;
}

.listTemplate div {
    margin: 5px;
}

/* Style the ListView control. */
#listView {
    -ms-grid-column: 1;
    -ms-grid-row: 1;
    height: 500px; 
    width: 500px; 
    border: 2px solid gray;
}

#listView .win-container {
    margin: 10px;
}

#listView .win-container:hover {
    color: red;
}

/* Style the ItemContainer control.*/
#listViewDetail {
    -ms-grid-column: 2;
    -ms-grid-row: 1;
}

#listViewDetailInfo {
    width: 300px;
}

The example uses some pre-defined data to populate the ListView control. The data is contained in a file named 'data.js' contained in the js folder (js/data.js). Use the following instructions to add the ListView data to the app.

Dn423315.wedge(en-us,WIN.10).gifTo add a JavaScript data file to your app

  1. In Solution Explorer, right-click the js folder and choose Add > New JavaScript File.

  2. In the Add New Item dialog box, in the Name box, type 'data.js' and then click Add..

  3. In Solution Explorer, double-click the new JavaScript file and add the following code.

    (function () {
        "use strict";
    
        // Define the dataset.
        var dataArray = [
            { title: "Basic banana", 
              text: "Low-fat frozen yogurt", 
              description: "Go bananas for some frozen yogurt." },
            { title: "Banana blast", 
              text: "Ice cream", 
              description: "More banana than allowed by law." },
            { title: "Brilliant banana", 
              text: "Frozen custard", 
              description: "Custard with banana; an excellent desert." },
            { title: "Orange surprise", 
              text: "Sherbet", 
              description: "Orange sherbert with a little extra something." },
            { title: "Original orange", 
              text: "Sherbet", 
              description: "The orange sherbert you know and love." },
            { title: "Vanilla", 
              text: "Ice cream", 
              description: "The one and only, classic vanilla ice cream." },
            { title: "Very vanilla", 
              text: "Frozen custard", 
              description: "What's better than custard with vanilla flavoring?" },
            { title: "Marvelous mint", 
              text: "Gelato", 
              description: "Mint meets gelato in this delicious desert." },
            { title: "Succulent strawberry", 
              text: "Sorbet", 
              description: "A joyful confection of strawberries." }
        ];
    
        // Load the dataset into a List object.
        var dataList = new WinJS.Binding.List(dataArray);
    
        // Expose the List object to the rest of the app.
        WinJS.Namespace.define("DataExample", {
            itemList: dataList
        });
    
    })();
    

Note  You can replace the provided data with whatever data you need. To replace the data source, you can change the data set that is passed to the WinJS.Binding.List constructor method.

If you decide to use some data source other than an IListDataSource object, it must implement the moveBefore, moveAfter, and moveToStart methods. In practice, it is recommended that you use the List object provided by WinJS to wrap your data source.

Also note that the Template object defined in the HTML markup for the app takes a data source that contains items with title and text properties. If your data does not contain title or text properties, you need to adjust the definition of the Template object.

 

Step 2: Add reordering capabilities to the ListView control

You can add reordering capabilities to a ListView control very easily. It only requires a very slight change or addition in code. Basically, you only need to set the itemsReorderable property for the ListView control to 'true'. (The default setting is 'false'.)

You can do this declaratively in the HTML markup for the control or you can add this capability at runtime using JavaScript. The following example demonstrates how to add the reordering capability by adjusting the HTML markup for the control.

<!-- The definition of the ListView control. 
    Note that the data-win-options attribute for the 
    control includes the itemsReorderable property. -->
<div id="listView"
    data-win-control="WinJS.UI.ListView"
    data-win-options="{
        itemDataSource : DataExample.itemList.dataSource,
        itemTemplate: select('#listTemplate'),
        itemsReorderable : true,
        layout: { type : WinJS.UI.GridLayout }
    }">
</div>

The next example demonstrates how to add the reordering capability to a ListView control at runtime using JavaScript.

(function () {

    // Other JavaScript code ...

    // Get a reference to the ListView control.
    var listView = 
        document.querySelector('#listView').winControl;

    // Set the controls itemsReorderable property.
    listView.itemsReorderable = true;

    // Other JavaScript code ...

})();

After you change the itemsReorderable property for the ListView control, run the project (press F5). Select an item in the ListView and drag it to another location within the sameListView.

Step 3: Add drag functionality to a ListView

At a basic level, you can add drag functionality to a ListView control just as easily as you add a reorder capability. The ListView control contains an itemsDraggable property that you can set declaratively in the HTML for the control or you can change at runtime.

The next example shows how to add simple drag functionality to a ListView in the HTML markup for the control.

<!-- The definition of the ListView control. 
    Note that  the data-win-options attribute for the 
    control includes the itemsDraggable property. -->
<div id="listView"
    data-win-control="WinJS.UI.ListView"
    data-win-options="{
        itemDataSource : DataExample.itemList.dataSource,
        itemTemplate: select('#listTemplate'),
        itemsDraggable : true,
        layout: { type : WinJS.UI.GridLayout }
    }">
</div>

The next example shows how to add simple drag functionality to a ListView control at runtime by using JavaScript.

(function () {

    // Other JavaScript code ...

    // Get a reference to the ListView control.
    var listView = 
        document.querySelector('#listView').winControl;

    // Set the controls itemsReorderable property.
    listView.itemsDraggable = true;

    // Other JavaScript code ...

})();

After you set the itemsDraggable property to true, run the app (press F5). In the app, select an item from the ListView control and drag it outside of the ListView. The item is ghosted over the interface of the app outside of the ListView. When you release the mouse button, the item disappears. The appropriate drop events are then fired.

If you want to interact with the underlying datasource of a ListView control, you need to also implement handlers for some of the drag and drop events of the ListView. In the following example, a handler has been added to the ListView.itemdragstart event as well as the dragover and drop events of the ItemContainer.

To use this code with the previous sample, add this code to the app.onactivated event handler defined in the default.js file provided in the Blank app template (js/default.js).

// Get the data from the ListView when the user drags an item.
listView.addEventListener("itemdragstart", function (evt) {

    // Store the index of the item from the data source of 
    // the ListView in the DataTransfer object of the event.
    evt.detail.dataTransfer.setData("Text",
        JSON.stringify(evt.detail.dragInfo.getIndices()));
});

// Allows the drop to occur. The default behavior disallows
// an element from being dropped upon another.
listViewDetailInfo.addEventListener('dragover', function (evt) {
    evt.preventDefault();
});

// Insert the content (from the ListView) into the ItemContainer.
listViewDetailInfo.addEventListener('drop', function (evt) {

    // Get the index of the selected item out of the event object.
    var dragIndex = JSON.parse(evt.dataTransfer.getData("Text")),
        dataSource = listView.winControl.itemDataSource;

    // Extract the selected data from the data source 
    // connected to the ListView control.
    dataSource.itemFromIndex(Number(dragIndex)).
        then(function (item) {
            if (item) {
                var itemData = item.data;

                // Update the ItemContainer with the data from
                // the item dragged from the ListView control.
                listViewDetailInfo.querySelector('h4').innerText = itemData.title;
                listViewDetailInfo.querySelector('h6').innerText = itemData.text;
                istViewDetailInfo.querySelector('span').innerText = itemData.description;
            }
        });

});

In the previous example, the selected data dragged from the ListView has been stored in the DataTransfer object associated with the itemdragstart event. Because both handlers can access the same data source, only the index of the selected item is stored. Otherwise, you could serialize the object as a JSON-formatted string in the DataTransfer object.

In the handler for the dragover event for the ItemContainer, the default behavior is suppressed (the default behavior is to disallow dropping one element onto another). In the drop event handler for the ItemContainer object, the index of the selected item from the data source is extracted from the DataTransfer object and then the item is retrieved from the data source of the ListView. Finally, the HTML of the ItemContainer is updated with the new data.

Note   If your app reorders or drags and drops items between groups in a grouped ListView control, you need to remove items from the data source and then reinsert them into the new group. You cannot use moveAfter, moveBefore, or moveToStart to accomplish this.

 

Step 4: Add drop functionality to a ListView

You can add drop functionality to a ListView control similarly to how drop functionality was added to the ItemContainer control in the previous example.

Use the following code example to add the ability to drop data from the ItemContainer into the ListView.

To use this code with the previous sample, add this code to the app.onactivated event handler defined in the default.js file provided in the Blank app template (js/default.js).

// Drop content (from the ItemContainer) onto the ListView control.
listView.addEventListener("itemdragdrop", function (evt) {
    if (evt.detail.dataTransfer) {
        var dragData = JSON.parse(
            evt.detail.dataTransfer.getData("Text"));

        // It's a good idea to validate the data before 
        // attempting to insert it into the data source!
        if (dragData && dragData.title && dragData.text) {
            var dropIndex = evt.detail.insertAfterIndex;

            // Insert the new item into the data source.
            DataExample.itemList.splice(dropIndex, 0, {
                title: dragData.title,
                text: dragData.text,
                description: dragData.description
            });
        }
    }
});

// Allows the drop to occur. The default behavior disallows
// an element from being dropped upon another.
listView.addEventListener("itemdragenter", function (evt) {
    if (evt.detail.dataTransfer &&
        evt.detail.dataTransfer.types.contains("Text")) {
        evt.preventDefault();
    }
});

// Drag content from the ItemContainer.
listViewDetailInfo.addEventListener('dragstart', function (evt) {

    // Get the data displayed in the ItemContainer and
    // store it in an anonymous object.
    var target = evt.target,
        title = target.querySelector('h4').innerText,
        text = target.querySelector('h6').innerText,
        description = target.querySelector('span').innerText,
        dragData = {
            source: target.id,
            title: title,
            text: text,
            description: description
        };
    
    // Store the data in the DataTransfer object as a
    // JSON-formatted string.                
    evt.dataTransfer.setData("Text", 
        JSON.stringify(dragData));
});

In the preceding code example, an event handler is added to the dragstart event of the ItemContainer as well as the itemdragenter and itemdragdrop event of the ListView control. The handler for the ItemContainer.dragstart event extracts the data from the ItemContainer and stores it in the DataTransfer object associated with the event as a JSON-formatted string. In the ListView.onitemdragenter event handler, the default behavior of the event is suppressed to allow the HTML content to be dropped into the ListView control. Finally, when the ListView.onitemdragdrop event is raised, the data is extracted from the DataTransfer object and then inserted into the data source of the ListView control.

Note   If the user attempts to reorder a ListView control using the keyboard, the DataTransfer object passed as an argument to the itemdragdrop event is undefined. In the handler for the itemdragdrop event, you need to ensure that the DataTransfer object exists before attempting to read the data it contains.

 

Remarks

For more information about how to use ListView controls and enable reordering, dragging, and dropping within ListView controls, see the HTML ListView drag-and-drop and reordering sample.

Complete example

HTML ListView drag-and-drop and reordering sample

Controls