dom-rb 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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