SteveSanderson/knockout — Issue #362: attr binding should only setAttribute if changed
Author: huntc
Created: 2012-03-07 09:51:37+00:00
State: open

Setting some attributes of an element can cause a heavy duty operation to occur, even if the value set is equal to the value that is already there. A case in point is calling setAttribute on the src attribute of an iframe element. This will cause an iframe to be reloaded.

The following logic should be applied within the attr binding when setting an attribute instead of calling just setAttribute:

attrValueStr = attrValue.toString();
existingAttrValueStr = element.getAttribute(attrName);
if (existingAttrValueStr === null
|| existingAttrValueStr !== attrValueStr) {
element.setAttribute(attrName, attrValueStr);
}

Meanwhile I have created a work-around "attrIf" binding that is based on "attr" and performs the comparison:

attrIf = {
'update' : function(element, valueAccessor, allBindingsAccessor) {
var attrName, attrValue, attrValueStr, existingAttrValueStr, value;
value = ko.utils.unwrapObservable(valueAccessor()) || {};
for ( attrName in value) {
if (typeof attrName === "string") {
attrValue = ko.utils.unwrapObservable(value[attrName]);

// To cover cases like "attr: { checked:someProp }", we want
// to remove the attribute entirely when someProp is a "no
// value"-like value (strictly null, false, or undefined)
// (because the absence of the "checked" attr is how to mark
// an element as not checked, etc.)
if ((attrValue === false) || (attrValue === null)
|| (attrValue === undefined)) {
element.removeAttribute(attrName);
} else {
// We are careful to mutate existing values as this can
// cause some browsers to perform an action e.g. WebKit
// likes to reload an iframe if the src attribute is set
// despite whether it is the same as the existing src
// value.
attrValueStr = attrValue.toString();
existingAttrValueStr = element.getAttribute(attrName);
if (existingAttrValueStr === null
|| existingAttrValueStr !== attrValueStr) {
element.setAttribute(attrName, attrValueStr);
}
}
}
}
}
};