html-native 0.1.0 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2ec4658fd7a7e2a040535c411ca661cc80291d705b59099c93b6eecd184fb35e
4
- data.tar.gz: e96476a674a70ad4e28ba9ab223057bbc3bbdcab00ae91d1ff39c1788e383131
3
+ metadata.gz: 10608cfe99deda77bf919d12ed385dff6364260d596fbb672968c6ed708ca320
4
+ data.tar.gz: 511d614f09a21a27506d25289a0f31f36d92864d1235eb2cc26f4c5008ac0aa8
5
5
  SHA512:
6
- metadata.gz: 85deca5f040b4268a9b3cf533483f2fbe91de270e5b6dbaf35817cea6363e49e008863481d24eea85c9c18123715caf21cfbe08f2f2c872cd844866ab95c956f
7
- data.tar.gz: 7eca06fcf63a3159af0e2de5c4502ad5d969707de4c3f0221df07ca73b9d0b49e224edae049d8c6a5f59c1f390664c56221c9b74bc499ee4e213817c100ac6e6
6
+ metadata.gz: 476f3e92ab73de39f6d05bf96fca229237e44de9e140f25f012e3556a04f61500dc85b8a5c216a1c799bf7db1b8cc14514f8ab65e48563813b6d98792b0964e2
7
+ data.tar.gz: 804e56ce1f00a988bc5a590d13351ccd11175d1a7c7be64d7076a4c653ed8a868fb3bd0955da6251f32af161dfeabc3466930b2f8a4bdddc352ea88f1f3b60f6
data/lib/html-native.rb CHANGED
@@ -3,13 +3,6 @@ require "html-native/builder"
3
3
 
4
4
  module HTMLComponent
5
5
 
6
- # Excluded currently because it makes checking in `Builder` ugly
7
- # Makes `include` and `extend` work exactly the same.
8
- # It's a dirty hack based on laziness, and strict use of `extend` is preferred.
9
- # def self.included(base)
10
- # base.extend(self)
11
- # end
12
-
13
6
  # Generates generation methods for each HTML5-valid tag. These methods have the
14
7
  # name of the tag. Note that this interferes with the builtin `p` method.
15
8
  TAG_LIST.each do |tag|
@@ -24,6 +17,10 @@ module HTMLComponent
24
17
  end
25
18
  end
26
19
 
20
+ # Creates a module that encompasses the given block in an HTMLComponent
21
+ # context. This gives access to methods in the block as though the block was
22
+ # declared as the `render` function in a module extending HTMLComponent
23
+ # (pretty much because it is).
27
24
  def self.singleton(&block)
28
25
  Module.new do
29
26
  extend HTMLComponent
@@ -55,6 +52,6 @@ module HTMLComponent
55
52
  "#{k}=\"#{v}\"" # render this appropriately for numeric fields (might already)
56
53
  end
57
54
  end.join(" ")
58
- formatted.empty? ? "" : " " + formatted
55
+ formatted.prepend(" ") unless formatted.empty?
59
56
  end
60
57
  end
@@ -1,14 +1,29 @@
1
1
  require "html-native"
2
2
  module HTMLComponent
3
+ # Represents a String being constructed, and can be more or less treated
4
+ # as a String. Builder creates a String from whatever is put into it, but
5
+ # it delays construction until it's absolutely necessary, then it caches the
6
+ # result.
3
7
  class Builder
8
+ # Build a new string builder instance, immediately constructing and caching
9
+ # the initial value.
4
10
  def initialize(strings = [])
5
- if strings.kind_of? String
6
- @strings = [strings]
11
+ @strings = []
12
+ @cache = case strings.class
13
+ when Array
14
+ strings.join
15
+ when Enumerable
16
+ strings.to_a.join
7
17
  else
8
- @strings = strings.dup
18
+ strings.to_s
9
19
  end
20
+ @cached = true
10
21
  end
11
22
 
23
+ # Appends a value to the Builder instance. If it is another builder, it is
24
+ # added, but not converted to a String yet. If it is an HTMLComponent, it is
25
+ # rendered. If it is anything else, it is converted to a String. This
26
+ # invalidates the cache.
12
27
  def +(string)
13
28
  if string.kind_of? Builder
14
29
  @strings << string
@@ -17,11 +32,49 @@ module HTMLComponent
17
32
  else
18
33
  @strings << string.to_s
19
34
  end
35
+ @cached = false
20
36
  self
21
37
  end
22
38
 
39
+ alias_method :<<, :+
40
+
41
+ # Same as +, but allows multiple values to be appended.
42
+ def concat(*strings)
43
+ strings.each do |s|
44
+ self + s
45
+ end
46
+ self
47
+ end
48
+
49
+ # Converts the Builder to a String. If the cache is valid, it is returned.
50
+ # Otherwise, the new result is created, cached, and returned.
23
51
  def to_s
24
- @strings.join
52
+ unless @cached
53
+ @cache << @strings.join
54
+ @strings.clear
55
+ @cached = true
56
+ end
57
+ @cache
25
58
  end
59
+
60
+ alias_method :to_str, :to_s
61
+
62
+ # If the method does not exist on Builder, it is sent to String, by way
63
+ # of the rendered Builder result. Modify-in-place methods will affect the
64
+ # underlying String.
65
+ def method_missing(method, *args, &block)
66
+ to_s.send(method, *args, &block)
67
+ end
68
+
69
+ # If String responds to the method, then Builder also responds to it.
70
+ def respond_to_missing?(method, include_all)
71
+ "".respond_to?(method, include_all)
72
+ end
73
+ end
74
+ end
75
+
76
+ class String
77
+ def component
78
+ HTMLComponent::Builder.new(self)
26
79
  end
27
80
  end
@@ -2,6 +2,11 @@ require "html-native"
2
2
  require "html-native/builder"
3
3
 
4
4
  module Enumerable
5
+ # Maps the given block to each element of *enum*. The result of each iteration
6
+ # is added to an HTMLComponent::Builder instance, which is returned after the
7
+ # final iteration.
8
+ #
9
+ # If no block is given, an enumerator is returned instead.
5
10
  def component_map
6
11
  if block_given?
7
12
  result = HTMLComponent::Builder.new
@@ -14,134 +19,276 @@ module Enumerable
14
19
  end
15
20
  end
16
21
 
22
+ # Returns an OrderedListComponent representing the *enum*.
23
+ #
24
+ # See OrderedListComponent#new for details on how to apply attributes.
17
25
  def to_ol(attributes: {})
18
26
  OrderedListComponent.new(self, attributes: attributes)
19
27
  end
20
28
 
29
+ # Returns an UnorderedListComponent representing the *enum*.
30
+ #
31
+ # See UnorderedListComponent#new for details on how to apply attributes.
21
32
  def to_ul(attributes: {})
22
33
  UnorderedListComponent.new(self, attributes: attributes)
23
34
  end
24
35
  end
25
36
 
37
+ # OrderedListComponent represents an HTML ordered list based on an Enumerable
38
+ # collection.
39
+ #
40
+ # Attributes in an OrderedListComponent are separated into multiple groups since
41
+ # there are multiple kinds of tags. These groups are:
42
+ # - list - The attributes associated with the <ol> element
43
+ # - item - The attributes associated with <li> elements
44
+ #
45
+ # For example, to have a list with 20px padding and the class of "list-item"
46
+ # given to each item, you could write:
47
+ # ```
48
+ # OrderedListComponent.new(data, attributes:
49
+ # {list: {style: "padding: 20px"}, item: {class: "list-item"}})
50
+ #
51
+ # ```
52
+ # which is equivalent to
53
+ # ```
54
+ # <ol style="padding: 20px">
55
+ # <li class="list-item">...</li>
56
+ # <li class="list-item">...</li>
57
+ # ...
58
+ # </ol>
59
+ # ```
26
60
  class OrderedListComponent
27
61
  include HTMLComponent
28
62
 
29
- def initialize(data, attributes: {})
63
+ # Creates a new instance of OrderedListComponent from the values of *data*.
64
+ #
65
+ # If a block is given, each item in *data* is passed to it to render the list
66
+ # items. If no block is given, *data* are used directly.
67
+ def initialize(data, attributes: {}, &block)
30
68
  @list_data = data
31
69
  @list_attributes = attributes[:list] || {}
32
70
  @item_attributes = attributes[:item] || {}
71
+ @block = block
33
72
  end
34
73
 
35
- def render(&block)
74
+ # Converts the OrderedListComponent instance to the equivalent HTML.
75
+ #
76
+ # *render* can be called directly, but that usually isn't necessary.
77
+ # HTMLComponent::Builder handles this automatically, so it only needs to be
78
+ # done if there is no prior instance of one.
79
+ def render
36
80
  ol(@list_attributes) do
37
81
  @list_data.component_map do |l|
38
82
  li(@item_attributes) do
39
- block_given? ? yield(l) : l.to_s
83
+ @block ? @block.call(l) : l
40
84
  end
41
85
  end
42
86
  end
43
87
  end
44
88
  end
45
89
 
90
+ # UnorderedListComponent represents an HTML unordered list generated from an
91
+ # Enumerable collection.
92
+ #
93
+ # Attributes in an UnorderedListComponent are separated into multiple groups since
94
+ # there are multiple kinds of tags. These groups are:
95
+ # - list - The attributes associated with the <ul> element
96
+ # - item - The attributes associated with <li> elements
97
+ #
98
+ # For example, to have a list with 20px padding and the class of "list-item"
99
+ # given to each item, you could write:
100
+ # ```
101
+ # UnorderedListComponent.new(data, attributes:
102
+ # {list: {style: "padding: 20px"}, item: {class: "list-item"}})
103
+ #
104
+ # ```
105
+ # which is equivalent to
106
+ # ```
107
+ # <ul style="padding: 20px">
108
+ # <li class="list-item">...</li>
109
+ # <li class="list-item">...</li>
110
+ # ...
111
+ # </ul>
112
+ # ```
46
113
  class UnorderedListComponent
47
114
  include HTMLComponent
48
115
 
49
- def initialize(data, attributes: {})
116
+ # Creates a new instance of UnorderedListComponent from the values of *data*.
117
+ #
118
+ # If a block is given, each item in *data* is passed to it to render the list
119
+ # items. If no block is given, *data* are used directly.
120
+ def initialize(data, attributes: {}, &block)
50
121
  @list_data = data
51
122
  @list_attributes = attributes[:list] || {}
52
123
  @item_attributes = attributes[:item] || {}
124
+ @block = block
53
125
  end
54
126
 
55
- def render(&block)
127
+ # Converts the UnorderedListComponent instance to the equivalent HTML.
128
+ #
129
+ # *render* can be called directly, but that usually isn't necessary.
130
+ # HTMLComponent::Builder handles this automatically, so it only needs to be
131
+ # done if there is no prior instance of one.
132
+ def render
56
133
  ul(@list_attributes) do
57
134
  @list_data.component_map do |l|
58
135
  li(@item_attributes) do
59
- block_given? ? yield(l) : l.to_s
136
+ @block ? @block.call(l) : l
60
137
  end
61
138
  end
62
139
  end
63
140
  end
64
141
  end
65
142
 
143
+ # ListComponent represents an HTML list based on an Enumerable collection.
144
+ #
145
+ # Attributes in an ListComponent are separated into multiple groups since
146
+ # there are multiple kinds of tags. These groups are:
147
+ # - list - The attributes associated with the <ul> element
148
+ # - item - The attributes associated with <li> elements
149
+ #
150
+ # For example, to have an ordered list with 20px padding and the class of
151
+ # "list-item" given to each item, you could write:
152
+ # ```
153
+ # ListComponent.new(data, ordered: true, attributes:
154
+ # {list: {style: "padding: 20px"}, item: {class: "list-item"}})
155
+ #
156
+ # ```
157
+ # which is equivalent to
158
+ # ```
159
+ # <ol style="padding: 20px">
160
+ # <li class="list-item">...</li>
161
+ # <li class="list-item">...</li>
162
+ # ...
163
+ # </ol>
164
+ # ```
66
165
  class ListComponent
67
- def initialize(data, attributes: {}, ordered: false)
68
- @list = ordered ? OrderedListComponent.new(data, attributes) :
69
- UnorderedListComponent.new(data, attributes)
166
+ # Creates a new instance of ListComponent from the values of *data*.
167
+ #
168
+ # This list can be either ordered or unordered, depending on *ordered* parameter.
169
+ # If *ordered* is true, the list will be ordered, otherwise it will be unordered.
170
+ # *ordered* is false by default.
171
+ #
172
+ # If a block is given, each item in *data* is passed to it to render the list
173
+ # items. If no block is given, *data* are used directly.
174
+ def initialize(data, attributes: {}, ordered: false, &block)
175
+ @list = ordered ? OrderedListComponent.new(data, attributes, &block) :
176
+ UnorderedListComponent.new(data, attributes, &block)
70
177
  end
71
178
 
72
- def render(&block)
73
- @list.render(&block)
179
+ # Converts the ListComponent instance to the equivalent HTML.
180
+ #
181
+ # *render* can be called directly, but that usually isn't necessary.
182
+ # HTMLComponent::Builder handles this automatically, so it only needs to be
183
+ # done if there is no prior instance of one.
184
+ def render
185
+ @list.render
74
186
  end
75
187
  end
76
188
 
189
+ # TableRowComponent represents an HTML table row generated from an
190
+ # Enumerable collection.
191
+ #
192
+ # Attributes in an TableRowComponent are separated into multiple groups since
193
+ # there are multiple kinds of tags. These groups are:
194
+ # - row - The attributes associated with the <tr> element
195
+ # - cell - The attributes associated with <td> elements
196
+ #
197
+ # For example, to have a row with 20px padding and the class of "table-cell"
198
+ # given to each cell, you could write:
199
+ # ```
200
+ # TableRowComponent.new(data, attributes:
201
+ # {row: {style: "padding: 20px"}, cell: {class: "list-item"}})
202
+ #
203
+ # ```
204
+ # which is equivalent to
205
+ # ```
206
+ # <tr style="padding: 20px">
207
+ # <td class="list-item">...</td>
208
+ # <td class="list-item">...</td>
209
+ # ...
210
+ # </tr>
211
+ # ```
77
212
  class TableRowComponent
78
213
  include HTMLComponent
79
214
 
80
- def initialize(data, attributes: {})
215
+ # Creates a new instance of TableRowComponent from the values of *data*.
216
+ #
217
+ # If a block is given, each item in *data* is passed to it to render the row
218
+ # cells. If no block is given, *data* are used directly.
219
+ def initialize(data, attributes: {}, &block)
81
220
  @data = data
82
221
  @row_attributes = attributes[:row] || {}
83
222
  @cell_attributes = attributes[:cell] || {}
223
+ @block = block
84
224
  end
85
225
 
86
- def render(&block)
226
+ # Converts the TableRowComponent instance to the equivalent HTML.
227
+ #
228
+ # *render* can be called directly, but that usually isn't necessary.
229
+ # HTMLComponent::Builder handles this automatically, so it only needs to be
230
+ # done if there is no prior instance of one.
231
+ def render
87
232
  tr(@row_attributes) do
88
233
  @data.component_map do |c|
89
- td(@cell_attributes) {block_given? ? yield(c) : c}
234
+ td(@cell_attributes) {@block ? @block.call(c) : c}
90
235
  end
91
236
  end
92
237
  end
93
238
  end
94
239
 
95
- # This needs some reworking, since it's not intuitive
240
+ # TableComponent represents an HTML table generated from an Enumerable
241
+ # collection.
242
+ #
243
+ # Attributes in an TableComponent are separated into multiple groups since
244
+ # there are multiple kinds of tags. These groups are:
245
+ # - table - The attributes associated with the <table> element.
246
+ # - header - The attributes associated with <tr> element representing the header.
247
+ # - header_cell - The attributes associated with <th> elements.
248
+ # - row - The attributes associated with all <tr> elements, including the header.
249
+ # - cell - The attributes associated with <td> and <th> elements.
250
+ #
251
+ # Refer to other components for the format for applying attributes.
96
252
  class TableComponent
97
253
  include HTMLComponent
98
254
 
99
- def initialize(header, rows, attributes: {})
100
- @header = header
101
- @rows = rows
255
+ # Creates a new instance of TableComponent from the values of *rows*.
256
+ #
257
+ # *rows* is an Enumerable collection of Enumerable objects.
258
+ #
259
+ # If a block is given, each item in *rows* is passed to it to render the table
260
+ # cells, not including the header. If no block is given, *rows* is used directly.
261
+ def initialize(rows, col_names: [], row_names: [], attributes: {}, &block)
262
+ @rows = rows.map(&:to_a)
263
+ @header = col_names.to_a
264
+ row_names = row_names.to_a
265
+ unless row_names.empty?
266
+ row_names.each_with_index do |name, i|
267
+ if i < @rows.size
268
+ @rows[i].prepend(name)
269
+ else
270
+ @rows << [name]
271
+ end
272
+ end
273
+ end
274
+ @header.prepend("") unless row_names.empty? || @header.empty?
275
+
102
276
  @table_attributes = attributes[:table] || {}
103
277
  @header_attributes = attributes[:header] || {}
104
278
  @header_cell_attributes = attributes[:header_cell] || {}
105
279
  @row_attributes = attributes[:row] || {}
106
280
  @cell_attributes = attributes[:cell] || {}
281
+ @block = block
107
282
  end
108
283
 
109
- # header options:
110
- # array - use as header
111
- # symbol - if :from_data, then use first row, if :none, set @header to nil
112
- def self.from_array(data, attributes: {}, header: :none)
113
- head = rows = nil
114
- if header == :from_data
115
- head = data[0]
116
- rows = data[1..]
117
- else
118
- head = header.kind_of?(Array) ? header : nil
119
- rows = data
120
- end
121
- new(head, rows, attributes: attributes)
122
- end
123
-
124
- def self.from_hash(data, attributes: {}, vertical: true)
125
- if vertical
126
- rowcount = data.values.map(&:length).max
127
- rows = [] * rowcount
128
- data.each do |k,col|
129
- rowcount.times do |i|
130
- rows[i] << (i < col.size ? col[i] : nil)
131
- end
132
- end
133
- new(data.keys, rows, attributes: attributes)
134
- else
135
- rows = data.map do |k, v|
136
- [k] + v
137
- end
138
- new(nil, rows, attributes: attributes)
139
- end
140
- end
141
-
142
- def render(&block)
284
+ # Converts the TableComponent instance to the equivalent HTML.
285
+ #
286
+ # *render* can be called directly, but that usually isn't necessary.
287
+ # HTMLComponent::Builder handles this automatically, so it only needs to be
288
+ # done if there is no prior instance of one.
289
+ def render
143
290
  table(@table_attributes) do
144
- if @header
291
+ unless @header.empty?
145
292
  tr(@row_attributes.merge(@header_attributes)) do
146
293
  @header.component_map do |h|
147
294
  th(@cell_attributes.merge(@header_cell_attributes)) {h}
@@ -151,47 +298,87 @@ class TableComponent
151
298
  Builder.new
152
299
  end +
153
300
  @rows.component_map do |row|
154
- TableRowComponent.new(row, attributes: {row: @row_attributes, cell: @cell_attributes}).render(&block)
301
+ attributes = {row: @row_attributes, cell: @cell_attributes}
302
+ TableRowComponent.new(row, attributes: attributes, &@block)
155
303
  end
156
304
  end
157
305
  end
158
306
  end
159
307
 
308
+ # DropdownComponent represents an HTML table row generated from an
309
+ # Enumerable collection.
310
+ #
311
+ # Attributes in an DropdownComponent are separated into multiple groups since
312
+ # there are multiple kinds of tags. These groups are:
313
+ # - menu - The attributes associated with the <select> element
314
+ # - item - The attributes associated with <option> elements
160
315
  class DropdownComponent
161
316
  include HTMLComponent
162
317
 
163
- def initialize(choices, name, attributes: {})
318
+ # Creates a new instance of DropdownComponent from the values of *choices*.
319
+ #
320
+ # If a block is given, each item in *choices* is passed to it to render the
321
+ # item. If no block is given, *choices* is used directly.
322
+ def initialize(choices, name, attributes: {}, &block)
164
323
  @choices = choices
165
324
  @name = name
166
325
  @menu_attributes = attributes[:menu]
167
326
  @item_attributes = attributes[:item]
327
+ @block = block
168
328
  end
169
329
 
170
- def render(&block)
330
+ # Converts the DropdownComponent instance to the equivalent HTML.
331
+ #
332
+ # *render* can be called directly, but that usually isn't necessary.
333
+ # HTMLComponent::Builder handles this automatically, so it only needs to be
334
+ # done if there is no prior instance of one.
335
+ def render
171
336
  select(@menu_attributes.merge({name: @name, id: "#{@name}-dropdown"})) do
172
337
  @choices.component_map do |c|
173
- option(@item_attributes.merge({value: c})) {block_given? ? yield(c) : c}
338
+ option(@item_attributes.merge({value: c})) {@block ? @block.call(c) : c}
174
339
  end
175
340
  end
176
341
  end
177
342
  end
178
343
 
344
+ # RadioGroupComponent represents an HTML radio button group generated from an
345
+ # Enumerable collection.
346
+ #
347
+ # Each item has a unique id of the form of `"#{name}-{choice}"`, where name is
348
+ # the provided *name* option, and choice is value of that button.
349
+ #
350
+ # Each item produces a button and a label.
351
+ #
352
+ # Attributes in an RadioGroupComponent are separated into multiple groups since
353
+ # there are multiple kinds of tags. These groups are:
354
+ # - button - The attributes associated with the <input type: "radio"> elements.
355
+ # - label - The attributes associated with <label> elements.
179
356
  class RadioGroupComponent
180
357
  include HTMLComponent
181
358
 
182
- def initialize(choices, name, attributes: {}, labelled: true)
359
+ # Creates a new instance of RadioGroupComponent from the values of *choices*.
360
+ #
361
+ # If a block is given, each item in *choices* is passed to it to render the
362
+ # label. If no block is given, *choices* is used directly.
363
+ def initialize(choices, name, attributes: {}, labelled: true, &block)
183
364
  @choices = choices
184
365
  @name = name
185
366
  @button_attributes = attributes[:button]
186
367
  @label_attributes = attributes[:label]
187
368
  @labelled = labelled
369
+ @block = block
188
370
  end
189
371
 
190
- def render(&block)
372
+ # Converts the RadioGroupComponent instance to the equivalent HTML.
373
+ #
374
+ # *render* can be called directly, but that usually isn't necessary.
375
+ # HTMLComponent::Builder handles this automatically, so it only needs to be
376
+ # done if there is no prior instance of one.
377
+ def render
191
378
  @choices.component_map do |c|
192
379
  id = "#{@name}-#{c}"
193
380
  input({type: "radio", id: id, name: @name, value: c}) +
194
- (@labelled ? (label({for: id}) {block_given? ? yield(c) : c}) : nil)
381
+ (@labelled ? (label({for: id}) {@block ? @block.call(c) : c}) : nil)
195
382
  end
196
383
  end
197
384
  end
@@ -1,4 +1,6 @@
1
1
  module HTMLComponent
2
+ # A list of all valid HTML5 elements. These are used to generate generator
3
+ # methods within HTMLComponent contexts.
2
4
  TAG_LIST = [
3
5
  :html,
4
6
  :base, :head, :link, :meta, :style, :title,
@@ -19,12 +21,12 @@ module HTMLComponent
19
21
  :option, :output, :progress, :select, :textarea,
20
22
  :details, :dialog, :menu, :summary,
21
23
  :slot, :template
22
- ] + (1..6).map{|i| :"h#{i}"}
23
-
24
- def self.tags
25
- TAG_LIST.dup
26
- end
24
+ ]
27
25
 
26
+ # A list of all limited attributes in HTML5. Any attribute not listed here are
27
+ # free to use in any element, as long as it is not in FORBIDDEN_ATTRIBUTES.
28
+ #
29
+ # Any attributes listed here will only be allowed in the associated elements.
28
30
  LIMITED_ATTRIBUTES = {
29
31
  accept: [:form, :input],
30
32
  "accept-charset": [:form],
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: html-native
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kellen Watt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-25 00:00:00.000000000 Z
11
+ date: 2021-02-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: An html generation DSL designed for fluid code creation.
14
14
  email: