Dynamic Sortable Table using Backbone.js

by helgatheviking

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<div id="container">
</div>
<button id="add-product">Add</button>

<script type="text/template" id="products-table">
  <table id="my-table">
    <thead>
      <tr>
        <th>Product</th>
        <th>Quantity</th>
        <th>&nbsp;</th>
      </tr>
    </thead>
  </table>

</script>

<script type="text/template" id="product-row">

  <td class="product-title">
    <%= title %>
  </td>
  <td class="product-quantity">
    <input type="number" name="data[<%= product_id %>][quantity]" value="<%= quantity %>" />
  </td>
  <td class="product-remove">
    <a class="delete-product " href="#">Remove</a>
  </td>

</script>

CSS

table {
  border-collapse: collapse;
  margin-bottom: 1em;
}

th,
td {
  border: 1px solid black;
  font-size: 1em;
  font-family: Arial;
  padding: .25em;
}

input { width: 4em; text-align: center; }

tr.ui-sortable-handle { cursor: move; }

JavaScript

var myApplication = {};

(function($, myApplication) {

  // Model
  myApplication.Product = Backbone.Model.extend({
    defaults: {
      "quantity": 1,
      "title": "",
      "product_id": ""
    }
  });

  // Collection
  myApplication.ProductsCollection = Backbone.Collection.extend({
    model: myApplication.Product,
    el: "#container"
  });

  // Singular Row View
  myApplication.productView = Backbone.View.extend({

    model: myApplication.Product,
    tagName: 'tr',

    events: {
      'click .delete-product': 'removeProduct',
      'change .product-quantity :input': 'setQuantity'
    },

    // Get the template from the DOM.
    template: _.template($("#product-row").html()),

    // Render the single model.
    render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
    },

    // Remove/destroy a model.
    removeProduct: function(e) {
      e.preventDefault();
      this.model.destroy();
      myApplication.productList.render();
    },

    // Persist the quantity in the model.
    setQuantity: function(e) {
      value = this.$el.find('.product-quantity :input').val();
      this.model.set('quantity', value);
    }

  });

  // List View
  myApplication.productListTable = Backbone.View.extend({

    el: "#container",
    template: _.template($("#products-table").html()),

    addSingle: function(model) {
      var view = new productView({
        model: model
      });
      this.$el.find('tbody').prepend(view.render().el);
    },

    // Callback for SelectWoo sectionn
    addProduct: function(event, attributes) {
      Product = new myApplication.Product(attributes);
      this.collection.add(Product, {
        at: 0
      });
      this.render();
    },

    events: {
      'click .delete-product': 'removeProduct',
      'addFreeGiftProduct': 'addProduct',
    },

    render: function() {

      this.$el.children().remove();

      if (this.collection.length) {

       ...