Skip to content

Commit

Permalink
Optimize template binding to only re-render template if 'data' or 'if…
Browse files Browse the repository at this point in the history
…/ifnot' values have changed.
  • Loading branch information
mbest committed Sep 29, 2012
1 parent 12e1caf commit a48f3d6
Showing 1 changed file with 37 additions and 26 deletions.
63 changes: 37 additions & 26 deletions src/templating/templating.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
ko.utils.domData.set(element, templateComputedDomDataKey, (newComputed && newComputed.isActive()) ? newComputed : undefined);
}

var templateParamCacheDomDataKey = '__ko_templateParamData';
ko.bindingHandlers['template'] = {
'init': function(element, valueAccessor) {
// Support anonymous templates
Expand All @@ -171,44 +172,54 @@
container = ko.utils.moveCleanedNodesToContainerElement(templateNodes); // This also removes the nodes from their current parent
new ko.templateSources.anonymousTemplate(element)['nodes'](container);
}
// Store object for tracking changes to binding parameters
ko.utils.domData.set(element, templateParamCacheDomDataKey, {});
return { 'controlsDescendantBindings': true };
},
'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var bindingValue = ko.utils.unwrapObservable(valueAccessor());
var templateName;
var shouldDisplay = true;

if (typeof bindingValue == "string") {
templateName = bindingValue;
} else {
templateName = bindingValue['name'];
var templateName = ko.utils.unwrapObservable(valueAccessor()),
options = {},
shouldDisplay = true,
dataValue,
paramCache = ko.utils.domData.get(element, templateParamCacheDomDataKey),
didUpdate = true,
templateComputed = null;

if (typeof templateName != "string") {
options = templateName;
templateName = options['name'];

// Support "if"/"ifnot" conditions
if ('if' in bindingValue)
shouldDisplay = shouldDisplay && ko.utils.unwrapObservable(bindingValue['if']);
if ('ifnot' in bindingValue)
shouldDisplay = shouldDisplay && !ko.utils.unwrapObservable(bindingValue['ifnot']);
}
if ('if' in options)
shouldDisplay = ko.utils.unwrapObservable(options['if']);
if (shouldDisplay && 'ifnot' in options)
shouldDisplay = !ko.utils.unwrapObservable(options['ifnot']);

var templateComputed = null;
dataValue = ko.utils.unwrapObservable(options['data']);
}

if ((typeof bindingValue === 'object') && ('foreach' in bindingValue)) { // Note: can't use 'in' operator on strings
if ('foreach' in options) {
// Render once for each data point (treating data set as empty if shouldDisplay==false)
var dataArray = (shouldDisplay && bindingValue['foreach']) || [];
templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, /* options: */ bindingValue, element, bindingContext);
var dataArray = (shouldDisplay && options['foreach']) || [];
templateComputed = ko.renderTemplateForEach(templateName || element, dataArray, options, element, bindingContext);
} else if (!shouldDisplay) {
ko.virtualElements.emptyNode(element);
} else if (paramCache.shouldDisplay && paramCache.dataValue === dataValue) {
// If shouldDisplay is still true and dataValue didn't change, don't re-render the template
didUpdate = false;
} else {
if (shouldDisplay) {
// Render once for this single data point (or use the viewModel if no data was provided)
var innerBindingContext = (typeof bindingValue == 'object') && ('data' in bindingValue)
? bindingContext['createChildContext'](ko.utils.unwrapObservable(bindingValue['data']), bindingValue['as']) // Given an explitit 'data' value, we create a child binding context for it
: bindingContext; // Given no explicit 'data' value, we retain the same binding context
templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, /* options: */ bindingValue, element);
} else
ko.virtualElements.emptyNode(element);
// Render once for this single data point (or use the viewModel if no data was provided)
var innerBindingContext = ('data' in options) ?
bindingContext['createChildContext'](dataValue, options['as']) : // Given an explitit 'data' value, we create a child binding context for it
bindingContext; // Given no explicit 'data' value, we retain the same binding context
templateComputed = ko.renderTemplate(templateName || element, innerBindingContext, options, element);
}
paramCache.shouldDisplay = shouldDisplay;
paramCache.dataValue = dataValue;

// It only makes sense to have a single template computed per element (otherwise which one should have its output displayed?)
disposeOldComputedAndStoreNewOne(element, templateComputed);
if (didUpdate)
disposeOldComputedAndStoreNewOne(element, templateComputed);
}
};

Expand Down

0 comments on commit a48f3d6

Please sign in to comment.