Skip to content

Instantly share code, notes, and snippets.

@lancejpollard
Created August 10, 2012 15:02
Show Gist options
  • 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
###
@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