block by nitaku 3d4329dccfb4ef9dd4ed

Backbone.js exercise

Full Screen

A minimal example of Backbone.js.

index.js

// Generated by CoffeeScript 1.10.0
(function() {
  var list;

  list = new Items();

  new AppView({
    el: 'body',
    collection: list
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Backbone</title>
    <link type="text/css" href="index.css" rel="stylesheet"/>
    
    <!-- dependencies -->
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="//underscorejs.org/underscore-min.js"></script>
    <script src="//backbonejs.org/backbone-min.js"></script>
    <script src="backbone.d3view.js"></script>
    <script src="backbone.localStorage-min.js"></script>
    
    <!-- your views go here -->
    <script src="AppView.js"></script>
    <script src="ItemView.js"></script>

    <!-- your models go here -->
    <script src="Items.js"></script>
  </head>
  
  <body>
    <script src="index.js"></script>
  </body>
</html>

AppView.coffee

window.AppView = Backbone.D3View.extend
  events:
    'click .create_btn': 'new_item'

  initialize: () ->
    @bar = @d3el.append 'div'
      .attr
        class: 'bar'
          
    @input = @bar.append 'input'
      .attr
        class: 'input_field'
    @input.node().value = 'Empty item'
          
    @bar.append 'button'
      .text 'Create'
      .attr
        class: 'create_btn'
          
    @list = @d3el.append 'div'
      .attr
        class: 'list'
    
    @listenTo @collection, 'add', @render_one
    @listenTo @collection, 'reset', @render_all
    
    @collection.fetch()
    
  render_one: (item) ->
    view = new ItemView
      model: item
      
    @list.node().appendChild(view.el)
    view.render()
    
  render_all: () ->
    @collection.each(@render_one, this)
    
  new_item: () ->
    @collection.create
      string: @input.node().value
      color: [Math.random()*360,20,90]
      

AppView.js

// Generated by CoffeeScript 1.10.0
(function() {
  window.AppView = Backbone.D3View.extend({
    events: {
      'click .create_btn': 'new_item'
    },
    initialize: function() {
      this.bar = this.d3el.append('div').attr({
        "class": 'bar'
      });
      this.input = this.bar.append('input').attr({
        "class": 'input_field'
      });
      this.input.node().value = 'Empty item';
      this.bar.append('button').text('Create').attr({
        "class": 'create_btn'
      });
      this.list = this.d3el.append('div').attr({
        "class": 'list'
      });
      this.listenTo(this.collection, 'add', this.render_one);
      this.listenTo(this.collection, 'reset', this.render_all);
      return this.collection.fetch();
    },
    render_one: function(item) {
      var view;
      view = new ItemView({
        model: item
      });
      this.list.node().appendChild(view.el);
      return view.render();
    },
    render_all: function() {
      return this.collection.each(this.render_one, this);
    },
    new_item: function() {
      return this.collection.create({
        string: this.input.node().value,
        color: [Math.random() * 360, 20, 90]
      });
    }
  });

}).call(this);

ItemView.coffee

window.ItemView = Backbone.D3View.extend

  namespace: null
  tagName: 'div'
  
  events:
    'click': 'suicide'

  initialize: () ->
    @d3el.classed 'item', true
    
    @listenTo @model, 'change', @render
    @listenTo @model, 'destroy', @remove
    
  render: () ->
    @d3el.text @model.get 'string'
    @d3el
      .style
        background: d3.hcl.apply this, @model.get 'color'
    
  suicide: () ->
    @model.destroy()
    

ItemView.js

// Generated by CoffeeScript 1.10.0
(function() {
  window.ItemView = Backbone.D3View.extend({
    namespace: null,
    tagName: 'div',
    events: {
      'click': 'suicide'
    },
    initialize: function() {
      this.d3el.classed('item', true);
      this.listenTo(this.model, 'change', this.render);
      return this.listenTo(this.model, 'destroy', this.remove);
    },
    render: function() {
      this.d3el.text(this.model.get('string'));
      return this.d3el.style({
        background: d3.hcl.apply(this, this.model.get('color'))
      });
    },
    suicide: function() {
      return this.model.destroy();
    }
  });

}).call(this);

Items.coffee

window.Item = Backbone.Model.extend
  defaults:
    string: null
    color: null

window.Items = Backbone.Collection.extend
  model: Item
  localStorage: new Backbone.LocalStorage("itemexample-backbone")
  

Items.js

// Generated by CoffeeScript 1.10.0
(function() {
  window.Item = Backbone.Model.extend({
    defaults: {
      string: null,
      color: null
    }
  });

  window.Items = Backbone.Collection.extend({
    model: Item,
    localStorage: new Backbone.LocalStorage("itemexample-backbone")
  });

}).call(this);

backbone.d3view.js

// Backbone.D3View.js 0.3.1
// ---------------

//     (c) 2015 Adam Krebs
//     Backbone.D3View may be freely distributed under the MIT license.
//     For all details and documentation:
//     https://github.com/akre54/Backbone.D3View

(function (factory) {
  if (typeof define === 'function' && define.amd) { define(['backbone', 'd3'], factory);
  } else if (typeof exports === 'object') { module.exports = factory(require('backbone'), require('d3'));
  } else { factory(Backbone, d3); }
}(function (Backbone, d3) {

  // Cached regex to match an opening '<' of an HTML tag, possibly left-padded
  // with whitespace.
  var paddedLt = /^\s*</;

  var ElementProto = (typeof Element !== 'undefined' && Element.prototype) || {};
  var matchesSelector = ElementProto.matches ||
    ElementProto.webkitMatchesSelector ||
    ElementProto.mozMatchesSelector ||
    ElementProto.msMatchesSelector ||
    ElementProto.oMatchesSelector;

  Backbone.D3ViewMixin = {

    // A reference to the d3 selection backing the view.
    d3el: null,

    namespace: d3.ns.prefix.svg,

    $: function(selector) {
      return this.el.querySelectorAll(selector);
    },

    $$: function(selector) {
      return this.d3el.selectAll(selector);
    },

    _removeElement: function() {
      this.undelegateEvents();
      this.d3el.remove();
    },

    _createElement: function(tagName) {
      var ns = typeof this.namespace === 'function' ? this.namespace() : this.namespace;
      return ns ?
         document.createElementNS(ns, tagName) :
         document.createElement(tagName);
    },

    _setElement: function(element) {
      if (typeof element == 'string') {
        if (paddedLt.test(element)) {
          var el = document.createElement('div');
          el.innerHTML = element;
          this.el = el.firstChild;
        } else {
          this.el = document.querySelector(element);
        }
      } else {
        this.el = element;
      }

      this.d3el = d3.select(this.el);
    },

    _setAttributes: function(attributes) {
      this.d3el.attr(attributes);
    },

    // `delegate` supports two- and three-arg forms. The `selector` is optional.
    delegate: function(eventName, selector, listener) {
      if (listener === undefined) {
        listener = selector;
        selector = null;
      }

      var view = this;
      var wrapped = function(event) {
        var node = event.target,
            idx = 0,
            o = d3.event;

        d3.event = event;

        // The `event` object is stored in `d3.event` but Backbone expects it as
        // the first argument to the listener.
        if (!selector) {
          listener.call(view, d3.event, node.__data__, idx++);
          d3.event = o;
          return;
        }

        while (node && node !== view.el) {
          if (matchesSelector.call(node, selector)) {
            listener.call(view, d3.event, node.__data__, idx++);
          }
          node = node.parentNode;
        }
        d3.event = o;
      };

      var map = this._domEvents || (this._domEvents = {});
      var handlers = map[eventName] || (map[eventName] = []);
      handlers.push({selector: selector, listener: listener, wrapped: wrapped});

      this.el.addEventListener(eventName, wrapped, false);
      return this;
    },

    undelegate: function(eventName, selector, listener) {
      if (!this._domEvents || !this._domEvents[eventName]) return;

      if (typeof selector !== 'string') {
        listener = selector;
        selector = null;
      }

      var handlers = this._domEvents[eventName].slice();
      var i = handlers.length;
      while (i--) {
        var handler = handlers[i];

        var match = (listener ? handler.listener === listener : true) &&
            (selector ? handler.selector === selector : true);

        if (!match) continue;

        this.el.removeEventListener(eventName, handler.wrapped, false);
        this._domEvents[eventName].splice(i, 1);
      }
    },

    undelegateEvents: function() {
      var map = this._domEvents, el = this.el;
      if (!el || !map) return;

      Object.keys(map).forEach(function(eventName) {
        map[eventName].forEach(function(handler) {
          el.removeEventListener(eventName, handler.wrapped, false);
        });
      });

      this._domEvents = {};
      return this;
    }
  };

  Backbone.D3View = Backbone.View.extend(Backbone.D3ViewMixin);

  return Backbone.D3View;
}));

backbone.localStorage-min.js

/**
 * Backbone localStorage Adapter
 * Version 1.1.16
 *
 * https://github.com/jeromegn/Backbone.localStorage
 */(function(a,b){typeof exports=="object"&&typeof require=="function"?module.exports=b(require("backbone")):typeof define=="function"&&define.amd?define(["backbone"],function(c){return b(c||a.Backbone)}):b(Backbone)})(this,function(a){function b(){return((1+Math.random())*65536|0).toString(16).substring(1)}function c(){return b()+b()+"-"+b()+"-"+b()+"-"+b()+"-"+b()+b()+b()}function d(a){return a===Object(a)}function e(a,b){var c=a.length;while(c--)if(a[c]===b)return!0;return!1}function f(a,b){for(var c in b)a[c]=b[c];return a}function g(a,b){if(a==null)return void 0;var c=a[b];return typeof c=="function"?a[b]():c}return a.LocalStorage=window.Store=function(a,b){if(!this.localStorage)throw"Backbone.localStorage: Environment does not support localStorage.";this.name=a,this.serializer=b||{serialize:function(a){return d(a)?JSON.stringify(a):a},deserialize:function(a){return a&&JSON.parse(a)}};var c=this.localStorage().getItem(this.name);this.records=c&&c.split(",")||[]},f(a.LocalStorage.prototype,{save:function(){this.localStorage().setItem(this.name,this.records.join(","))},create:function(a){return!a.id&&a.id!==0&&(a.id=c(),a.set(a.idAttribute,a.id)),this.localStorage().setItem(this._itemName(a.id),this.serializer.serialize(a)),this.records.push(a.id.toString()),this.save(),this.find(a)},update:function(a){this.localStorage().setItem(this._itemName(a.id),this.serializer.serialize(a));var b=a.id.toString();return e(this.records,b)||(this.records.push(b),this.save()),this.find(a)},find:function(a){return this.serializer.deserialize(this.localStorage().getItem(this._itemName(a.id)))},findAll:function(){var a=[];for(var b=0,c,d;b<this.records.length;b++)c=this.records[b],d=this.serializer.deserialize(this.localStorage().getItem(this._itemName(c))),d!=null&&a.push(d);return a},destroy:function(a){this.localStorage().removeItem(this._itemName(a.id));var b=a.id.toString();for(var c=0,d;c<this.records.length;c++)this.records[c]===b&&this.records.splice(c,1);return this.save(),a},localStorage:function(){return localStorage},_clear:function(){var a=this.localStorage(),b=new RegExp("^"+this.name+"-");a.removeItem(this.name);for(var c in a)b.test(c)&&a.removeItem(c);this.records.length=0},_storageSize:function(){return this.localStorage().length},_itemName:function(a){return this.name+"-"+a}}),a.LocalStorage.sync=window.Store.sync=a.localSync=function(b,c,d){var e=g(c,"localStorage")||g(c.collection,"localStorage"),f,h,i=a.$?a.$.Deferred&&a.$.Deferred():a.Deferred&&a.Deferred();try{switch(b){case"read":f=c.id!=undefined?e.find(c):e.findAll();break;case"create":f=e.create(c);break;case"update":f=e.update(c);break;case"delete":f=e.destroy(c)}}catch(j){j.code===22&&e._storageSize()===0?h="Private browsing is unsupported":h=j.message}return f?(d&&d.success&&(a.VERSION==="0.9.10"?d.success(c,f,d):d.success(f)),i&&i.resolve(f)):(h=h?h:"Record Not Found",d&&d.error&&(a.VERSION==="0.9.10"?d.error(c,h,d):d.error(h)),i&&i.reject(h)),d&&d.complete&&d.complete(f),i&&i.promise()},a.ajaxSync=a.sync,a.getSyncMethod=function(b,c){var d=c&&c.ajaxSync;return!d&&(g(b,"localStorage")||g(b.collection,"localStorage"))?a.localSync:a.ajaxSync},a.sync=function(b,c,d){return a.getSyncMethod(c,d).apply(this,[b,c,d])},a.LocalStorage});

index.coffee

list = new Items()

new AppView
  el: 'body'
  collection: list
  

index.css

html, body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

body {
  display: flex;
  flex-direction: column;
}
.bar {
  height: 28px;
  background: #DDD;
  border-bottom: 1px solid gray;
  padding: 4px;
}
.bar > * {
  height: 100%;
  box-sizing: border-box;
  margin-right: 4px;
}
.list {
  height: 0;
  flex-grow: 1;
}
.item {
  font-family: sans-serif;
  font-size: 14px;
  padding: 6px;
}
.item:hover {
  text-decoration: line-through;
  cursor: pointer;
}