Skip to content

Commit

Permalink
allows nodeLists to track deep children, this will probably be useful…
Browse files Browse the repository at this point in the history
… for the ordering bug, but for now, solves #2090
  • Loading branch information
justinbmeyer committed Dec 2, 2015
1 parent 8f4ab3b commit 645a988
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 156 deletions.
5 changes: 3 additions & 2 deletions component/component.js
Expand Up @@ -223,7 +223,8 @@ steal("can/util", "can/view/callbacks","can/view/elements.js","can/view/bindings
// ## Rendering

// Keep a nodeList so we can kill any directly nested nodeLists within this component
var nodeList = can.view.nodeLists.register([], undefined, true);
var nodeList = can.view.nodeLists.register([], undefined, componentTagData.parentNodeList || true, false);
nodeList.expression = "<"+this.tag+">";
teardownFunctions.push(function(){
can.view.nodeLists.unregister(nodeList);
});
Expand Down Expand Up @@ -281,7 +282,7 @@ steal("can/util", "can/view/callbacks","can/view/elements.js","can/view/bindings
// use the same scope as the <content> tag was found within.
lightTemplateData = contentTagData;
}

if(contentTagData.parentNodeList) {
var frag = subtemplate( lightTemplateData.scope, lightTemplateData.options, contentTagData.parentNodeList );
elements.replace([el], frag);
Expand Down
22 changes: 22 additions & 0 deletions component/component_test.js
Expand Up @@ -1761,6 +1761,28 @@ steal("can-simple-dom", "can/util/vdom/build_fragment","can", "can/map/define",
can.trigger(can.$(document), 'click');
});

test("<content> tag doesn't leak memory", function(){

can.Component.extend({
tag: 'my-viewer',
template: can.stache('<div><content /></div>')
});

var template = can.stache('{{#show}}<my-viewer>{{value}}</my-viewer>{{/show}}');

var map = new can.Map({show: true, value: "hi"});

can.append(can.$("#qunit-fixture"), template(map));

map.attr("show", false);
map.attr("show", true);
map.attr("show", false);
map.attr("show", true);
map.attr("show", false);
equal(map._bindings,1, "only one binding");
can.remove(can.$("#qunit-fixture>*"));
});

// PUT NEW TESTS THAT NEED TO TEST AGAINST MUSTACHE JUST ABOVE THIS LINE
}

Expand Down
17 changes: 11 additions & 6 deletions view/live/live.js
Expand Up @@ -106,18 +106,20 @@ steal('can/util',
},
// a helper function that renders something and adds its nodeLists to newNodeLists
// in the right way for both stache and mustache.
renderAndAddToNodeLists = function(newNodeLists, nodeList, render, context, args){
renderAndAddToNodeLists = function(newNodeLists, parentNodeList, render, context, args){
var itemNodeList = [];

if(nodeList) {
nodeLists.register(itemNodeList,null, true);
if(parentNodeList) {
nodeLists.register(itemNodeList,null, parentNodeList, true);
itemNodeList.parentList = parentNodeList;
itemNodeList.expression = "#each SUBEXPRESSION";
}

var itemHTML = render.apply(context, args.concat([itemNodeList])),
itemFrag = getLiveFragment(itemHTML);

var childNodes = can.makeArray(getChildNodes(itemFrag));
if(nodeList) {
if(parentNodeList) {
nodeLists.update(itemNodeList, childNodes);
newNodeLists.push(itemNodeList);
} else {
Expand Down Expand Up @@ -293,6 +295,7 @@ steal('can/util',
},
// Called when items are removed or when the bindings are torn down.
remove = function (ev, items, index, duringTeardown, fullTeardown) {

if (!afterPreviousEvents) {
return;
}
Expand All @@ -313,12 +316,14 @@ steal('can/util',
indexMap[i](i);
}

// adds the falsey section if the list is empty
addFalseyIfEmpty(list, falseyRender, masterNodeList, nodeList);




// don't remove elements during teardown. Something else will probably be doing that.
if(!fullTeardown) {
// adds the falsey section if the list is empty
addFalseyIfEmpty(list, falseyRender, masterNodeList, nodeList);
can.remove(can.$(itemsToRemove));
} else {
nodeLists.unregister(masterNodeList);
Expand Down
75 changes: 58 additions & 17 deletions view/node_lists/node_lists.js
Expand Up @@ -128,6 +128,13 @@ steal('can/util', 'can/view/elements.js', function (can) {
map[id(node, idMap)] = replacements[i];
}
return map;
},
addUnfoundAsDeepChildren = function(list, rMap, foundIds){
for(var repId in rMap) {
if(!foundIds[repId]) {
list.newDeepChildren.push(rMap[repId]);
}
}
};

// ## Registering & Updating
Expand Down Expand Up @@ -195,35 +202,48 @@ steal('can/util', 'can/view/elements.js', function (can) {
oldListLength
].concat(newNodes));

// Replacements are nodes that have replaced the original element this is on.
// We can't simply insert elements because stache does children before parents.
if(nodeList.replacements){
nodeLists.nestReplacements(nodeList);
nodeList.deepChildren = nodeList.newDeepChildren;
nodeList.newDeepChildren = [];
} else {
nodeLists.nestList(nodeList);
}

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]].
// Finds 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, ...]
// If a replacement is not found, it was improperly added, so we add it as a deepChild.
nestReplacements: function(list){
var index = 0,
// temporary id map that is limited to this call
idMap = {},
// replacements are in reverse order in the DOM
rMap = replacementMap(list.replacements, idMap),
rCount = list.replacements.length;
rCount = list.replacements.length,
foundIds = {};

while(index < list.length && rCount) {
var node = list[index],
replacement = rMap[readId(node, idMap)];
nodeId = readId(node, idMap),
replacement = rMap[nodeId];
if( replacement ) {
list.splice( index, itemsInChildListTree(replacement), replacement );
foundIds[nodeId] = true;
rCount--;
}
}
index++;
}
// Only do this if
if(rCount) {
addUnfoundAsDeepChildren(list, rMap, foundIds );
}

list.replacements = [];
},
// ## nodeLists.nestList
Expand Down Expand Up @@ -292,20 +312,27 @@ steal('can/util', 'can/view/elements.js', function (can) {
},
// ## nodeLists.register
// Registers a nodeList and returns the nodeList passed to register
register: function (nodeList, unregistered, parent) {
register: function (nodeList, unregistered, parent, directlyNested) {
// If a unregistered callback has been provided assign it to the nodeList
// as a property to be called when the nodeList is unregistred.
can.cid(nodeList);
nodeList.unregistered = unregistered;
nodeList.parentList = parent;

if(parent === true) {
// this is the "top" parent in stache
nodeList.replacements = [];
} else if(parent) {
// TOOD: remove
parent.replacements.push(nodeList);
if(parent) {
nodeList.deepChildren = [];
nodeList.newDeepChildren = [];
nodeList.replacements = [];
} else {
if(parent !== true) {
if(directlyNested) {
parent.replacements.push(nodeList);
}
else {
parent.newDeepChildren.push(nodeList);
}
}
}
else {
nodeLists.nestList(nodeList);
}

Expand Down Expand Up @@ -333,24 +360,38 @@ steal('can/util', 'can/view/elements.js', function (can) {
} else {
// Recursively unregister each of the child lists in
// the nodeList.
push.apply(nodes, nodeLists.unregister(node));
push.apply(nodes, nodeLists.unregister(node, true));
}
});

can.each(nodeList.deepChildren, function(nodeList){
nodeLists.unregister(nodeList, true);
});

return nodes;
},

// ## nodeLists.unregister
// Unregister's a nodeList and returns the unregistered nodes.
// Call if the nodeList is no longer being updated. This will
// also unregister all child nodeLists.
unregister: function (nodeList) {
var nodes = nodeLists.unregisterChildren(nodeList);
unregister: function (nodeList, isChild) {
var nodes = nodeLists.unregisterChildren(nodeList, true);

// If an 'unregisted' function was provided during registration, remove
// it from the list, and call the function provided.
if (nodeList.unregistered) {
var unregisteredCallback = nodeList.unregistered;
delete nodeList.unregistered;
delete nodeList.replacements;
nodeList.replacements = nodeList.unregistered = null;
if(!isChild) {
var deepChildren = nodeList.parentList && nodeList.parentList.deepChildren;
if(deepChildren) {
var index = deepChildren.indexOf(nodeList);
if(index !== -1) {
deepChildren.splice(index,1);
}
}
}
unregisteredCallback();
}
return nodes;
Expand Down
4 changes: 2 additions & 2 deletions view/stache/mustache_core.js
Expand Up @@ -195,7 +195,7 @@ steal("can/util",

var nodeList = [this];
nodeList.expression = ">" + partialName;
nodeLists.register(nodeList, null, state.directlyNested ? parentSectionNodeList || true : true);
nodeLists.register(nodeList, null, parentSectionNodeList || true, state.directlyNested);

var partialFrag = can.compute(function(){
var localPartialName = partialName;
Expand Down Expand Up @@ -295,7 +295,7 @@ steal("can/util",
nodeList.expression = expressionString;
// register this nodeList.
// Regsiter it with its parent ONLY if this is directly nested. Otherwise, it's unencessary.
nodeLists.register(nodeList, null, state.directlyNested ? parentSectionNodeList || true : true);
nodeLists.register(nodeList, null, parentSectionNodeList || true, state.directlyNested);


// Get the evaluator. This does not need to be cached (probably) because if there
Expand Down
3 changes: 2 additions & 1 deletion view/stache/mustache_helpers.js
Expand Up @@ -41,7 +41,8 @@ steal("can/util", "./utils.js","can/view/live",function(can, utils, live){
// so that if the html is re
var nodeList = [el];
nodeList.expression = "live.list";
can.view.nodeLists.register(nodeList, null, options.nodeList);
can.view.nodeLists.register(nodeList, null, options.nodeList, true);
// runs nest replacements
can.view.nodeLists.update(options.nodeList, [el]);

var cb = function (item, index, parentNodeList) {
Expand Down
1 change: 0 additions & 1 deletion view/stache/test/leak.html

This file was deleted.

0 comments on commit 645a988

Please sign in to comment.