snabberb 0.3.0 → 1.0.0

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: f6178782a46938a5569124c4fc28c148ed953a9bccf8cd7e4be2ba1835ca2b2c
4
- data.tar.gz: 925315dd99157e735c0c05771349473eee08381a567a4a532348e2287630a0d4
3
+ metadata.gz: 5a1c844a2e4053181db278aeee2cd4c97ccd982e783c6ffb2b6df48391fd0a7b
4
+ data.tar.gz: 53e6e1712e779c653f7d429048f44e77cb9be525619d5276ee441e00e4ccb92b
5
5
  SHA512:
6
- metadata.gz: b1b5b865a8bc623aef95f22f5c2cb1c53020ed7ec41d06b7f604cd88ba6ed94e677b0f765d23fd4bc96a7f09bac010263d2bcabe4c3894c8e7ee0ca882bbed0c
7
- data.tar.gz: 72f2d035af6f132b2a06fb165ea6ee20ab58ad50d165b41557ca028815b356808549346dd729cafc076d95c8642939cdf365f8a1ec9d045cde8b06eb25920c29
6
+ metadata.gz: 4a0c84b3558683af920af98de8e341f0bb6c7d1a2320d1354a8cea76d7a73b83f6eaf99aae9115b5150e5f0643579b24c4b89076188756b04342dbae237db4a3
7
+ data.tar.gz: 1a3b2bed264e1443d38368dea5b05d93abe6f0a1544c0f88b25357d2c9e042456493418779721b250c0f52db5b9e3bccb33099ecb926ff92e409fbf7423bb8fe
data/.gitignore CHANGED
@@ -8,6 +8,8 @@
8
8
  /tmp/
9
9
  /examples/roda/public
10
10
  /examples/roda/build
11
+ /node_modules/
12
+ package-lock.json
11
13
 
12
14
  .DS_STORE
13
15
  *.swp
@@ -7,6 +7,9 @@ Metrics/AbcSize:
7
7
  Metrics/BlockLength:
8
8
  Enabled: False
9
9
 
10
+ Metrics/ClassLength:
11
+ Enabled: False
12
+
10
13
  Metrics/CyclomaticComplexity:
11
14
  Enabled: False
12
15
 
data/README.md CHANGED
@@ -105,7 +105,7 @@ class NestedExample < Snabberb::Component
105
105
  h(:div, { style: { width: '100px' } }, [
106
106
  h(:div, 'hello'),
107
107
  ])
108
- ](
108
+ ])
109
109
  end
110
110
  end
111
111
  ```
@@ -150,6 +150,14 @@ Snabberb.prerender_script('LayoutClass', 'ApplicationClass', 'application_id', j
150
150
 
151
151
  A detailed example can be found [in the Roda example](examples/roda).
152
152
 
153
+ ### Generating HTML from a File
154
+
155
+ You can generate HTML from a component with a file.
156
+
157
+ Snabberb.html\_script('path/to/my\_component.rb', **needs)
158
+
159
+ This reads in the ruby file at the path and generates javascript that calls html on the CamelCased version of the file name.
160
+
153
161
  ## Installation
154
162
 
155
163
  Add this line to your application's Gemfile:
@@ -0,0 +1,22 @@
1
+ // browserify build.js -p esmify -s snabbdom > opal/vendor/snabbdom.js
2
+ import { init } from './node_modules/snabbdom/build/package/init'
3
+ import { h } from './node_modules/snabbdom/build/package/h'
4
+ import { toVNode } from './node_modules/snabbdom/build/package/tovnode'
5
+
6
+ import { attributesModule } from './node_modules/snabbdom/build/package/modules/attributes'
7
+ import { classModule } from './node_modules/snabbdom/build/package/modules/class'
8
+ import { datasetModule } from './node_modules/snabbdom/build/package/modules/dataset'
9
+ import { eventListenersModule } from './node_modules/snabbdom/build/package/modules/eventlisteners'
10
+ import { propsModule } from './node_modules/snabbdom/build/package/modules/props'
11
+ import { styleModule } from './node_modules/snabbdom/build/package/modules/style'
12
+
13
+ module.exports.init = init
14
+ module.exports.h = h
15
+ module.exports.toVNode = toVNode
16
+
17
+ module.exports.attributesModule = attributesModule
18
+ module.exports.classModule = classModule
19
+ module.exports.datasetModule = datasetModule
20
+ module.exports.eventListenersModule = eventListenersModule
21
+ module.exports.propsModule = propsModule
22
+ module.exports.styleModule = styleModule
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- snabberb (0.2.3)
4
+ snabberb (0.5.0)
5
5
  opal (~> 1.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- ast (2.4.0)
10
+ ast (2.4.1)
11
11
  c_lexer (2.6.4.1.1)
12
12
  ast (~> 2.4.0)
13
13
  parser (= 2.6.4.1)
@@ -16,7 +16,7 @@ GEM
16
16
  parser (~> 2.6)
17
17
  parser (2.6.4.1)
18
18
  ast (~> 2.4.0)
19
- rack (2.2.2)
19
+ rack (2.2.3)
20
20
 
21
21
  PLATFORMS
22
22
  ruby
@@ -28,4 +28,4 @@ DEPENDENCIES
28
28
  snabberb!
29
29
 
30
30
  BUNDLED WITH
31
- 2.1.2
31
+ 2.1.4
@@ -1,32 +1,32 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- snabberb (0.2.5)
4
+ snabberb (0.5.0)
5
5
  opal (~> 1.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- ast (2.4.0)
10
+ ast (2.4.1)
11
11
  c_lexer (2.6.4.1.1)
12
12
  ast (~> 2.4.0)
13
13
  parser (= 2.6.4.1)
14
- concurrent-ruby (1.1.6)
14
+ concurrent-ruby (1.1.7)
15
15
  execjs (2.7.0)
16
- libv8 (7.3.492.27.1)
17
- mini_racer (0.2.9)
18
- libv8 (>= 6.9.411)
16
+ libv8 (8.4.255.0)
17
+ mini_racer (0.3.1)
18
+ libv8 (~> 8.4.255)
19
19
  opal (1.0.3)
20
20
  ast (>= 2.3.0)
21
21
  parser (~> 2.6)
22
- opal-sprockets (0.4.8.1.0.3.7)
22
+ opal-sprockets (0.4.9.1.0.3.7)
23
23
  opal (~> 1.0.0)
24
24
  sprockets (~> 3.7)
25
25
  tilt (>= 1.4)
26
26
  parser (2.6.4.1)
27
27
  ast (~> 2.4.0)
28
- rack (2.2.2)
29
- roda (3.30.0)
28
+ rack (2.2.3)
29
+ roda (3.36.0)
30
30
  rack
31
31
  sprockets (3.7.2)
32
32
  concurrent-ruby (~> 1.0)
@@ -46,4 +46,4 @@ DEPENDENCIES
46
46
  snabberb!
47
47
 
48
48
  BUNDLED WITH
49
- 2.1.2
49
+ 2.1.4
@@ -13,12 +13,12 @@ module Snabberb
13
13
  wrap_h(obj)
14
14
  when Array
15
15
  wrap_a(obj)
16
- when Numeric
16
+ when Numeric, TrueClass, FalseClass
17
17
  obj
18
18
  when nil
19
19
  'Opal.nil'
20
20
  else
21
- wrap_s(obj)
21
+ wrap_s(obj.to_s)
22
22
  end
23
23
  end
24
24
 
@@ -38,6 +38,21 @@ module Snabberb
38
38
  "Opal.hash(#{args})"
39
39
  end
40
40
 
41
+ # takes in a file and needs
42
+ # calls html on the CamelCased version of the file with the needs
43
+ def self.html_script(file, **needs)
44
+ klass = file.split('/').last
45
+ .split('.').first
46
+ .split('_').map(&:capitalize).join
47
+
48
+ script = <<~RUBY
49
+ #{File.read(file)}
50
+ #{klass}.html(`#{wrap(needs)}`)
51
+ RUBY
52
+
53
+ Opal.compile(script).strip.chomp(';')
54
+ end
55
+
41
56
  def self.prerender_script(layout, application, application_id, javascript_include_tags: '', **needs)
42
57
  needs = wrap(needs)
43
58
  attach_func = wrap_s("Opal.$$.#{application}.$attach(\"#{application_id}\", #{needs})")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Snabberb
4
- VERSION = '0.3.0'
4
+ VERSION = '1.0.0'
5
5
  end
@@ -1,16 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'erb'
3
4
  require 'native'
4
-
5
5
  require 'vendor/snabbdom'
6
- require 'vendor/snabbdom-attributes'
7
- require 'vendor/snabbdom-class'
8
- require 'vendor/snabbdom-eventlisteners'
9
- require 'vendor/snabbdom-props'
10
- require 'vendor/snabbdom-style'
11
- require 'vendor/snabbdom-to-html'
12
- require 'vendor/tovnode'
13
-
14
6
  require 'snabberb/component'
15
7
 
16
8
  module Snabberb
@@ -5,6 +5,88 @@ module Snabberb
5
5
  attr_accessor :node
6
6
  attr_reader :root
7
7
 
8
+ %x{
9
+ const VOID = new Set([
10
+ 'area',
11
+ 'base',
12
+ 'br',
13
+ 'col',
14
+ 'embed',
15
+ 'hr',
16
+ 'img',
17
+ 'input',
18
+ 'keygen',
19
+ 'link',
20
+ 'meta',
21
+ 'param',
22
+ 'source',
23
+ 'track',
24
+ 'wbr',
25
+ ])
26
+
27
+ const IGNORE = new Set([
28
+ 'attributes',
29
+ 'childElementCount',
30
+ 'children',
31
+ 'classList',
32
+ 'clientHeight',
33
+ 'clientLeft',
34
+ 'clientTop',
35
+ 'clientWidth',
36
+ 'currentStyle',
37
+ 'firstElementChild',
38
+ 'innerHTML',
39
+ 'lastElementChild',
40
+ 'nextElementSibling',
41
+ 'ongotpointercapture',
42
+ 'onlostpointercapture',
43
+ 'onwheel',
44
+ 'outerHTML',
45
+ 'previousElementSibling',
46
+ 'runtimeStyle',
47
+ 'scrollHeight',
48
+ 'scrollLeft',
49
+ 'scrollLeftMax',
50
+ 'scrollTop',
51
+ 'scrollTopMax',
52
+ 'scrollWidth',
53
+ 'tabStop',
54
+ 'tagName',
55
+ ])
56
+
57
+ const BOOLEAN = new Set([
58
+ 'disabled',
59
+ 'visible',
60
+ 'checked',
61
+ 'readonly',
62
+ 'required',
63
+ 'allowfullscreen',
64
+ 'autofocus',
65
+ 'autoplay',
66
+ 'compact',
67
+ 'controls',
68
+ 'default',
69
+ 'formnovalidate',
70
+ 'hidden',
71
+ 'ismap',
72
+ 'itemscope',
73
+ 'loop',
74
+ 'multiple',
75
+ 'muted',
76
+ 'noresize',
77
+ 'noshade',
78
+ 'novalidate',
79
+ 'nowrap',
80
+ 'open',
81
+ 'reversed',
82
+ 'seamless',
83
+ 'selected',
84
+ 'sortable',
85
+ 'truespeed',
86
+ 'typemustmatch',
87
+ ])
88
+ }
89
+
8
90
  # You can define needs in each component. They are automatically set as instance variables
9
91
  #
10
92
  # For example:
@@ -57,7 +139,7 @@ module Snabberb
57
139
  end
58
140
 
59
141
  def html
60
- `toHTML(#{render})`
142
+ node_to_s(render)
61
143
  end
62
144
 
63
145
  # Building block for dom elements using Snabbdom h and Snabberb components.
@@ -96,11 +178,12 @@ module Snabberb
96
178
  return unless request_ids.empty?
97
179
 
98
180
  @@patcher ||= %x{snabbdom.init([
99
- snabbdom_attributes.default,
100
- snabbdom_class.default,
101
- snabbdom_eventlisteners.default,
102
- snabbdom_props.default,
103
- snabbdom_style.default,
181
+ snabbdom.attributesModule,
182
+ snabbdom.classModule,
183
+ snabbdom.datasetModule,
184
+ snabbdom.eventListenersModule,
185
+ snabbdom.propsModule,
186
+ snabbdom.styleModule,
104
187
  ])}
105
188
  node = @root.render
106
189
  @@patcher.call(@root.node, node)
@@ -151,6 +234,114 @@ module Snabberb
151
234
  end
152
235
  end
153
236
  end
237
+
238
+ # rubocop:disable Lint/UnusedMethodArgument
239
+ def parse_sel(sel)
240
+ %x{
241
+ let tag = ''
242
+ let id = ''
243
+ const classes = {}
244
+ const parts = sel.split(".")
245
+ const last = parts.length - 1
246
+
247
+ parts.forEach((part, index) => {
248
+ if (index == last) {
249
+ part = part.split('#')
250
+ if (part.length > 1) id = part[1]
251
+ part = part[0]
252
+ index == 0 ? tag = part : classes[part] = true
253
+ } else if (!tag) {
254
+ tag = part
255
+ } else {
256
+ classes[part] = true
257
+ }
258
+ })
259
+
260
+ return {
261
+ tag: tag,
262
+ id: id,
263
+ classes: classes,
264
+ }
265
+ }
266
+ end
267
+
268
+ def node_to_s(vnode)
269
+ %x{
270
+ if (!vnode.sel) return self.$escape(vnode.text)
271
+
272
+ const sel = self.$parse_sel(vnode.sel)
273
+
274
+ for (const key in vnode.data.class) {
275
+ vnode.data.class[key] ? sel['classes'][key] = true : delete sel['classes'][key]
276
+ }
277
+
278
+ let attributes = {}
279
+ if (sel['id'].length > 0) attributes['id'] = sel['id']
280
+
281
+ const classes = Object.keys(sel['classes'])
282
+ if (classes.length > 0) attributes['class'] = classes.join(' ')
283
+
284
+ for (const key in vnode.data.attrs) {
285
+ attributes[key] = vnode.data.attrs[key]
286
+ }
287
+
288
+ for (const key in vnode.data.dataset) {
289
+ attributes['data-' + key] = vnode.data.dataset[key]
290
+ }
291
+
292
+ for (const key in vnode.data.props) {
293
+ if (!IGNORE.has(key)) {
294
+ const value = vnode.data.props[key]
295
+
296
+ if (BOOLEAN.has(key)) {
297
+ if (value) attributes[key] = key
298
+ } else {
299
+ attributes[key] = value
300
+ }
301
+ }
302
+ }
303
+
304
+ const styles = []
305
+
306
+ for (let key in vnode.data.style) {
307
+ const value = vnode.data.style[key]
308
+ key = key.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
309
+ styles.push(key + ': ' + value)
310
+ }
311
+
312
+ if (styles.length > 0) attributes['style'] = styles.join('; ')
313
+
314
+ attributes = Object.keys(attributes).map(key =>
315
+ self.$escape(key) + '="' + self.$escape(attributes[key]) + '"'
316
+ )
317
+
318
+ const tag = sel['tag']
319
+ const elements = ['<' + tag]
320
+ if (attributes.length > 0) elements.push(' ' + attributes.join(' '))
321
+ elements.push('>')
322
+
323
+ if (!VOID.has(tag)) {
324
+ if (vnode.data.props && vnode.data.props.innerHTML) {
325
+ elements.push(vnode.data.props.innerHTML)
326
+ } else if (vnode.text) {
327
+ elements.push(self.$escape(vnode.text))
328
+ } else if (vnode.children) {
329
+ vnode.children.forEach(child =>
330
+ elements.push(self.$node_to_s(child))
331
+ )
332
+ }
333
+
334
+ elements.push('</' + tag + '>')
335
+ }
336
+
337
+ return elements.join('')
338
+ }
339
+ end
340
+ # rubocop:enable Lint/UnusedMethodArgument
341
+
342
+ def escape(html)
343
+ ERB::Util.html_escape(html)
344
+ end
154
345
  end
155
346
 
156
347
  # Sublcass this to prerender applications.