htmless 0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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]
|