---
title: "Product Drag and Drop with Knockout"  
description: "Enhancing user interaction and providing intuitive interfaces is crucial. One of the popular methods to achieve this is through drag-and-drop function"  
author: "Ravi Vishwakarma"  
published: 2024-05-31  
updated: 2024-05-31  
canonical: https://www.mindstick.com/articles/335973/product-drag-and-drop-with-knockout  
category: "knockcout js"  
tags: ["javascript", "javascript library", "javascript framework", "knockout.js"]  
reading_time: 7 minutes  

---

# Product Drag and Drop with Knockout

#### Drag and Drop with Knockout.js

In modern [web development](https://www.mindstick.com/services/web-development), [enhancing user](https://answers.mindstick.com/qa/102581/what-role-does-google-s-project-strobe-play-in-enhancing-user-privacy) interaction and providing intuitive interfaces is crucial. One of the popular methods to achieve this is through drag-and-drop functionality. Knockout.js, a powerful JavaScript library that follows the MVVM (Model-View-ViewModel) pattern, can be seamlessly integrated with drag-and-drop features to create dynamic and interactive [web applications](https://www.mindstick.com/blog/11464/improve-your-understanding-of-web-applications). This article will guide you through implementing drag-and-drop functionality in a Knockout.js-based application.

#### Why Use Knockout.js?

Knockout.js offers a clean way to manage [complex data](https://answers.mindstick.com/qa/35624/what-type-of-language-do-you-prefer-for-writing-complex-data-structures)-driven interfaces. It simplifies the process of keeping the UI in sync with underlying data models using declarative bindings. When combined with drag-and-drop, it can significantly enhance the [user experience](https://yourviews.mindstick.com/view/87076/explore-your-career-in-user-experience-design) by allowing intuitive interactions with the UI elements.

#### Setting Up the Environment

Before diving into the [implementation](https://yourviews.mindstick.com/view/60537/nationalwide-nrc-implementation-bjp-looting-congress-s-idea), ensure you have the necessary libraries included in your HTML file:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Drag and Drop Demo</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.1/knockout-latest.min.js"></script>
  <link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <!-- Your HTML content here -->
  <script src="script.js"></script>
</body>
</html>
```

#### HTML Structure

We will create a [basic structure](https://answers.mindstick.com/qa/37771/mit-scientists-have-designed-a-robotic-system-for-3d-printing-the-basic-structure-of-which) with two sections: one for selecting items and another for dropping the [selected items](https://www.mindstick.com/forum/352/i-need-to-display-my-checkboxlist-selected-items-in-a-label-how-to-do).

```html
<!-- Main container using Bootstrap's grid system -->
  <div class="container" data-bind="">
    <div>
      <!-- Header with main title -->
      <h1>
        Drag and Drop
      </h1>
      <hr />
    </div>

    <!-- Row for drag and drop sections -->
    <div class="row g-3">
      <!-- Column for selecting items to drag -->
      <div class="col-12 col-md-6 h-100" title="Select it">
        <p class="fw-semibold">Select It</p>
        <div class="droptarget border h-100 w-100 rounded-3 p-2" ondrop="drop(event)" ondragover="allowDrop(event)">
          <!-- List of products to drag -->
          <ul class="list-group" data-bind="foreach: products">
            <li data-bind="attr: { 'data-productid' : id, id : 'dragtarget' + id }" ondragstart="dragStart(event)"
              ondragend="dragEnd(event)" draggable="true" class="list-group-item product">

              <!-- Controls for adding or removing a product -->
              <div class="d-flex float-end position-absolute top-0 end-0">
                <span onclick="removeProduct(this)" title="Remove this product"
                class="d-none fs-5 pe-2 remove-product text-danger">
                <i class="bi bi-dash-circle-fill"></i></span>
                <span onclick="addProduct(this)" title="Add this product"
                class="fs-5 pe-2 add-product text-success">
                <i class="bi bi-plus-circle-fill"></i></span>
              </div>

              <!-- Product details -->
              <div class="d-flex">
                <img class="img-fluid" width="100" height="100" data-bind="attr: { src: img, alt: name }" />
                <div class="ms-2 w-100 h-100">
                  <h3 data-bind="text: name"></h3>
                  <p class="m-0 fw-semibold" data-bind="text: price"></p>
                  <div class="float-start text-warning fs-4 stars" data-bind="foreach: stars">
                    <span>★</span> <!-- Unicode character for a filled star -->
                  </div>
                </div>
              </div>
            </li>
          </ul>
        </div>
      </div>

      <!-- Column for dropping selected items -->
      <div class="col-12 col-md-6 h-100" title="Drop it">
        <div class="d-flex justify-content-between">
          <p class="fw-semibold">Drop Here</p>
          <p class="fw-semibold">
            <span>Total Products - </span>
            <span data-bind="text: selectedProducts().length"></span>
          </p>
        </div>

        <!-- Drop area for products -->
        <div id="droptarget" class="droptarget border h-100 w-100 rounded-3 p-2 list-group drop-area-element overflow-y-auto"
          ondrop="drop(event)" ondragover="allowDrop(event)">
        </div>

        <!-- Total price display -->
        <p class="fw-bold float-end pt-3">
          <span>Price - $</span>
          <span data-bind="text: totalSelectedProductPrice"></span>
        </p>
      </div>
    </div>

    <!-- Footer with additional info -->
    <hr />
    <div>
      <p class="fw-semibold" id="demo"></p>
    </div>
  </div>
```

#### Write needed CSS

```css
Write needed CSS
.droptarget {
   width: 100%;
   height: 100%;
   min-height: 100px;
   height: 700px!important;
}
.drop-area-element{
   position: relative;
}
.drop-area-element .remove-product {
   display: block!important;
   cursor: pointer;
}
.add-product {
   cursor: pointer;
}
.drop-area-element .add-product{
   display: none;
}
```

#### JavaScript Logic

Now, let's implement the JavaScript logic to handle the drag-and-drop events and manage the data using Knockout.js.

```javascript
/* Events fired on the drag target */
function dragStart(event) {
  // Store the ID of the dragged element
  event.dataTransfer.setData("Text", event.target.id);
  // Display a message indicating dragging has started
  document.getElementById("demo").innerHTML = "Started dragging";
}

function dragEnd(event) {
  // Display a message indicating dragging has ended
  document.getElementById("demo").innerHTML = "Finished dragging.";
}

/* Events fired on the drop target */
function allowDrop(event) {
  // Prevent the default behavior to allow dropping
  event.preventDefault();
}

function drop(event) {
  // Prevent the default behavior
  event.preventDefault();
  // Get the ID of the dragged element
  const data = event.dataTransfer.getData("Text");
  // Find the element by ID
  let ele = document.getElementById(data);
  if(ele.dataset && ele.dataset.productid){
    // Check if there is space available for the product
    if(model.isSpaceAvailableForProduct(ele.dataset.productid)){
      // Add the product to the selected products list
      model.addSelectedProduct(ele.dataset.productid);
      // Append the cloned element to the drop target
      event.target.appendChild(ele.cloneNode(true));
    } else {
      // Alert the user if the maximum limit is reached
      window.alert("Maximum 2 products can be added.");
    }
  }
}

function addProduct(event){
  // Get the parent element of the clicked add button
  let parent = event.parentNode.parentNode;

  if (parent.dataset && parent.dataset.productid) {
    // Check if there is space available for the product
    if(model.isSpaceAvailableForProduct(parent.dataset.productid)){
      // Add the product to the selected products list
      model.addSelectedProduct(parent.dataset.productid);
      // Append the cloned parent element to the drop target
      document.getElementById('droptarget').appendChild(parent.cloneNode(true));
    } else {
      // Alert the user if the maximum limit is reached
      window.alert("Maximum 2 products can be added.");
    }
  }
}

function removeProduct(event) {
  // Get the parent element of the clicked remove button
  let parent = event.parentNode.parentNode;

  if (parent.dataset && parent.dataset.productid) {
    // Remove the product from the selectedProducts array
    model.removeSelectedProduct(parent.dataset.productid);
    // Remove the parent element from the DOM
    parent.parentNode.removeChild(parent);
  }
}

// Class representing a Product
class Product {
  constructor(productId, name, img, price, stars) {
    this.id = productId;
    this.name = name;
    this.img = img;
    this.price = price;
    this.stars = ko.observableArray(this.generateStars(stars)); // Generate 5 stars

}

  generateStars(count) {
    // Generate an array of stars up to the given count
    return Array.from({ length: 5 }, (_, i) => i < count).filter(Boolean);
  }
}

// ViewModel class for Knockout.js
class ViewModel {
  constructor() {
    var self = this;
    self.name = ko.observable("aksdfhaksdhf");
    self.dragMessage = ko.observable("");
    self.selectedProductsIds = ko.observableArray([]);

    self.selectedProducts = ko.observableArray([]);

    // Function to get a product by ID
    self.getProductById = function(productId, dataSource) {
      return ko.utils.arrayFirst(dataSource(), function(product) {
        return product.id === parseInt(productId);
      });
    };

    self.addSelectedProduct = function(productId){
      self.selectedProducts.push(self.getProductById(productId, self.products));
    };

    self.removeSelectedProduct = function(productId) {
      var index = self.selectedProducts.indexOf(self.getProductById(productId, self.selectedProducts));
      if (index !== -1) {
        self.selectedProducts.splice(index, 1);
      }
    };

    self.totalSelectedProductPrice = ko.computed(function() {
      var selectedProducts = self.selectedProducts(); // Assuming selectedProducts() returns an array of Product objects

      // Use reduce to sum up the prices of all selected products
      var totalPrice = selectedProducts.reduce(function(total, product) {
        // Parse the price and add it to the total
        return total + parseFloat(product.price.replace('$', ''));
      }, 0);

      // Return the total price rounded to two decimal places
      return totalPrice.toFixed(2);
    });

    self.countFrequencyOfNumber = function(arr, num) {
      // Use Array.prototype.reduce to count occurrences of num in arr
      return arr.reduce(function(count, element) {
        return count + (element.id === parseInt(num) ? 1 : 0);
      }, 0);
    };

    self.isSpaceAvailableForProduct = function(productId) {
      // Check if the count of productId is less than 2
      return self.countFrequencyOfNumber(self.selectedProducts(), productId) < 2;
    };

    // Create an array of Product objects
    self.products = ko.observableArray([
      new Product(1, 'Puma Shoe', 'https://rukminim2.flixcart.com/image/850/1000/l432ikw0/shoe/6/0/l/-original-imagf255et5szt5s.jpeg?q=90&crop=false', '$100.00', 5),
      new Product(2, 'Flight Sleeper', 'https://img3.junaroad.com/uiproducts/20423620/pri_175_p-1697706717.jpg', '$20.00', 4),
      new Product(3, 'Bucket', 'https://nutristar.co.in/cdn/shop/products/1_488b7df8-acf2-409c-8faa-21ae2bb08001_1024x1024.jpg?v=1579738972', '$30.00', 2),
      new Product(4, 'Socks', 'https://m.media-amazon.com/images/I/91mNKVcE0WL._AC_UY1100_.jpg', '$5.00', 3),
      new Product(5, 'Pen', 'https://5.imimg.com/data5/SELLER/Default/2020/10/PX/KF/AW/20193325/ink-pen.jpg', '$50.00', 4)
    ]);
  }
}

// Instantiate ViewModel and apply bindings
var model = new ViewModel();
ko.applyBindings(model);
```

### Explanation

**HTML Structure**:

- **Drag Source**: A list of products that can be dragged.
- **Drop Target**: An area where the products can be dropped.

**JavaScript Logic**:

- **Drag-and-Drop Handlers**: Functions to handle **dragStart**, **dragEnd**, **allowDrop**, and **drop** events.
- **Product Class**: Represents a product with properties and a method to generate star ratings.
- **ViewModel Class**: Manages the application's state, including the list of products, selected products, and various helper functions.

####

#### Conclusion

By integrating Knockout.js with drag-and-drop functionality, you can create a more interactive and engaging [user interface](https://answers.mindstick.com/qa/32832/is-it-possible-to-use-or-add-a-fragment-without-using-a-user-interface). This guide has provided a foundational understanding of implementing these features, allowing you to [enhance your](https://yourviews.mindstick.com/view/84452/10-ppc-automation-tools-to-enhance-your-ad-campaigns) web applications significantly. With further customization and optimization, the possibilities are endless. Happy coding!

\

---

Original Source: https://www.mindstick.com/articles/335973/product-drag-and-drop-with-knockout

Copyright © MindStick Software Pvt. Ltd. This Markdown version is provided for developers, AI systems, and offline reading.
