goat 0.2.12 → 0.3.0

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/lib/goat/html.rb CHANGED
@@ -1,147 +1,265 @@
1
1
  module Goat
2
- class HTMLBuilder
3
- class ::String
4
- def to_html(builder, comp)
5
- builder.string_to_html(self, comp)
2
+ module DOMTools
3
+ class Traverser
4
+ def initialize(tree, dlg, transpose)
5
+ @tree = tree
6
+ @dlg = dlg
7
+ @transpose = transpose
6
8
  end
7
- end
8
9
 
9
- class ::Array
10
- def to_html(builder, comp)
11
- raise InvalidBodyError.new(self) if self.include?(nil)
12
- builder.array_to_html(self, comp)
10
+ def dom_node?(node)
11
+ node.is_a?(Array) && node.first.is_a?(Symbol)
13
12
  end
14
- end
15
13
 
16
- class InvalidBodyError < RuntimeError
17
- attr_reader :body
14
+ def tag(node); node[0]; end
15
+ def attrs(node); node[1] if node[1].is_a?(Hash); end
16
+ def body(node); node[1].is_a?(Hash) ? node[2..-1] : node[1..-1]; end
17
+ def domid(node); attrs(node) ? attrs(node)[:id] : nil; end
18
18
 
19
- def initialize(body)
20
- super("Invalid body: #{body.inspect}")
21
- @body = body
19
+ def to_node(tag, attrs, body)
20
+ tag = [tag]
21
+ tag << attrs if attrs
22
+ tag << body
22
23
  end
23
- end
24
-
25
- class TagBuilder
26
- # TODO: gmail trick of only a single onclick() handler
27
24
 
28
- def self.build(tag, attrs, body, comp)
29
- self.new(tag, attrs, body, comp).dispatch
25
+ def replacement_block
26
+ @rep = nil
27
+ @replacement_block ||= lambda {|new| @rep = new}
30
28
  end
31
29
 
32
- def initialize(tag, attrs, body, comp)
33
- @tag = tag
34
- @attrs = attrs
35
- @body = body
36
- @comp = comp
30
+ def traverse(node)
31
+ if node.is_a?(String)
32
+ @dlg.string(node, &replacement_block)
33
+ @rep || node
34
+ elsif dom_node?(node)
35
+ @dlg.node(node, &replacement_block)
36
+ rep = @rep || node
37
+
38
+ if @transpose
39
+ if rep != node
40
+ rep
41
+ else
42
+ to_node(tag(node), attrs(node), traverse(body(node)))
43
+ end
44
+ else
45
+ traverse(body(node))
46
+ end
47
+ elsif node.is_a?(Array)
48
+ if node.include?(nil)
49
+ raise "Invalid array: #{node.inspect}"
50
+ end
37
51
 
38
- rewrite_attrs
52
+ if node.size == 1
53
+ traverse(node.first)
54
+ else
55
+ if @transpose
56
+ node.map{|x| traverse(x)}
57
+ else
58
+ node.each{|x| traverse(x)}
59
+ end
60
+ end
61
+ elsif node.kind_of?(Component)
62
+ @dlg.component(node, &replacement_block)
63
+ @rep || node
64
+ else
65
+ raise "Unknown object in the dom: #{node.inspect}"
66
+ end
39
67
  end
40
68
 
41
- def rewrite_attrs
42
- new = {}
69
+ def traverse!
70
+ traverse(@tree)
71
+ end
43
72
 
44
- @attrs.map_to_hash do |k, v|
45
- if k == :onclick && v.kind_of?(Hash)
46
- raise "Invalid onclick" unless v.include?(:target) && v.include?(:selector)
47
- target = v[:target]
48
- sel = v[:selector]
73
+ class BlockTraverser
74
+ # would be infinitely nicer if you could just yield from a block
75
+ def initialize(blk, transpose)
76
+ @blk = blk
77
+ @transpose = transpose
78
+ end
49
79
 
50
- key = @comp.register_handler(target, sel)
51
- new[k] = "Goat.remoteDispatch('#{@comp.id}', '#{key}')"
52
- new[:href] = "#"
53
- elsif k == :class && v.kind_of?(Array)
54
- new[:class] = @attrs[:class].join(' ')
55
- else
56
- new[k] = v
80
+ def node(node, &blk)
81
+ if @transpose
82
+ @blk.call(node, blk)
83
+ else
84
+ @blk.call(node)
57
85
  end
58
86
  end
59
87
 
60
- @attrs = new
61
- end
62
-
63
- def form_tag
64
- target = @attrs.delete(:target)
65
- [:form, @attrs, @body + [[:input, {:type => 'hidden', :name => '_component', :value => @comp.id}]]]
66
- end
67
-
68
- def a_tag
69
- if action = @attrs.delete(:action)
70
- # [:a, {}, 'dead']
71
- # key = @comp.register_callback(action)
72
- # @attrs[:onclick] = "Goat.remoteDispatch('#{@comp.id}', '#{key}')"
73
- # @attrs[:href] = '#'
74
- # [:a, @attrs, @body]
75
- action[:target] ||= @comp
76
- action[:args] ||= []
77
- key = @comp.register_callback(ActionProc.new { action })
78
- @attrs[:onclick] = "return Goat.remoteDispatch('#{@comp.id}', '#{key}')"
79
- @attrs[:href] = '#'
80
- [:a, @attrs, @body]
81
- else
82
- identity
88
+ def string(str, &blk)
89
+ if @transpose
90
+ @blk.call(str, blk)
91
+ else
92
+ @blk.call(str)
93
+ end
83
94
  end
84
- end
85
95
 
86
- def identity
87
- [@tag, @attrs, @body]
96
+ def component(c, &blk)
97
+ if @transpose
98
+ @blk.call(c, blk)
99
+ else
100
+ @blk.call(c)
101
+ end
102
+ end
88
103
  end
89
-
90
- def dispatch
91
- meth = "#{@tag}_tag".to_sym
92
104
 
93
- if self.respond_to?(meth)
94
- self.send(meth)
105
+ def self.traverse(tree, dlg=nil, transpose=false, &blk)
106
+ d = nil
107
+
108
+ if dlg
109
+ d = dlg
110
+ elsif blk
111
+ d = BlockTraverser.new(blk, transpose)
95
112
  else
96
- identity
113
+ raise "Need a delegate"
97
114
  end
115
+
116
+ self.new(tree, d, transpose).traverse!
117
+ end
118
+
119
+ def self.transpose(tree, dlg=nil, &blk)
120
+ traverse(tree, dlg, true, &blk)
98
121
  end
99
122
  end
100
123
 
101
- def standalone_tags
102
- %w{br img input}
124
+ def self.traverse(tree, dlg=nil, &blk); Traverser.traverse(tree, dlg, &blk); end
125
+ def self.transpose(tree, dlg=nil, &blk); Traverser.transpose(tree, dlg, &blk); end
126
+
127
+ def self.expanded_dom(dom)
128
+ DOMTools.transpose(dom) do |elt, update|
129
+ if elt.kind_of?(Component)
130
+ raise "Component #{elt} has no ID: was super's initialize called?" unless elt.id
131
+ Dynamic[:expander].component_used(elt)
132
+ update.call(elt.component(elt.expanded_dom))
133
+ end
134
+ end
103
135
  end
104
136
 
105
- def inject_prefix(attrs, id)
106
- attrs.map_to_hash {|k, v| [k, v.prefix_ns(id)]}
137
+ def self.inject_prefixes(id, dom)
138
+ DOMTools.traverse(dom) do |elt|
139
+ if elt.kind_of?(Array) && elt.first.is_a?(Symbol) && elt[1].is_a?(Hash)
140
+ attrs = elt[1]
141
+ elt[1] = attrs.map_to_hash do |k, v|
142
+ if v.kind_of?(String)
143
+ [k, v.prefix_ns(id)]
144
+ elsif v.kind_of?(Array) && HTMLBuilder::ARRAY_ATTRS.include?(k)
145
+ [k, v]
146
+ else
147
+ raise "Invalid object #{v.inspect} to get a prefix in dom:\n#{dom.inspect}"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ dom
153
+ end
154
+
155
+ class ::String
156
+ def to_html(builder)
157
+ builder.string_to_html(self)
158
+ end
107
159
  end
108
160
 
109
- def attrs_to_html(attrs)
110
- attrs.map {|k, v| "#{k}=\"#{v}\""}.join(' ')
161
+ class ::Array
162
+ def to_html(builder)
163
+ raise InvalidBodyError.new(self) if self.include?(nil)
164
+ builder.array_to_html(self)
165
+ end
111
166
  end
112
167
 
113
- def string_to_html(str, comp)
114
- str
168
+ class InvalidBodyError < RuntimeError
169
+ attr_reader :body
170
+
171
+ def initialize(body)
172
+ super("Invalid body: #{body.inspect}")
173
+ @body = body
174
+ end
115
175
  end
116
176
 
117
- def array_to_html(ar, comp)
118
- if ar.first.kind_of?(Symbol)
119
- tag = ar[0]
120
- have_attrs = ar[1].is_a?(Hash)
121
- attrs = have_attrs ? ar[1] : {}
122
- body = ar[(have_attrs ? 2 : 1)..-1]
177
+ class HTMLBuilder
178
+ ARRAY_ATTRS = [:class]
123
179
 
124
- tag, attrs, body = TagBuilder.build(tag, attrs, body, comp)
125
- attrs = inject_prefix(attrs, comp.id)
126
- lonely = standalone_tags.include?(tag.to_s)
180
+ class TagBuilder
181
+ # TODO: gmail trick of only a single onclick() handler
127
182
 
128
- open_tag = "<#{tag}#{attrs.empty? ? '' : (' ' + attrs_to_html(attrs))}>"
129
- body_html = body.empty? ? '' : body.map{|x| x.to_html(self, comp)}.join
130
- close_tag = (lonely ? '' : "</#{tag}>")
183
+ def self.build(tag, attrs, body)
184
+ self.new(tag, attrs, body).dispatch
185
+ end
186
+
187
+ def initialize(tag, attrs, body)
188
+ @tag = tag
189
+ @attrs = attrs
190
+ @body = body
191
+
192
+ rewrite_attrs
193
+ end
194
+
195
+ def rewrite_attrs
196
+ new = {}
197
+
198
+ @attrs.map_to_hash do |k, v|
199
+ if k == :class && v.kind_of?(Array)
200
+ new[:class] = @attrs[:class].join(' ')
201
+ else
202
+ new[k] = v
203
+ end
204
+ end
205
+
206
+ @attrs = new
207
+ end
208
+
209
+ def identity
210
+ [@tag, @attrs, @body]
211
+ end
212
+
213
+ def dispatch
214
+ meth = "#{@tag}_tag".to_sym
131
215
 
132
- "#{open_tag}#{body_html}#{close_tag}"
133
- else
134
- ar.map{|x| x.to_html(self, comp)}.join
216
+ if self.respond_to?(meth)
217
+ self.send(meth)
218
+ else
219
+ identity
220
+ end
221
+ end
135
222
  end
136
- end
137
223
 
138
- def initialize(component, input)
139
- @input = input
140
- @component = component
141
- end
224
+ def standalone_tags
225
+ %w{br img input}
226
+ end
227
+
228
+ def attrs_to_html(attrs)
229
+ attrs.map {|k, v| "#{k}=\"#{v}\""}.join(' ')
230
+ end
231
+
232
+ def string_to_html(str)
233
+ str
234
+ end
235
+
236
+ def array_to_html(ar)
237
+ if ar.first.kind_of?(Symbol)
238
+ tag = ar[0]
239
+ have_attrs = ar[1].is_a?(Hash)
240
+ attrs = have_attrs ? ar[1] : {}
241
+ body = ar[(have_attrs ? 2 : 1)..-1]
142
242
 
143
- def html
144
- @input.to_html(self, @component)
243
+ tag, attrs, body = TagBuilder.build(tag, attrs, body)
244
+ lonely = standalone_tags.include?(tag.to_s)
245
+
246
+ open_tag = "<#{tag}#{attrs.empty? ? '' : (' ' + attrs_to_html(attrs))}>"
247
+ body_html = body.empty? ? '' : body.map{|x| x.to_html(self)}.join
248
+ close_tag = (lonely ? '' : "</#{tag}>")
249
+
250
+ "#{open_tag}#{body_html}#{close_tag}"
251
+ else
252
+ ar.map{|x| x.to_html(self)}.join
253
+ end
254
+ end
255
+
256
+ def initialize(input)
257
+ @input = input
258
+ end
259
+
260
+ def html
261
+ @input.to_html(self)
262
+ end
145
263
  end
146
264
  end
147
265
  end
@@ -1,29 +1,32 @@
1
1
  var Component = Class.extend({
2
- init: function(cls, id) {
2
+ init: function(cls, id, args) {
3
3
  this.id = id;
4
4
  this.cls = cls;
5
+ this.loaded = false;
6
+
7
+ for(var k in args) {
8
+ this[k] = args[k]
9
+ }
5
10
  },
6
11
 
7
12
  onLoad: function() {
8
13
  this.node = $("#" + this.id);
9
14
  },
15
+
16
+ isLoaded: function() {
17
+ return this.loaded;
18
+ },
19
+
20
+ load: function() {
21
+ this.onLoad();
22
+ this.loaded = true;
23
+ },
10
24
 
11
- rpc: function(name, args, callback, er) {
12
- args['_id'] = this.id;
13
- args['_rpc'] = name;
14
- args['_component'] = this.cls;
15
- $.ajax({
16
- type: 'POST',
17
- url: '/rpc',
18
- data: args,
19
- success: callback,
20
- error: er,
21
- dataType: 'json',
22
- async: true
23
- });
25
+ rpc: function(name) {
26
+ return new GoatRPC(this, name);
24
27
  },
25
28
 
26
29
  $: function(sel) {
27
30
  return $("#" + this.id + " " + sel);
28
31
  }
29
- });
32
+ });
@@ -0,0 +1,38 @@
1
+ require 'term/ansicolor'
2
+
3
+ module Log
4
+ def self.logname; File.basename($0); end
5
+ def self.logpath; "/tmp/#{logname}.log"; end
6
+ def self.lockpath; "/tmp/#{logname}.lock"; end
7
+ def self.logf; @log ||= File.open(logpath, 'a'); end
8
+
9
+ def self.log(lvl, msg, termmsg=nil)
10
+ logf.puts("#{Time.now.asctime} #{msg}")
11
+ logf.flush
12
+
13
+ termmsg ||= msg
14
+ $stderr.puts(termmsg[0..90])
15
+ end
16
+
17
+ def self.log_message(msg)
18
+ log("#{msg['type']} #{msg.inspect}", "#{Term::ANSIColor.green(msg['type'])}: #{msg.inspect}")
19
+ end
20
+ end
21
+
22
+ def log(lvl, msg, tmsg=nil); Log.log(lvl, msg, tmsg); end
23
+ def logd(msg); log(:debug, msg); end
24
+ def logw(msg); log(:warn, msg); end
25
+ def loge(msg); log(:error, msg); end
26
+
27
+ module Goat
28
+ # for use with EM::Connection
29
+ module JSONMessages
30
+ def send_message(type, msg)
31
+ send_data(msg.merge('type' => type).to_json + "\n")
32
+ end
33
+
34
+ def send_messages(msgs)
35
+ send_data(msgs.map{|msg| msg[1].merge('type' => msg[0])}.to_json + "\n")
36
+ end
37
+ end
38
+ end