• Jump To … +
    Client.coffee ClientModel.coffee Container.coffee Mediator.coffee PageMap.coffee RequestHandler.coffee Server.coffee SocketHandler.coffee Utils.coffee index.coffee index.coffee
  • Container.coffee

  • ¶

    NPM dependencies

  • ¶

    jquery
    tf
    diso.router

    $      = require('jquery')
    Tag    = require('tf')
    Router = require('diso.router')
  • ¶

    Local dependencies

    PageMap

    PageMap = require('./PageMap')
  • ¶

    body tag attribute used to communicate the page’s name between the server and the client

    PAGE_ATTR = 'data-page'
  • ¶

    ClientContainer

  • ¶

    Used by the client to sync the initial serverside render with the clientside page, perform navigation between and within pages via HTML5 history api

    class ClientContainer
      constructor : (args)->
        map       = args.map
        pages     = args.pages
  • ¶

    PageMap is used for routing / page lookup

        @_page_map = new PageMap(
          map   : map
          pages : pages
        )
  • ¶

    router is used to

        @_router = new Router()
        @_router.route(page_map.routes)
  • ¶

    Basicaly, traverse the dom and instantiate views for their associated dom nodes

      sync : (args)->
        init_data = args.init_data
        callback  = args.callback
        
        page_name = $('body').attr(PAGE_ATTR)
    
        unless page_name
          error = new Error("HTML body is missing #{PAGE_ATTR} attribute")
          return callback(error, null)
    
        unless (page_name of @_pages)
          error = new Error("No page named #{page_name}")
          return callback(error)
      
        route = @_router.match(url : document.location)
        Page  = @_pages[page_name]
    
        @_page = Page.create(
          data   : @_initial_data.view_data
          id_map : @_initial_data.id_map
          url    : document.location
          route  : route
        )
    
      sync : (id_map)->        
        ids = Object.keys(id_map)
        temp_ids = Object.keys(@_subviews)
        
        if (ids.length != temp_ids.length)
          throw "Mismatch between subview id lengths"
        
        for i in [0..(ids.length - 1)]
          id = ids[i]
          map = id_map[id]
          
          temp_id = temp_ids[i]
          view = @_subviews[temp_id]
          
          if view
            view.setId(id)
            view.setContainer()
            view.sync(map)
    
    
    
    
        page = new @(options)
        page_id = Object.keys(id_map)[0]
        page.setId(page_id)
        page.setContainer()
        
        map = id_map[page_id]
        page.sync(map)
        
        page.run()
        
        page
  • ¶

    _initializeHistory

  • ¶

    Initialize the html5 history api. Using the browser api rather than adapter and falling back to full page loads if it isn’t support http://diveintohtml5.info/history.html

      _initializeHistory : ()->
        if @_supportsHistory()
          $(window).on('popstate', @_onPopState)
  • ¶

    _supportsHistory

  • ¶

    returns true if the user’s browser supports HTML5 history http://caniuse.com/#search=history

      _supportsHistory : ()->
        !!(window.history?.pushState)
  • ¶

    _onPopState

  • ¶

    called when user presses back button
    TODO: handle this correctly / integrate with router

      _onPopState : ()=>
        console.log("HANDLE POP STATE YO:" + document.location)
  • ¶

    _pushHistory

  • ¶

    add url to the history. this will change the location bar to url

      _pushHistory : (url)->
        window.history.pushState(null, null, url)
    
    
      pushPage  : ()->
      popPage   : ()->
    
      navigate : (args)=>
        unless @_supportsHistory()
  • ¶

    TODO : find way to pass the JWT-token via get param or header

          window.location = url
          return
    
        if ('url' of args)
          url   = args.url
          route = @_router.match(url : url)      
        else if ('route' of args)
          route = args.route
          url   = @_router.format(route)
        else
          error = new Error("Must pass url or route arg to navigate")
          return @_onError(error)
    
        page = @_page_map.page(
          route : route
          url   : url
          store : @_store
        )
    
        unless page
          error = new Error("No page for #{route.name}")
          return @_onError(error)
    
        page.load((error)=>
          console.log("LOADED PAGE!")
          @_changePage(page)
          @_pushHistory(url) 
        )
    
      setBody : (body)->
        if @body
          @removeSubview(@body)
        @body = body
        @addSubview(@body)
      
      swapBody : (new_body)->
        @body.removeBehaviors()
        $body = $("##{@body.id}")
        $body.replaceWith(new_body.html())
        @setBody(new_body)
        new_body.run()
  • ¶

    server, the container provides all the HTML needed outside of

    the body tag.

    class ServerContainer
      scripts     : []
      styles      : []
  • ¶

    idMap

  • ¶

    called on the server to build a map of all the view ids for the client to use for sync.

      idMap : ()->    
        _idMap = (view)->
          map = {}
          for id, subview of view.subviews()
            map[view.id] = _idMap(subview)
          map
    
        map = {}
        map[@page.id] = _idMap(@page)
        map
        
      meta : (data)->
        metadata = {
          name : {
            description : data.description || @description
            viewport    : @viewport || 'width=device-width, initial-scale=1'
          }
          itemprop : {
            name        : data.title
            description : data.description
            image       : data.image
            url         : data.url
          }
          property : {
            "og:title"       : data.title
            "og:description" : data.description
            "og:image"       : data.image
            "og:type"        : data.type || 'website'
            "og:url"         : data.url
            "og:site_name"   : data.site_name || data.title
          }
        }
          
        html = Tag.meta(charset : 'utf-8') + "\n"
    
        for attr, m of metadata
          for name, content of m
            opts = { 
              content : content
            }
            opts[attr] = name
            html += Tag.meta(opts) + "\n"
          
        html
        
      head : ()->
        @page.head()
        
      headers : ()->
        @page.headers()
    
      status : ()->
        @page.status()
  • ¶

    html

  • ¶

    render this container as html

      html : ()->
  • ¶

    TODO

        """
          <!DOCTYPE html>
          <html>
            <head>
              #{@meta(@page.meta())}
    
              <title>#{@page.meta().title || @site_name}</title>
    
              #{(Tag.stylesheet(href: href) for href in @styles).join("\n")}
              
              #{(Tag.script(src: src) for src in @scripts).join("\n")}
              
              #{@head()}        
            </head>
            
            <body #{PAGE_ATTR}="#{@page.constructor.name}">
              #{@page.html()}
            </body>
          </html>
        """
  • ¶

    html

  • ¶

    render this container as text

      text : ()->
        @page.text()
  • ¶

    html

  • ¶

    render this container as json

      json : ()->
        @page.json()
        
    module.exports = {
      Server : ServerContainer
      Client : ClientContainer
    }