zafu 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/History.txt +9 -0
- data/README.rdoc +56 -0
- data/Rakefile +56 -0
- data/lib/zafu/all.rb +17 -0
- data/lib/zafu/compiler.rb +7 -0
- data/lib/zafu/controller_methods.rb +49 -0
- data/lib/zafu/handler.rb +57 -0
- data/lib/zafu/info.rb +4 -0
- data/lib/zafu/markup.rb +186 -0
- data/lib/zafu/mock_helper.rb +42 -0
- data/lib/zafu/node_context.rb +96 -0
- data/lib/zafu/parser.rb +564 -0
- data/lib/zafu/parsing_rules.rb +257 -0
- data/lib/zafu/process/ajax.rb +90 -0
- data/lib/zafu/process/conditional.rb +45 -0
- data/lib/zafu/process/context.rb +45 -0
- data/lib/zafu/process/html.rb +168 -0
- data/lib/zafu/process/ruby_less.rb +145 -0
- data/lib/zafu/template.rb +25 -0
- data/lib/zafu/test_helper.rb +19 -0
- data/lib/zafu.rb +7 -0
- data/rails/init.rb +1 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/markup_test.rb +232 -0
- data/test/mock/params.rb +19 -0
- data/test/node_context_test.rb +190 -0
- data/test/ruby_less_test.rb +37 -0
- data/test/test_helper.rb +9 -0
- data/test/zafu_test.rb +57 -0
- data/zafu.gemspec +83 -0
- metadata +124 -0
data/.gitignore
ADDED
data/History.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
== 0.5.0 2010-03-21
|
2
|
+
|
3
|
+
* 6 major enhancement
|
4
|
+
* Initial release.
|
5
|
+
* Uses helpers to resolve methods.
|
6
|
+
* Compiles 'html' with rubyless declarations.
|
7
|
+
* Guesses main ivar from view.
|
8
|
+
* Tries to resolve method by adding current node parameter.
|
9
|
+
* Added basic conditional execution with 'else' and dummy 'if' clauses.
|
data/README.rdoc
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= zafu
|
2
|
+
|
3
|
+
* http://zenadmin.org/en/zafu
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Provides a powerful templating language based on xhtml for rails.
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* The current implementation of zafu is in zena's repository (http://zenadmin.org).
|
12
|
+
The content will be moved here as soon as zafu works as a standalone gem.
|
13
|
+
|
14
|
+
== SYNOPSIS:
|
15
|
+
|
16
|
+
<ul do='images where name like "%flower%" in site'>
|
17
|
+
<li do='each'>
|
18
|
+
<r:img/>
|
19
|
+
<r:link/>
|
20
|
+
</li>
|
21
|
+
</ul>
|
22
|
+
|
23
|
+
== REQUIREMENTS:
|
24
|
+
|
25
|
+
* FIX (not very clear yet)
|
26
|
+
* yamltest
|
27
|
+
|
28
|
+
== INSTALL:
|
29
|
+
|
30
|
+
* !! not ready for deployment, please do not install !!
|
31
|
+
* sudo gem install zafu
|
32
|
+
|
33
|
+
== LICENSE:
|
34
|
+
|
35
|
+
(The MIT License)
|
36
|
+
|
37
|
+
Copyright (c) 2007-2009 Gaspard Bucher
|
38
|
+
|
39
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
40
|
+
a copy of this software and associated documentation files (the
|
41
|
+
'Software'), to deal in the Software without restriction, including
|
42
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
43
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
44
|
+
permit persons to whom the Software is furnished to do so, subject to
|
45
|
+
the following conditions:
|
46
|
+
|
47
|
+
The above copyright notice and this permission notice shall be
|
48
|
+
included in all copies or substantial portions of the Software.
|
49
|
+
|
50
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
51
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
52
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
53
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
54
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
55
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
56
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require(File.join(File.dirname(__FILE__), 'lib/zafu/info'))
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.version = Zafu::VERSION
|
9
|
+
gem.name = 'zafu'
|
10
|
+
gem.summary = %Q{Provides a powerful templating language based on xhtml for rails}
|
11
|
+
gem.description = %Q{Provides a powerful templating language based on xhtml for rails}
|
12
|
+
gem.email = "gaspard@teti.ch"
|
13
|
+
gem.homepage = "http://zenadmin.org/zafu"
|
14
|
+
gem.authors = ["Gaspard Bucher"]
|
15
|
+
gem.add_development_dependency "shoulda", ">= 0"
|
16
|
+
gem.add_dependency "rubyless", ">= 0.4.0"
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
Rake::TestTask.new(:test) do |test|
|
26
|
+
test.libs << 'lib' << 'test'
|
27
|
+
test.pattern = 'test/**/*_test.rb'
|
28
|
+
test.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
begin
|
32
|
+
require 'rcov/rcovtask'
|
33
|
+
Rcov::RcovTask.new do |test|
|
34
|
+
test.libs << 'test'
|
35
|
+
test.pattern = 'test/**/*_test.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
task :rcov do
|
40
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
task :test => :check_dependencies
|
45
|
+
|
46
|
+
task :default => :test
|
47
|
+
|
48
|
+
require 'rake/rdoctask'
|
49
|
+
Rake::RDocTask.new do |rdoc|
|
50
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "zafu #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
data/lib/zafu/all.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'zafu/parsing_rules'
|
2
|
+
# require 'zafu/process/ajax'
|
3
|
+
require 'zafu/process/html'
|
4
|
+
require 'zafu/process/ruby_less'
|
5
|
+
require 'zafu/process/context'
|
6
|
+
require 'zafu/process/conditional'
|
7
|
+
|
8
|
+
module Zafu
|
9
|
+
All = [
|
10
|
+
Zafu::ParsingRules,
|
11
|
+
# Zafu::Process::Ajax,
|
12
|
+
Zafu::Process::HTML,
|
13
|
+
Zafu::Process::Context,
|
14
|
+
Zafu::Process::Conditional,
|
15
|
+
Zafu::Process::RubyLess
|
16
|
+
]
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Zafu
|
2
|
+
module ControllerMethods
|
3
|
+
def self.included(base)
|
4
|
+
base.helper_method :zafu_context, :get_template_text, :template_url_for_asset
|
5
|
+
if RAILS_ENV == 'development'
|
6
|
+
base.class_eval do
|
7
|
+
def render_for_file_with_rebuild(template_path, status = nil, layout = nil, locals = {}) #:nodoc:
|
8
|
+
path = template_path.respond_to?(:path_without_format_and_extension) ? template_path.path_without_format_and_extension : template_path
|
9
|
+
logger.info("Rendering #{path}" + (status ? " (#{status})" : '')) if logger
|
10
|
+
# if params[:rebuild] == 'true'
|
11
|
+
t = self.view_paths.find_template(template_path, 'html')
|
12
|
+
t.previously_last_modified = nil
|
13
|
+
# end
|
14
|
+
render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status
|
15
|
+
rescue => err
|
16
|
+
puts err.backtrace.join("\n")
|
17
|
+
end
|
18
|
+
alias_method_chain :render_for_file, :rebuild
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def zafu_node(name, klass)
|
24
|
+
zafu_context[:node] = Zafu::NodeContext.new(name, klass)
|
25
|
+
end
|
26
|
+
|
27
|
+
def zafu_context
|
28
|
+
@zafu_context ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method should return the template for a given 'src' and
|
32
|
+
# 'base_path'.
|
33
|
+
def get_template_text(path, base_path)
|
34
|
+
[path, "#{base_path}/#{path}"].each do |p|
|
35
|
+
begin
|
36
|
+
t = self.view_paths.find_template(p, 'html') # FIXME: format ?
|
37
|
+
rescue ActionView::MissingTemplate
|
38
|
+
t = nil
|
39
|
+
end
|
40
|
+
return [t.source, t.path, t.base_path] if t
|
41
|
+
end
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def template_url_for_asset(opts)
|
46
|
+
opts[:src]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/zafu/handler.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'zafu/parser'
|
2
|
+
require 'zafu/markup'
|
3
|
+
require 'zafu/node_context'
|
4
|
+
require 'zafu/template'
|
5
|
+
|
6
|
+
module Zafu
|
7
|
+
class Handler < ActionView::TemplateHandler
|
8
|
+
include ActionView::TemplateHandlers::Compilable
|
9
|
+
|
10
|
+
def compile(template)
|
11
|
+
@template = template
|
12
|
+
helper = Thread.current[:view]
|
13
|
+
if !helper.respond_to?(:zafu_context)
|
14
|
+
raise Exception.new("Please add \"include Zafu::ControllerMethods\" into your ApplicationController for zafu to work properly.")
|
15
|
+
end
|
16
|
+
ast = Zafu::Template.new(template, self)
|
17
|
+
context = helper.zafu_context.merge(:helper => helper)
|
18
|
+
context[:node] ||= get_zafu_node_from_view(helper)
|
19
|
+
rb = ast.to_ruby('@output_buffer', context)
|
20
|
+
";@erb = %q{#{ast.to_erb(context)}};#{rb}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_template_text(path, base_path)
|
24
|
+
if path == @template.path && base_path.nil?
|
25
|
+
[@template.source, @template.path, @template.base_path]
|
26
|
+
else
|
27
|
+
Thread.current[:view].get_template_text(path, base_path)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def get_zafu_node_from_view(view)
|
33
|
+
controller = view.controller
|
34
|
+
if controller.class.to_s =~ /\A([A-Z]\w+?)s?[A-Z]/
|
35
|
+
ivar = "@#{$1.downcase}"
|
36
|
+
if var = controller.instance_variable_get(ivar.to_sym)
|
37
|
+
name = ivar
|
38
|
+
klass = var.class
|
39
|
+
elsif var = controller.instance_variable_get(ivar + 's')
|
40
|
+
name = ivar + 's'
|
41
|
+
klass = [var.first.class]
|
42
|
+
end
|
43
|
+
return Zafu::NodeContext.new(name, klass) if name
|
44
|
+
end
|
45
|
+
raise Exception.new("Could not guess main instance variable from request parameters, please add something like \"zafu_node('@var_name', Page)\" in your action.")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class ActionView::Template
|
51
|
+
attr_reader :view
|
52
|
+
def render_template_with_zafu(view, local_assigns = {})
|
53
|
+
Thread.current[:view] = view
|
54
|
+
render_template_without_zafu(view, local_assigns)
|
55
|
+
end
|
56
|
+
alias_method_chain :render_template, :zafu
|
57
|
+
end
|
data/lib/zafu/info.rb
ADDED
data/lib/zafu/markup.rb
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
module Zafu
|
2
|
+
# A Markup object is used to hold information on the tag used (<li>), it's parameters (.. class='xxx') and
|
3
|
+
# indentation.
|
4
|
+
class Markup
|
5
|
+
EMPTY_TAGS = %w{meta input}
|
6
|
+
STEAL_PARAMS = [:class, :id, :style]
|
7
|
+
|
8
|
+
# Tag used ("li" for example). The tag can be nil (no tag).
|
9
|
+
attr_accessor :tag
|
10
|
+
# Tag parameters (.. class='xxx' id='yyy')
|
11
|
+
attr_accessor :params
|
12
|
+
# Dynamic tag parameters that should not be escaped. For example: (.. class='<%= @node.klass %>')
|
13
|
+
attr_accessor :dyn_params
|
14
|
+
# Ensure wrap is not called more then once unless this attribute has been reset in between
|
15
|
+
attr_accessor :done
|
16
|
+
# Space to insert before tag
|
17
|
+
attr_accessor :space_before
|
18
|
+
# Space to insert after tag
|
19
|
+
attr_accessor :space_after
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
# Parse parameters into a hash. This parsing supports multiple values for one key by creating additional keys:
|
24
|
+
# <tag do='hello' or='goodbye' or='gotohell'> creates the hash {:do=>'hello', :or=>'goodbye', :or1=>'gotohell'}
|
25
|
+
def parse_params(text)
|
26
|
+
return {} unless text
|
27
|
+
return text if text.kind_of?(Hash)
|
28
|
+
params = {}
|
29
|
+
rest = text.strip
|
30
|
+
while (rest != '')
|
31
|
+
if rest =~ /(.+?)=/
|
32
|
+
key = $1.strip.to_sym
|
33
|
+
rest = rest[$&.length..-1].strip
|
34
|
+
if rest =~ /('|")(|[^\1]*?[^\\])\1/
|
35
|
+
rest = rest[$&.length..-1].strip
|
36
|
+
key_counter = 1
|
37
|
+
while params[key]
|
38
|
+
key = "#{key}#{key_counter}".to_sym
|
39
|
+
key_counter += 1
|
40
|
+
end
|
41
|
+
|
42
|
+
if $1 == "'"
|
43
|
+
params[key] = $2.gsub("\\'", "'")
|
44
|
+
else
|
45
|
+
params[key] = $2.gsub('\\"', '"')
|
46
|
+
end
|
47
|
+
else
|
48
|
+
# error, bad format, return found params.
|
49
|
+
break
|
50
|
+
end
|
51
|
+
else
|
52
|
+
# error, bad format
|
53
|
+
break
|
54
|
+
end
|
55
|
+
end
|
56
|
+
params
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize(tag)
|
61
|
+
@done = false
|
62
|
+
@tag = tag
|
63
|
+
@params = {}
|
64
|
+
@dyn_params = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
# Set params either using a string (like "alt='time passes' class='zen'")
|
68
|
+
def params=(p)
|
69
|
+
if p.kind_of?(Hash)
|
70
|
+
@params = p
|
71
|
+
else
|
72
|
+
@params = Markup.parse_params(p)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Steal html parameters from an existing hash (the stolen parameters are removed
|
77
|
+
# from the argument)
|
78
|
+
def steal_html_params_from(p)
|
79
|
+
@params ||= {}
|
80
|
+
STEAL_PARAMS.each do |k|
|
81
|
+
next unless p[k]
|
82
|
+
@params[k] = p.delete(k)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Compile dynamic parameters as ERB. A parameter is considered dynamic if it
|
87
|
+
# contains the string eval "#{...}"
|
88
|
+
def compile_params(helper)
|
89
|
+
@params.each do |key, value|
|
90
|
+
if value =~ /^(.*)\#\{(.*)\}(.*)$/
|
91
|
+
@params.delete(key)
|
92
|
+
if $1 == '' && $3 == ''
|
93
|
+
append_dyn_param(key, "<%= #{RubyLess.translate($2, helper)} %>")
|
94
|
+
else
|
95
|
+
append_dyn_param(key, "<%= #{RubyLess.translate_string(value, helper)} %>")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Set dynamic html parameters.
|
102
|
+
def set_dyn_params(hash)
|
103
|
+
hash.keys.each do |k|
|
104
|
+
@params.delete(k)
|
105
|
+
end
|
106
|
+
@dyn_params.merge!(hash)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Set static html parameters.
|
110
|
+
def set_params(hash)
|
111
|
+
hash.keys.each do |k|
|
112
|
+
@dyn_params.delete(k)
|
113
|
+
end
|
114
|
+
@params.merge!(hash)
|
115
|
+
end
|
116
|
+
|
117
|
+
def append_param(key, value)
|
118
|
+
if prev_value = @dyn_params[key]
|
119
|
+
@dyn_params[key] = "#{prev_value} #{value}"
|
120
|
+
elsif prev_value = @params[key]
|
121
|
+
@params[key] = "#{prev_value} #{value}"
|
122
|
+
else
|
123
|
+
@params[key] = value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def append_dyn_param(key, value)
|
128
|
+
if prev_value = @params.delete(key)
|
129
|
+
@dyn_params[key] = "#{prev_value} #{value}"
|
130
|
+
elsif prev_value = @dyn_params[key]
|
131
|
+
@dyn_params[key] = "#{prev_value} #{value}"
|
132
|
+
else
|
133
|
+
@dyn_params[key] = value
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Define the DOM id from a node context
|
138
|
+
def set_id(erb_id)
|
139
|
+
params[:id] = nil
|
140
|
+
dyn_params[:id] = erb_id
|
141
|
+
end
|
142
|
+
|
143
|
+
# Wrap the given text with our tag. If 'append' is not empty, append the text
|
144
|
+
# after the tag parameters: <li class='foo'[APPEND HERE]>text</li>.
|
145
|
+
def wrap(text, *append)
|
146
|
+
return text if @done
|
147
|
+
append ||= []
|
148
|
+
if @tag
|
149
|
+
if text.blank? && EMPTY_TAGS.include?(@tag)
|
150
|
+
res = "<#{@tag}#{params_to_html}#{append.join('')}/>"
|
151
|
+
else
|
152
|
+
res = "<#{@tag}#{params_to_html}#{append.join('')}>#{text}</#{@tag}>"
|
153
|
+
end
|
154
|
+
else
|
155
|
+
res = text
|
156
|
+
end
|
157
|
+
@done = true
|
158
|
+
|
159
|
+
(@space_before || '') + res + (@space_after || '')
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def params_to_html
|
164
|
+
para = []
|
165
|
+
keys = []
|
166
|
+
|
167
|
+
@dyn_params.each do |k,v|
|
168
|
+
keys << k
|
169
|
+
para << " #{k}='#{v}'"
|
170
|
+
end
|
171
|
+
|
172
|
+
@params.each do |k,v|
|
173
|
+
next if keys.include?(k)
|
174
|
+
|
175
|
+
if !v.to_s.include?("'")
|
176
|
+
para << " #{k}='#{v}'"
|
177
|
+
else
|
178
|
+
para << " #{k}=\"#{v.to_s.gsub('"','\"')}\"" # TODO: do this work in all cases ?
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# we sort so that the output is always the same (needed for testing)
|
183
|
+
para.sort.join('')
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Zafu
|
2
|
+
# This is the 'src_helper' used when none is provided. Its main purpose is to provide some information
|
3
|
+
# during testing.
|
4
|
+
class MockHelper
|
5
|
+
def initialize(strings = {})
|
6
|
+
@strings = strings
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_template_text(opts)
|
10
|
+
src = opts[:src]
|
11
|
+
folder = (opts[:base_path] && opts[:base_path] != '') ? opts[:base_path][1..-1].split('/') : []
|
12
|
+
src = src[1..-1] if src[0..0] == '/' # just ignore the 'relative' or 'absolute' tricks.
|
13
|
+
url = (folder + src.split('/')).join('_')
|
14
|
+
if test = @strings[url]
|
15
|
+
return [test['src'], url.split('_').join('/')]
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def template_url_for_asset(opts)
|
22
|
+
"/test_#{opts[:type]}/#{opts[:src]}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(sym, *args)
|
26
|
+
arguments = args.map do |arg|
|
27
|
+
if arg.kind_of?(Hash)
|
28
|
+
res = []
|
29
|
+
arg.each do |k,v|
|
30
|
+
unless v.nil?
|
31
|
+
res << "#{k}:#{v.inspect.gsub(/'|"/, "|")}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
res.sort.join(' ')
|
35
|
+
else
|
36
|
+
arg.inspect.gsub(/'|"/, "|")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
res = "[#{sym} #{arguments.join(' ')}]"
|
40
|
+
end
|
41
|
+
end # DummyHelper
|
42
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Zafu
|
2
|
+
class NodeContext
|
3
|
+
# The name of the variable halding the current object or list ("@node", "var1")
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
# The type of object contained in the current context (Node, Page, Image)
|
7
|
+
attr_reader :klass
|
8
|
+
|
9
|
+
# The current DOM prefix to use when building DOM ids. This is set by the parser when
|
10
|
+
# it has a name or dom id defined ('main', 'related', 'list', etc).
|
11
|
+
attr_writer :dom_prefix
|
12
|
+
|
13
|
+
def initialize(name, klass, up = nil)
|
14
|
+
@name, @klass, @up = name, klass, up
|
15
|
+
end
|
16
|
+
|
17
|
+
def move_to(name, klass)
|
18
|
+
NodeContext.new(name, klass, self)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Since the idiom to write the node context name is the main purpose of this class, it
|
22
|
+
# deserves this shortcut.
|
23
|
+
def to_s
|
24
|
+
name
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return true if the NodeContext represents an element of the given type. We use 'will_be' because
|
28
|
+
# it is equivalent to 'is_a', but for future objects (during rendering).
|
29
|
+
def will_be?(type)
|
30
|
+
klass.ancestors.include?(type)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return a new node context that corresponds to the current object when rendered alone (in an ajax response or
|
34
|
+
# from a direct 'show' in a controller). The returned node context has no parent (up is nil).
|
35
|
+
# The convention is to use the class of the current object to build this name.
|
36
|
+
def as_main
|
37
|
+
NodeContext.new("@#{klass.to_s.underscore}", klass)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Generate a unique DOM id for this element based on dom_scopes defined in parent contexts.
|
41
|
+
def dom_id
|
42
|
+
@dom_id ||= begin
|
43
|
+
if @up
|
44
|
+
[dom_prefix] + @up.dom_scopes + [make_scope_id]
|
45
|
+
else
|
46
|
+
[dom_prefix] + [make_scope_id]
|
47
|
+
end.compact.uniq.join('_')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# This holds the current context's unique name if it has it's own or one from the hierarchy. If
|
52
|
+
# none is found, it builds one... How ?
|
53
|
+
def dom_prefix
|
54
|
+
@dom_prefix || (@up ? @up.dom_prefix : nil)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Mark the current context as being a looping element (each) whose DOM id needs to be propagated to sub-nodes
|
58
|
+
# in order to ensure uniqueness of the dom_id (loops in loops problem).
|
59
|
+
def dom_scope!
|
60
|
+
@dom_scope = true
|
61
|
+
end
|
62
|
+
|
63
|
+
def get(klass)
|
64
|
+
if list_context?
|
65
|
+
if self.klass.first.ancestors.include?(klass)
|
66
|
+
NodeContext.new("#{self.name}.first", self.klass.first)
|
67
|
+
elsif @up
|
68
|
+
@up.get(klass)
|
69
|
+
else
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
elsif self.klass.ancestors.include?(klass)
|
73
|
+
return self
|
74
|
+
elsif @up
|
75
|
+
@up.get(klass)
|
76
|
+
else
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def list_context?
|
82
|
+
klass.kind_of?(Array)
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
# List of scopes defined in ancestry (used to generate dom_id).
|
87
|
+
def dom_scopes
|
88
|
+
(@up ? @up.dom_scopes : []) + (@dom_scope ? [make_scope_id] : [])
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
def make_scope_id
|
93
|
+
"<%= #{@name}.zip %>"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|