dom-rb 0.1.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7bea94521028abe3b88220fd369b2ed399d49094
4
+ data.tar.gz: 2fd651272b2923fb793a1b38d2c46ce814ce6856
5
+ SHA512:
6
+ metadata.gz: 2c72e3408c30567670e74eecd2461bc22b4d72606b19dcfb282664451c5feba2abbbf34b408633875c5cd0c09ee91dfce4a1779a2754484127ba7e9c6bf948af
7
+ data.tar.gz: ee2775001232bf81ea60da2d4b5c0b985b38857384369efeba9eccab04a501fb6d3f1ef6a232a3a9125fe581c73e94ea3171a2218680c637e4ecf98de27538e1
@@ -0,0 +1,101 @@
1
+ require 'opal-browser'
2
+
3
+ # Add binding management to Browser::DOM::Node
4
+
5
+ class Browser
6
+ class DOM
7
+ class Document
8
+ def active_element
9
+ el = `#@native.activeElement()`
10
+ if el
11
+ Browser::DOM::Node.new(el)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ class Browser
19
+ class DOM
20
+ class Element
21
+ end
22
+ end
23
+ end
24
+
25
+ class Browser
26
+ class DOM
27
+ class Node
28
+ # requires jquery and bootstrap.js - will fail otherwise
29
+ def check_jquery
30
+ %x(
31
+ if (typeof jQuery === 'undefined') {
32
+ throw new Error('Bootstrap\'s JavaScript requires jQuery')
33
+ }
34
+ )
35
+ end
36
+
37
+ def check_bootstrap
38
+ %x(
39
+ if (typeof($.fn.tooltip) === 'undefined') {
40
+ throw new Error('Bootstrap.js not loaded')
41
+ }
42
+ )
43
+ end
44
+
45
+ # TODO: replicate this for all Bootstrap js features
46
+ # TODO: refactor all bootstrap stuff to separate module
47
+ # arg may be:
48
+ # 0. nil - attaches tooltip to node
49
+ # 1. hash of options and attaches tooltip to node
50
+ # 2. 'show'
51
+ # 3. 'hide'
52
+ # 4. 'toggle'
53
+ # 4. 'destroy'
54
+ # see http://getbootstrap.com/javascript/#tooltips
55
+ def tooltip(arg=nil)
56
+ check_bootstrap
57
+ `$(#@native).tooltip(#{arg.to_n})`
58
+ end
59
+
60
+ def popover(arg=nil)
61
+ check_bootstrap
62
+ `$(#@native).popover(#{arg.to_n})`
63
+ end
64
+
65
+ def bindings=(bindings)
66
+ # store bindings in js element
67
+ `#@native.voltBindings = #{bindings}`
68
+ end
69
+
70
+ def bindings
71
+ # bindings stored in js element
72
+ b = `#@native.voltBindings`
73
+ `b === undefined` ? nil : b
74
+ end
75
+
76
+ # bindings set (if required) by Paggio::HTML::Element
77
+ def start_bindings
78
+ if bindings
79
+ bindings.each do |b|
80
+ # cannot use self as self is transient
81
+ b.start(self.id)
82
+ end
83
+ end
84
+ end
85
+
86
+ def stop_bindings
87
+ if bindings
88
+ bindings.each do |b|
89
+ b.stop
90
+ end
91
+ end
92
+ end
93
+
94
+ def bindings_count
95
+ b = bindings
96
+ b ? b.size : 0
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,210 @@
1
+ require 'opal-browser'
2
+ require 'dom/globals'
3
+ require 'dom/builder/binding'
4
+ require 'dom/browser_ext/node'
5
+ require 'dom/util'
6
+ require 'dom/debug'
7
+
8
+ module CSR
9
+ module DOM
10
+ module Builder
11
+ include CSR::DOM::Globals
12
+ include CSR::DOM::Util
13
+ include CSR::DOM::Debug
14
+
15
+ module_function
16
+
17
+ def br
18
+ tag(:br)
19
+ end
20
+
21
+ def div(attributes: nil, content: nil)
22
+ tag(:div, attributes: attributes, content: content)
23
+ end
24
+
25
+ # Any attribute or content which is a BoundProc will be
26
+ # converted to bindings. See CSR::DOM::Binding.
27
+ # Any attributes or content which is a Proc will be
28
+ # resolved by proc.call.
29
+ def tag(name, attributes: nil, content: nil)
30
+ # self.debug_level = 3
31
+ attributes = attributes ? attributes : {}
32
+ namespace = attributes[:namespace]
33
+ element = document.create_element(name, {namespace: namespace})
34
+ id = attributes.delete(:id) || attributes.delete('id')
35
+ if id
36
+ id = id.call if Proc === id
37
+ element.id = id
38
+ end
39
+ bindings = []
40
+ attributes.each do |key, value|
41
+ if key == :style
42
+ value = resolve_value(element, value, bindings, id, :style, name)
43
+ if value
44
+ value = normalize_style(value)
45
+ element.style(value)
46
+ end
47
+ elsif key[0,2] == 'on'
48
+ # event stuff is in browser/event/base.rb
49
+ event = key[2..-1]
50
+ element.on(event, &value)
51
+ else
52
+ value = resolve_value(element, value, bindings, id, key, name)
53
+ if value
54
+ key = sanitize_attribute(key)
55
+ element[key] = value
56
+ end
57
+ end
58
+ end
59
+ content = resolve_value(element, content, bindings, id, :content, name)
60
+ if content
61
+ content = sanitize_content(content)
62
+ element << content
63
+ end
64
+ if bindings
65
+ element.bindings = bindings
66
+ end
67
+ element
68
+ end
69
+
70
+ def bind(proc=nil, &block)
71
+ BoundProc.new(proc, &block)
72
+ end
73
+
74
+ def resolve_value(element, value, bindings, id, attr, tag_name)
75
+ # debug 1, ->{[ __FILE__, __LINE__, __method__, "tag=#{tag_name} value=#{value} bindings=#{bindings} id=#{id} attr=#{attr}" ]}
76
+ result = case value
77
+ when BoundProc
78
+ # debug 1, ->{[ __FILE__, __LINE__, __method__, "BoundProc===value" ]}
79
+ binding = Binding.new(
80
+ # This proc was set in bind and should expect a Binding as argument
81
+ # It can set up a watch, and should immediately set the binding value.
82
+ value.proc,
83
+ # this is local proc to update element with new value
84
+ -> (value) {
85
+ update_element(element, id, attr, value)
86
+ }
87
+ )
88
+ bindings << binding
89
+ # value proc should have set value in binding
90
+ binding.value
91
+ when Proc
92
+ # debug 1, ->{[ __FILE__, __LINE__, __method__, "Proc===value" ]}
93
+ value.call
94
+ else
95
+ # debug 1, ->{[ __FILE__, __LINE__, __method__, "value=#{value}" ]}
96
+ value
97
+ end
98
+ # debug 1, ->{[ __FILE__, __LINE__, __method__, "result=#{result}" ]}
99
+ result
100
+ end
101
+
102
+ def sanitize_attribute(key)
103
+ # key.to_s.gsub(/_([a-z])/) {|v| v[1].upcase}
104
+ key.to_s.gsub(/_/, '-')
105
+ end
106
+
107
+ def sanitize_content(content)
108
+ debug 3, ->{[ __FILE__, __LINE__, __method__, "content.class=#{content.class}" ]}
109
+ case content
110
+ when Browser::DOM::Element
111
+ content
112
+ when Enumerable
113
+ content.map {|e| sanitize_content(e)}
114
+ else
115
+ content.to_s
116
+ end
117
+ end
118
+
119
+ def update_element(element, id, attr, value)
120
+ debug 2, ->{[ __FILE__, __LINE__, __method__, "element=#{element} id=#{id} attr=#{attr} value=#{value}" ]}
121
+ case attr
122
+ when :content
123
+ content = sanitize_content(value)
124
+ debug 2, ->{[ __FILE__, __LINE__, __method__, "content=#{content}"]}
125
+ element.clear
126
+ element << content
127
+ when :style
128
+ debug 2, ->{[ __FILE__, __LINE__, __method__ ]}
129
+ element.style(normalize_style(value || {}))
130
+ else
131
+ debug 2, ->{[ __FILE__, __LINE__, __method__ ]}
132
+ element[attr] = value
133
+ end
134
+ end
135
+
136
+ def <<(*args, &block)
137
+ debug 1, ->{[ __FILE__, __LINE__, __method__, "size=#{args.size} args=#{args}" ]}
138
+ if block
139
+ self << block.call
140
+ else
141
+ if args.size == 1
142
+ dom_root << case args[0]
143
+ when Hash
144
+ name = args[0].delete(:tag)
145
+ tag(name, args[0])
146
+ else
147
+ args[0]
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ alias_method :add, :<<
154
+
155
+ # Returns a Browser::DOM::Element with given id or nil.
156
+ def element(id)
157
+ document[id.to_s]
158
+ end
159
+
160
+ alias_method :el, :element
161
+ alias_method :[], :element
162
+
163
+ def remove(element)
164
+ fail "#{self.class.name}##{__method__} not implemented"
165
+ end
166
+
167
+ def replace(element)
168
+ fail "#{self.class.name}##{__method__} not implemented"
169
+ end
170
+
171
+ def traverse_dom(node_or_nodeset = nil, &block)
172
+ node_or_nodeset ||= dom_root
173
+ (NodeSet === node_or_nodeset ? node_or_nodeset : [node_or_nodeset]).each do |node|
174
+ yield DOM(node)
175
+ node.children.each do |child|
176
+ traverse_dom(DOM(child), &block)
177
+ end
178
+ end
179
+ end
180
+
181
+ def stop_bindings(id = nil)
182
+ switch_bindings(id, start: false)
183
+ end
184
+
185
+ def start_bindings(id = nil)
186
+ switch_bindings(id, start: true)
187
+ end
188
+
189
+ def switch_bindings(id = nil, start: true)
190
+ first = nil
191
+ if id
192
+ first = self[id]
193
+ unless first
194
+ fail "#{self.class.name}##{__method__}:#{__LINE_} : no element with id '#{id}'"
195
+ end
196
+ end
197
+ traverse_dom(first) do |node|
198
+ start ? node.start_bindings : node.stop_bindings
199
+ end
200
+ end
201
+
202
+ def shutdown
203
+ stop_bindings
204
+ clear_interval
205
+ end
206
+
207
+
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,44 @@
1
+ # Usage
2
+ #
3
+ # tag(
4
+ # 'h3',
5
+ # content: bind do |binding|
6
+ # # set up the watch
7
+ # -> {
8
+ # binding.value = user.name
9
+ # }.watch!
10
+ # end
11
+ # )
12
+
13
+ module CSR
14
+ module DOM
15
+ class BoundProc
16
+ attr_reader :proc
17
+
18
+ def initialize(proc=nil, &block)
19
+ @proc = proc || block
20
+ end
21
+ end
22
+
23
+ class Binding
24
+
25
+ attr_reader :value
26
+
27
+ def initialize(value_proc, action_proc)
28
+ @value_proc = value_proc
29
+ @action_proc = action_proc
30
+ @value = nil
31
+ @value_proc.call(self)
32
+ end
33
+
34
+ # TODO: make equality checking optional?
35
+ def value=(arg)
36
+ unless @value == arg
37
+ @value = arg
38
+ @action_proc.call(arg)
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,343 @@
1
+ require 'dom/builder/base'
2
+
3
+ # adds helper to CSR::DOM::Builder
4
+
5
+ module CSR
6
+ module DOM
7
+ module Builder
8
+
9
+ module_function
10
+
11
+ def icon(name, callback: nil, css: nil, float: nil, margins: nil, style: nil)
12
+ _style = {
13
+ text_align: 'center',
14
+ vertical_align: 'middle',
15
+ color: 'inherit',
16
+ background_color: 'inherit',
17
+ cursor: 'pointer',
18
+ }
19
+ if margins
20
+ _style =_style.merge(margins)
21
+ elsif float
22
+ _style = _style.merge(
23
+ case float.to_sym
24
+ when :left
25
+ { margin_right: '0.5em' } # { margin_top: '0.2em', margin_right: '0.5em' }
26
+ when :right
27
+ { margin_left: '0.5em' } # { margin_top: '0.2em', margin_right: '0.5em' }
28
+ else
29
+ { } # { margin_top: '0.2em', margin_right: '0.5em' }
30
+ end
31
+ )
32
+ end
33
+ # arg style overrides any defaults
34
+ _style = _style.merge(style) if style
35
+ _class = iconify(name)
36
+ _class = "#{_class} #{css}" if css
37
+ _class = "#{_class} pull-#{float}" if float
38
+ attributes = {
39
+ class: _class,
40
+ style: _style
41
+ }.merge(
42
+ callback ? { onclick: callback } : {}
43
+ )
44
+ tag(
45
+ :span,
46
+ attributes: attributes
47
+ )
48
+ end
49
+
50
+ def icon_with_anchor(icon_name, href, float: nil, margins: nil, icon_css: nil, anchor_css: nil, icon_style: nil, anchor_style: nil)
51
+ _icon = icon(icon_name, css: icon_css, float: float, margins: margins, style: icon_style)
52
+ tag(
53
+ :a,
54
+ attributes: {
55
+ href: href,
56
+ style: anchor_style,
57
+ class: anchor_css || '',
58
+ },
59
+ content: _icon
60
+ )
61
+ end
62
+
63
+ def plus_sign_icon(href = nil)
64
+ icon_with_anchor(
65
+ :plus_sign,
66
+ href,
67
+ icon_style: { color: 'lightgreen'}
68
+ )
69
+ end
70
+
71
+ def remove_sign_icon(callback = nil)
72
+ icon(
73
+ :remove_sign,
74
+ callback: callback,
75
+ style: {color: 'red'}
76
+ )
77
+ end
78
+
79
+ def refresh_icon(callback = nil)
80
+ icon(
81
+ :refresh,
82
+ callback: callback,
83
+ style: {color: 'inherit'}
84
+ )
85
+ end
86
+
87
+ def save_icon(callback = nil)
88
+ icon(
89
+ :save,
90
+ callback: callback,
91
+ style: {color: 'inherit'}
92
+ )
93
+ end
94
+
95
+ def hamburger_icon(callback = nil)
96
+ icon(
97
+ :menu_hamburger,
98
+ callback: callback,
99
+ style: {color: 'inherit'}
100
+ )
101
+ end
102
+
103
+ # Returns a span element with icon and dropdown menu
104
+ #
105
+ # 1. icon: is the name of the (bootstrap) icon
106
+ # 2. items: should be hashes containing menu items
107
+ # e.g. { callback: ->{}, href: '#', content: 'list item'}
108
+ # 3. content: of the div (apart from the icon)
109
+ # 4. pull: which side of div to pull the span, 'right' or 'left'
110
+ # 5. style: any additional styling fro the span/icon
111
+ def drop_down_icon(icon: 'menu-hamburger', items: [], pull: 'right', style: {})
112
+ pull = pull.to_s
113
+ right = pull == 'right'
114
+ tag(
115
+ :span,
116
+ attributes: {
117
+ class: "pull-#{pull}"
118
+ },
119
+ content: tag(
120
+ :div,
121
+ attributes: {
122
+ class: 'dropdown',
123
+ },
124
+ content: [
125
+ tag(
126
+ :div,
127
+ attributes: {
128
+ class: 'dropdown-toggle',
129
+ # type: 'button',
130
+ data_toggle: 'dropdown',
131
+ style: {
132
+ font_size: 'smaller',
133
+ # margin_top: '0.2em',
134
+ # margin_bottom: '0.1em',
135
+ margin_left: right ? '0.5em' : '0.3em',
136
+ margin_right: right ? '0.3em' : '0.5em',
137
+ vertical_align: 'middle',
138
+ color: 'inherit',
139
+ background_color: 'inherit',
140
+ }.merge(
141
+ style
142
+ )
143
+ },
144
+ content: icon(
145
+ icon,
146
+ style: {
147
+ font_size: 'smaller',
148
+ # margin_top: '0.2em',
149
+ margin_bottom: '0.2em',
150
+ margin_left: right ? '0.5em' : '0.3em',
151
+ margin_right: right ? '0.3em' : '0.5em',
152
+ vertical_align: 'middle',
153
+ color: 'inherit',
154
+ background_color: 'inherit',
155
+ }.merge(
156
+ style
157
+ )
158
+ )
159
+ ),
160
+ tag(
161
+ :ul,
162
+ attributes: {
163
+ class: "dropdown-menu#{right ? ' dropdown-menu-right' : nil}"
164
+ },
165
+ content: items.map { |item|
166
+ content = item[:content]
167
+ `console.log(#{"#{__FILE__}[#{__LINE__}]: adding item #{content} to menu icon #{icon}"})`
168
+ if content == 'divider' || content == 'separator'
169
+ tag(
170
+ :li,
171
+ attributes: {
172
+ role: 'separator',
173
+ class: 'divider',
174
+ }
175
+ )
176
+ else
177
+ attrs = {}
178
+ attrs[:href] = item[:href] if item[:href]
179
+ attrs[:onclick] = item[:callback] if item[:callback]
180
+ tag(
181
+ :li,
182
+ content: tag(
183
+ :div,
184
+ content: content,
185
+ attributes: attrs
186
+ )
187
+ )
188
+ end
189
+ }
190
+ )
191
+ ]
192
+ )
193
+ )
194
+ end
195
+
196
+ def plain_anchor(content, href)
197
+ tag(
198
+ :a,
199
+ attributes: {
200
+ href: href,
201
+ style: { color: 'inherit', background_color: 'inherit'}
202
+ },
203
+ content: content
204
+ )
205
+ end
206
+
207
+ def div_with_icon(callback, icon: nil, pull: nil, attributes: nil, content: nil, icon_style: {}, tooltip: nil, popover: nil)
208
+ icon ||= 'question-sign'
209
+ pull ||= 'left'
210
+ pull = pull.to_s
211
+ icon = tag(
212
+ :span,
213
+ attributes: {
214
+ onclick: callback,
215
+ class: "glyphicon glyphicon-#{icon} pull-#{pull}",
216
+ style: {
217
+ font_size: 'smaller',
218
+ margin_left: '0.5em',
219
+ margin_right: '0.5em',
220
+ vertical_align: 'middle',
221
+ color: 'inherit',
222
+ background_color: 'inherit',
223
+ }.merge(
224
+ icon_style # argument style overrides default
225
+ )
226
+ }
227
+ )
228
+ debug 1, ->{[__FILE__, __LINE__, __method__, "tooltip=#{tooltip} popover=#{popover}"]}
229
+ if tooltip
230
+ icon.tooltip(tooltip)
231
+ elsif popover
232
+ icon.popover(popover)
233
+ end
234
+ tag(
235
+ :div,
236
+ attributes: {
237
+ style: { cursor: 'pointer' }
238
+ }.merge(attributes || {}),
239
+ content: arrify(content, icon)
240
+ )
241
+ end
242
+
243
+ def div_with_sort_icon(callback, direction: 0, content: nil)
244
+ if direction != 0
245
+ tag(
246
+ :div,
247
+ attributes: {
248
+ onclick: callback,
249
+ style: { cursor: 'pointer' }
250
+ },
251
+ content: arrify(content) + [
252
+ tag(:span,
253
+ attributes: {
254
+ class: "glyphicon glyphicon-triangle-#{direction > 0 ? 'top' : 'bottom'}",
255
+ style: {
256
+ font_size: 'smaller',
257
+ margin_left: '0.5em',
258
+ vertical_align: 'middle',
259
+ color: 'inherit',
260
+ background_color: 'inherit',
261
+ }
262
+ }
263
+ )
264
+ ]
265
+ )
266
+ else
267
+ tag(
268
+ :div,
269
+ attributes: {
270
+ onclick: callback,
271
+ style: { cursor: 'pointer' }
272
+ },
273
+ content: content
274
+ )
275
+ end
276
+ end
277
+
278
+ def div_with_menu_up_down(callback, up: true, down: false, content: nil, pull: 'left')
279
+ div_with_up_down_icon(callback, which: :menu, up: up, down: down, content: content, pull: pull)
280
+ end
281
+
282
+ def div_with_collapse_up_down(callback, up: true, down: false, content: nil, pull: 'left')
283
+ div_with_up_down_icon(callback, which: :collapse, up: up, down: down, content: content, pull: pull)
284
+ end
285
+
286
+ # which can be :collapse or :menu (or string equivalents)
287
+ def div_with_up_down_icon(callback, which: :menu, up: true, down: false, content: nil, pull: 'left')
288
+ up = up && !down
289
+ pull = pull.to_s
290
+ left = pull == 'left'
291
+ tag(
292
+ :div,
293
+ attributes: {
294
+ onclick: callback,
295
+ style: { cursor: 'pointer' }
296
+ },
297
+ content: [
298
+ tag(
299
+ :span,
300
+ attributes: {
301
+ class: "glyphicon glyphicon-#{which}-#{up ? 'up' : 'down'} pull-#{pull}",
302
+ style: {
303
+ font_size: 'smaller',
304
+ margin_top: '0.2em',
305
+ margin_left: left ? '0.3em' : '0.5em',
306
+ margin_right: left ? '0.5em' : '0.3em',
307
+ vertical_align: 'middle',
308
+ color: 'inherit',
309
+ background_color: 'inherit',
310
+ }
311
+ }
312
+ )
313
+ ] + arrify(content)
314
+ )
315
+ end
316
+
317
+ # Returns a div element with given content and an icon to left or right.
318
+ #
319
+ # 1. icon: is the name of the (bootstrap) icon
320
+ # 2. items: should be hashes containing menu items
321
+ # e.g. { callback: ->{}, href: '#', content: 'list item'}
322
+ # 3. content: of the div (apart from the icon)
323
+ # 4. pull: which side of div to pull the icon, 'right' or 'left'
324
+ def div_with_dropdown_icon(icon: 'menu-hamburger', items: [], content: nil, pull: 'right')
325
+ tag(
326
+ :div,
327
+ content: arrify(
328
+ drop_down_icon(icon: icon, items: items, pull: pull),
329
+ content
330
+ )
331
+ )
332
+ end
333
+
334
+ # TODO: generalize from bootstrap
335
+ def iconify(icon_name)
336
+ "glyphicon glyphicon-#{icon_name.to_s.gsub('_', '-')}"
337
+ end
338
+
339
+ end
340
+
341
+ end
342
+ end
343
+
@@ -0,0 +1,7 @@
1
+ require 'dom/builder/base'
2
+ require 'dom/builder/helpers'
3
+
4
+ if $dom
5
+ fail "##{__FILE__}[#{__LINE__}] : $dom already defined as '#{$dom}'"
6
+ end
7
+ $dom = CSR::DOM::Builder