goat 0.3.45 → 0.3.46

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.
@@ -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}