If anyone has a better way of achieving the same desired affect, with a different structure, I'm perfectly happy abandoning this design.
My motivation here is to create an inherited javascript widget template system for use in Rails 3. I want javascript widgets a user can place on their page and have a variety of pre-defined template that they can choose from.
Ideally, each template will have "base" javascript code, and any customizations can be layered on through a set of child templates (thus making a skin/new functionality for the template).
This is complicated in that I want to use multiple controllers for the same templates.
Controllers
Folder Tree- app
- controllers
- widgets
- modules
- activities
- content
- images
- communities
- modules
- activities
- content
- images
- users
- modules
- activities
- content
- images
- widgets
- controllers
Views
Just a note on views, in each "template" directory, i've placed a file called config.rb. This file defines a templates parent template name and folder location. I'll show how this loads up in the template resolver.
Examples inheritance config files
located in app/views/widgets/communities/modules/template_custom_2/config.rb
module InheritedTemplateConfig
def inherited_parent_base_path
"widgets/communities/modules"
end
def inherited_parent_template_name
"template_custom_1"
end
end
located in app/views/widgets/communities/modules/template_custom_1/config.rb
module InheritedTemplateConfig
def inherited_parent_base_path
"widgets/communities/modules"
end
def inherited_parent_template_name
"template"
end
end
located in app/views/widgets/communities/modules/template/config.rb
module InheritedTemplateConfig
def inherited_parent_base_path
"widgets/communities"
end
def inherited_parent_template_name
"template"
end
end
Example template files
located in app/views/widgets/communities/modules/template_custom_2/modules.js.erb
<%= render :partial => "(TEMPLATE)/same_template_test" %>
<%= render :partial => "(PARENT_TEMPLATE)/parent_template_test" %>
Folder Tree
- app
- views
- widgets
- modules
- template
- template_custom_1
- template_custom_2
- activities
- template
- content
- template
- images
- template
- communities
- template
- modules
- template
- template_custom_a
- template_custom_b
- activities
- template
- content
- template
- images
- template
- users
- template
- modules
- template
- activities
- template
- content
- template
- images
- template
- modules
- widgets
- views
So, a few path examples and where I would expect the template chain of folders to resolve.
- /widgets/modules.js
- /widgets/modules/template
- /widgets/communities/1/modules.js?template=custom_a
- /widgets/communities/modules/template_custom_a
- /widgets/communities/modules/template
- /widgets/modules/template
My Failed Implementation
Just want to say, this is so close to working. Unfortunately, I have had 0 luck in the resolvers (PARENT_TEMPLATE) find_template function case, it recurses infinitely. This is because I have no way of knowing which template to skip when resolving.
The only thing that doesn't work is rendering a partial using <% render :partial => "(PARENT_TEMPLATE)/my_file" %>
class Widgets::Communities::ModulesController < ApplicationController
before_filter do
@community = Community.find(params[:community_id])
@template = params[:template].to_s
@template = "_#{@template}" if !@template.empty?
# Path to start searching for template files
prepend_view_path WidgetResolver.new("widgets/communities/modules", "template#{@template}")
end
def script
respond_to do |format|
format.js { render "modules" }
end
end
end
# Walks through from the sub_path to the base_path to find the template file
# This allows you to base one template on another template
class WigetResolver < ActionView::FileSystemResolver
attr_accessor :templates, :base_path
# Loads our current templates config, and any parent templates config
def load_template_config(base_path, template_name)
config_path = "#{Rails.root}/app/views/#{base_path}/#{template_name}/config.rb"
if File.exists?(config_path)
self.templates << {:template_name => template_name, :base_path => base_path} # This is a valid template, add it to our search path
load(config_path)
extend InheritedTemplateConfig
if !self.inherited_parent_base_path.nil? && !self.inherited_parent_template_name.nil?
self.load_template_config(self.inherited_parent_base_path, self.inherited_parent_template_name)
end
end
end
def initialize(base_path, template_name)
self.templates = []
# From our base config, load our complete config
self.load_template_config(base_path, template_name)
super("app/views")
end
def find_templates(name, prefix, partial, details)
# We want to use our custom template matching
if prefix.match(/^(TEMPLATE)/)
self.templates.each_with_index { |template,i|
result = super(name, "#{template[:base_path]}/#{template[:template_name]}", partial, details)
# We found the custom template path
return result if result.present?
}
# ----------------------------------------------------
# ERROR HERE - recurses to max depth
# ----------------------------------------------------
elsif prefix.match(/^(PARENT_TEMPLATE)/)
self.templates.each_with_index { |template,i|
# We need to skip template above the current partials template file path - HOW DO WE DO THIS?!
result = super(name, "#{template[:base_path]}/#{template[:template_name]}", partial, details)
# We found the custom template path
return result if result.present?
}
end
super(name, prefix, partial, details)
end
end
So - the question. How can I know what the current partials working directory is in find_templates? If I knew this, I could skip all templates above it, and never get the infinite recurse.