goat 0.3.45 → 0.3.46

Sign up to get free protection for your applications and to get access to all the features.
@@ -223,6 +223,24 @@ module Goat
223
223
  nil
224
224
  end
225
225
 
226
+ def components_updated(msg)
227
+ ensure_keys(msg, %w{txn pgid updates})
228
+
229
+ pgid = msg['pgid']
230
+ txn = msg['txn']
231
+ updates = msg['updates'].map{|u| ComponentUpdate.from_hash(u)}
232
+
233
+ updates.map(&:added).flatten(1).each do |c|
234
+ @registry.add_component(pgid, c)
235
+ end
236
+ updates.map(&:removed).flatten.each{|c| @registry.delete_component(c)}
237
+
238
+ updates.each {|u| handle_component_update(txn, u)}
239
+ send_updates(pgid)
240
+
241
+ {'type' => 'update_ack', 'components' => updates.map{|u| u.skel.id}}
242
+ end
243
+
226
244
  def live_components(msg)
227
245
  ensure_keys(msg, %w{class spec})
228
246
 
@@ -239,7 +257,11 @@ module Goat
239
257
  def fetch_component(msg)
240
258
  ensure_keys(msg, %w{id})
241
259
 
242
- @registry.find_id(msg['id']).to_hash
260
+ if c = @registry.find_id(msg['id'])
261
+ c.to_hash
262
+ else
263
+ raise "Couldn't find component #{msg['id']}"
264
+ end
243
265
  end
244
266
 
245
267
  def send_message(chsrv_id, type, msg)
@@ -307,16 +329,6 @@ module Goat
307
329
  end
308
330
  end
309
331
 
310
- def components_updated(msg)
311
- ensure_keys(msg, %w{txn pgid updates})
312
-
313
- txn = msg['txn']
314
- msg['updates'].each{|u| handle_component_update(txn, ComponentUpdate.from_hash(u))}
315
- send_updates(msg['pgid'])
316
-
317
- nil
318
- end
319
-
320
332
  def page_connected(msg)
321
333
  ensure_keys(msg, %w{pgid chsrv version})
322
334
 
@@ -1,6 +1,6 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'goat'
3
- s.version = '0.3.45'
3
+ s.version = '0.3.46'
4
4
  s.summary = 'Pre-release beta version of Goat'
5
5
  s.author = 'Patrick Collison'
6
6
  s.email = 'patrick@collison.ie'
@@ -31,7 +31,7 @@ spec = Gem::Specification.new do |s|
31
31
  lib/goat/dynamic.rb
32
32
  lib/goat/extn.rb
33
33
  lib/goat/goat.js
34
- lib/goat/html.rb
34
+ lib/goat/dom.rb
35
35
  lib/goat/js/component.js
36
36
  lib/goat/mongo.rb
37
37
  lib/goat/net-common.rb
@@ -24,7 +24,7 @@ require 'goat/common'
24
24
  require 'goat/net-common'
25
25
  require 'goat/notifications'
26
26
  require 'goat/extn'
27
- require 'goat/html'
27
+ require 'goat/dom'
28
28
  require 'goat/static'
29
29
  require 'goat/autobind'
30
30
  require 'goat/state-srv'
@@ -806,10 +806,7 @@ module Goat
806
806
 
807
807
  def register_page(distiller)
808
808
  live = distiller.all_components.select(&:live_enabled?)
809
- pg_spec = live.map do |c|
810
- c.pgid = @id
811
- c.skel
812
- end
809
+ pg_spec = live.map(&:skel)
813
810
 
814
811
  StateSrvClient.register_page(@id, self.user, pg_spec)
815
812
  end
@@ -822,7 +819,7 @@ module Goat
822
819
 
823
820
  def inject_html_from_dom(canvas)
824
821
  exp = nil
825
- helper = ExpansionHelper.new
822
+ helper = ExpansionHelper.new(@id)
826
823
 
827
824
  Dynamic.let(:expander => helper) do
828
825
  exp = DOMTools.expanded_dom(self._dom)
@@ -856,11 +853,6 @@ module Goat
856
853
  render_to_layout(canvas, layout)
857
854
  end
858
855
 
859
- #def render_component(c)
860
- # canvas.components << c
861
- # c.html
862
- #end
863
-
864
856
  def response
865
857
  Rack::Response.new(self.html, 200, {})
866
858
  end
@@ -898,183 +890,25 @@ module Goat
898
890
  end
899
891
  end
900
892
 
901
- class JustRerenderError < RuntimeError; end
902
-
903
- class DOMDiff
904
- def self.diff(old, new, id)
905
- self.new(old, new, id).diff
906
- end
907
-
908
- def initialize(old, new, id)
909
- @old = old
910
- @new = new
911
- @id = id
912
- @diffs = []
913
- end
914
-
915
- def diff
916
- nested_application(minimized_changes(desc(@old, @new, @id)))
917
- end
918
-
919
- def nested_application(ch)
920
- applied = Set.new
921
-
922
- ch.sort_by{|x| x[3]}.each do |c|
923
- if (c[0] == :rem || c[0] == :add) && !applied.include?(c)
924
- if n = ch.detect{|x| x != c && x[2] == c[2] && x[3] >= c[3]}
925
- n[3] += (c[0] == :rem) ? -1 : 1
926
- end
927
- end
928
-
929
- applied << c
930
- end
931
-
932
- ch
933
- end
934
-
935
- def minimized_changes(ch)
936
- new = []
937
- merged = Set.new
938
- ch.each do |c|
939
- if c[0] == :rem
940
- if n = ch.detect{|x| x[0] == :add && x[2] == c[2] && x[3] == c[3]}
941
- new << [:rep, *n[1..3]]
942
- merged << n
943
- else
944
- new << c
945
- end
946
- else
947
- new << c
948
- end
949
- end
950
- new.reject{|c| merged.include?(c)}
951
- end
952
-
953
- def added(new, par, pos=nil); [:add, new, par, pos]; end
954
- def removed(old, par, pos=nil); [:rem, old, par, pos]; end
955
-
956
- def dom_node?(node)
957
- node.is_a?(Array) && node.first.is_a?(Symbol)
958
- end
959
- def tag(node); node[0]; end
960
- def attrs(node); node[1] if node[1].is_a?(Hash); end
961
- def body(node); node[1].is_a?(Hash) ? node[2..-1] : node[1..-1]; end
962
- def domid(node); attrs(node) ? attrs(node)[:id] : nil; end
963
-
964
- def localized_change(dold, dnew, par, changes)
965
- old = dold.element
966
- new = dnew.element
967
- #$stderr.puts "changes: #{changes.inspect} / #{old.inspect} / #{par.inspect}"
968
- if changes.all?{|ch| ch[2].nil?} && par
969
- [added(new, par, dold.position), removed(old, par, dnew.position)]
970
- else
971
- changes
972
- end
973
- end
974
-
975
- def is_replacement?(d1, d2)
976
- d1 && d2 && (
977
- (d1.action == '+' && d2.action == '-') ||
978
- (d1.action == '-' && d2.action == '+'))
979
- end
980
-
981
- def old_and_new(d1, d2)
982
- d1.action == '+' ? [d2, d1] : [d1, d2]
983
- end
984
-
985
- def array_desc(old, new, par)
986
- #$stderr.puts "array_desc #{old.inspect} #{new.inspect} #{par.inspect}"
987
-
988
- chgs = Diff::LCS.diff(old, new).map do |diff|
989
- #$stderr.puts "diff: #{diff.inspect}"
990
- if is_replacement?(diff[0], diff[1])
991
- dold, dnew = old_and_new(diff[0], diff[1])
992
- old = dold.element
993
- new = dnew.element
994
- if dom_node?(old) && dom_node?(new) && tag(old) == tag(new) && compare_attrs(old, new)
995
- localized_change(dold, dnew, par, desc(body(old), body(new), domid(old)))
996
- elsif old.is_a?(Array) && new.is_a?(Array) #&& old.all?{|x| !dom_node?(x)} && new.all?{|x| !dom_node?(x)}
997
- array_desc(old, new, par)
998
- else
999
- [added(new, par, diff[1].position), removed(old, par, diff[0].position)]
1000
- end
1001
- else
1002
- if diff.size == 1
1003
- diff = diff[0]
1004
- if diff.action == '+'
1005
- [added(diff.element, par, diff.position)]
1006
- elsif diff.action == '-'
1007
- [removed(diff.element, par, diff.position)]
1008
- else
1009
- raise "Don't understand diff in a bad way"
1010
- end
1011
- else
1012
- $stderr.puts "Don't understand diff"
1013
- #$stderr.puts "Diff failed"
1014
- raise JustRerenderError
1015
- end
1016
- end
1017
- end
1018
-
1019
- chgs.flatten(1)
1020
- end
1021
-
1022
- def compare_attrs(a, b)
1023
- if a.is_a?(Hash) && b.is_a?(Hash)
1024
- a_, b_ = a.clone, b.clone
1025
- a.select{|k,v| a_.delete(k) if v =~ /^dom_/}
1026
- b.select{|k,v| b_.delete(k) if v =~ /^dom_/}
1027
- a_ == b_
1028
- else
1029
- false
1030
- end
1031
- end
1032
-
1033
- def node_desc(old, new, par)
1034
- #$stderr.puts "node_desc #{old.inspect} #{new.inspect} #{par.inspect}"
893
+ class ExpansionHelper
894
+ attr_reader :components
1035
895
 
1036
- if tag(old) != tag(new) || !compare_attrs(old, new)
1037
- [removed(old, par), added(new, par)]
1038
- else # only body changed (maybe)
1039
- bold = body(old)
1040
- bnew = body(new)
1041
- if bold && bnew
1042
- desc(bold, bnew, domid(old))
1043
- elsif bold && !bnew
1044
- [removed(bold, domid(old))]
1045
- else
1046
- [added(bnew, domid(old))]
1047
- end
1048
- end
896
+ def initialize(pgid)
897
+ @components = {}
898
+ @pgid = pgid
1049
899
  end
1050
900
 
1051
- def desc(old, new, par)
1052
- #$stderr.puts "desc #{old.inspect} #{new.inspect} #{par.inspect}"
1053
- #$stderr.puts "desc #{self.class.name} #{par.inspect}"
1054
-
1055
- if old.class != new.class
1056
- [added(new, par), removed(old, par)]
1057
- elsif old.is_a?(Array)
1058
- dom_node?(old) ? node_desc(old, new, domid(old)) : array_desc(old, new, par)
1059
- elsif old.is_a?(String)
1060
- if old != new
1061
- [added(new, par), removed(old, par)]
1062
- end
1063
- else
1064
- raise TypeError.new("Unknown object in the DOM: #{old.class} #{new.class}")
1065
- end
901
+ def component_used(c)
902
+ @components[c.id] = c
903
+ c.pgid = @pgid
1066
904
  end
1067
- end
1068
905
 
1069
- class ExpansionHelper
1070
- attr_reader :components
1071
-
1072
- def initialize
1073
- @components = Set.new
906
+ def component(id)
907
+ @components[id]
1074
908
  end
1075
909
 
1076
- def component_used(c)
1077
- @components << c
910
+ def components
911
+ @components.values
1078
912
  end
1079
913
  end
1080
914
 
@@ -1107,6 +941,11 @@ module Goat
1107
941
  end
1108
942
 
1109
943
  def self.dispatch_updates(txn, pgid, ups)
944
+ pgups = ups.map{|u| u.skel.pgid}.uniq
945
+ if pgups.size > 1
946
+ raise "Got updates for different pages: #{pgups.inspect}"
947
+ end
948
+
1110
949
  StateSrvClient.components_updated(
1111
950
  txn,
1112
951
  pgid,
@@ -1150,7 +989,8 @@ module Goat
1150
989
  def self.rerender_and_update_inner(to_process)
1151
990
  # send updates as we calculate them; don't wait to compute everything
1152
991
  unless to_process.empty?
1153
- rerender(to_process.shift)
992
+ skel = to_process.shift
993
+ rerender(skel)
1154
994
  unless to_process.empty?
1155
995
  EM.next_tick { rerender_and_update_inner(to_process) }
1156
996
  end
@@ -1179,7 +1019,7 @@ module Goat
1179
1019
  def rerender
1180
1020
  Profile.in(:rerender)
1181
1021
 
1182
- helper = ExpansionHelper.new
1022
+ helper = ExpansionHelper.new(@pgid)
1183
1023
 
1184
1024
  Profile.in(:expansion)
1185
1025
  Dynamic.let(:expander => helper) do
@@ -1188,19 +1028,21 @@ module Goat
1188
1028
  Profile.out(:expansion)
1189
1029
 
1190
1030
  Profile.in(:diff)
1191
- diff = DOMDiff.diff(@old_dom, @expanded_dom, @id)
1031
+ diff = DOMTools::DOMDiff.dom_diff(@old_dom, @expanded_dom, @id)
1192
1032
  Profile.out(:diff)
1193
1033
 
1034
+ Goat.logd "#{self.class}/#{self.id} diff: " + diff.inspect
1035
+
1194
1036
  if diff.size == 0
1195
1037
  # pass
1196
- $stderr.puts "No changes"
1197
- elsif diff.size < 0 # TODO constant
1198
- rerender_partially(diff)
1038
+ elsif diff.size <= 3
1039
+ rerender_partially(helper, diff)
1199
1040
  else
1200
- rerender_fully(helper.components)
1041
+ rerender_fully(helper, helper.components)
1201
1042
  end
1202
- rescue JustRerenderError # partial updater gave up
1203
- rerender_fully(helper.components)
1043
+ rescue Goat::DOMTools::PartialUpdateFailed
1044
+ Goat.logw "Partial update failed; falling back on a full rerender"
1045
+ rerender_fully(helper, helper.components)
1204
1046
  ensure
1205
1047
  Profile.out(:rerender)
1206
1048
  end
@@ -1231,16 +1073,48 @@ module Goat
1231
1073
  @live_spec = skel.live_spec
1232
1074
  end
1233
1075
 
1234
- def rerender_partially(diff)
1235
- $stderr.puts "should do #{diff.inspect} in #{self.class.name}/#{@id}"
1236
- StateSrvClient.component_updated(
1237
- self.class.name,
1238
- @id,
1239
- @expanded_dom
1240
- )
1076
+ def rerender_partially(expander, diff)
1077
+ updates = []
1078
+
1079
+ added, removed = [], []
1080
+
1081
+ diff.each do |d|
1082
+ type, par, pos, tree, id = d
1083
+ cs = []
1084
+ u = {
1085
+ 'type' => type.to_s,
1086
+ 'parent' => par,
1087
+ 'position' => pos
1088
+ }
1089
+
1090
+ if type == :add
1091
+ added += DOMTools.dom_components(tree).map{|cid| expander.component(cid)}
1092
+
1093
+ distiller = DOMDistiller.new(tree, added + [self])
1094
+ u['html'] = dom_html(tree)
1095
+ u['js'] = distiller.script
1096
+ u['css'] = distiller.style
1097
+ elsif type == :rem
1098
+ b = DOMTools.body(tree).first
1099
+ u['tag'] = DOMTools.tag(b).to_s if DOMTools.dom_node?(b)
1100
+ u['id'] = DOMTools.attrs(tree) ? DOMTools.attrs(tree)[:id] : nil
1101
+
1102
+ removed += DOMTools.dom_components(tree) # just want the raw IDs
1103
+ else
1104
+ raise "Bad diff: #{d.inspect}"
1105
+ end
1106
+
1107
+ raise "Bad position" unless u['position'].kind_of?(Integer)
1108
+
1109
+ updates << u
1110
+ end
1111
+
1112
+ UpdateDispatcher.component_updated(
1113
+ @pgid,
1114
+ ComponentUpdate.new(self.skel, updates, added.map(&:skel), removed))
1241
1115
  end
1242
1116
 
1243
- def rerender_fully(cs)
1117
+ def rerender_fully(expander, cs)
1244
1118
  Profile.in(:distillation)
1245
1119
  distiller = DOMDistiller.new(@expanded_dom, cs + [self])
1246
1120
  js = distiller.script
@@ -1249,16 +1123,19 @@ module Goat
1249
1123
 
1250
1124
  Profile.in(:update_send)
1251
1125
 
1126
+ added = (DOMTools.dom_components(@expanded_dom) - DOMTools.dom_components(@old_dom)).map{|cid| expander.component(cid)}
1127
+ removed = DOMTools.dom_components(@old_dom) - DOMTools.dom_components(@expanded_dom)
1128
+
1252
1129
  UpdateDispatcher.component_updated(
1253
1130
  @pgid,
1254
1131
  ComponentUpdate.new(
1255
1132
  self.skel,
1256
1133
  [{'type' => 'rep',
1257
- 'html' => dom_html(@expanded_dom),
1134
+ 'html' => dom_html(component(@expanded_dom)),
1258
1135
  'js' => js,
1259
1136
  'css' => css,
1260
1137
  'parent' => @id,
1261
- 'position' => nil}]))
1138
+ 'position' => nil}], added.map(&:skel), removed))
1262
1139
 
1263
1140
  Profile.out(:update_send)
1264
1141
  end
@@ -1354,13 +1231,28 @@ module Goat
1354
1231
  DOMTools.inject_prefixes(self.id, dom)
1355
1232
  end
1356
1233
 
1357
- def component(body)
1234
+ def component(tree)
1235
+ # if the first element of the tree is a tbody tag, we don't enclose it in
1236
+ # a div (as we usually do for almost all components), since we'll likely
1237
+ # end up in an (illegal) <table><div><tbody> situation. Instead, we use
1238
+ # the tbody itself as the container. In doing so, we clobber existing
1239
+ # attributes on the tbody: if you have a top-level tbody tag in a Component,
1240
+ # we're stealing it for our own uses.
1241
+
1358
1242
  attrs = {:id => id, :class => component_name_hierarchy.join(' ')}
1359
1243
 
1360
- if body.kind_of?(Array) && body.first == :tbody
1361
- [:tbody, attrs, body[1..-1]]
1244
+ if DOMTools.car_tag(tree) == :tbody
1245
+ [:tbody, attrs, DOMTools.body(DOMTools.normalized_tags(tree).first)]
1362
1246
  else
1363
- [:div, attrs, body]
1247
+ [:div, attrs, tree]
1248
+ end
1249
+ end
1250
+
1251
+ def unencapsulated(tree)
1252
+ if DOMTools.car_tag(tree) == :tbody
1253
+ DOMTools.body(DOMTools.normalized_tags(tree).first)
1254
+ else
1255
+ tree
1364
1256
  end
1365
1257
  end
1366
1258
 
@@ -1405,10 +1297,6 @@ module Goat
1405
1297
  self.class.scope_css(self.__css, @id, '#')
1406
1298
  end
1407
1299
 
1408
- def model_changed(item, notif)
1409
- render
1410
- end
1411
-
1412
1300
  def register_callback(callback)
1413
1301
  # if @callbacks.values.include?(callback)
1414
1302
  # @callbacks.to_a.detect{|k, v| k if v == callback}