Chapter 4

Dynamic objects

{}





4.1 Syntax

brandSpankingNewObject = {}
anotherOne = {}





{title: 'Way of the Dragon'}





{title: 'Way of the Dragon', star: 'Bruce Lee'}





{title: 'Way of the Dragon', actors: ['Bruce Lee', 'Chuck Norris']}





{title: 'Way of the Dragon', info: {budget: '$130000', released: '1972'}}





movie = {title: 'Way of the Dragon'}





{title: 'Enter the Dragon', info: {budget: '$850000', released: '1973'}}





title: 'Enter the Dragon'
info:
  budget: '$850000'
  released: '1973'





Listing 4.1 Comparison of YAML literal with brace literal notation

YAML object literals
futurama =        
  characters: [
    'Fry'
    'Leela'
    'Bender'
    'The Professor'
    'Scruffy'
  ]
  quotes: [
    'Good news everyone!'
    'Bite my shiny metal'
  ]
  
Brace object literals
futurama = {    
  characters: [
    'Fry',
    'Leela',
    'Bender',
    'The Professor',
    'Scruffy'
  ],
  quotes: [
    'Good news everyone!'
    'Bite my shiny metal'
  ]
}





4.2 Key-value stores

phoneNumbers =             
  hannibal: '555-5551'
  darth: '555-5552'
  hal9000: 'disconnected'
  freddy: '555-5554'
  'T-800': '555-5555'  





phoneNumbers.darth
# '555-5552'





phoneNumbers.hal9000
# 'disconnected'





phoneNumbers.T-800 
# NaN





phoneNumbers.T - 800 





phoneNumbers['T-800']
# '555-5555'





phoneNumbers['freddy']
# '555-5554'





friendToCall = 'hannibal'
phoneNumbers[friendToCall]
# '555-5551'
friendToCall = 'darth' phoneNumbers[friendToCall] # '555-5552'





phoneNumbers.kevin = '555-5556'
phoneNumbers['Agent Smith'] = '555-5557'





phoneNumbers =
  hannibal:      '555-5551'
  darth:         '555-5552'
  hal9000:       'disconnected'
  freddy:        '555-5554'
  'T-800':       '555-5555'
  'kevin':       '555-5556'
  'Agent Smith': '555-5557'





phoneNumbers.hannibal = '555-5525'
phoneNumbers.hannibal
# '555-55525'





'hal9000' of phoneNumbers
# true
'skeletor' of phoneNumbers # false





Lisning 4.2 A simple phone book

phonebook =
  numbers:                       
    hannibal: '555-5551'         
    darth: '555-5552'            
    hal9000: 'disconnected'      
    freddy: '555-5554'           
    'T-800': '555-5555'          
  list: ->                                                
    "#{name}: #{number}" for name, number of @numbers     
  add: (name, number) ->                                  
    if not (name of @numbers)                             
      @numbers[name] = number                             
  get: (name) ->                                          
    if name of @numbers                                   
      "#{name}: #{@numbers[name]}"                        
    else                                                  
"#{name} not found"
console.log "Phonebook. Commands are add get list and exit."
process.stdin.setEncoding 'utf8'
stdin = process.openStdin()
stdin.on 'data', (chunk) -> args = chunk.split(' ') command = args[0].trim() name = args[1].trim() if args[1] number = args[2].trim() if args[2] switch command when 'add' res = phonebook.add(name, number) if name and number console.log res when 'get' console.log phonebook.get(name) if name when 'list' console.log phonebook.list() when 'exit' process.exit 1





> coffee phonebook.coffee
Phonebook. Commands are add, get, list and exit.





hannibal: 555-5551
darth: 555-5552
hal9000: disconnected
freddy: 555-5554
T-800: 555-5555





> get freddy
freddy: 555-5554





> add kevin 555-5556
kevin: 555-556





orderMaserati = (color) ->
  """Order summary             
  - Make: Gran Turismo S
  - Color: #{color}        
  """





orderMaserati 'Nero Carbonio'
# Order summary:
# - Make: Gran Turismo S
# - Color: Nero Carbonio





orderMaserati = (exteriorColor, interiorColor) ->  
  """Order summary:
- Make: Gran Turismo
Options: - Exterior color: #{exteriorColor} - Interior color: #{interiorColor} """





orderMaserati = (exteriorColor, interiorColor, wheelRims) ->
  """Order summary:
Make: Gran Turismo
Options: Exterior color: #{exteriorColor} Interior color: #{interiorColor} Wheel rims: #{options.wheelRims} """





orderMaserati = (options) ->
  """Order summary: 
Make: Gran Turismo
Options: Exterior color: #{options.exterior} Interior color: #{options.interior} Interior trim: #{options.trim} Wheel rims: #{options.rims} """





orderMaserati exterior:'red', interior:'red', trim:'walnut', wheels:'18'





Order summary:
Make: Gran Turismo
Options: Exterior color: red Interior color: red Interior trim: walnut Wheel rims: 18





element = {}
styles = 
  width: '10px'
  color: 'red'
css element, styles
element.style.width
# '10px'
element.style.color
# 'red'





4.3 Comprehensions

views =
  'ren': 30
  'stimpy': 10





views.ren = views.ren + 1





number + 1 for number in [1,2,3,4,5]
# [2,3,4,5,6]





expression for property of object





Listing 4.3 Comprehension compared to for...in loop

CoffeeScript
movie =
  title: 'From Dusk till Dawn'
  released: '1996'
  director: 'Robert Rodriguez'
  writer: 'Quentin Tarantino'


for property of movie
  console.log property

JavaScript
var movie = {
  title: 'From Dusk till Dawn',
  released: '1996',
  director: 'Robert Rodriguez',
  writer: 'Quentin Tarantino'
}

for (var property in movie) {
  console.log(property);
}

Listing 4.4 Comprehension as expression

CoffeeScript
properties = (prop for prop of movie)



JavaScript
var properties = [];
for (var prop in movie) {
  properties.push(prop);
}





name for name of {bob: 152, john: 139, tracy: 209} 
# ['bob', 'john', 'tracy']





views =
  '/reviews/pool-of-radiance': 121
  '/reviews/summer-games': 90
  '/reviews/wasteland': 139
  '/reviews/impossible-mission': 76





page for page of views





[ '/reviews/pool-of-radiance'
  '/reviews/summer-games'
  '/reviews/wasteland'
  '/reviews/impossible-mission' ]





value for property, value of object





score for name, score of {bob: 152, john: 139, tracy: 209}
# [152, 139, 209] 





count for page, count of views





[ 121, 90, 139, 76 ]





expression for property, value of object





for property, value of object
  expression





sum = 0
for page, count of views
  sum = sum + count
# [121, 211, 350, 426]





Listing 4.5 Page views

views = {}
viewsIncrement = (key) -> views[key] ?= 0
views[key] = views[key] + 1
total = -> sum = 0 for own page, count of views sum = sum + count sum





if !views['donatello']?
  views['donatello'] = 1





views['donatello'] ?= 0





views[key] =? 0





for own url, count of views





4.4 Structured data

Listing 4.6 JSON status updates

response = {            
  "results": {
    "23446": {
      "user": "Guard",
      "text": "Found them? In Mercia?! The coconut's tropical!"
    },
    "23445": {
      "user": "Arthur",
      "text": "We found them."
    },
    "23443": {
      "user": "Guard",
      "text": "Where'd you get the coconuts?"
    }
  }
} 





response.results
# { "23443": # { user: "Guard", # text: "Where\'d you get the coconuts?" }, # "23445": # { user: "Arthur", # text: "We found them." }, # "23446": # { user: "Guard", # text: "Found them? In Mercia?! The coconut\'s tropical!" } # }





response.results['23443'].user
# "Guard"
response.results['23445'].text
# "We found them."
response.results['23446'] # {user: "Guard", text: "Found them? In Mercia?! The coconut\'s tropical!"}





4.5 Binding

yourObject =
  someFunction: ->





yourObject.someFunction()





<!doctype html>
<title>How many people are coming?</title>
<body>
<div id='#some-element'>Change this text</div>





someElement = document.querySelector('#some-element')
someElement.onclick = ->
  someElement.innerHTML 'Got clicked!'





<!doctype html>
<title>How many people are coming?</title>
<body>
<div id='#some-element'>Change this text</div>
<div id='#some-other-element'>Change this text</div>





someElement = document.querySelector('#some-element')
someOtherElement = document.querySelector('#some-other-element')
someElement.onclick = ->
someElement.innerHTML = 'Got clicked!'
someOtherElement.onclick = -> someOtherElement.innerHTML = 'Got clicked!'





someElement.onclick = ->
  someElement.innerHTML = 'Got clicked!'





someElement.onclick = ->
  this.innerHTML = 'Got clicked!'





someElement.onclick = ->
  @innerHTML = 'Got clicked!'





@.innerHTML 'Got clicked!' 





markAsClicked = ->                
@innerHTML = 'Got clicked!'
someElement.onclick = markAsClicked someOtherElement.onclick = markAsClicked





document.querySelector('#some-element').onclick = ->





someElement.onclick = ->
  setTimeout ->
    @innerHTML 'Got clicked'      
  , 1000





noumenon =
  value: 'noumenon',       
  itself: -> @      





noumenon.itself().value
# 'noumenon'





detachedFromObject = noumenon.itself
detachedFromObject() # 'undefined'





other =
value: 'other'
other.itself = noumenon.itself
other.itself().value # 'other'





noumenon.itself = -> console.log @value





setTimeout noumenon.itself, 1000
# 'undefined'





someElement.onclick = ->
  clickedElement = @                   
  setTimeout ->
    clickedElement.innerHTML 'Got clicked'  
  , 1000





someElement.onclick = ->
  setTimeout =>                    
    this.innerHTML 'Got clicked'
 , 1000





4.6 Prototypes

cassette = 
  title: "Awesome songs. To the max!"
  duration: "10:34"
  released: "1988"
  track1: "Safety Dance - Men Without Hats"
  track2: "Funkytown - Lipps, Inc"
  track3: "Electric Avenue - Eddy Grant"
  track4: "We Built This City - Starship"





cassetteCopy = {}
for own property, value of cassette
  cassetteCopy[property] = value





cassetteCopy.track3
# "Electric Avenue ? Eddy Grant"





cassette.track5 = "Rock Me Amadeus - Falco"





cassetteCopy.track5?
# false





musicDevice = Object.create cassette





cassette.track6 = "Sledgehammer ? Peter Gabriel"
musicDevice.track6 is "Sledgehammer ? Peter Gabriel"
# true





musicDevice.track1 = "Toy Soldiers - Markita"
musicDevice.track1 is "Toy Soldiers - Markita"
# true





cassette.track1 is "Safety Dance - Men Without Hats"
# true





musicDevice.track7 = " Buffalo Stance - Neneh Cherry"
cassette.track7?
# false





4.7 Behavior

businessViews = {}
pleasureViews = {}





increment = (recipient, key) ->             
  recipient[key] ?= 0
  recipient[key] = recipient[key] + 1
total = (recipient) ->                      
  sum = 0
  for own page of recipient
    sum = sum + 1
  sum





increment businessViews, '/products/fade-o-meter/enterprise-edition'





Listing 4.7 Page views revisited

views =
  clear: ->
    @pages = {}                
  increment: (key) ->
    @pages ?= {}                       
    @pages[key] ?= 0
    @pages[key] = @pages[key] + 1
  total: ->
    sum = 0
    for own page, count of @pages
      sum = sum + count
    sum





businessViews = Object.create views
personalViews = Object.create views





for i in [1..100] by 1
businessViews.increment '/product-details/2454'
businessViews.total()
# 100
personalViews.total() # 0





views.pagesTotal = ->
  (url for own url of @).length
businessViews.pagesTotal()





4.8 Classes

function Views () { 
  this.pages = {};
}
businessViews = new Views();





Listing 4.8 A page views class

class Views                           
  constructor: ->           
    @pages = {}             
  increment: (key) ->                 
    @pages[key] ?= 0                  
    @pages[key] = @pages[key] + 1     
  total: () ->                       
    sum = 0                          
    for own url, count of @pages     
      sum = sum + count              
sum
businessViews = new Views personalViews = new Views





class Empty





empty = new Empty





newViewsObject = new Views





businessViews is personalViews
# false





objectReferenceOne = {}
objectReferenceTwo = objectReferenceOne
objectReferenceTwo is objectReferenceOne
# true





class Black
green = new Black





green is new Black
# false





businessViews is personalViews
# false





4.9 Putting it together

> coffee 4.9.coffee





Listing 4.9 Page views web application

http = require 'http'
class Views constructor: -> @pages = {} increment: (key) -> @pages[key] ?= 0 @pages[key] = @pages[key] + 1 total: -> sum = 0 for own url, count of @pages sum = sum + count
sum
businessViews = new Views
personalViews = new Views
server = http.createServer (request, response) -> renderHit = (against) -> against.increment request.url response.writeHead 200, 'Content-Type': 'text/html'
response.end "recorded"
if request.url is '/' response.writeHead 200, 'Content-Type': 'text/html' response.end """ Personal: #{personalViews.total()} Business: #{businessViews.total()} """ else if /\/business\/.*/.test request.url renderHit businessViews else if /\/personal\/.*/.test request.url renderHit personalViews else response.writeHead 404, 'Content-Type': 'text/html'
response.end "404"
server.listen 8080, '127.0.0.1'





4.10 Summary