Class: Goat::Component

Inherits:
Object show all
Includes:
HTMLHelpers
Defined in:
lib/goat.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from HTMLHelpers

#jsesc

Constructor Details

#initializeComponent

Returns a new instance of Component.



1247
1248
1249
1250
# File 'lib/goat.rb', line 1247

def initialize
  @id = 'dom_' + String.random(10)
  @parent = Dynamic[:parent] if Dynamic.variable?(:parent)
end

Instance Attribute Details

#handlersObject (readonly)

Returns the value of attribute handlers.



1122
1123
1124
# File 'lib/goat.rb', line 1122

def handlers
  @handlers
end

#idObject (readonly)

Returns the value of attribute id.



1122
1123
1124
# File 'lib/goat.rb', line 1122

def id
  @id
end

#initargsObject (readonly)

Returns the value of attribute initargs.



1122
1123
1124
# File 'lib/goat.rb', line 1122

def initargs
  @initargs
end

#pageObject (readonly)

Returns the value of attribute page.



1122
1123
1124
# File 'lib/goat.rb', line 1122

def page
  @page
end

#paramsObject (readonly)

Returns the value of attribute params.



1122
1123
1124
# File 'lib/goat.rb', line 1122

def params
  @params
end

#parentObject

Returns the value of attribute parent.



1123
1124
1125
# File 'lib/goat.rb', line 1123

def parent
  @parent
end

Class Method Details

.__cssObject



1288
# File 'lib/goat.rb', line 1288

def self.__css; @css; end

.__scriptObject



1279
# File 'lib/goat.rb', line 1279

def self.__script; @script; end

.clientside(js) ⇒ Object



1291
1292
1293
1294
# File 'lib/goat.rb', line 1291

def self.clientside(js)
  script(js)
  @wired = true
end

.css(css) ⇒ Object



1286
# File 'lib/goat.rb', line 1286

def self.css(css); @css = css; end

.from_skel(skel) ⇒ Object



1202
1203
1204
1205
1206
# File 'lib/goat.rb', line 1202

def self.from_skel(skel)
  inst = Goat.new_without_initialize(self)
  inst.load_skel(skel)
  inst
end

.get_rpc(name, opts = {}, &blk) ⇒ Object



1317
1318
1319
# File 'lib/goat.rb', line 1317

def self.get_rpc(name, opts={}, &blk)
  rpc(name, opts.merge(:is_get => true), &blk)
end

.live_enabledObject



1127
# File 'lib/goat.rb', line 1127

def self.live_enabled; @live_enabled = true; end

.live_enabled?Boolean

Returns:

  • (Boolean)


1128
# File 'lib/goat.rb', line 1128

def self.live_enabled?; @live_enabled; end

.live_rpc(name, opts = {}, &blk) ⇒ Object



1313
1314
1315
# File 'lib/goat.rb', line 1313

def self.live_rpc(name, opts={}, &blk)
  rpc(name, opts.merge(:live => true), &blk)
end

.rerender(skel) ⇒ Object



1147
1148
1149
1150
1151
1152
1153
1154
# File 'lib/goat.rb', line 1147

def self.rerender(skel)
  Profile.in(:create_component)
  c = Kernel.fetch_class(skel.cls).from_skel(skel)
  c.deserialize(skel.live_state)
  Profile.out(:create_component)

  c.rerender
end

.rerender_and_update(spec) ⇒ Object



1141
1142
1143
1144
1145
# File 'lib/goat.rb', line 1141

def self.rerender_and_update(spec)
  cls = self.name
  to_process = StateSrvClient.live_components(cls, spec)
  rerender_and_update_inner(to_process)
end

.rerender_and_update_inner(to_process) ⇒ Object



1131
1132
1133
1134
1135
1136
1137
1138
1139
# File 'lib/goat.rb', line 1131

def self.rerender_and_update_inner(to_process)
  # send updates as we calculate them; don't wait to compute everything
  unless to_process.empty?
    rerender(to_process.shift)
    unless to_process.empty?
      EM.next_tick { rerender_and_update_inner(to_process) }
    end
  end
end

.rpc(name, opts = {}, &blk) ⇒ Object



1306
1307
1308
1309
1310
1311
# File 'lib/goat.rb', line 1306

def self.rpc(name, opts={}, &blk)
  Goat.rpc_handlers[self.name.to_s] ||= {}
  Goat.rpc_handlers[self.name.to_s][name.to_s] = opts

  App.send(:define_method, "rpc_#{name}", blk)
end

.scope_css(css, prefix, dot_or_hash) ⇒ Object



1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
# File 'lib/goat.rb', line 1363

def self.scope_css(css, prefix, dot_or_hash)
  # #%foo, .%bar, etc
  rep = css.gsub(/(.)%(\w+)([\ \t\{])/) do |str|
    p = $1
    m = $2
    ws = $3
    "#{p}#{prefix}_#{m}#{ws}"
  end
  rep.gsub(/(^|\W)%([\W\{])/) do |str|
    "#{$1}#{dot_or_hash}#{prefix}#{$2}"
  end
end

.scoped_cssObject



1376
1377
1378
# File 'lib/goat.rb', line 1376

def self.scoped_css
  scope_css(self.__css, self.name, '.')
end

.script(script) ⇒ Object



1277
# File 'lib/goat.rb', line 1277

def self.script(script); @script = AutoBind.process(script); end

.wired?Boolean

Returns:

  • (Boolean)


1296
# File 'lib/goat.rb', line 1296

def self.wired?; @wired; end

Instance Method Details

#__cssObject



1289
# File 'lib/goat.rb', line 1289

def __css; @css; end

#__scriptObject



1280
# File 'lib/goat.rb', line 1280

def __script; @script; end

#_domObject



1347
1348
1349
1350
1351
1352
1353
# File 'lib/goat.rb', line 1347

def _dom
  if Dynamic.variable?(:in_a_dom) && !Dynamic.variable(:parent)
    raise "You must set the parent of a component before inserting it into the DOM tree. Try calling #parent=."
  end

  Dynamic.let(:parent => self, :in_a_dom => true) { self.dom }
end

#clientside_argsObject



1298
# File 'lib/goat.rb', line 1298

def clientside_args; []; end

#clientside_instanceObject



1300
1301
1302
1303
1304
# File 'lib/goat.rb', line 1300

def clientside_instance
  args = [id, @parent ? @parent.id : nil, clientside_args]
  argjson = args.to_json[1..-2] # strip the braces
  "(new #{self.class.name}('#{self.class.name}', #{argjson}))"
end

#component(body) ⇒ Object



1333
1334
1335
1336
1337
1338
1339
1340
1341
# File 'lib/goat.rb', line 1333

def component(body)
  attrs = {:id => id, :class => component_name_hierarchy.join(' ')}

  if body.kind_of?(Array) && body.first == :tbody
    [:tbody, attrs, body[1..-1]]
  else
    [:div, attrs, body]
  end
end

#component_name_hierarchy(cls = self.class) ⇒ Object



1321
1322
1323
1324
1325
1326
1327
# File 'lib/goat.rb', line 1321

def component_name_hierarchy(cls=self.class)
  if cls == Component
    []
  else
    component_name_hierarchy(cls.superclass) + [cls.name]
  end
end

#css(css) ⇒ Object



1287
# File 'lib/goat.rb', line 1287

def css(css); @css = css; end

#deserialize(state) ⇒ Object



1260
1261
# File 'lib/goat.rb', line 1260

def deserialize(state)
end

#dom_as_expandedObject



1355
1356
1357
# File 'lib/goat.rb', line 1355

def dom_as_expanded
  @expanded_dom
end

#dom_html(dom) ⇒ Object



1359
1360
1361
# File 'lib/goat.rb', line 1359

def dom_html(dom)
  DOMTools::HTMLBuilder.new(dom).html
end

#erb(*args, &blk) ⇒ Object



1267
1268
1269
# File 'lib/goat.rb', line 1267

def erb(*args, &blk)
  ERBRunner.new(nil, nil, @params).erb(*args, &blk)
end

#expanded_domObject



1343
1344
1345
# File 'lib/goat.rb', line 1343

def expanded_dom
  @expanded_dom ||= DOMTools.expanded_dom(inject_prefixes(self._dom))
end

#inject_prefixes(dom) ⇒ Object



1329
1330
1331
# File 'lib/goat.rb', line 1329

def inject_prefixes(dom)
  DOMTools.inject_prefixes(self.id, dom)
end

#live_enabled?Boolean

Returns:

  • (Boolean)


1129
# File 'lib/goat.rb', line 1129

def live_enabled?; self.class.live_enabled?; end

#live_for(spec) ⇒ Object



1252
1253
1254
# File 'lib/goat.rb', line 1252

def live_for(spec)
  @live_spec = spec
end

#live_specObject



1263
1264
1265
# File 'lib/goat.rb', line 1263

def live_spec
  @live_spec ? @live_spec : raise("No live_spec specified")
end

#load_skel(skel) ⇒ Object



1208
1209
1210
1211
1212
1213
# File 'lib/goat.rb', line 1208

def load_skel(skel)
  @id = skel.id
  @pgid = skel.pgid
  @old_dom = skel.dom
  @live_spec = skel.live_spec
end

#model_changed(item, notif) ⇒ Object



1384
1385
1386
# File 'lib/goat.rb', line 1384

def model_changed(item, notif)
  render
end

#partial_erb(*args, &blk) ⇒ Object



1271
1272
1273
# File 'lib/goat.rb', line 1271

def partial_erb(*args, &blk)
  ERBRunner.new(nil, nil, @params).partial_erb(*args, &blk)
end

#pgid=(id) ⇒ Object



1189
# File 'lib/goat.rb', line 1189

def pgid=(id); @pgid = id; end

#register_callback(callback) ⇒ Object



1388
1389
1390
1391
1392
1393
1394
1395
1396
# File 'lib/goat.rb', line 1388

def register_callback(callback)
  # if @callbacks.values.include?(callback)
  #   @callbacks.to_a.detect{|k, v| k if v == callback}
  # else
    key = String.random
    @callbacks[key] = callback
    key
  # end
end

#rerenderObject



1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
# File 'lib/goat.rb', line 1160

def rerender
  Profile.in(:rerender)

  helper = ExpansionHelper.new

  Profile.in(:expansion)
  Dynamic.let(:expander => helper) do
    @expanded_dom = self.expanded_dom
  end
  Profile.out(:expansion)

  Profile.in(:diff)
  diff = DOMDiff.diff(@old_dom, @expanded_dom, @id)
  Profile.out(:diff)

  if diff.size == 0
    # pass
    $stderr.puts "No changes"
  elsif diff.size < 0 # TODO constant
    rerender_partially(diff)
  else
    rerender_fully(helper.components)
  end
rescue JustRerenderError # partial updater gave up
  rerender_fully(helper.components)
ensure
  Profile.out(:rerender)
end

#rerender_fully(cs) ⇒ Object



1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
# File 'lib/goat.rb', line 1224

def rerender_fully(cs)
  Profile.in(:distillation)
  distiller = DOMDistiller.new(@expanded_dom, cs + [self])
  js = distiller.script
  css = distiller.style
  Profile.out(:distillation)

  Profile.in(:update_send)

  UpdateDispatcher.component_updated(
    @pgid,
    ComponentUpdate.new(
      self.skel,
      [{'type' => 'rep',
        'html' => dom_html(@expanded_dom),
        'js' => js,
        'css' => css,
        'parent' => @id,
        'position' => nil}]))

  Profile.out(:update_send)
end

#rerender_partially(diff) ⇒ Object



1215
1216
1217
1218
1219
1220
1221
1222
# File 'lib/goat.rb', line 1215

def rerender_partially(diff)
  $stderr.puts "should do #{diff.inspect} in #{self.class.name}/#{@id}"
  StateSrvClient.component_updated(
    self.class.name,
    @id,
    @expanded_dom
  )
end

#scoped_cssObject



1380
1381
1382
# File 'lib/goat.rb', line 1380

def scoped_css
  self.class.scope_css(self.__css, @id, '#')
end

#script(script) ⇒ Object



1278
# File 'lib/goat.rb', line 1278

def script(script); @script = AutoBind.process(script); end

#serializeObject



1256
1257
1258
# File 'lib/goat.rb', line 1256

def serialize
  {}
end

#skelObject



1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
# File 'lib/goat.rb', line 1191

def skel
  ComponentSkeleton.new(
    'pgid' => @pgid,
    'class' => self.class.name,
    'id' => @id,
    'live_spec' => live_spec,
    'live_state' => serialize,
    'dom' => @expanded_dom
  )
end

#updateObject



1156
1157
1158
# File 'lib/goat.rb', line 1156

def update
  rerender
end

#wire_scriptObject



1282
1283
1284
# File 'lib/goat.rb', line 1282

def wire_script
  "Goat.wireComponent('#{id}', #{clientside_instance})"
end