Skip to content

Commit

Permalink
make stache take a nodeList as a 3rd argument, have a component give …
Browse files Browse the repository at this point in the history
…its template a nodeList that is tied to the component's lifecycle for #1593
  • Loading branch information
justinbmeyer committed Apr 3, 2015
1 parent 32b9ff8 commit 78f8357
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 33 deletions.
39 changes: 29 additions & 10 deletions component/component.js
Expand Up @@ -110,7 +110,14 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m
viewModelPropertyUpdates = {},
// the object added to the viewModel
componentScope,
frag;
frag,
// an array of teardown stuff that should happen when the element is removed
teardownFunctions = [],
callTeardownFunctions = function(){
for(var i = 0, len = teardownFunctions.length ; i < len; i++) {
teardownFunctions[i]();
}
};

// ## Scope

Expand Down Expand Up @@ -180,7 +187,7 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m
compute.unbind("change", handler);
} else {
// Make sure we unbind (there's faster ways of doing this)
can.bind.call(el, "removed", function () {
teardownFunctions.push(function () {
compute.unbind("change", handler);
});
// Setup the two-way binding
Expand Down Expand Up @@ -256,7 +263,6 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m

// ## Helpers


// Setup helpers to callback with `this` as the component
can.each(this.helpers || {}, function (val, prop) {
if (can.isFunction(val)) {
Expand All @@ -265,13 +271,14 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m
};
}
});



// Teardown reverse bindings when the element is removed
var tearDownBindings = function(){
teardownFunctions.push(function(){
can.each(handlers, function (handler, prop) {
componentScope.unbind(prop, handlers[prop]);
});
};
});

// ## `events` control

Expand All @@ -287,17 +294,23 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m
var oldDestroy = this._control.destroy;
this._control.destroy = function(){
oldDestroy.apply(this, arguments);
tearDownBindings();
callTeardownFunctions();
};
this._control.on();
} else {
can.bind.call(el, "removed", function () {
tearDownBindings();
callTeardownFunctions();
});
}

// ## Rendering

// Keep a nodeList so we can kill any directly nested nodeLists within this component
var nodeList = can.view.nodeLists.register([], undefined, true);
teardownFunctions.push(function(){
can.view.nodeLists.unregister(nodeList);
});

// If this component has a template (that we've already converted to a renderer)
if (this.constructor.renderer) {
// If `options.tags` doesn't exist set it to an empty object.
Expand Down Expand Up @@ -340,18 +353,24 @@ steal("can/util", "can/view/callbacks","can/control", "can/observe", "can/view/m
}
};
// Render the component's template
frag = this.constructor.renderer(renderedScope, hookupOptions.options.add(options));
frag = this.constructor.renderer(renderedScope, hookupOptions.options.add(options), nodeList);
} else {
// Otherwise render the contents between the
if(hookupOptions.templateType === "legacy") {
frag = can.view.frag(hookupOptions.subtemplate ? hookupOptions.subtemplate(renderedScope, hookupOptions.options.add(options)) : "");
} else {
frag = hookupOptions.subtemplate ? hookupOptions.subtemplate(renderedScope, hookupOptions.options.add(options)) : document.createDocumentFragment();
// we need to be the parent ... or we need to
frag = hookupOptions.subtemplate ?
hookupOptions.subtemplate(renderedScope, hookupOptions.options.add(options), nodeList) :
document.createDocumentFragment();
}

}
// Append the resulting document fragment to the element
can.appendChild(el, frag);

// update the nodeList with the new children so the mapping gets applied
can.view.nodeLists.update(nodeList, el.childNodes);
}
});

Expand Down
31 changes: 12 additions & 19 deletions component/component_test.js
Expand Up @@ -1515,19 +1515,16 @@ steal("can", "can/map/define", "can/component", "can/view/stache" ,"can/route",
});

test('DOM trees not releasing when referencing can.Map inside can.Map in template (#1593)', function() {
var baseTemplate = '{{#if show}}<layout></layout>{{/if}}',
layoutTemplate = '{{#if state.inner}}<internal></internal>{{/if}}',
showLayout = can.compute(true),
var baseTemplate = can.stache('{{#if show}}<my-outside></my-outside>{{/if}}'),
show = can.compute(true),
state = new can.Map({
inner: {
foo: 'bar'
}
inner: 1
});

var removeCount = 0;

can.Component.extend({
tag: 'internal',
tag: 'my-inside',
events: {
removed: function() {
removeCount++;
Expand All @@ -1536,27 +1533,23 @@ steal("can", "can/map/define", "can/component", "can/view/stache" ,"can/route",
});

can.Component.extend({
tag: 'layout',
template: can.stache(layoutTemplate)
tag: 'my-outside',
template: can.stache('{{#if state.inner}}<my-inside></my-inside>{{/if}}')
});

can.append( can.$("#qunit-fixture"), can.stache(baseTemplate)({
show: showLayout,
can.append( can.$("#qunit-fixture"), baseTemplate({
show: show,
state: state
}) );

showLayout(false);
show(false);
state.removeAttr('inner');

equal(removeCount, 1, 'internal removed once');

show(true);
state.attr('inner', 2);

showLayout(true);
state.attr('inner', {
foo: 'bar'
});

showLayout(false);
state.removeAttr('inner');

equal(removeCount, 2, 'internal removed twice');
Expand Down
4 changes: 4 additions & 0 deletions view/node_lists/node_lists.js
Expand Up @@ -203,6 +203,10 @@ steal('can/util', 'can/view/elements.js', function (can) {

return oldNodes;
},
// Goes through each node in the list. [el1, el2, el3, ...]
// Ginds the nodeList for that node in repacements. el1's nodeList might look like [el1, [el2]].
// Replaces that element and any other elements in the node list with the
// nodelist itself. resulting in [ [el1, [el2]], el3, ...]
nestReplacements: function(list){
var index = 0,
// temporary id map that is limited to this call
Expand Down
4 changes: 2 additions & 2 deletions view/stache/html_section.js
Expand Up @@ -61,14 +61,14 @@ steal("can/util","can/view/target","./utils.js","./mustache_core.js",function( c
compile: function(){
var compiled = this.stack.pop().compile();

return function(scope, options){
return function(scope, options, nodeList){
if ( !(scope instanceof can.view.Scope) ) {
scope = new can.view.Scope(scope || {});
}
if ( !(options instanceof mustacheCore.Options) ) {
options = new mustacheCore.Options(options || {});
}
return compiled.hydrate(scope, options);
return compiled.hydrate(scope, options, nodeList);
};
},
push: function(chars){
Expand Down
3 changes: 2 additions & 1 deletion view/stache/mustache_core.js
Expand Up @@ -312,7 +312,8 @@ steal("can/util",
scope: scope,
contexts: scope,
hash: hash,
nodeList: nodeList
nodeList: nodeList,
exprData: exprData
});

args.push(helperOptions);
Expand Down
2 changes: 1 addition & 1 deletion view/stache/stache.js
Expand Up @@ -95,7 +95,7 @@ steal(
var cur = {
tag: state.node && state.node.tag,
attr: state.attr && state.attr.name,
directlyNested: state.sectionElementStack[state.sectionElementStack.length - 1] === "section"
directlyNested: state.sectionElementStack.length ? state.sectionElementStack[state.sectionElementStack.length - 1] === "section" : true
};
return overwrites ? can.simpleExtend(cur, overwrites) : cur;
},
Expand Down
31 changes: 31 additions & 0 deletions view/stache/stache_test.js
Expand Up @@ -3828,5 +3828,36 @@ steal("can/view/stache", "can/view","can/test","can/view/mustache/spec/specs","s

});

test("possible to teardown immediate nodeList (#1593)", function(){
expect(3);
var map = new can.Map({show: true});
var oldBind = map.bind,
oldUnbind = map.unbind;

map.bind = function(){
ok(true, "bound");
return oldBind.apply(this, arguments);
};
map.unbind = function(){
ok(true, "unbound");
return oldUnbind.apply(this, arguments);
};

var template = can.stache("{{#if show}}<span/>TEXT{{/if}}");
var nodeList = can.view.nodeLists.register([], undefined, true);
var frag = template(map,{},nodeList);
can.view.nodeLists.update(nodeList, frag.childNodes);

equal(nodeList.length, 1, "our nodeList has the nodeList of #if show");

can.view.nodeLists.unregister(nodeList);

// has to be async b/c of the temporary bind for performance
stop();
setTimeout(function(){
start();
},10);

});

});

0 comments on commit 78f8357

Please sign in to comment.