Chapter 5

Composing objects

5.1 Being classical

data =
  X100: 
    description: "A really cool camera"
    stock: 5
  X1:
    description: "An awesome camera"
    stock: 6





"X100: a camera (5 in stock)"





for own name, info of data 
"#{name}: #{info.description} (#{info.stock} in stock)"
# ["X100: A really cool camera (5 in stock)", # "X1: An awesome camera (6 in stock)"]





purchase = (product) ->
for own name, info of data elem = document.createElement "li" elem.innerHTML = "#{name}: #{info.description} (#{info.stock} in stock)" elem.onclick = purchase name





Listing 5.1 A simple Camera class

class Camera 
  constructor: (name, info) ->    
    @name = name                  
    @info = info                  
  render: ->                                                    
    "#{@name}: #{@info.description} (#{@info.stock} in stock)"  
  purchase: ->     





x1 = new Camera 'X1', {
  description: 'An awesome camera', stock: 5
}





x1.name
#'X1'
x1.info
#{description: "An awesome camera", stock: 5}





x1.render()
# "Camera: X1: An Awesome camera (5)"





x1.render
# [Function]





class Shop                      
  constructor: (data) ->        
    for name, info of data      
      new Camera name, info 





data =                                   
  X100:                                  
    description: "A really cool camera"  
    stock: 5                             
  X1:                                      
    description: "An awesome camera"       
    stock: 6 





shop = new Shop data





> coffee 5.13.coffee 5.2                      
# => Visit http://localhost:8080/ in your browser





Listing 5.2 Agtron?s camera shop client application (version 1)

http = (method, src, callback) ->               
  handler = ->                                  
    if @readyState is 4 and @status is 200      
      unless @responseText is null              
callback JSON.parse @responseText
client = new XMLHttpRequest client.onreadystatechange = handler client.open method, src
client.send()
get = (src, callback) ->
http "GET", src, callback
post = (src, callback) ->
http "POST", src, callback
class Camera constructor: (name, info) -> @name = name @info = info @view = document.createElement 'div' @view.className = "camera" document.body.appendChild @view @view.onclick = => @purchase()
@render()
render: ->
@view.innerHTML = "#{@name} (#{@info.stock} stock)"
purchase: -> if @info.stock > 0 post "/json/purchase/camera/#{@name}", (res) => if res.status is "success" @info = res.update
@render()
class Shop constructor: -> get '/json/list/camera', (data) -> for own name, info of data
new Camera name, info
shop = new Shop





5.2 Class inheritance

class Product





class Camera extends Product
class Robot extends Product
class Skateboard extends Product





class Product 
  constructor: (name, info) ->   
    @name = name                 
@info = info
render: ->
"#{@name}: #{@info.description} (#{@info.stock} in stock)"
purchase: -> if @info.stock > 0 post "/json/purchase/camera/#{@name}", (res) => if res.status is "success" @info = res.update
@render()
class Skateboard extends Product





skateOMatic = new Skateboard "Skate-o-matic", {
  description: "It's a skateboard"
  stock: 1
}
skateOMatic.render() # Skate-o-matic: "It\'s a skateboard (1 in stock)"





skateOMatic = new Skateboard "Skate-o-matic", {
  description: "It's a skateboard"
  stock: 1
}
skateOMatic.render() # Skate-o-matic: "It\'s a skateboard (1 in stock)"





class Camera extends Product
  megapixels: ->
    @info.megapixels || 'Unknown'





x11 = new Camera "x11", {
  description: "The future of photography",
  stock: 4,
  megapixels: 20
}
sk8orama = new Skateboard "Sk8orama", { description: "A trendy skateboard", stock: 4
}
x11.megapixels?
# true
x11.megapixels()
# 20
sk8orama.megapixels? # false





product =
  render: -> 
  purchase: ->





construct = (prototype, name, info) ->
  object = Object.create prototype           
  object.name = name                     
  object.info = info                     
object
clock = construct product, 'clock', stock: 5





class Product
  constructor: ->    
  render: ->          
  purchase: ->  





camera = Object.create product
camera.megapixels = ->





x11 = construct camera, '', stock: 6





Listing 5.3 Agtron?s shop client application with multiple product categories

# http function omitted ? see listing 5.2  
# get function omitted ? see listing 5.2     
# post function omitted ? see listing 5.2
class Product constructor: (name, info) -> @name = name @info = info @view = document.createElement 'div' @view.className = "product" document.body.appendChild @view @view.onclick = => @purchase()
@render()
render: -> renderInfo = (key,val) -> "<div>#{key}: #{val}</div>" displayInfo = (renderInfo(key, val) for own key, val of @info)
@view.innerHTML = "#{@name} #{displayInfo.join ''}"
purchase: -> if @info.stock > 0 post "/json/purchase/#{@purchaseCategory}/#{@name}", (res) => if res.status is "success" @info = res.update
@render()
class Camera extends Product purchaseCategory: 'camera'
megapixels: -> @info.megapixels || "Unknown"
class Skateboard extends Product purchaseCategory: 'skateboard'
length: -> @info.length || "Unknown"
class Shop constructor: -> get '/json/list', (data) -> for own category of data for own name, info of data[category] switch category when 'camera' new Camera name, info when 'skateboard'
new Skateboard name, info
shop = new Shop





5.3 Class variables and properties

class Product
render: ->
instanceOfProduct = new Product instanceOfProduct.render





Product.recall()





products = [                      
    name: "Shark repellant"       
  ,                               
    name: "Duct tape"             
]          





find = (query) ->
  (product for product in products when product.name is query)





find "Duct tape"
# [ { name: 'Duct tape' } ] 





class Shop
  constructor: (data) ->
  products = []
  for own name, info of data
    products.push new Product(name, info)





Product.find('Pixelmator-X21')
# [ Camera { name="Pixelmator-X21", info={...} } ]





Product.find = (query) ->
  (product for product in products when product.name is query)





class SecretAgent
secretWord = "antiquing"
secretWord? # false





class SecretAgent
  secretWord = "antiquing"          
  licensedToKill: yes               





class SecretAgent
  secretWord = "antiquing"    
  greet: (word) ->
    if word is secretWord     
"Hello, how are you?"
bob = new SecretAgent bob.greet "antiquing" # "Hello, how are you?"





class Product
products
Product.find = (query) -> (product for product in products when product.name is query)





class Product
  @find = (what) ->      
"#{what} not found"
Product.find "zombie survival kit" # "zombie survival kit not found"





class Product
  instances = []            
  @find = (query) ->                   
    (product for product in instances when product.name is query)
  constructor: (name) ->
    instances = instances.concat [@]  
    @name = name





new Product "Green", {}
Product.find 'Green'
# [ { name: 'Green' } ]





Listing 5.4 Agtron?s shop client application with find

# http, get and post functions omitted ? see listing 5.2
class Product
products = []
@find = (query) -> for product in products product.unmark() for product in products when product.name is query product.mark()
product
constructor: (name, info) -> products.push @ @name = name @info = info @view = document.createElement 'div' @view.className = "product" document.body.appendChild @view @view.onclick = => @purchase()
@render()
render: -> show = ("<div>#{key}: #{val}</div>" for own key, val of @info).join ''
@view.innerHTML = "#{@name} #{show}"
purchase: -> if @info.stock > 0 post "/json/purchase/#{@purchaseCategory}/#{@name}", (res) => if res.status is "success" @info = res.update
@render()
mark: ->
@view.style.border = "1px solid black"
unmark: ->
@view.style.border = "none"
# class Camera omitted ? see listing 5.3
# class Skateboard omitted ? see listing 5.3
class Shop constructor: -> @view = document.createElement 'input' @view.onchange = -> Product.find @value document.body.appendChild @view @render() get '/json/list', (data) -> for own category of data for own name, info of data[category] switch category when 'camera' new Camera name, info when 'skateboard'
new Skateboard name, info
render: ->
@view.innerHTML = ""
shop = new Shop





products = [
    name: "Shark repellant"
  ,
    name: "Duct tape" 
]
class Product
Product.find = (query) ->
(product for product in products when product.name is query)
class Camera extends Product
Camera.find? # true





class Product
class Camera extends Product
Product.find = (what) -> "#{what} not found" Product.find?
# true
Camera.find? # false





5.4 Overriding and super

class Human
Human.rights = ['Life', 'Liberty', 'the pursuit of happiness'] Human.rights
# ['Life', 'Liberty', 'the pursuit of happiness']
Human.rights = Human.rights.concat ['To party'] Human.rights # ['Life', 'Liberty', 'the pursuit of happiness', 'To party']





class Gallery
  constructor: ->
  render: ->





class Camera
  constructor: ->
    @gallery = new Gallery





class Camera extends Product
  render: ->
    @view.innerHTML = """        
    #{@name}: #{@info.stock}     
    {@gallery.render()}          
    """              





class Camera extends Product
  constructor: (name, info) ->
    @name = name
    @info = info
    @gallery = new Gallery      
  render: ->
    @view.innerHTML = """
    #{@name}: #{@info.stock}
    {@gallery.render()}          
 """





class Product
Product.find = (query) ->
(product for product in products when product.name is query)
class Camera extends Product
x1 = new Camera 'X1', {} Product.find 'X1' # []





class Product
  constructor: (name, cost) ->
    @name = name
    @cost = cost
  price: ->
@cost
class Camera extends Product
markup = 2
price: -> super()*markup





camera = new Camera 'X10', 10
camera.price()
# 20





Listing 5.5 Agtron?s shop client application with camera gallery

# http, get and post functions omitted from this listing
class Gallery constructor: (@photos) -> render: -> images = for photo in @photos "<li><img src='#{photo}' alt='sample photo' /></li>"
"<ul class='gallery'>#{images.join ''}</ul>"
class Product constructor: (name, info) -> @name = name @info = info @view = document.createElement 'div' @view.className = 'product' document.querySelector('.page').appendChild @view @render() render: ->
@view.innerHTML = "#{@name}: #{@info.stock}"
class Camera extends Product constructor: (name, info) -> @gallery = new Gallery info.gallery super name, info @view.className += ' camera' render: -> @view.innerHTML = """ #{@name} (#{@info.stock}) #{@gallery.render()}
"""
class Shop constructor: -> @view = document.createElement 'div' document.querySelector('.page').appendChild @view document.querySelector('.page').className += ' l55' @render() get '/json/list', (data) -> for own category of data for own name, info of data[category] switch category when 'camera' new Camera name, info else
new Product name, info
render: () ->
@view.innerHTML = ""
shop = new Shop





class Gallery
class Camera extends Product constructor: (name, info) -> @gallery = new Gallery
super
pixelmatic = new Camera 'The Pixelmatic 5000', {} pixelmatic.name # 'The Pixelmatic 5000'





5.5 Modifying prototypes

class Camera
  render: ->                   
    if /'Lacia'/.test @name    
      "Special deal"   





Listing 5.6 CoffeeScript class and compilation to JavaScript

CoffeeScript
class Simple
  constructor: ->
    @name = "simple object"

simple = new Simple



JavaScript
var Simple = (function() {
  function Simple() {
    this.name = 'simple';
  }
  return Simple;
})();

simple = new Simple();





SteamShovel = (name) ->                  
@name = name
steamShovel = new SteamShovel 'Gus' steamShovel.name
# 'Gus'
steamShovel.constructor # [Function]





Listing 5.7 CoffeeScript class, constructor, and method compilation

CoffeeScript
class SteamShovel
  constructor (name) ->
    @name = name
  speak: ->
    "Hurry up!"

gus = new SteamShovel
gus.speak()
# Hurry up!



JavaScript
var SteamShovel = (function() {
  function SteamShovel(name) {
    this.name = name;
  }
  SteamShovel.prototype.speak =  
    function() {
   return "Hurry up!";
 };
 return SteamShovel;
  };
gus = new SteamShovel();
gus.speak();





SteamShovel.prototype = {}





SteamShovel.prototype.grumpy = yes





gus.grumpy
# true





class Example
example = new Example
Example.prototype.justAdded = -> "just added!"
example.justAdded() # "just added!"





Example::justAdded = -> "just added!"





Example.prototype.justAdded = -> "just added!"





Listing 5.8 Agtron?s shop client application with specials

# http omitted from this listing
# get omitted from this listing
# post omitted from this listing
class Product constructor: (name, info) -> @name = name @info = info @view = document.createElement 'div' @view.className = "product #{@category}" document.querySelector('.page').appendChild @view @view.onclick = => @purchase()
@render()
render: ->
@view.innerHTML = @template()
purchase: -> if @info.stock > 0 post "/json/purchase/#{@category}/#{@name}", (res) => if res.status is "success" @info = res.update
@render()
template: => """ <h2>#{@name}</h2> <dl class='info'> <dt>Stock</dt> <dd>#{@info.stock}</dd> <dt>Specials?</dt> <dd>#{@specials.join(',') || 'No'}</dd> </dl>
"""
class Camera extends Product category: 'camera'
megapixels: -> @info.megapixels || "Unknown"
class Skateboard extends Product category: 'skateboard'
length: -> @info.length || "Unknown"
class Shop constructor: -> unless Product::specials? Product::specials = [] @view = document.createElement 'div' @render() get '/json/list', (data) -> for own category of data for own name, info of data[category] if info.special? Product::specials.push info.special switch category when 'camera' new Camera name, info when 'skateboard'
new Skateboard name, info
render: ->
@view.innerHTML = ""
shop = new Shop





Listing 5.9 Product listings with specials

{
  'camera': {
    'Fuji-X100': {
      'description': 'a camera',
      'stock': 5,
      'arrives': 'December 25, 2012 00:00',
      'megapixels': 12.3
    }
  },
  'skateboard': {
    'Powell-Peralta': {
      'description': 'a skateboard',
      'stock': 3,
      'arrives': 'December 25, 2012 00:00',
      'length': '23.3 inches'
    }
  }
}





products =
  camera:
    'Fuji-X100':
      description: 'a camera'
      stock: 5
      arrives: 'December 25, 2012 00:00'
      megapixels: 12.3
  skateboard:
    'Powell-Peralta':
      description: 'a skateboard'
      stock: 3
      arrives: 'December 25, 2012 00:00'
         length: '23.3 inches'





5.6 Extending built-ins

object = {}
array = []
string = ''





object = new Object
array = new Array
string = new String





['yin','yang'].join 'and'
# 'yin and yang'





Array::join = -> "Array::join was redefined"
['yin','yang'].join 'and'
# "Array::join this was overridden"





"October 13, 1975 11:13:00"





new Date "October 13, 1975 11:13:00"





productAvailable = new Date "October 13, 1975 11:13:00"
productAvailable.daysFromToday()





Date::daysFromToday = ->
  millisecondsInDay = 86400000
  today = new Date
  diff = @ - today
  Math.floor diff/millisecondsInDay





christmas = new Date "December 25, 2012 00:00"
christmas.daysFromToday()
#339          





Listing 5.10 Agtron?s shop client application with stock arrivals

# http omitted
# get omitted 
# put omitted
Date::daysFromToday = -> millisecondsInDay = 86400000 today = new Date diff = @ - today
Math.floor diff/millisecondsInDay
class Product
products = []
@find = (query) -> for product in products product.unmark() for product in products when product.name is query product.mark()
product
constructor: (name, info) -> products.push @ @name = name @info = info @view = document.createElement 'div' @view.className = "product #{@category}" document.querySelector('.page').appendChild @view @view.onclick = => @purchase()
@render()
render: ->
@view.innerHTML = @template()
purchase: -> if @info.stock > 0 post "/json/purchase/#{@purchaseCategory}/#{@name}", (res) => if res.status is "success" @info = res.update
@render()
template: => """ <h2>#{@name}</h2> <dl class='info'> <dt>Stock</dt> <dd>#{@info.stock}</dd> <dt>New stock arrives in</dt> <dd>#{new Date(@info.arrives).daysFromToday()} days</dd> </dl>
"""
mark: ->
@view.style.border = "1px solid black";
unmark: ->
@view.style.border = "none";
class Camera extends Product category: 'camera'
megapixels: -> @info.megapixels || "Unknown"
class Skateboard extends Product category: 'skateboard'
length: -> @info.length || "Unknown"
class Shop constructor: -> unless Product::specials? Product::specials = [] @view = document.createElement 'div' @render() get '/json/list', (data) -> for own category of data for own name, info of data[category] if info.special? Product::specials.push info.special switch category when 'camera' new Camera name, info when 'skateboard'
new Skateboard name, info
render: -> @view = document.createElement 'div' document.querySelector('.page').appendChild @view @view.innerHTML = """ <form class='search'> Search: <input id='search' type='text' /> <button id='go'>Go</button> </form> """ @search = document.querySelector '#search' @go = document.querySelector '#go' @go.onclick = => Product.find @search.value false @search.onchange = -> Product.find @value
false
shop = new Shop





class ExtendedDate extends Date
  daysFromToday: ->
    millisecondsInDay = 86400000
    today = new Date
    diff = @ - today
    Math.floor diff/millisecondsInDay





5.7 Mixins

class Announcement
pigsFly = new Announcement
pigsFly.purchase()
# Error





class Renderer
class Product extends Renderer
class Camera extends Product
class Announcement extends Renderer





htmlRenderer =                                 
  render: ->                                   
    unless @view?                              
      @view = document.createElement 'div'     
      document.body.appendChild @view          
      @view.innerHTML = """                    
      #{@name}                                 
      #{@info}                                 
      """       





class Donut
  constructor: (name,info) ->
    @name = name
    @info = info





Donut::render = htmlRenderer.render





dwarves = 
  bashful: -> 'Bashful'
  doc: -> 'Doc'
  dopey: -> 'Dopey'





class FairyTale
  for key, value of dwarves    
    FairyTale::[key] = value   





class Camera                 
  include @, htmlRenderer    
# ReferenceError: include is not defined





x1 = new Camera
x1.render()     
#renders





include = (klass, module) ->     
  for key, value of module       
    klass::[key] = value 





Listing 5.11 Mixin class

class Mixin
  constructor: (methods) ->
    for name, body of methods
      @[name] = body
  include: (klass) ->          
    for key, value of @        
klass::[key] = value
htmlRenderer = new Mixin
render: -> "rendered"
class Camera
htmlRenderer.include @
leica = new Camera()
leica.render() #rendered





accumulate = (initial, numbers, accumulator) ->     
  total = initial or 0                              
  for number in numbers                             
    total = accumulator total, number               
total
sum = (acc, current) -> acc + current accumulate 0, [5,5,5], sum # 15





class Product
  instances = []
  constructor: (stock) ->
    instances.push @
    @stock = stock





class Product
  instances = []
  constructor: (stock) ->
    instances.push @
    @stock = stock
  stock: ->
    stockAccumulator = (acc, current) -> acc + current.stock
    accumulate(0, instances, stockAccumulator)





enumerable =                                          
  accumulate: (initial=0, accumulator, sequence) ->    
    total = initial                                    
    for element in sequence                            
      total = accumulator total, element         
    total





include = (klass, module) ->   
  for key, value of module     
klass[key] = value
class Product include Product, enumerable instances = [] accumulator = (acc, current) -> acc + current.stock @stockTotal = -> @accumulate(0, accumulator, instances) constructor: (stock) -> instances.push @
@stock = stock
trinkets = new Product 12 valium = new Product 8 laser = new Product 3 Product.stockTotal() # 23





Object::antEater = ->
  "I'm an ant eater!"





antFarm = new Product { stock:1 }
antFarm.antEater()
# "I'm an ant eater!"





include = (klass, module) ->
  for own key, value of module      
    klass[key] = value





htmlRenderer = Object.create null





class Mixin
  @:: = null
  constructor: (from, to) ->
    for key, val of from
      to[key] = val





5.8 Putting it together

Listing 5.12 Agtron?s shop client application

server =
  http: (method, src, callback) ->            
    handler = ->                                 
      if @readyState is 4 and @status is 200      
        unless @responseText is null             
callback JSON.parse @responseText
client = new XMLHttpRequest client.onreadystatechange = handler client.open method, src
client.send()
get: (src, callback) ->
@http "GET", src, callback
post: (src, callback) ->
@http "POST", src, callback
class View @:: = null @include = (to, className) => for key, val of @ to::[key] = val @handler = (event, fn) -> @node[event] = fn @update = -> unless @node? @node = document.createElement 'div' @node.className = @constructor.name.toLowerCase() document.querySelector('.page').appendChild @node
@node.innerHTML = @template()
class Product View.include @ products = [] @find = (query) -> (product for product in products when product.name is query) constructor: (@name, @info) -> products.push @ @template = => """ #{@name} """ @update() @handler "onclick", @purchase purchase: => if @info.stock > 0 server.post "/json/purchase/#{@category}/#{@name}", (res) => if res.status is "success" @info = res.update
@update()
class Camera extends Product category: 'camera'
megapixels: -> @info.megapixels || "Unknown"
class Skateboard extends Product category: 'skateboard'
length: -> @info.length || "Unknown"
class Shop View.include @ constructor: -> @template = ->
"<h1>News: #{@breakingNews}</h1>"
server.get '/json/news', (news) => @breakingNews = news.breaking
@update()
server.get '/json/list', (data) -> for own category of data for own name, info of data[category] switch category when 'camera' new Camera name, info when 'skateboard'
new Skateboard name, info
shop = new Shop





Listing 5.13 Agtron?s shop server application

http = require 'http'
url = require 'url'
coffee = require 'coffee-script'
data = require('./data').all
news = require('./news').all
script = "./#{process.argv[2]}.coffee" client = "" require('fs').readFile script, 'utf-8', (err, data) -> if err then throw err
client = data
css = "" require('fs').readFile './client.css', 'utf-8', (err, data) -> if err then throw err css = data
headers = (res, status, type) ->
res.writeHead status, 'Content-Type': "text/#{type}"
view = """ <!doctype html> <head> <title>Agtron's Cameras</title> <link rel='stylesheet' href='/css/client.css'></link> </head> <body> <script src='/js/client.js'></script> </body> </html>
"""
server = http.createServer (req, res) -> path = url.parse(req.url).pathname if req.method == "POST" category = /^\/json\/purchase\/([^/]*)\/([^/]*)$/.exec(path)?[1] item = /^\/json\/purchase\/([^/]*)\/([^/]*)$/.exec(path)?[2] if category? and item? and data[category][item].stock > 0 data[category][item].stock -= 1 headers res, 200, 'json' res.write JSON.stringify status: 'success', update: data[category][item] else res.write JSON.stringify status: 'failure' res.end() return switch path when '/json/list' headers res, 200, 'json' res.end JSON.stringify data when '/json/list/camera' headers res, 200, 'json' cameras = data.camera res.end JSON.stringify data.camera when '/json/news' headers res, 200, 'json' res.end JSON.stringify news when '/js/client.js' headers res, 200, 'javascript' writeClientScript = (script) -> res.end coffee.compile(script) readClientScript writeClientScript when '/css/client.css' headers res, 200, 'css' res.end css when '/' headers res, 200, 'html' res.end view else if path.match /^\/images\/(.*)\.png$/gi fs.readFile ".#{path}", (err, data) -> if err headers res, 404, 'image/png' res.end() else headers res, 200, 'image/png' res.end data, 'binary' else headers res, 404, 'html'
res.end '404'
server.listen 8080, '127.0.0.1', -> console.log 'Visit http://localhost:8080/ in your browser'





5.9 Summary