diff --git a/lib/handlebars_assets/config.rb b/lib/handlebars_assets/config.rb index 9a54aa0..4fe63f5 100644 --- a/lib/handlebars_assets/config.rb +++ b/lib/handlebars_assets/config.rb @@ -6,7 +6,7 @@ module HandlebarsAssets module Config extend self - attr_writer :compiler, :compiler_path, :ember, :multiple_frameworks, + attr_writer :compiler, :compiler_runtime, :compiler_path, :ember, :multiple_frameworks, :haml_options, :known_helpers, :known_helpers_only, :options, :patch_files, :patch_path, :path_prefix, :slim_options, :template_namespace, :precompile, :haml_enabled, :slim_enabled, @@ -17,6 +17,10 @@ def compiler @compiler || 'handlebars.js' end + def compiler_runtime + @compiler_runtime || 'handlebars.runtime.js' + end + def self.configure yield self end @@ -94,7 +98,7 @@ def template_namespace end def handlebars_extensions - @hbs_extensions ||= ['hbs', 'handlebars'] + @hbs_extensions ||= %w(hbs handlebars) end def hamlbars_extensions diff --git a/lib/handlebars_assets/handlebars.rb b/lib/handlebars_assets/handlebars.rb index 7e2eb20..b1f89df 100644 --- a/lib/handlebars_assets/handlebars.rb +++ b/lib/handlebars_assets/handlebars.rb @@ -5,7 +5,6 @@ module HandlebarsAssets class Handlebars class << self - def precompile(*args) context.call('Handlebars.precompile', *args) end diff --git a/lib/handlebars_assets/server.rb b/lib/handlebars_assets/server.rb new file mode 100644 index 0000000..6cce9ba --- /dev/null +++ b/lib/handlebars_assets/server.rb @@ -0,0 +1,29 @@ +require 'handlebars_assets' + +%w(responder controller handlers/handlebars extensions).each do |lib| + require File.join('handlebars_assets', 'server', lib) +end + +module HandlebarsAssets + module Server + extend self + HANDLER = HandlebarsAssets::Server::Handlers::Handlebars + + def self.register_template_handlers + Config.handlebars_extensions.each do |ext| + ActionView::Template.register_template_handler(ext, HANDLER) + end + end + + def self.register_mime_types + Config.handlebars_extensions.each do |ext| + Mime::Type.register_alias 'text/html', ext.to_sym + end + end + end +end + +if defined?(Rails) + ::HandlebarsAssets::Server.register_template_handlers + ::HandlebarsAssets::Server.register_mime_types +end diff --git a/lib/handlebars_assets/server/controller.rb b/lib/handlebars_assets/server/controller.rb new file mode 100644 index 0000000..43aef90 --- /dev/null +++ b/lib/handlebars_assets/server/controller.rb @@ -0,0 +1,61 @@ +module HandlebarsAssets + module Server + # Prepare a controller to use respond_with + # to render a Handlebars template (for GET requests, currently) + # + # You must require 'handlebar_assets/server' + # in your controller, as it is not loaded by default. + # + # require 'handlebars_assets/server' + # + # class MyApp < ActionController::Base + # include HandlebarsAssets::Server::Controller + # + # respond_to :hbs # or :handlebars + # + # def show + # @bike = Bike.find(params[:id]) + # render_with(@bike) # Look ma, no 'to_hbs'! + # end + # end + # + # Your class must define #to_hbs (or #to_handlebars) + # that returns a Hash of JSON-compatible objects. + # + # You can specify the location of your templates: + # + # handlebars_templates path: 'app/assets/...' + # + # -or- + # + # handlebars_templates paths: ['array', 'of', 'paths'] + # + # 'app/assets/javascripts/templates' is prepended on + # include, so you do not need to add this line + # if you keep your templates in that directory + # + # If you support other mime types (like JSON), in the same + # controller, you'll need to give your template a 'js' or + # 'jst' format in order to keep Rails from trying to use + # the view for everything: + # + # show.js.hbs # *not* show.hbs or show.html.hbs + # + module Controller + extend ActiveSupport::Concern + + DEFAULT_HBS_TEMPLATE_PATH = 'app/assets/javascripts/templates' + + included do + self.responder = HandlebarsAssets::Server::Responder + + def self.handlebars_templates(options = {}) + paths = options[:paths] || options[:path] || [] + Array(paths).reverse_each { |p| prepend_view_path(p) } + end + + handlebars_templates path: DEFAULT_HBS_TEMPLATE_PATH + end + end + end +end diff --git a/lib/handlebars_assets/server/extensions.rb b/lib/handlebars_assets/server/extensions.rb new file mode 100644 index 0000000..286e64b --- /dev/null +++ b/lib/handlebars_assets/server/extensions.rb @@ -0,0 +1,30 @@ +module HandlebarsAssets + module Server + module Extensions + def context_for(template, extra = '') + tmpl = "var template = Handlebars.template(#{precompile(template)})" + context = [runtime, extra, tmpl].join('; ') + + ExecJS.compile(context) + end + + def render(template, *args) + locals = args.last.is_a?(Hash) ? args.pop : {} + extra = args.first.to_s + context_for(template, extra).call('template', locals) + end + + protected + + def runtime + @runtime ||= runtime_path.read + end + + def runtime_path + @runtime_path ||= assets_path.join(HandlebarsAssets::Config.compiler_runtime) + end + end + end +end + +::HandlebarsAssets::Handlebars.send(:extend, HandlebarsAssets::Server::Extensions) diff --git a/lib/handlebars_assets/server/handlers/handlebars.rb b/lib/handlebars_assets/server/handlers/handlebars.rb new file mode 100644 index 0000000..ab9dd1f --- /dev/null +++ b/lib/handlebars_assets/server/handlers/handlebars.rb @@ -0,0 +1,31 @@ +module HandlebarsAssets + module Server + module Handlers + class Handlebars + def self.call(template) + new.call(template) + end + + def call(template) + if template.locals.blank? + "#{template.source.inspect}.html_safe" + else + <<-HBS + variable_names = controller.instance_variable_names + variable_names -= %w[@template] + if controller.respond_to?(:protected_instance_variables) + variable_names -= controller.protected_instance_variables + end + variable_names.reject! { |name| name.starts_with? '@_' } + + variables = variable_names.inject({}) { |acc,name| acc[name.sub(/^@/, "")] = controller.instance_variable_get(name); acc } + variables.merge!(local_assigns) + + HandlebarsAssets::Handlebars.render(#{template.source.inspect}, variables).html_safe + HBS + end + end + end + end + end +end diff --git a/lib/handlebars_assets/server/responder.rb b/lib/handlebars_assets/server/responder.rb new file mode 100644 index 0000000..be06570 --- /dev/null +++ b/lib/handlebars_assets/server/responder.rb @@ -0,0 +1,44 @@ +module HandlebarsAssets + module Server + # Provides the requisite #to_{format} methods a Responder needs + # to handle respond_with + class Responder < ActionController::Responder + # Ask the resource to give a HandleBars-compatible + # representation and then passes it back to + # render with the appropriate new options. + # + # Add the HBS-compatible version of the resource + # to the locals hash, and ensure [:js, :jst] is in + # the list of requested formats. + def to_handlebars + display resource, resource_options if resourceful? + end + alias_method :to_hbs, :to_handlebars + + private + + # Aggregate the options for displaying this resource + # and return the new Hash. + def resource_options + { locals: resource_locals, formats: resource_formats } + end + + # Merge any user-supplied locals with the formatted resource + # and return the Hash + def resource_locals + (options.delete(:locals) || {}).merge!(resource.send(:"to_#{format}")) + end + + # Merge any user-supplied formats with our + # supported format(s) and return the new Array. + def resource_formats + supported_formats | (options.delete(:formats) || []) + end + + # :nodoc: + def supported_formats + [:js, :jst] + end + end + end +end