Skip to content

Instantly share code, notes, and snippets.

@lancejpollard
Created August 10, 2012 15:02
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save lancejpollard/3314813 to your computer and use it in GitHub Desktop.
Save lancejpollard/3314813 to your computer and use it in GitHub Desktop.
Tower + Ember Views
# Random helper method to force Ember to render (I think this is it, comes in handy)
Ember.fast = ->
Ember.run.autorun()
Ember.run.currentRunLoop.flush('render')
# app/client/views/users/new.coffee
App.UserNewView = Ember.View.extend(templateName: 'new')
# config/routes.coffee
Tower.Route.draw ->
@resources 'users'
@resources 'posts'
# The `resources` method creates 7 routes mapping to 7 controller actions.
# It creates those routes with the `match` method.
# The `match` method on the client is overridden to include this:
# `Tower.router.insertRoute(route)`
# That then builds the state tree on the router here:
# https://github.com/viatropos/tower/blob/402f19595f0d2fe1f85c973fead7d76fd682e820/packages/tower-net/client/states.coffee#L68
# Then see the `Ember.Route.create` in states.coffee.
# It delegates `enter`, `exit`, and `connectOutlets` to the controller.
# The controller calls `enter` when it first enters,
# and `enterAction` when it switches between actions. Same with exit/exitAction.
# The `route.connectOutlets` calls `controller.call`, which actually calls
# the action on the controller (and runs the before/after callbacks like normal).
# The last part is calling `@render 'someAction'` from controller action method,
# which gets expanded into the `viewClass` and figures out the outlet to render this
# action view into. That's the part I'm finishing now.
# Currently it's hacked in partly here:
# https://github.com/viatropos/tower/blob/402f19595f0d2fe1f85c973fead7d76fd682e820/packages/tower-view/shared/rendering.coffee#L15
# and here:
# https://github.com/viatropos/tower/blob/402f19595f0d2fe1f85c973fead7d76fd682e820/packages/tower-view/client/emberHelper.coffee#L22
# The other last part is the Watchfile task that builds the templates.js
#
# From the developers perspective, for a resource like "users",
# all you do is write templates, single-line routes,
# and a controller with the actions that call `@render action`.
# You can pass in an `@render 'show', outlet: 'popup'` too, but the simple case is 1 outlet.
# All of this will be implemented as defaults so you don't have to write any actions on the controller.
#
# Then in the templates, you'd have an {{action "destroy"}} do destroy a record,
# but this hasn't been implemented yet.
# app/client/views/users/show.coffee
App.UserShowView = Ember.View.extend(templateName: 'show')
# See the Watchfile method below, which generates the templates.js that basically looks like this:
Ember.TEMPLATES['new'] = Ember.computed(-> Ember.Handlebars.compile('new user!'))
Ember.TEMPLATES['show'] = Ember.computed(-> Ember.Handlebars.compile('show user!'))
# The ideal/optimized way of compiling the ember templates is not fully fleshed out yet, but using computed properties like this basically lazily compiles them, which is good enough for now.
// this is what's actually generated, the templates.coffee is for human readability :)
// will clean this up over time
Tower.View.cache = {
'app/views/users/show': Ember.computed(function() { return Ember.Handlebars.compile('show!'); })
};
_.extend(Ember.TEMPLATES, Tower.View.cache);
class App.UsersController extends Tower.Controller
new: ->
@render 'new'
show: ->
@render 'show' # Ember.TEMPLATES['app/views/users/show']
coffeecup = require('coffeecup')
watch /app\/views.*\.coffee$/
update: (path, callback) ->
try
nodes = path.replace("app/views/", "").split("/")
name = {}
data = File.read(path)
id = nodes.join("/")
selector = id
name = ""
#@broadcast body: data, id: id, selector: selector, path: "/#{name}"
files = File.files("app/views")
result = []
for file in files
continue unless file.match(/app\/views\/admin\/(index|tiles.*|categories.*).coffee$/)
continue unless file.match(/\.coffee$/)
result.push [file.replace(/\.coffee$/, ""), File.read(file)]
template = "Tower.View.cache =\n"
# this is different from before
iterator = (item, next) =>
template += " '#{item[0]}': Ember.Handlebars.compile('"
# make it render to HTML for ember
template += "#{coffeecup.render(item[1])}')\n"
next()
async.forEachSeries result, iterator, (error) =>
template += '_.extend(Ember.TEMPLATES, Tower.View.cache)\n'
mint.coffee template, bare: true, (error, string) =>
if error
console.log error
return callback(error)
else
File.write "public/javascripts/templates.js", string
callback()
catch error
callback(error)
client:
update: (data) ->
Tower.View.cache ||= {}
Tower.View.cache[data.id] = Ember.TEMPLATES[data.id] = data
###
if data.reload
window.location = data.path
else
Tower.get data.path
###
@lancejpollard
Copy link
Author

To change the url, use:

Tower.router.transitionTo('users.new')

I haven't dug in further yet to make a better way.

@lancejpollard
Copy link
Author

Tower.router.transitionTo('users.new', optionalParams)

@lancejpollard
Copy link
Author

For menu navigation, you want to link to controller actions, and maybe have the menu items be highlighted when active. There's booleans on the controller for when the controller is active, controller.isActive, and when each action is active, isIndexActive, isXActive (maybe useful in sidebars):

ul class: 'nav', ->
  li ->
    a '{{action index target="App.usersController"}} {{bindAttr class="isActive" target="App.usersController"}}', 'Users'
  li ->
    a '{{action index target="App.postsController"}} {{bindAttr class="isActive" target="App.postsController"}}', 'Posts'

# you may be able to use `with` here to make it easier to read, haven't tried though.
ul class: 'nav', ->
  li ->
    text '{{with App.usersController}}'
    a '{{action index}} {{bindAttr class="isActive"}}', 'Users'
    text '{{/with}}'

# ...or the `with` helper
ul class: 'nav', ->
  li ->
    hWith 'App.usersController', ->
      a '{{action index}} {{bindAttr class="isActive"}}', 'Users'

@avaranovich
Copy link

I wonder what would be the relation between controller and router. In ember -- these are conceptually different things. Here in tower you seems to "merge" controller and router in a more traditional MVC approach. Is that correct observation or you still plan to introduce a separate routing concept? (as I understand, Tower.router is wrapper around ember router, and supposed to be used only internally -- or it can be extended, e.g. by handling "enter" event in a custom way?).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment