<!DOCTYPE html> <html> <head> <link href="//cdn.jsdelivr.net/picnicss/4.1.1/picnic.min.css" rel="stylesheet"> <link rel="stylesheet" href="style.css"> <meta charset="utf-8"> <title>Mag.JS</title> </head> <body> <nav> <h1>Hello Mag.JS!</h1> <a target="_tab" href="https://github.com/magnumjs/mag.js">GitHub</a> </nav> <article class="card"> <header> <h2>Multiple Modules</h2> </header> <footer> <div id="app"> <label> <input type="checkbox" name="product"> <span class="toggle button">Product Table</span> </label> <label> <input type="checkbox" name="invoice"> <span class="toggle button">Invoice Form</span> </label> <Invoices></Invoices> <Products></Products> </div> <div class="templates hide"> <div class="ProductRow"> <div class="row"> <div class="row-cell name">NAME</div> <div class="row-cell price">PRICE</div> </div> </div> <div class="ProductCategoryRow"> <div class="row row-header"> <category></category> </div> </div> <div class="ProductTable"> <div class="row header row-header"> <div>Name &nbsp; <i></i></div> </div> <div class="row rows"></div> </div> <div id="InvoiceForm"> <form class="commentForm"> <input name="category" type="text" placeholder="The category" /> <input name="name" type="text" placeholder="Product name" /> <input name="price" type="text" placeholder="Price" /> <input type="submit" value="Post" /> </form> </div> </div> </footer> </article> <script src="//rawgit.com/magnumjs/mag.js/master/mag-latest.min.js"></script> <script src="//rawgit.com/magnumjs/mag.js/master/dist/mag.addons.0.22.min.js"></script> <script src="dataservice.js"></script> <script src="invoiceform.js"></script> <script src="products.js"></script> <script src="producttable.js"></script> <script src="app.js"></script> </body> </html>
var app = {}

app.onHeaderClick = function(name) {

  var headers = this.props.products.getHeaders()
  var data = this.props.products.getData()

  headers.forEach(function(header) {
    if (header.name == name) {
      header.selected = header.selected == "^" ? "v" : "^"
    } else {
      header.selected = ""
    }
  })

  var first = data[0],
    prop = name.toLowerCase()
  data.sort(function(a, b) {
    return a[prop] > b[prop] ? 1 : a[prop] < b[prop] ? -1 : 0
  })
  if (first === data[0]) data.reverse()

}

app.controller = function(props) {

  this.product = {};
  this.invoice = {};
  this.items = props.products.getData();
}

app.view = function(state, props) {

  state.Products = state.product._checked ? ProductTable({
    products: state.items,
    headers: props.products.getHeaders(),
    onHeaderClick: this.onHeaderClick
  }) : null;

  state.Invoices = state.invoice._checked ? InvoiceForm({
    products: props.products,
    onCommentSubmit: ()=>{
      state.items = props.products.getData()
    }
  }) : null;

}

dataService.setData(PRODUCTS)
dataService.setHeaders(HEADERS);

var props = {
  products: dataService
}

mag.module("app", app, props)
li:empty,
.hide {
  display: none;
}

a {
  display: block;
}

a:after {
  content: " \bb";
}

.mainButton {
  font-size: 1.5em;
}

nav a {
  float: right;
  margin-top: -50px;
}

body {
  background: #fff;
  text-align: left;
  width: 90%;
  max-width: 960px;
  margin: 0 auto;
  padding: 20px 0 0;
}

nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3em;
  padding: 0 .6em;
  background: #fff;
  box-shadow: 0 0 0.2em rgba(17, 17, 17, 0.2);
  z-index: 10000;
  transition: all .3s;
  transform-style: preserve-3d;
}

header {
  font-weight: bold;
  position: relative;
  border-bottom: 1px solid #eee;
  padding: .6em .8em;
}

footer {
  padding: .8em;
}

article {
  top: 100px;
}

.card {
  max-width: 100%;
  display: block;
  position: relative;
  box-shadow: 0;
  border-radius: .2em;
  border: 1px solid #ccc;
  overflow: hidden;
  text-align: left;
  background: #fff;
  margin-bottom: .6em;
  padding: 6px;
  transition: all .3s ease;
}


/* table */

.row-header {
  text-align: left;
  font-weight: 900;
  color: #fff;
  background-color: #0074d9;
}
.row-title {
  font-weight: bold
}
.row {
  width: calc(100% + 2 * 0.6em);
  display: table;
  table-layout: fixed;
  padding-left: 4px;
}
.row.outofstock .name {
  color: red;
}
.row:nth-child(even) {
  background: rgba(0, 0, 0, 0.05);
}
.row-header div,
.row-cell {
  float: left;
  width: 50%;
}

.header div {
  cursor: pointer;
}
.header div i {
  vertical-align:super;
  font-size:10px;
}


#Mag.JS

## plunk boilerplate of "Things"

Basic Mag.JS boilerplate to count and create a list of things dynamically.

var  ProductTable = mag('ProductTable', ({products, headers, onHeaderClick})=>{
  


  var lastCategory = null, rows=[], header={div:[]};
    if (products) {

  header.div = headers.map(function(head){
    return {i: head.selected, _text: head.name, _onClick: ()=>onHeaderClick(head.name)}
  })

      products.forEach(function(product, i) {
        if (product.category !== lastCategory) {
          rows.push(ProductCategoryRow({
            category: product.category,
            key: product.category + i
          }));
        }
        rows.push(ProductRow({
          product: product,
          key: product.name
        }));
        lastCategory = product.category;
      });
    }
  
  return {rows, header};
 
});

var ProductCategoryRow  = mag('ProductCategoryRow', ({category})=>({category: category}))
var ProductRow  = mag('ProductRow', (props)=>{
 
      var name = props.product.stocked ?
      props.product.name : {
        _style: 'color: red',
        _text: props.product.name
      };

    var price = props.product.price;
  return {name, price};
}, {product: {}})
var PRODUCTS = [
  {category: 'Sporting Goods', price: '$49.99', stocked: true, name: 'Football'},
  {category: 'Sporting Goods', price: '$9.99', stocked: true, name: 'Baseball'},
  {category: 'Sporting Goods', price: '$29.99', stocked: false, name: 'Basketball'},
  {category: 'Electronics', price: '$99.99', stocked: true, name: 'iPod Touch'},
  {category: 'Electronics', price: '$399.99', stocked: false, name: 'iPhone 5'},
  {category: 'Electronics', price: '$199.99', stocked: true, name: 'Nexus 7'}
];

var HEADERS = [{name: "Name", selected: ""},{name: "Price", selected: ""}];
var InvoiceForm = mag("InvoiceForm", {
  handleCommentSubmit: function(comment) {
      setTimeout(() => {

        var comments = this.state.data.getData();
        // Optimistically set an id on the new comment. It will be replaced by an
        // id generated by the server. In a production application you would likely
        // not use Date.now() for this and would have a more robust system in place.
        comment.id = Date.now();
        var newComments = comments.concat([comment]);
        this.state.data.setData(newComments);
        this.props.onCommentSubmit()
      }, 450)
    },
    controller: function(props){
      this.data = props.products;
    },
    view: function(state, props) {
      state.form = {
        _onSubmit: function(e) {
          e.preventDefault();
          if (!state.category || !state.name || !state.price) {
            return;
          }

          var data =   {
            category: state.category,
            price: state.price,
            stocked: true,
            name: state.name
          };

          this.handleCommentSubmit(data);

          state.category = '';
          state.price = '';
          state.name = '';
        }
      }
    }
})
var dataService= {data:[], headers: []};

dataService.getData = function(){
  return this.data;
}

dataService.setData =function(data){
  this.data=data;
}

dataService.getHeaders = function(){
  return this.headers;
}

dataService.setHeaders =function(headers){
  this.headers=headers;
}