htmless 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +19 -0
- data/README.md +7 -0
- data/README_FULL.md +585 -0
- data/lib/copy_to_doc.rb +43 -0
- data/lib/htmless/abstract/abstract_double_tag.rb +144 -0
- data/lib/htmless/abstract/abstract_single_tag.rb +18 -0
- data/lib/htmless/abstract/abstract_tag.rb +246 -0
- data/lib/htmless/abstract.rb +252 -0
- data/lib/htmless/data/html5.rb +179 -0
- data/lib/htmless/data.rb +11 -0
- data/lib/htmless/doc.rb +394 -0
- data/lib/htmless/dynamic_classes.rb +216 -0
- data/lib/htmless/formatted.rb +43 -0
- data/lib/htmless/helper.rb +24 -0
- data/lib/htmless/pool.rb +72 -0
- data/lib/htmless/rails.rb +40 -0
- data/lib/htmless/standard.rb +48 -0
- data/lib/htmless/strings_injector.rb +43 -0
- data/lib/htmless.rb +3 -0
- data/lib/js.rb +176 -0
- data/spec/htmless_spec.rb +290 -0
- metadata +279 -0
@@ -0,0 +1,216 @@
|
|
1
|
+
module Htmless
|
2
|
+
|
3
|
+
# When extended into a class it enables easy defining and extending classes in the class.
|
4
|
+
#
|
5
|
+
# class A
|
6
|
+
# extend DynamicClasses
|
7
|
+
# dynamic_classes do
|
8
|
+
# def_class :A do
|
9
|
+
# def to_s
|
10
|
+
# 'a'
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
# def_class :B, :A do
|
14
|
+
# class_eval <<-RUBYCODE, __FILE__, __LINE__+1
|
15
|
+
# def to_s
|
16
|
+
# super + 'b'
|
17
|
+
# end
|
18
|
+
# RUBYCODE
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# class B < A
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# class C < A
|
27
|
+
# dynamic_classes do
|
28
|
+
# extend_class :A do
|
29
|
+
# def to_s
|
30
|
+
# 'aa'
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# puts A.dc[:A] # => #<Class:0x00000001d449b8(A.dc[:A])>
|
37
|
+
# puts B.dc[:A] # => #<Class:0x00000001d42398(B.dc[:A])>
|
38
|
+
# puts B.dc[:A].new # => a
|
39
|
+
# puts B.dc[:B].new # => ab
|
40
|
+
# puts C.dc[:B].new # => aab
|
41
|
+
#
|
42
|
+
# Last example is the most interesting. It prints 'aab' not 'ab' because of the extension in class C. Class :B has
|
43
|
+
# as ancestor extended class :A from C therefore the two 'a'.
|
44
|
+
module DynamicClasses
|
45
|
+
|
46
|
+
# Adds ability to describe itself when class is defined without constant
|
47
|
+
module Describable
|
48
|
+
def self.included(base)
|
49
|
+
base.singleton_class.send :alias_method, :original_to_s, :to_s
|
50
|
+
base.extend ClassMethods
|
51
|
+
end
|
52
|
+
|
53
|
+
module ClassMethods
|
54
|
+
# sets +description+
|
55
|
+
# @param [String] description
|
56
|
+
def _description=(description)
|
57
|
+
@_description = description
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
super.gsub(/>$/, "(#{@_description})>")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
klass = respond_to?(:rclass) ? self.rclass : self.class
|
67
|
+
super.gsub(klass.original_to_s, klass.to_s)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class DescribableClass
|
72
|
+
include Describable
|
73
|
+
end
|
74
|
+
|
75
|
+
ClassDefinition = Struct.new(:name, :base, :superclass_or_name, :definition)
|
76
|
+
ClassExtension = Struct.new(:name, :base, :definition)
|
77
|
+
|
78
|
+
class Classes
|
79
|
+
attr_reader :base, :class_definitions, :classes, :class_extensions
|
80
|
+
|
81
|
+
def initialize(base)
|
82
|
+
raise unless base.is_a? Class
|
83
|
+
@base = base
|
84
|
+
@class_definitions = { }
|
85
|
+
@class_extensions = { }
|
86
|
+
@classes = { }
|
87
|
+
end
|
88
|
+
|
89
|
+
# define a class
|
90
|
+
# @param [Symbol] name
|
91
|
+
# @param [Symbol, Class, nil] superclass_or_name
|
92
|
+
# when Symbol then dynamic class is found
|
93
|
+
# when Class then this class is used
|
94
|
+
# when nil then Object is used
|
95
|
+
# @yield definition block is evaluated inside the class defining it
|
96
|
+
def def_class(name, superclass_or_name = nil, &definition)
|
97
|
+
raise ArgumentError, "name is not a Symbol" unless name.is_a?(Symbol)
|
98
|
+
unless superclass_or_name.is_a?(Symbol) || superclass_or_name.is_a?(Class) || superclass_or_name.nil?
|
99
|
+
raise ArgumentError, "superclass_or_name is not a Symbol, Class or nil"
|
100
|
+
end
|
101
|
+
raise ArgumentError, "definition is nil" unless definition
|
102
|
+
raise ArgumentError, "Class #{name} already defined" if class_definition(name)
|
103
|
+
@class_definitions[name] = ClassDefinition.new(name, base, superclass_or_name, definition)
|
104
|
+
end
|
105
|
+
|
106
|
+
# extends already defined class by adding a child,
|
107
|
+
# @param [Symbol] name
|
108
|
+
# @yield definition block is evaluated inside the class extending it
|
109
|
+
def extend_class(name, &definition)
|
110
|
+
raise ArgumentError, "name is not a Symbol" unless name.is_a?(Symbol)
|
111
|
+
raise ArgumentError, "definition is nil" unless definition
|
112
|
+
raise ArgumentError, "Class #{name} not defined" unless class_definition(name)
|
113
|
+
@class_extensions[name] = ClassExtension.new(name, base, definition)
|
114
|
+
end
|
115
|
+
|
116
|
+
# triggers loading of all defined classes
|
117
|
+
def load!
|
118
|
+
class_names.each { |name| self[name] }
|
119
|
+
end
|
120
|
+
|
121
|
+
# @return [Class] defined class
|
122
|
+
def [](name)
|
123
|
+
return @classes[name] if @classes[name]
|
124
|
+
return nil unless klass_definition = class_definition(name)
|
125
|
+
|
126
|
+
superclass = case klass_definition.superclass_or_name
|
127
|
+
when Symbol then
|
128
|
+
self[klass_definition.superclass_or_name]
|
129
|
+
when Class then
|
130
|
+
klass = Class.new(klass_definition.superclass_or_name)
|
131
|
+
klass.send :include, Describable
|
132
|
+
klass._description = "Describable#{klass_definition.superclass_or_name}"
|
133
|
+
klass
|
134
|
+
when nil then
|
135
|
+
DescribableClass
|
136
|
+
end
|
137
|
+
|
138
|
+
set_up_klass = lambda do |klass, description, block|
|
139
|
+
klass._description = description
|
140
|
+
klass.instance_variable_set :@dynamic_class_base, base
|
141
|
+
klass.singleton_class.send :attr_reader, :dynamic_class_base
|
142
|
+
klass.class_eval &block
|
143
|
+
end
|
144
|
+
|
145
|
+
klass = Class.new(superclass)
|
146
|
+
set_up_klass.call klass, "#{base}.dc[:#{klass_definition.name}]", klass_definition.definition
|
147
|
+
|
148
|
+
class_extensions(name).each do |klass_extension|
|
149
|
+
klass = Class.new klass
|
150
|
+
set_up_klass.call klass, "#{base}.dc[:#{klass_extension.name}]", klass_extension.definition
|
151
|
+
end
|
152
|
+
|
153
|
+
@classes[name] = klass
|
154
|
+
end
|
155
|
+
|
156
|
+
def class_names
|
157
|
+
ancestors.map(&:class_definitions).map(&:keys).flatten
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
def class_definition(name)
|
163
|
+
@class_definitions[name] or (ancestor.send :class_definition, name if ancestor)
|
164
|
+
end
|
165
|
+
|
166
|
+
def class_extensions(name)
|
167
|
+
([*(ancestor.send :class_extensions, name if ancestor)] + [@class_extensions[name]]).compact
|
168
|
+
end
|
169
|
+
|
170
|
+
def ancestors
|
171
|
+
([self] + [*(ancestor.ancestors if ancestor)]).compact
|
172
|
+
end
|
173
|
+
|
174
|
+
def ancestor
|
175
|
+
@base.superclass.dynamic_classes if @base.superclass.kind_of?(DynamicClasses)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# hook to create Classes instance
|
180
|
+
def self.extended(base)
|
181
|
+
base.send :create_dynamic_classes
|
182
|
+
super
|
183
|
+
end
|
184
|
+
|
185
|
+
# hook to create Classes instance in descendants
|
186
|
+
def inherited(base)
|
187
|
+
base.send :create_dynamic_classes
|
188
|
+
super
|
189
|
+
end
|
190
|
+
|
191
|
+
# call this to get access to Classes instance to define/extend classes inside +definition+
|
192
|
+
# calls Classes#load! to preload defined classes
|
193
|
+
# @yield [Proc, nil] definition
|
194
|
+
# a Proc enables writing class definitions/extensions
|
195
|
+
# @return [Classes] when definition is nil
|
196
|
+
def dynamic_classes(&definition)
|
197
|
+
if definition
|
198
|
+
@dynamic_classes.instance_eval &definition
|
199
|
+
# @dynamic_classes.load!
|
200
|
+
nil
|
201
|
+
else
|
202
|
+
@dynamic_classes
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
alias_method :dc, :dynamic_classes
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def create_dynamic_classes
|
211
|
+
@dynamic_classes = Classes.new(self)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'htmless/standard'
|
2
|
+
|
3
|
+
module Htmless
|
4
|
+
# Builder implementation with formatting (indented by ' ')
|
5
|
+
# Slow down is less then 1%
|
6
|
+
class Formatted < Standard
|
7
|
+
|
8
|
+
dynamic_classes do
|
9
|
+
extend_class :AbstractTag do
|
10
|
+
def open(attributes = nil)
|
11
|
+
@output << @_str_newline << @_str_spaces.fetch(@stack.size, @_str_space) << @_str_lt << @tag_name
|
12
|
+
@builder.current = self
|
13
|
+
attributes(attributes)
|
14
|
+
default
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
extend_class :AbstractDoubleTag do
|
20
|
+
def with
|
21
|
+
flush_classes
|
22
|
+
@output << @_str_gt
|
23
|
+
@content = nil
|
24
|
+
@builder.current = nil
|
25
|
+
yield
|
26
|
+
#if (content = yield).is_a?(String)
|
27
|
+
# @output << EscapeUtils.escape_html(content, false)
|
28
|
+
#end
|
29
|
+
@builder.flush
|
30
|
+
@output << @_str_newline << @_str_spaces.fetch(@stack.size-1, @_str_space) << @_str_slash_lt <<
|
31
|
+
@stack.pop << @_str_gt
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def comment(comment)
|
38
|
+
flush
|
39
|
+
@_output << @_str_newline << @_str_spaces.fetch(@_stack.size, @_str_space) << @_str_comment_start <<
|
40
|
+
comment.to_s << @_str_comment_end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Htmless
|
2
|
+
module Helper
|
3
|
+
|
4
|
+
# adds instance method to the class. Method accepts any instance of builder and returns it after rendering.
|
5
|
+
# @param [Symbol] method_name
|
6
|
+
# @yield [self] builder_block is evaluated inside builder and accepts instance of a rendered object as parameter
|
7
|
+
# @example
|
8
|
+
# class User
|
9
|
+
# # ...
|
10
|
+
# include HammerBuilder::Helper
|
11
|
+
#
|
12
|
+
# builder :menu do |user|
|
13
|
+
# li user.name
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# User.new.menu(HammerBuilder::Standard.get).to_html! #=> "<li>Name</li>"
|
18
|
+
def builder(method_name, &builder_block)
|
19
|
+
define_method(method_name) do |builder, *args|
|
20
|
+
builder.dive(self, *args, &builder_block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/htmless/pool.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Htmless
|
2
|
+
|
3
|
+
# Creating builder instances is expensive, therefore you can use Pool to go around that
|
4
|
+
# @example
|
5
|
+
# pool = Pool.new Formatted
|
6
|
+
# pool.get.go_in do
|
7
|
+
# # some rendering
|
8
|
+
# end.to_xhtml! # => output and releases the builder to pool
|
9
|
+
class Pool
|
10
|
+
|
11
|
+
module Helper
|
12
|
+
def release
|
13
|
+
@_origin.release self
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String] output and releases the builder to pool
|
17
|
+
def to_html!
|
18
|
+
to_html
|
19
|
+
ensure
|
20
|
+
release
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :klass
|
25
|
+
|
26
|
+
def initialize(klass)
|
27
|
+
@klass = klass
|
28
|
+
@pool = []
|
29
|
+
klass.send :include, Helper
|
30
|
+
end
|
31
|
+
|
32
|
+
# This the preferred way of getting new Builder. If you forget to release it, it does not matter -
|
33
|
+
# builder gets GCed after you lose reference
|
34
|
+
# @return [Abstract]
|
35
|
+
def get
|
36
|
+
if @pool.empty?
|
37
|
+
@klass.new.instance_exec(self) { |origin| @_origin = origin; self }
|
38
|
+
else
|
39
|
+
@pool.pop
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# returns +builder+ back into pool *DONT* forget to lose the reference to the +builder+
|
44
|
+
# @param [Abstract]
|
45
|
+
def release(builder)
|
46
|
+
raise TypeError unless builder.is_a? @klass
|
47
|
+
builder.reset
|
48
|
+
@pool.push builder
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def size
|
53
|
+
@pool.size
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class SynchronizedPool < Pool
|
58
|
+
def initialize(klass)
|
59
|
+
super(klass)
|
60
|
+
@mutex = Mutex.new
|
61
|
+
end
|
62
|
+
|
63
|
+
def get
|
64
|
+
@mutex.synchronize { super }
|
65
|
+
end
|
66
|
+
|
67
|
+
def release(builder)
|
68
|
+
@mutex.synchronize { super(builder) }
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'htmless/formatted'
|
2
|
+
|
3
|
+
warn '"htmless/rails" is very early experiment'
|
4
|
+
|
5
|
+
module Htmless::Rails
|
6
|
+
class AbstractBuilder
|
7
|
+
extend Htmless::Helper
|
8
|
+
|
9
|
+
attr_reader :controller
|
10
|
+
|
11
|
+
def initialize(controller)
|
12
|
+
@controller = controller
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
ActionController::Renderers.add :hb do |klass_or_obj, options|
|
18
|
+
obj = case
|
19
|
+
when klass_or_obj.kind_of?(Class)
|
20
|
+
klass_or_obj.new(self)
|
21
|
+
when klass_or_obj.nil? || klass_or_obj == self
|
22
|
+
self.class.to_s.gsub(/Controller/, 'Builder').constantize.new(self)
|
23
|
+
else
|
24
|
+
klass_or_obj
|
25
|
+
end
|
26
|
+
|
27
|
+
$htmless_pool ||= Htmless::SynchronizedPool.new(Htmless::Formatted) # FIXME
|
28
|
+
|
29
|
+
render(
|
30
|
+
:text => $htmless_pool.get.
|
31
|
+
go_in { render obj, "#{options[:method] || options[:template]}" }.to_html!,
|
32
|
+
:layout => true)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'htmless/abstract'
|
2
|
+
|
3
|
+
module Htmless
|
4
|
+
|
5
|
+
# Builder implementation without formating (one line output)
|
6
|
+
class Standard < Abstract
|
7
|
+
|
8
|
+
dynamic_classes do
|
9
|
+
extend_class :AbstractTag do
|
10
|
+
# add global HTML5 attributes
|
11
|
+
self.add_attributes Data::HTML5.abstract_attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
Data::HTML5.double_tags.each do |tag|
|
15
|
+
next if tag.name == :html
|
16
|
+
|
17
|
+
def_class Abstract.camelize_string(tag.name.to_s).to_sym, :AbstractDoubleTag do
|
18
|
+
set_tag tag.name
|
19
|
+
self.add_attributes tag.attributes
|
20
|
+
end
|
21
|
+
|
22
|
+
base.define_tag(tag.name)
|
23
|
+
end
|
24
|
+
|
25
|
+
html_tag = Data::HTML5.double_tags.find { |t| t.name == :html }
|
26
|
+
def_class :Html, :AbstractDoubleTag do
|
27
|
+
set_tag html_tag.name
|
28
|
+
self.add_attributes html_tag.attributes
|
29
|
+
|
30
|
+
def default
|
31
|
+
attribute :xmlns ,'http://www.w3.org/1999/xhtml'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
base.define_tag(html_tag.name)
|
35
|
+
|
36
|
+
Data::HTML5.single_tags.each do |tag|
|
37
|
+
def_class Abstract.camelize_string(tag.name.to_s).to_sym, :AbstractSingleTag do
|
38
|
+
set_tag tag.name
|
39
|
+
self.add_attributes tag.attributes
|
40
|
+
end
|
41
|
+
|
42
|
+
base.define_tag(tag.name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Htmless
|
2
|
+
class StringsInjector
|
3
|
+
|
4
|
+
attr_reader :strings, :objects_to_update
|
5
|
+
|
6
|
+
def initialize(&block)
|
7
|
+
@strings = Hash.new {|hash, key| raise ArgumentError "missing key #{key}" }
|
8
|
+
@objects_to_update = []
|
9
|
+
instance_eval &block
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](name)
|
13
|
+
strings[name]
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(name, value)
|
17
|
+
name = name.to_sym
|
18
|
+
raise "string #{name} is already set to #{value}" if strings.has_key?(name) && self[name] != value
|
19
|
+
replace name, value
|
20
|
+
end
|
21
|
+
|
22
|
+
def replace(name, value)
|
23
|
+
name = name.to_sym
|
24
|
+
strings[name] = value
|
25
|
+
update_objects name
|
26
|
+
end
|
27
|
+
|
28
|
+
def inject_to(obj)
|
29
|
+
@objects_to_update << obj
|
30
|
+
strings.keys.each { |name| update_object obj, name }
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def update_objects(name)
|
36
|
+
objects_to_update.each { |obj| update_object obj, name }
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_object(obj, name)
|
40
|
+
obj.instance_variable_set(:"@_str_#{name}", self[name])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/htmless.rb
ADDED
data/lib/js.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
path = File.expand_path(File.dirname(__FILE__))
|
2
|
+
$: << path unless $:.include? path
|
3
|
+
|
4
|
+
require "htmless"
|
5
|
+
require 'v8'
|
6
|
+
|
7
|
+
#cxt = V8::Context.new
|
8
|
+
#p cxt.eval(<<-JS)
|
9
|
+
# var a = a || {};
|
10
|
+
# JSON.stringify(a)
|
11
|
+
#JS
|
12
|
+
|
13
|
+
# TODO escape " generated by builder
|
14
|
+
|
15
|
+
class JSBuilder < Htmless::Standard
|
16
|
+
strings_injector.strings.each do |key, value|
|
17
|
+
if value =~ /"/
|
18
|
+
strings_injector.replace key, value.gsub('"', '\"')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
POOL = Htmless::Pool.new(JSBuilder)
|
24
|
+
|
25
|
+
class Injector # < BasicObject
|
26
|
+
def initialize(name)
|
27
|
+
@name, @methods = name, []
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](key)
|
31
|
+
method_missing(key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(name, *args, &block)
|
35
|
+
@methods << name.to_s
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def __reset__!
|
40
|
+
@methods.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
def __name__
|
44
|
+
@name
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
js = "{{#{@name}.#{@methods.join('.')}}}"
|
49
|
+
__reset__!
|
50
|
+
js
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_str
|
54
|
+
to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module ToJs
|
59
|
+
include Htmless::Helper
|
60
|
+
|
61
|
+
def builder(name, &block)
|
62
|
+
JsRenderers.add_renderer self, name, &block
|
63
|
+
super name, &block
|
64
|
+
end
|
65
|
+
|
66
|
+
def as_json
|
67
|
+
{ :class => self.class.to_s, :data => json_data }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class JsRenderersImpl
|
72
|
+
def initialize
|
73
|
+
@render_methods = { }
|
74
|
+
@templates = { }
|
75
|
+
end
|
76
|
+
|
77
|
+
def add_renderer(klass, name, &block)
|
78
|
+
@render_methods[klass] ||= { }
|
79
|
+
@render_methods[klass][name] = block
|
80
|
+
end
|
81
|
+
|
82
|
+
def renderer(klass, name)
|
83
|
+
@render_methods[klass][name]
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_js
|
87
|
+
@render_methods.each do |klass, names|
|
88
|
+
@templates[klass] ||= { }
|
89
|
+
names.each do |name, block|
|
90
|
+
@templates[klass][name] = JsMethodBuilder.new(klass, name, block).build
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@templates
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class JsMethodBuilder
|
98
|
+
attr_reader :klass, :name, :block, :builder
|
99
|
+
|
100
|
+
def initialize(klass, name, block)
|
101
|
+
@klass, @name, @block = klass, name, block
|
102
|
+
@builder = POOL.get
|
103
|
+
@injectors = Array.new(block.arity) { |i| Injector.new("arg#{i}") }
|
104
|
+
end
|
105
|
+
|
106
|
+
def build
|
107
|
+
builder.raw head
|
108
|
+
builder.dive(*@injectors, &block)
|
109
|
+
builder.raw foot
|
110
|
+
builder.to_html!.gsub(/\{\{([^}]*)\}\}/, '"+\1+"')
|
111
|
+
ensure
|
112
|
+
@builder = nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def head
|
116
|
+
<<-JS.chomp
|
117
|
+
var templates = templates || {};
|
118
|
+
templates["#{klass}"] = templates["#{klass}"] || {};
|
119
|
+
templates["#{klass}"].#{name} = function (#{@injectors.map(&:__name__).join(', ')}) {
|
120
|
+
var _buf;
|
121
|
+
_buf = "
|
122
|
+
JS
|
123
|
+
end
|
124
|
+
|
125
|
+
def foot
|
126
|
+
%(";\n return _buf;\n};\n)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
JsRenderers = JsRenderersImpl.new
|
131
|
+
|
132
|
+
A = Module.new
|
133
|
+
class A::Record
|
134
|
+
extend ToJs
|
135
|
+
|
136
|
+
def name
|
137
|
+
'a_record'
|
138
|
+
end
|
139
|
+
|
140
|
+
def klass
|
141
|
+
'class'
|
142
|
+
end
|
143
|
+
|
144
|
+
def items
|
145
|
+
[{ :name => 'a' }, { :name => 'b' }]
|
146
|
+
end
|
147
|
+
|
148
|
+
def json_data
|
149
|
+
{ :name => name,
|
150
|
+
:klass => klass,
|
151
|
+
:items => items
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
builder :content do |r|
|
156
|
+
p :class => r.klass do
|
157
|
+
text r.name
|
158
|
+
#ul do
|
159
|
+
# r.items.each do |item|
|
160
|
+
# r.item self, item
|
161
|
+
# end
|
162
|
+
#end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
builder :item do |_, item|
|
167
|
+
li item[:name]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
record = A::Record.new
|
172
|
+
|
173
|
+
#puts POOL.get.render(record, :content).to_html!
|
174
|
+
|
175
|
+
puts JsRenderers.to_js[A::Record][:content]
|
176
|
+
puts JsRenderers.to_js[A::Record][:item]
|