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,144 @@
|
|
1
|
+
module Htmless
|
2
|
+
class Abstract
|
3
|
+
dynamic_classes do
|
4
|
+
|
5
|
+
def_class :AbstractDoubleTag, :AbstractTag do ###import
|
6
|
+
nil
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
def initialize(builder)
|
10
|
+
super
|
11
|
+
@content = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
# allows data-* attributes and id, classes by method_missing
|
15
|
+
def method_missing(method, *args, &block)
|
16
|
+
method = method.to_s
|
17
|
+
if method =~ METHOD_MISSING_REGEXP
|
18
|
+
if $1
|
19
|
+
self.rclass.add_attributes Data::Attribute.new(method.to_sym, :string)
|
20
|
+
self.send method, *args, &block
|
21
|
+
else
|
22
|
+
attributes(if args.last.is_a?(Hash)
|
23
|
+
args.pop
|
24
|
+
end)
|
25
|
+
content args.first
|
26
|
+
self.__send__($3 == '!' ? :id : :class, $2.gsub(@_str_underscore, @_str_dash), &block)
|
27
|
+
end
|
28
|
+
else
|
29
|
+
super(method, *args, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
def open(*args, &block)
|
35
|
+
attributes = if args.last.is_a?(Hash)
|
36
|
+
args.pop
|
37
|
+
end
|
38
|
+
content args[0]
|
39
|
+
super attributes
|
40
|
+
@stack << @tag_name
|
41
|
+
if block
|
42
|
+
with &block
|
43
|
+
else
|
44
|
+
self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @api private
|
49
|
+
# closes the tag
|
50
|
+
def flush
|
51
|
+
flush_classes
|
52
|
+
@output << @_str_gt
|
53
|
+
@output << CGI.escapeHTML(@content) if @content
|
54
|
+
@output << @_str_slash_lt << @stack.pop << @_str_gt
|
55
|
+
@content = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
# sets content of the double tag
|
59
|
+
# @example
|
60
|
+
# div 'content' # => <div>content</div>
|
61
|
+
# div.content 'content' # => <div>content</div>
|
62
|
+
# div :content => 'content' # => <div>content</div>
|
63
|
+
def content(content)
|
64
|
+
@content = content.to_s
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# renders content of the double tag with block
|
69
|
+
# @yield content of the tag
|
70
|
+
# @example
|
71
|
+
# div { text 'content' } # => <div>content</div>
|
72
|
+
# div :id => 'id' do
|
73
|
+
# text 'content'
|
74
|
+
# end # => <div id="id">content</div>
|
75
|
+
def with
|
76
|
+
flush_classes
|
77
|
+
@output << @_str_gt
|
78
|
+
@content = nil
|
79
|
+
@builder.current = nil
|
80
|
+
yield
|
81
|
+
#if (content = yield).is_a?(String)
|
82
|
+
# @output << EscapeUtils.escape_html(content)
|
83
|
+
#end
|
84
|
+
@builder.flush
|
85
|
+
@output << @_str_slash_lt << @stack.pop << @_str_gt
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
|
89
|
+
alias_method :w, :with
|
90
|
+
|
91
|
+
def mimic(obj, &block)
|
92
|
+
super(obj, &nil)
|
93
|
+
return with(&block) if block
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def data(hash, &block)
|
98
|
+
super(hash, &nil)
|
99
|
+
return with(&block) if block
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def attribute(name, value, &block)
|
104
|
+
super(name, value, &nil)
|
105
|
+
return with(&block) if block
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
def attributes(attrs, &block)
|
110
|
+
super(attrs, &nil)
|
111
|
+
return with(&block) if block
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
# @api private
|
118
|
+
def self.define_attribute_method(attribute)
|
119
|
+
return if instance_methods(false).include?(attribute.name)
|
120
|
+
name = attribute.name.to_s
|
121
|
+
|
122
|
+
if instance_methods.include?(attribute.name)
|
123
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
124
|
+
def #{name}(*args, &block)
|
125
|
+
super(*args, &nil)
|
126
|
+
return with(&block) if block
|
127
|
+
self
|
128
|
+
end
|
129
|
+
RUBY
|
130
|
+
else
|
131
|
+
content_rendering = attribute_content_rendering(attribute)
|
132
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
133
|
+
def #{name}(content#{' = true' if attribute.type == :boolean}, &block)
|
134
|
+
#{content_rendering}
|
135
|
+
return with(&block) if block
|
136
|
+
self
|
137
|
+
end
|
138
|
+
RUBY
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end ###import
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Htmless
|
2
|
+
class Abstract
|
3
|
+
dynamic_classes do
|
4
|
+
def_class :AbstractSingleTag, :AbstractTag do ###import
|
5
|
+
nil
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
# closes the tag
|
9
|
+
def flush
|
10
|
+
flush_classes
|
11
|
+
@output << @_str_slash_gt
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
end ###import
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,246 @@
|
|
1
|
+
module Htmless
|
2
|
+
class Abstract
|
3
|
+
dynamic_classes do
|
4
|
+
def_class :AbstractTag do ###import
|
5
|
+
|
6
|
+
def self.strings_injector
|
7
|
+
dynamic_class_base.strings_injector
|
8
|
+
end
|
9
|
+
|
10
|
+
def self._attributes=(_attributes)
|
11
|
+
@_attributes = _attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def self._attributes
|
15
|
+
@_attributes or (superclass._attributes if superclass.respond_to? :_attributes)
|
16
|
+
end
|
17
|
+
|
18
|
+
self._attributes = []
|
19
|
+
|
20
|
+
# @return [Array<String>] array of available attributes for the tag
|
21
|
+
def self.attributes
|
22
|
+
_attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] tag's name
|
26
|
+
def self.tag_name
|
27
|
+
@tag || superclass.tag_name
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# sets the tag's name
|
33
|
+
# @api private
|
34
|
+
def self.set_tag(tag)
|
35
|
+
@tag = tag.to_s.freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
set_tag 'abstract'
|
39
|
+
|
40
|
+
# defines dynamically methods for attributes
|
41
|
+
# @api private
|
42
|
+
def self.define_attribute_methods
|
43
|
+
attributes.each { |attr| define_attribute_method(attr) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.inherited(base)
|
47
|
+
base.define_attribute_methods
|
48
|
+
end
|
49
|
+
|
50
|
+
# defines dynamically method for +attribute+
|
51
|
+
# @param [Data::Attribute] attribute
|
52
|
+
# @api private
|
53
|
+
def self.define_attribute_method(attribute)
|
54
|
+
return if instance_methods.include?(attribute.name)
|
55
|
+
name = attribute.name.to_s
|
56
|
+
content_rendering = attribute_content_rendering(attribute)
|
57
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
58
|
+
def #{name}(content#{' = true' if attribute.type == :boolean})
|
59
|
+
#{content_rendering}
|
60
|
+
self
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
|
65
|
+
# @api private
|
66
|
+
# @param [Data::Attribute] attribute
|
67
|
+
# @returns Ruby code as string
|
68
|
+
def self.attribute_content_rendering(attribute)
|
69
|
+
name = attribute.name.to_s
|
70
|
+
case attribute.type
|
71
|
+
when :string
|
72
|
+
strings_injector.add "attr_#{name}", " #{name.gsub('_', '-')}=#{strings_injector[:quote]}"
|
73
|
+
"@output << @_str_attr_#{name} << CGI.escapeHTML(content.to_s) << @_str_quote"
|
74
|
+
when :boolean
|
75
|
+
strings_injector.add(
|
76
|
+
"attr_#{name}",
|
77
|
+
" #{name.gsub('_', '-')}=#{strings_injector[:quote]}#{name}#{strings_injector[:quote]}")
|
78
|
+
"@output << @_str_attr_#{name} if content"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# adds attribute to class, triggers dynamical creation of needed instance methods etc.
|
83
|
+
# @param [Array<Data::Attribute>] attributes
|
84
|
+
# @api private
|
85
|
+
def self.add_attributes(attributes)
|
86
|
+
attributes = [attributes] unless attributes.is_a? Array
|
87
|
+
raise ArgumentError, attributes.inspect unless attributes.all? { |v| v.is_a? Data::Attribute }
|
88
|
+
self.send :_attributes=, _attributes + attributes
|
89
|
+
define_attribute_methods
|
90
|
+
end
|
91
|
+
|
92
|
+
public
|
93
|
+
|
94
|
+
attr_reader :builder
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def initialize(builder)
|
98
|
+
@builder = builder
|
99
|
+
@output = builder.instance_eval { @_output }
|
100
|
+
@stack = builder.instance_eval { @_stack }
|
101
|
+
@classes = []
|
102
|
+
@tag_name = self.rclass.tag_name
|
103
|
+
|
104
|
+
self.rclass.strings_injector.inject_to self
|
105
|
+
end
|
106
|
+
|
107
|
+
# @api private
|
108
|
+
def open(attributes = nil)
|
109
|
+
@output << @_str_lt << @tag_name
|
110
|
+
@builder.current = self
|
111
|
+
attributes(attributes)
|
112
|
+
default
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
# it renders attribute using defined attribute method or by rendering attribute directly
|
117
|
+
# @param [String, Symbol] name
|
118
|
+
# @param [#to_s] value
|
119
|
+
def attribute(name, value)
|
120
|
+
return __send__(name, value) if respond_to?(name)
|
121
|
+
@output << @_str_space << name.to_s << @_str_eql_quote << CGI.escapeHTML(value.to_s) << @_str_quote
|
122
|
+
self
|
123
|
+
end
|
124
|
+
|
125
|
+
# @example
|
126
|
+
# div.attributes :id => 'id' # => <div id="id"></div>
|
127
|
+
# div :id => 'id', :class => %w{left right} # => <div id="id" class="left right"></div>
|
128
|
+
# img :src => 'path' # => <img src="path"></div>
|
129
|
+
# attribute`s methods are called on background (in this case #id is called)
|
130
|
+
def attributes(attrs)
|
131
|
+
return self unless attrs
|
132
|
+
attrs.each do |attr, value|
|
133
|
+
if value.kind_of?(Array)
|
134
|
+
__send__(attr, *value)
|
135
|
+
else
|
136
|
+
__send__(attr, value)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
# original Ruby method for class, class is used for html classes
|
143
|
+
alias_method(:rclass, :class)
|
144
|
+
|
145
|
+
id_class = /^([\w]+)(!|)$/
|
146
|
+
data_attribute = /^data_([a-z_]+)$/
|
147
|
+
METHOD_MISSING_REGEXP = /#{data_attribute}|#{id_class}/ unless defined? METHOD_MISSING_REGEXP
|
148
|
+
|
149
|
+
# allows data-* attributes and id, classes by method_missing
|
150
|
+
def method_missing(method, *args, &block)
|
151
|
+
method = method.to_s
|
152
|
+
if method =~ METHOD_MISSING_REGEXP
|
153
|
+
if $1
|
154
|
+
self.rclass.add_attributes Data::Attribute.new(method, :string)
|
155
|
+
self.send method, *args
|
156
|
+
else
|
157
|
+
self.__send__($3 == '!' ? :id : :class, $2.gsub(@_str_underscore, @_str_dash))
|
158
|
+
self.attributes args.first
|
159
|
+
end
|
160
|
+
else
|
161
|
+
super(method, *args, &block)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#def respond_to?(symbol, include_private = false)
|
166
|
+
# symbol.to_s =~ METHOD_MISSING_REGEXP || super(symbol, include_private)
|
167
|
+
#end
|
168
|
+
|
169
|
+
strings_injector.add "attr_class", " class=#{strings_injector[:quote]}"
|
170
|
+
# adds classes to the tag by joining +classes+ with ' ' and skipping non-true classes
|
171
|
+
# @param [Array<#to_s>] classes
|
172
|
+
# @example
|
173
|
+
# class(!visible? && 'hidden', 'left') #=> class="hidden left" or class="left"
|
174
|
+
def class(*classes)
|
175
|
+
@classes.push(*classes.select { |c| c })
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
strings_injector.add "attr_id", " id=#{strings_injector[:quote]}"
|
180
|
+
# adds id to the tag by joining +values+ with '_'
|
181
|
+
# @param [Array<#to_s>] values
|
182
|
+
# @example
|
183
|
+
# id('user', 12) #=> id="user-15"
|
184
|
+
def id(*values)
|
185
|
+
@output << @_str_attr_id << CGI.escapeHTML(values.select { |v| v }.join(@_str_dash)) << @_str_quote
|
186
|
+
self
|
187
|
+
end
|
188
|
+
|
189
|
+
# adds id and class to a tag by an object
|
190
|
+
# @param [Object] obj
|
191
|
+
# To determine the class it looks for .htmless_ref or
|
192
|
+
# it uses class.to_s.underscore.tr('/', '-').
|
193
|
+
# To determine id it looks for #htmless_ref or it takes class and #id or #object_id.
|
194
|
+
# @example
|
195
|
+
# div[AUser.new].with { text 'a' } # => <div id="a_user_1" class="a_user">a</div>
|
196
|
+
def mimic(obj)
|
197
|
+
klass = if obj.class.respond_to? :htmless_ref
|
198
|
+
obj.class.htmless_ref
|
199
|
+
else
|
200
|
+
obj.class.to_s.scan(/[A-Z][a-z\d]*/).join('_').downcase.gsub('::', '-')
|
201
|
+
end
|
202
|
+
|
203
|
+
id = case
|
204
|
+
when obj.respond_to?(:htmless_ref)
|
205
|
+
obj.htmless_ref
|
206
|
+
when obj.respond_to?(:id)
|
207
|
+
[klass, obj.id]
|
208
|
+
else
|
209
|
+
[klass, obj.object_id]
|
210
|
+
end
|
211
|
+
#noinspection RubyArgCount
|
212
|
+
self.class(klass).id(id)
|
213
|
+
end
|
214
|
+
|
215
|
+
alias_method :[], :mimic
|
216
|
+
|
217
|
+
# renders data-* attributes by +hash+
|
218
|
+
# @param [Hash] hash
|
219
|
+
# @example
|
220
|
+
# div.data(:remote => true, :id => 'an_id') # => <div data-remote="true" data-id="an_id"></div>
|
221
|
+
def data(hash)
|
222
|
+
hash.each { |k, v| __send__ "data_#{k}", v }
|
223
|
+
self
|
224
|
+
end
|
225
|
+
|
226
|
+
protected
|
227
|
+
|
228
|
+
# this method is called on each tag opening, useful for default attributes
|
229
|
+
# @example html tag uses this to add xmlns attr.
|
230
|
+
# html # => <html xmlns="http://www.w3.org/1999/xhtml"></html>
|
231
|
+
def default
|
232
|
+
end
|
233
|
+
|
234
|
+
# flushes classes to output
|
235
|
+
# @api private
|
236
|
+
def flush_classes
|
237
|
+
unless @classes.empty?
|
238
|
+
@output << @_str_attr_class << CGI.escapeHTML(@classes.join(@_str_space)) << @_str_quote
|
239
|
+
@classes.clear
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end ###import
|
243
|
+
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
#require 'active_support/core_ext/string/inflections'
|
3
|
+
|
4
|
+
require 'htmless/dynamic_classes'
|
5
|
+
require 'htmless/strings_injector'
|
6
|
+
require 'htmless/data'
|
7
|
+
require 'htmless/data/html5'
|
8
|
+
require "htmless/pool"
|
9
|
+
require "htmless/helper"
|
10
|
+
|
11
|
+
module Htmless
|
12
|
+
|
13
|
+
# Abstract implementation of Builder
|
14
|
+
class Abstract
|
15
|
+
extend DynamicClasses
|
16
|
+
|
17
|
+
require "htmless/abstract/abstract_tag"
|
18
|
+
require "htmless/abstract/abstract_single_tag"
|
19
|
+
require "htmless/abstract/abstract_double_tag"
|
20
|
+
|
21
|
+
def self.strings_injector
|
22
|
+
@strings_injector ||= StringsInjector.new do
|
23
|
+
add :lt, '<'
|
24
|
+
add :gt, '>'
|
25
|
+
add :slash_lt, '</'
|
26
|
+
add :slash_gt, ' />'
|
27
|
+
add :dash, '-'
|
28
|
+
add :underscore, '_'
|
29
|
+
add :space, ' '
|
30
|
+
add :spaces, Array.new(300) { |i| (' ' * i).freeze }
|
31
|
+
add :newline, "\n"
|
32
|
+
add :quote, '"'
|
33
|
+
add :eql, '='
|
34
|
+
add :eql_quote, self[:eql] + self[:quote]
|
35
|
+
add :comment_start, '<!--'
|
36
|
+
add :comment_end, '-->'
|
37
|
+
add :cdata_start, '<![CDATA['
|
38
|
+
add :cdata_end, ']]>'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# << faster then +
|
43
|
+
# yield faster then block.call
|
44
|
+
# accessing ivar and constant is faster then accesing hash or cvar
|
45
|
+
# class_eval faster then define_method
|
46
|
+
# beware of strings in methods -> creates a lot of garbage
|
47
|
+
|
48
|
+
def self.tags=(tags)
|
49
|
+
@tags = tags
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.tags
|
53
|
+
@tags or (superclass.tags if superclass.respond_to? :tags)
|
54
|
+
end
|
55
|
+
|
56
|
+
def tags
|
57
|
+
self.class.tags
|
58
|
+
end
|
59
|
+
|
60
|
+
self.tags = []
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# defines instance method for +tag+ in builder
|
65
|
+
def self.define_tag(tag)
|
66
|
+
tag = tag.to_s
|
67
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
68
|
+
def #{tag}(*args, &block)
|
69
|
+
flush
|
70
|
+
@_#{tag}.open(*args, &block)
|
71
|
+
end
|
72
|
+
RUBY
|
73
|
+
self.tags += [tag]
|
74
|
+
end
|
75
|
+
|
76
|
+
public
|
77
|
+
|
78
|
+
|
79
|
+
# current tag being builded
|
80
|
+
attr_accessor :_current
|
81
|
+
alias_method :current, :_current
|
82
|
+
alias_method :current=, :_current=
|
83
|
+
|
84
|
+
|
85
|
+
# creates a new builder
|
86
|
+
# This is quite expensive, HammerBuilder::Pool should be used
|
87
|
+
def initialize()
|
88
|
+
@_output = ""
|
89
|
+
@_stack = []
|
90
|
+
@_current = nil
|
91
|
+
|
92
|
+
self.class.strings_injector.inject_to self
|
93
|
+
|
94
|
+
# tag classes initialization
|
95
|
+
tags.each do |klass|
|
96
|
+
instance_variable_set(:"@_#{klass}", self.class.dynamic_classes[camelize_string(klass).to_sym].new(self))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# escapes +text+ to output
|
101
|
+
def text(text)
|
102
|
+
flush
|
103
|
+
@_output << CGI.escapeHTML(text.to_s)
|
104
|
+
end
|
105
|
+
|
106
|
+
# unescaped +text+ to output
|
107
|
+
def raw(text)
|
108
|
+
flush
|
109
|
+
@_output << text.to_s
|
110
|
+
end
|
111
|
+
|
112
|
+
# inserts +comment+
|
113
|
+
def comment(comment)
|
114
|
+
flush
|
115
|
+
@_output << @_str_comment_start << comment.to_s << @_str_comment_end
|
116
|
+
end
|
117
|
+
|
118
|
+
# insersts CDATA with +content+
|
119
|
+
def cdata(content)
|
120
|
+
flush
|
121
|
+
@_output << @_str_cdata_start << content.to_s << @_str_cdata_end
|
122
|
+
end
|
123
|
+
|
124
|
+
# renders html5 doc type
|
125
|
+
# @example
|
126
|
+
# html5 # => <!DOCTYPE html>
|
127
|
+
def html5
|
128
|
+
raw "<!DOCTYPE html>\n"
|
129
|
+
end
|
130
|
+
|
131
|
+
# resets the builder to the state after creation - much faster then creating a new one
|
132
|
+
def reset
|
133
|
+
flush
|
134
|
+
@_output.clear
|
135
|
+
@_stack.clear
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
#def capture
|
140
|
+
# flush
|
141
|
+
# _output = @_output.clone
|
142
|
+
# _stack = @_stack.clone
|
143
|
+
# @_output.clear
|
144
|
+
# @_stack.clear
|
145
|
+
# yield
|
146
|
+
# to_html
|
147
|
+
#ensure
|
148
|
+
# @_output.replace _output
|
149
|
+
# @_stack.replace _stack
|
150
|
+
#end
|
151
|
+
|
152
|
+
# enables you to evaluate +block+ inside the builder with +variables+
|
153
|
+
# @example
|
154
|
+
# HammerBuilder::Formatted.new.go_in('asd') do |string|
|
155
|
+
# div string
|
156
|
+
# end.to_html #=> "<div>asd</div>"
|
157
|
+
#
|
158
|
+
def go_in(*variables, &block)
|
159
|
+
instance_exec *variables, &block
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
alias_method :dive, :go_in
|
164
|
+
|
165
|
+
# sets instance variables when block is yielded
|
166
|
+
# @param [Hash{String => Object}] instance_variables hash of names and values to set
|
167
|
+
# @yield block when variables are set, variables are cleaned up afterwards
|
168
|
+
def set_variables(instance_variables)
|
169
|
+
instance_variables.each { |name, value| instance_variable_set("@#{name}", value) }
|
170
|
+
yield(self)
|
171
|
+
instance_variables.each { |name, _| remove_instance_variable("@#{name}") }
|
172
|
+
self
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [String] output
|
176
|
+
def to_html()
|
177
|
+
flush
|
178
|
+
@_output.clone
|
179
|
+
end
|
180
|
+
|
181
|
+
# flushes open tag
|
182
|
+
# @api private
|
183
|
+
def flush
|
184
|
+
if @_current
|
185
|
+
@_current.flush
|
186
|
+
@_current = nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# renders +object+ with +method+
|
191
|
+
# @param [Object] object an object to render
|
192
|
+
# @param [Symbol] method a method name which is used for rendering
|
193
|
+
# @param args arguments passed to rendering method
|
194
|
+
# @yield block passed to rendering method
|
195
|
+
def render(object, method, *args, &block)
|
196
|
+
object.__send__ method, self, *args, &block
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
alias_method :r, :render
|
201
|
+
|
202
|
+
# renders js
|
203
|
+
# @option options [Boolean] :cdata (false) should cdata be used?
|
204
|
+
# @example
|
205
|
+
# js 'a_js_function();' #=> <script type="text/javascript">a_js_function();</script>
|
206
|
+
def js(js, options = {})
|
207
|
+
use_cdata = options.delete(:cdata) || false
|
208
|
+
script({ :type => "text/javascript" }.merge(options)) { use_cdata ? cdata(js) : text(js) }
|
209
|
+
end
|
210
|
+
|
211
|
+
# joins and renders +collection+ with +glue+
|
212
|
+
# @param [Array<Proc, Object>] collection of objects or lambdas
|
213
|
+
# @param [Proc, String] glue can be String which is rendered with #text or block to render
|
214
|
+
# @yield how to render objects from +collection+, Proc in collection does not use this block
|
215
|
+
# @example
|
216
|
+
# join([1, 1.2], lambda { text ', ' }) {|o| text o } # => "1, 1.2"
|
217
|
+
# join([1, 1.2], ', ') {|o| text o } # => "1, 1.2"
|
218
|
+
# join([->{ text 1 }, 1.2], ', ') {|o| text o } # => "1, 1.2"
|
219
|
+
def join(collection, glue = nil, &it)
|
220
|
+
# TODO as helper? two block method call #join(collection, &item).with(&glue)
|
221
|
+
glue_block = case glue
|
222
|
+
when String
|
223
|
+
lambda { text glue }
|
224
|
+
when Proc
|
225
|
+
glue
|
226
|
+
else
|
227
|
+
lambda {}
|
228
|
+
end
|
229
|
+
|
230
|
+
collection.each_with_index do |obj, i|
|
231
|
+
glue_block.call() if i > 0
|
232
|
+
obj.is_a?(Proc) ? obj.call : it.call(obj)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
|
237
|
+
private
|
238
|
+
|
239
|
+
def self.camelize_string(term)
|
240
|
+
term.
|
241
|
+
to_s.
|
242
|
+
sub(/^[a-z\d]*/) { $&.capitalize }.
|
243
|
+
gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.
|
244
|
+
gsub('/', '::')
|
245
|
+
end
|
246
|
+
|
247
|
+
def camelize_string(term)
|
248
|
+
self.class.camelize_string term
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|