JSFiddle - React, Tailwind, and code Playground

by nyothecat

HTML

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<div class="container traffic-controller">
        <div class="product-selection row box">
          <div class="col-med-12">
            <h2>Create New Product Definition</h2>
            <div class="col-md-6 form-group">
              <label class="control-label">Product Name</label>
              <select class="form-control" data-bind="options: mmProductList, value: selectedProduct"></select>
            </div>
            <div class="col-md-6 form-group">
              <button type="button" class="btn btn-primary new-product" data-bind="click: addProduct">Create</button>
            </div>
          </div>
        </div>
      <h2>Products</h2>
        <div id='productsList' data-bind="foreach: products">
            <div>Product Allocation: <span data-bind="text: $root.prodAllocation"></span></div>
            <div class="product clearfix box" data-bind="attr: { 'id': prodName }">
                <h2 data-bind="text: prodName">Product Name</h2>
                <form role="form">
                    <div><a href='#' data-bind='click: $root.removeProduct'>Delete</a></div>
                    <div data-bind="foreach: splits">
                        <div class="row">
                            <div class="col-md-4 form-group">
                              <label class="control-label">Page Name</label>
                              <select class="form-control" data-bind="options: page"></select>
                            </div>
                            <div class="col-md-1 form-group">
                              <label class="control-label">Allocation %</label>
                              <input type="text" class="form-control allocation" data-bind="value: allocation" placeholder="%">
     ...

CSS

label {
  font-size:13px;
}
/* generic box styles */
.box {
  padding:10px;
  margin:20px 0;
  border-radius:.2em;
}
button.delete {
  margin-top:20px;
  color:#323232;
  text-decoration:underline;
}
button.new-product {
  margin-top:20px;
}
/* direct wire styles */
.direct-wire {
  background: #ccebee;
}

/* default split */
.default-split {
  background: #eee;
}

/* product selection */
.product-selection {
  background: #5bb6bc;
  color:#fff;
}

/* product box */
.product {
  background: #ecf8f9;
}
/* progress bar */
.progress {
  overflow: hidden;
  height: 20px;
  margin: 10px 0;
  background-color: #f1f1f1;
  border-radius: 4px;
  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
}
.progress-bar {
  float: left;
  width: 0%;
  height: 100%;
  font-size: 12px;
  line-height: 20px;
  color: #ffffff;
  text-align: center;
  background-color: #1b8e96;
  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
  -webkit-transition: width 0.6s ease;
  transition: width 0.6s ease;
}
.progress-bar.warning {
  background-color: #C52E12;
}
.progress-error {
  color: #C52E12;
}

JavaScript

var initialData = [
        /* Sample data
            { prodName: "MSVR", splits: [
                { page: "Page Name", allocation: "10%"},
                { page: "Page Name", allocation: "20%"}]
        }*/
    ];
     
    var ProductsModel = function(products) {
        var self = this;
        self.mmAllPages = ko.observableArray(['Page 1', 'Page 2', 'Page 3']);
        self.mmProductList = ko.observableArray(['Product 1', 'Product 2', 'Product 3', 'Product 4']);
        self.selectedProduct = ko.observable();
        self.prodAllocation = ko.observable();

        self.products = ko.observableArray(ko.utils.arrayMap(products, function(contact) {
            return { prodName: contact.prodName, splits: ko.observableArray(contact.splits) };
        }));
     
        self.addProduct = function() {
            currentProduct = self.selectedProduct();
            self.products.push({
                prodName: currentProduct,
                splits: ko.observableArray()
            });
            self.mmProductList.remove(currentProduct);
        };
     
        self.removeProduct = function(contact) {
            self.products.remove(contact);
            self.mmProductList.push(currentProduct);
        };
     
        self.addDefinition = function(contact) {
            var s = {
                page: self.mmAllPages(),
                allocation: ko.observable(),
            };
            s.allocation.subscribe(function () { 
                var sum = 0;
                for (var i = 0; i < contact.splits().length; i++)
                    sum += contact.splits()[i].allocation() *1;
                self.prodAllocation(sum);
            });
            contact.splits.push(s);
            
        };

        self.removeDefinition = function(phone) {
            $.each(self.products(), function() { this.splits.remove(phone) })
        };
     
        self.save = function() {
            self.lastSavedJson(JSON.stringify(ko.toJS(self.products), null, 2));
...