forked from peerlibrary/meteor-blaze-components
-
Notifications
You must be signed in to change notification settings - Fork 0
/
lib.coffee
667 lines (525 loc) · 27.8 KB
/
lib.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
getTemplateInstance = (view) ->
while view and not view._templateInstance
view = view.parentView
view?._templateInstance
# More or less the same as aldeed:template-extension's template.get('component') just specialized.
# It allows us to not have a dependency on template-extension package and that we can work with Iron
# Router which has its own DynamicTemplate class which is not patched by template-extension and thus
# does not have .get() method.
templateInstanceToComponent = (templateInstanceFunc) ->
templateInstance = templateInstanceFunc?()
# Iron Router uses its own DynamicTemplate which is not a proper template instance, but it is
# passed in as such, so we want to find the real one before we start searching for the component.
templateInstance = getTemplateInstance templateInstance?.view
while templateInstance
return templateInstance.component if 'component' of templateInstance
templateInstance = getTemplateInstance templateInstance.view.parentView
null
getTemplateInstanceFunction = (view) ->
templateInstance = getTemplateInstance view
->
templateInstance
class ComponentsNamespaceReference
constructor: (@namespace, @templateInstance) ->
# We extend the original dot operator to support {{> Foo.Bar}}. This goes through a getTemplateHelper path, but
# we want to redirect it to the getTemplate path. So we mark it in getTemplateHelper and then here call getTemplate.
originalDot = Spacebars.dot
Spacebars.dot = (value, args...) ->
if value instanceof ComponentsNamespaceReference
return Blaze._getTemplate "#{ value.namespace }.#{ args.join '.' }", value.templateInstance
originalDot value, args...
originalInclude = Spacebars.include
Spacebars.include = (templateOrFunction, args...) ->
# If ComponentsNamespaceReference gets all the way to the Spacebars.include it means that we are in the situation
# where there is both namespace and component with the same name, and user is including a component. But namespace
# reference is created instead (because we do not know in advance that there is no Spacebars.dot call around lookup
# call). So we dereference the reference and try to resolve a template. Of course, a component might not really exist.
if templateOrFunction instanceof ComponentsNamespaceReference
templateOrFunction = Blaze._getTemplate templateOrFunction.namespace, templateOrFunction.templateInstance
originalInclude templateOrFunction, args...
# We override the original lookup method with a similar one, which supports components as well.
#
# Now the order of the lookup will be, in order:
# a helper of the current template
# a property of the current component
# the name of a component
# the name of a template
# global helper
# a property of the data context
#
# Returns a function, a non-function value, or null. If a function is found, it is bound appropriately.
#
# NOTE: This function must not establish any reactive dependencies itself. If there is any reactivity
# in the value, lookup should return a function.
#
# TODO: Should we also lookup for a property of the component-level data context (and template-level data context)?
Blaze._getTemplateHelper = (template, name, templateInstance) ->
isKnownOldStyleHelper = false
if template.__helpers.has name
helper = template.__helpers.get name
if helper is Blaze._OLDSTYLE_HELPER
isKnownOldStyleHelper = true
else if helper?
return wrapHelper bindDataContext(helper), templateInstance
else
return null
# Old-style helper.
if name of template
# Only warn once per helper.
unless isKnownOldStyleHelper
template.__helpers.set name, Blaze._OLDSTYLE_HELPER
unless template._NOWARN_OLDSTYLE_HELPERS
Blaze._warn "Assigning helper with `" + template.viewName + "." + name + " = ...` is deprecated. Use `" + template.viewName + ".helpers(...)` instead."
if template[name]?
return wrapHelper bindDataContext(template[name]), templateInstance
else
return null
return null unless templateInstance
# Do not resolve component helpers if inside Template.dynamic. The reason is that Template.dynamic uses a data context
# value with name "template" internally. But when used inside a component the data context lookup is then resolved
# into a current component's template method and not the data context "template". To force the data context resolving
# Template.dynamic should use "this.template" in its templates, but it does not, so we have a special case here for it.
return null if template.viewName in ['Template.__dynamicWithDataContext', 'Template.__dynamic']
# Blaze.View::lookup should not introduce any reactive dependencies, but we can simply ignore reactivity here because
# template instance probably cannot change without reconstructing the component as well.
component = Tracker.nonreactive ->
templateInstanceToComponent templateInstance
# Component.
if component
# This will first search on the component and then continue with mixins.
if mixinOrComponent = component.getFirstWith null, name
return wrapHelper bindComponent(mixinOrComponent, mixinOrComponent[name]), templateInstance
# A special case to support {{> Foo.Bar}}. This goes through a getTemplateHelper path, but we want to redirect
# it to the getTemplate path. So we mark it and leave to Spacebars.dot to call getTemplate.
# TODO: We should provide a BaseComponent.getComponentsNamespace method instead of accessing components directly.
if name and name of BlazeComponent.components
return new ComponentsNamespaceReference name, templateInstance
null
bindComponent = (component, helper) ->
if _.isFunction helper
_.bind helper, component
else
helper
bindDataContext = (helper) ->
if _.isFunction helper
->
data = Blaze.getData()
data ?= {}
helper.apply data, arguments
else
helper
wrapHelper = (f, templateFunc) ->
# XXX COMPAT WITH METEOR 1.0.3.2
return Blaze._wrapCatchingExceptions f, 'template helper' unless Blaze.Template._withTemplateInstanceFunc
return f unless _.isFunction f
->
self = @
args = arguments
Blaze.Template._withTemplateInstanceFunc templateFunc, ->
Blaze._wrapCatchingExceptions(f, 'template helper').apply self, args
addEvents = (view, component) ->
eventsList = component.events()
throw new Error "'events' method from the component '#{ component.componentName() or 'unnamed' }' did not return a list of event maps." unless _.isArray eventsList
for events in eventsList
eventMap = {}
for spec, handler of events
do (spec, handler) ->
eventMap[spec] = (args...) ->
event = args[0]
currentView = Blaze.getView event.currentTarget
templateInstance = getTemplateInstanceFunction currentView
if Template._withTemplateInstanceFunc
withTemplateInstanceFunc = Template._withTemplateInstanceFunc
else
# XXX COMPAT WITH METEOR 1.0.3.2.
withTemplateInstanceFunc = (templateInstance, f) ->
f()
# We set template instance based on the current target so that inside event handlers
# BlazeComponent.currentComponent() returns the component of event target.
withTemplateInstanceFunc templateInstance, ->
# We set view based on the current target so that inside event handlers
# BlazeComponent.currentData() (and Blaze.getData() and Template.currentData())
# returns data context of event target and not component/template.
Blaze._withCurrentView currentView, ->
handler.apply component, args
# Make sure CoffeeScript does not return anything. Returning from event
# handlers is deprecated.
return
Blaze._addEventMap view, eventMap
return
originalGetTemplate = Blaze._getTemplate
Blaze._getTemplate = (name, templateInstance) ->
# Blaze.View::lookup should not introduce any reactive dependencies, so we are making sure it is so.
template = Tracker.nonreactive ->
componentParent = templateInstanceToComponent templateInstance
BlazeComponent.getComponent(name)?.renderComponent componentParent
return template if template and (template instanceof Blaze.Template or _.isFunction template)
originalGetTemplate name
registerHooks = (template, hooks) ->
if template.onCreated
template.onCreated hooks.onCreated
template.onRendered hooks.onRendered
template.onDestroyed hooks.onDestroyed
else
# XXX COMPAT WITH METEOR 1.0.3.2.
template.created = hooks.onCreated
template.rendered = hooks.onRendered
template.destroyed = hooks.onDestroyed
registerFirstCreatedHook = (template, onCreated) ->
if template._callbacks
template._callbacks.created.unshift onCreated
else
# XXX COMPAT WITH METEOR 1.0.3.2.
oldCreated = template.created
template.created = ->
onCreated.call @
oldCreated?.call @
class BlazeComponent extends BaseComponent
# TODO: Figure out how to do at the BaseComponent level?
@getComponentForElement: (domElement) ->
return null unless domElement
# This uses the same check if the argument is a DOM element that Blaze._DOMRange.forElement does.
throw new Error "Expected DOM element." unless domElement.nodeType is Node.ELEMENT_NODE
templateInstanceToComponent =>
Blaze.getView(domElement)?._templateInstance
mixins: ->
[]
# When a component is used as a mixin, createMixins will call this method to set the parent
# component using this mixin. Extend this method if you want to do any action when parent is
# set, for example, add dependency mixins to the parent. Make sure you call super as well.
# TODO: Should this be a list of parents? So that the same mixin instance could be reused across components? And serve to communicate across them?
mixinParent: (mixinParent) ->
@_componentInternals ?= {}
# Setter.
if mixinParent
@_componentInternals.mixinParent = mixinParent
# To allow chaining.
return @
# Getter.
@_componentInternals.mixinParent or null
requireMixin: (nameOrMixin) ->
assert @_componentInternals?.mixins
# Do not do anything if mixin is already required. This allows multiple mixins to call requireMixin
# in mixinParent method to add dependencies, but if dependencies are already there, nothing happens.
return @ if @getMixin nameOrMixin
if _.isString nameOrMixin
# It could be that the component is not a real instance of the BlazeComponent class,
# so it might not have a constructor pointing back to a BlazeComponent subclass.
if @constructor.getComponent
mixinInstanceComponent = @constructor.getComponent nameOrMixin
else
mixinInstanceComponent = BlazeComponent.getComponent nameOrMixin
throw new Error "Unknown mixin '#{ nameOrMixin }'." unless mixinInstanceComponent
mixinInstance = new mixinInstanceComponent()
else if _.isFunction nameOrMixin
mixinInstance = new nameOrMixin()
else
mixinInstance = nameOrMixin
# We add mixin before we call mixinParent so that dependencies come after this mixin,
# and that we prevent possible infinite loops because of circular dependencies.
# TODO: For now we do not provide an official API to add dependencies before the mixin itself.
@_componentInternals.mixins.push mixinInstance
# We allow mixins to not be components, so methods are not necessary available.
# Set mixin parent.
if mixinInstance.mixinParent
mixinInstance.mixinParent @
assert.equal mixinInstance.mixinParent(), @
# Maybe mixin has its own mixins as well.
mixinInstance.createMixins?()
# If a mixin is adding a dependency using requireMixin after its mixinParent class (for example, in onCreate)
# and this is this dependency mixin, the view might already be created or rendered and callbacks were
# already called, so we should call them manually here as well. But only if he view has not been destroyed
# already. For those mixins we do not call anything, there is little use for them now.
unless @_componentInternals.templateInstance?.view.isDestroyed
mixinInstance.onCreated?() if not @_componentInternals.inOnCreated and @_componentInternals.templateInstance?.view.isCreated
mixinInstance.onRendered?() if not @_componentInternals.inOnRendered and @_componentInternals.templateInstance?.view.isRendered
# To allow chaining.
@
# Method to instantiate all mixins.
createMixins: ->
@_componentInternals ?= {}
# To allow calling it multiple times, but non-first calls are simply ignored.
return if @_componentInternals.mixins
@_componentInternals.mixins = []
for mixin in @mixins()
@requireMixin mixin
# To allow chaining.
@
getMixin: (nameOrMixin) ->
assert @_componentInternals?.mixins
if _.isString nameOrMixin
for mixin in @_componentInternals.mixins
# We do not require mixins to be components, but if they are, they can
# be referenced based on their component name.
mixinComponentName = mixin.componentName?() or null
return mixin if mixinComponentName and mixinComponentName is nameOrMixin
else
for mixin in @_componentInternals.mixins
# nameOrMixin is a class.
if mixin.constructor is nameOrMixin
return mixin
# nameOrMixin is an instance, or something else.
else if mixin is nameOrMixin
return mixin
null
# Calls the component (if afterComponentOrMixin is null) or the first next mixin
# after afterComponentOrMixin it finds, and returns the result.
callFirstWith: (afterComponentOrMixin, propertyName, args...) ->
mixin = @getFirstWith afterComponentOrMixin, propertyName
# TODO: Should we throw an error here? Something like calling a function which does not exist?
return unless mixin
if _.isFunction mixin[propertyName]
return mixin[propertyName] args...
else
return mixin[propertyName]
getFirstWith: (afterComponentOrMixin, propertyName) ->
assert @_componentInternals?.mixins
# If afterComponentOrMixin is not provided, we start with the component.
if not afterComponentOrMixin
return @ if propertyName of @
# And continue with mixins.
found = true
# If afterComponentOrMixin is the component, we start with mixins.
else if afterComponentOrMixin and afterComponentOrMixin is @
found = true
else
found = false
# TODO: Implement with a map between mixin -> position, so that we do not have to seek to find a mixin.
for mixin in @_componentInternals.mixins
return mixin if found and propertyName of mixin
found = true if mixin is afterComponentOrMixin
null
# This class method more or less just creates an instance of a component and calls its renderComponent
# method. But because we want to allow passing arguments to the component in templates, we have some
# complicated code around to extract and pass those arguments. It is similar to how data context is
# passed to block helpers. In a data context visible only to the block helper template.
# TODO: This could be made less hacky. See https://github.com/meteor/meteor/issues/3913
@renderComponent: (componentParent) ->
Tracker.nonreactive =>
componentClass = @
try
# We check data context in a non-reactive way, because we want just to peek into it
# and determine if data context contains component arguments or not. And while
# component arguments might change through time, the fact that they are there at
# all or not ("args" template helper was used or not) does not change through time.
# So we can check that non-reactively.
data = Template.currentData()
catch error
# The exception can be thrown when there is no current view which happens when
# there is no data context yet, thus also no arguments were provided through
# "args" template helper, so we just continue normally.
data = null
if data?.constructor isnt share.argumentsConstructor
component = new componentClass()
return component.renderComponent componentParent
# Arguments were provided through "args" template helper.
# We want to reactively depend on the data context for arguments, so we return a function
# instead of a template. Function will be run inside an autorun, a reactive context.
->
# We cannot use Template.getData() inside a normal autorun because current view is not defined inside
# a normal autorun. But we do not really have to depend reactively on the current view, only on the
# data context of a known (the closest Blaze.With) view. So we get this view by ourselves.
currentWith = Blaze.getView 'with'
# By default dataVar in the Blaze.With view uses ReactiveVar with default equality function which
# sees all objects as different. So invalidations are triggered for every data context assignments
# even if data has not really changed. This is why we use our own ReactiveVar with EJSON.equals
# which we keep updated inside an autorun. Because it uses EJSON.equals it will invalidate our
# function only if really changes. See https://github.com/meteor/meteor/issues/4073
reactiveArguments = new ReactiveVar [], EJSON.equals
# This autorun is nested in the outside autorun so it gets stopped
# automatically when the outside autorun gets invalidated.
assert Tracker.active
Tracker.autorun (computation) ->
data = currentWith.dataVar.get()
assert.equal data?.constructor, share.argumentsConstructor
reactiveArguments.set data._arguments
# Use arguments for the constructor. Here we register a reactive dependency on our own ReactiveVar.
component = new componentClass reactiveArguments.get()...
template = component.renderComponent componentParent
# It has to be the first callback so that other have a correct data context.
registerFirstCreatedHook template, ->
# Arguments were passed in as a data context. Restore original (parent) data
# context. Same logic as in Blaze._InOuterTemplateScope.
@view.originalParentView = @view.parentView
@view.parentView = @view.parentView.parentView.parentView
return template
renderComponent: (componentParent) ->
# To make sure we do not introduce any reactive dependency. This is a conscious design decision.
# Reactivity should be changing data context, but components should be more stable, only changing
# when structure change in rendered DOM. You can change the component you are including (or pass
# different arguments) reactively though.
Tracker.nonreactive =>
component = @
# If mixins have not yet been created.
component.createMixins()
# We do not allow template to be a reactive method.
componentTemplate = component.template()
if _.isString componentTemplate
templateBase = Template[componentTemplate]
throw new Error "Template '#{ componentTemplate }' cannot be found." unless templateBase
else if componentTemplate
templateBase = componentTemplate
else
throw new Error "Template for the component '#{ component.componentName() or 'unnamed' }' not provided."
# Create a new component template based on the Blaze template. We want our own template
# because the same Blaze template could be reused between multiple components.
# TODO: Should we cache these templates based on (componentName, templateBase) pair? We could use tow levels of ES6 Maps, componentName -> templateBase -> template. What about component arguments changing?
template = new Blaze.Template "BlazeComponent.#{ component.componentName() or 'unnamed' }", templateBase.renderFunction
# We on purpose do not reuse helpers, events, and hooks. Templates are used only for HTML rendering.
@component._componentInternals ?= {}
registerHooks template,
onCreated: ->
# @ is a template instance.
if componentParent
# component.componentParent is reactive, so we use Tracker.nonreactive just to make sure we do not leak any reactivity here.
Tracker.nonreactive =>
# TODO: Should we support that the same component can be rendered multiple times in parallel? How could we do that? For different component parents or only the same one?
assert not component.componentParent()
# We set the parent only when the component is created, not just constructed.
component.componentParent componentParent
componentParent.addComponentChild component
@view._onViewRendered =>
# Attach events the first time template instance renders.
return unless @view.renderCount is 1
# We first add event handlers from the component, then mixins.
componentOrMixin = null
while componentOrMixin = @component.getFirstWith componentOrMixin, 'events'
addEvents @view, componentOrMixin
@component = component
# TODO: Should we support that the same component can be rendered multiple times in parallel? How could we do that? For different component parents or only the same one?
assert not @component._componentInternals.templateInstance
@component._componentInternals.templateInstance = @
try
# We have to know if we should call onCreated on the mixin inside the requireMixin or not. We want to call
# it only once. If it requireMixin is called from onCreated of another mixin, then it will be added at the
# end and we will get it here at the end. So we should not call onCreated inside requireMixin because then
# onCreated would be called twice.
@component._componentInternals.inOnCreated = true
componentOrMixin = null
while componentOrMixin = @component.getFirstWith componentOrMixin, 'onCreated'
componentOrMixin.onCreated()
finally
delete @component._componentInternals.inOnCreated
@component._componentInternals.isCreated ?= new ReactiveVar true
@component._componentInternals.isCreated.set true
# Maybe we are re-rendering the component. So let's initialize variables just to be sure.
@component._componentInternals.isRendered ?= new ReactiveVar false
@component._componentInternals.isRendered.set false
@component._componentInternals.isDestroyed ?= new ReactiveVar false
@component._componentInternals.isDestroyed.set false
onRendered: ->
# @ is a template instance.
try
# Same as for onCreated above.
@component._componentInternals.inOnRendered = true
componentOrMixin = null
while componentOrMixin = @component.getFirstWith componentOrMixin, 'onRendered'
componentOrMixin.onRendered()
finally
delete @component._componentInternals.inOnRendered
@component._componentInternals.isRendered ?= new ReactiveVar true
@component._componentInternals.isRendered.set true
Tracker.nonreactive =>
assert.equal @component._componentInternals.isCreated.get(), true
onDestroyed: ->
@autorun (computation) =>
# We wait for all children components to be destroyed first.
# See https://github.com/meteor/meteor/issues/4166
return if @component.componentChildren().length
computation.stop()
Tracker.nonreactive =>
assert.equal @component._componentInternals.isCreated.get(), true
@component._componentInternals.isCreated.set false
@component._componentInternals.isRendered ?= new ReactiveVar false
@component._componentInternals.isRendered.set false
@component._componentInternals.isDestroyed ?= new ReactiveVar true
@component._componentInternals.isDestroyed.set true
# @ is a template instance.
componentOrMixin = null
while componentOrMixin = @component.getFirstWith componentOrMixin, 'onDestroyed'
componentOrMixin.onDestroyed()
if componentParent
# The component has been destroyed, clear up the parent.
component.componentParent null
componentParent.removeComponentChild component
# Remove the reference so that it is clear that template instance is not available anymore.
delete @component._componentInternals.templateInstance
template
template: ->
@callFirstWith(@, 'template') or @constructor.componentName()
onCreated: ->
onRendered: ->
onDestroyed: ->
isCreated: ->
@_componentInternals ?= {}
@_componentInternals.isCreated ?= new ReactiveVar false
@_componentInternals.isCreated.get()
isRendered: ->
@_componentInternals ?= {}
@_componentInternals.isRendered ?= new ReactiveVar false
@_componentInternals.isRendered.get()
isDestroyed: ->
@_componentInternals ?= {}
@_componentInternals.isDestroyed ?= new ReactiveVar false
@_componentInternals.isDestroyed.get()
insertDOMElement: (parent, node, before) ->
before ?= null
if parent and node and (node.parentNode isnt parent or node.nextSibling isnt before)
parent.insertBefore node, before
return
moveDOMElement: (parent, node, before) ->
before ?= null
if parent and node and (node.parentNode isnt parent or node.nextSibling isnt before)
parent.insertBefore node, before
return
removeDOMElement: (parent, node) ->
if parent and node and node.parentNode is parent
parent.removeChild node
return
events: ->
[]
# Component-level data context. Reactive. Use this to always get the
# top-level data context used to render the component.
data: ->
if @_componentInternals?.templateInstance?.view
return Blaze.getData @_componentInternals.templateInstance.view
undefined
# Caller-level data context. Reactive. Use this to get in event handlers the data
# context at the place where event originated (target context). In template helpers
# the data context where template helpers were called. In onCreated, onRendered,
# and onDestroyed, the same as @data(). Inside a template this is the same as this.
@currentData: ->
Blaze.getData()
# Method should never be overridden. The implementation should always be exactly the same as class method implementation.
currentData: ->
@constructor.currentData()
# Useful in templates to get a reference to the component.
component: ->
@
# Caller-level component. In most cases the same as @, but in event handlers
# it returns the component at the place where event originated (target component).
@currentComponent: ->
# Template.instance() registers a dependency on the template instance data context,
# but we do not need that. We just need a template instance to resolve a component.
Tracker.nonreactive =>
templateInstanceToComponent Template.instance
# Method should never be overridden. The implementation should always be exactly the same as class method implementation.
currentComponent: ->
@constructor.currentComponent()
firstNode: ->
view = @_componentInternals.templateInstance.view
if view._domrange and not view.isDestroyed
view._domrange.firstNode()
else
null
lastNode: ->
view = @_componentInternals.templateInstance.view
if view._domrange and not view.isDestroyed
view._domrange.lastNode()
else
null
# We copy utility methods ($, findAll, autorun, subscribe, etc.) from the template instance prototype.
for methodName, method of Blaze.TemplateInstance::
do (methodName, method) ->
BlazeComponent::[methodName] = (args...) ->
@_componentInternals.templateInstance[methodName] args...