Skip to content

Commit

Permalink
Merge branch 'master' into 54-support-xhtml
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveSanderson committed Feb 26, 2012
2 parents dd179ad + 08448b7 commit 1f334da
Show file tree
Hide file tree
Showing 12 changed files with 187 additions and 115 deletions.
23 changes: 12 additions & 11 deletions build/output/knockout-latest.debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -941,25 +941,26 @@ ko.observable = function (initialValue) {
}

ko.observable['fn'] = {
__ko_proto__: ko.observable,

"equalityComparer": function valuesArePrimitiveAndEqual(a, b) {
var oldValueIsPrimitive = (a === null) || (typeof(a) in primitiveTypes);
return oldValueIsPrimitive ? (a === b) : false;
}
};

var protoProperty = ko.observable.protoProperty = "__ko_proto__";
ko.observable['fn'][protoProperty] = ko.observable;

ko.isObservable = function (instance) {
if ((instance === null) || (instance === undefined) || (instance.__ko_proto__ === undefined)) return false;
if (instance.__ko_proto__ === ko.observable) return true;
return ko.isObservable(instance.__ko_proto__); // Walk the prototype chain
if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
if (instance[protoProperty] === ko.observable) return true;
return ko.isObservable(instance[protoProperty]); // Walk the prototype chain
}
ko.isWriteableObservable = function (instance) {
// Observable
if ((typeof instance == "function") && instance.__ko_proto__ === ko.observable)
if ((typeof instance == "function") && instance[protoProperty] === ko.observable)
return true;
// Writeable dependent observable
if ((typeof instance == "function") && (instance.__ko_proto__ === ko.dependentObservable) && (instance.hasWriteFunction))
if ((typeof instance == "function") && (instance[protoProperty] === ko.dependentObservable) && (instance.hasWriteFunction))
return true;
// Anything else
return false;
Expand Down Expand Up @@ -1222,11 +1223,11 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
return dependentObservable;
};

ko.dependentObservable['fn'] = {
__ko_proto__: ko.dependentObservable
};
var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
ko.dependentObservable[protoProp] = ko.observable;

ko.dependentObservable.__ko_proto__ = ko.observable;
ko.dependentObservable['fn'] = {};
ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;

ko.exportSymbol('dependentObservable', ko.dependentObservable);
ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
Expand Down
158 changes: 79 additions & 79 deletions build/output/knockout-latest.js

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions spec/defaultBindingsBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,16 @@ describe('Binding: CSS class name', {
value_of(testNode.childNodes[0].className).should_be("unrelatedClass1 unrelatedClass2 anotherRule myRule");
observable2(false);
value_of(testNode.childNodes[0].className).should_be("unrelatedClass1 unrelatedClass2 myRule");
},

'Should give the element a single CSS class without a leading space when the specified value is true': function() {
var observable1 = new ko.observable();
testNode.innerHTML = "<div data-bind='css: { myRule: someModelProperty }'>Hallo</div>";
ko.applyBindings({ someModelProperty: observable1 }, testNode);

value_of(testNode.childNodes[0].className).should_be("");
observable1(true);
value_of(testNode.childNodes[0].className).should_be("myRule");
}
});

Expand Down Expand Up @@ -903,6 +913,18 @@ describe('Binding: Attr', {
model.myprop(testValue);
value_of(testNode.childNodes[0].getAttribute("someAttrib")).should_be(null);
});
},

'Should be able to set class attribute and access it using className property': function() {
var model = { myprop : ko.observable("newClass") };
testNode.innerHTML = "<div class='oldClass' data-bind=\"attr: {'class': myprop}\"></div>";
value_of(testNode.childNodes[0].className).should_be("oldClass");
ko.applyBindings(model, testNode);
value_of(testNode.childNodes[0].className).should_be("newClass");
// Should be able to clear class also
model.myprop(undefined);
value_of(testNode.childNodes[0].className).should_be("");
value_of(testNode.childNodes[0].getAttribute("class")).should_be(null);
}
});

Expand Down
5 changes: 5 additions & 0 deletions spec/dependentObservableBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ describe('Dependent Observable', {
value_of(ko.isObservable(instance)).should_be(true);
},

'Should advertise that instances are computed': function () {
var instance = new ko.dependentObservable(function () { });
value_of(ko.isComputed(instance)).should_be(true);
},

'Should advertise that instances cannot have values written to them': function () {
var instance = new ko.dependentObservable(function () { });
value_of(ko.isWriteableObservable(instance)).should_be(false);
Expand Down
17 changes: 17 additions & 0 deletions spec/mappingHelperBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,22 @@ describe('Mapping helpers', {
value_of(typeof result).should_be('string');
var parsedResult = ko.utils.parseJson(result);
value_of(parsedResult).should_be({ a: "a-mapped", b: "b-mapped" });
},

'ko.toJSON should respect replacer/space options': function() {
var data = { a: 1 };

// Without any options
value_of(ko.toJSON(data)).should_be("{\"a\":1}");

// With a replacer
function myReplacer(x, obj) {
value_of(obj).should_be(data);
return "my replacement";
};
value_of(ko.toJSON(data, myReplacer)).should_be("\"my replacement\"");

// With spacer
value_of(ko.toJSON(data, undefined, " ")).should_be("{\n \"a\": 1\n}");
}
})
8 changes: 8 additions & 0 deletions spec/nativeTemplateEngineBehaviors.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('Native template engine', {

window.testDivTemplate = ensureNodeExistsAndIsEmpty("testDivTemplate");
window.testScriptTemplate = ensureNodeExistsAndIsEmpty("testScriptTemplate", "script");
window.testTextAreaTemplate = ensureNodeExistsAndIsEmpty("testTextAreaTemplate", "textarea");
window.templateOutput = ensureNodeExistsAndIsEmpty("templateOutput");
},

Expand All @@ -35,6 +36,13 @@ describe('Native template engine', {
ko.renderTemplate("testScriptTemplate", { name: 'bert' }, null, window.templateOutput);
value_of(window.templateOutput).should_contain_html("name: <div data-bind=\"text: name\">bert</div>");
},

'Named template can fetch template from <textarea> elements and data-bind on results': function () {
var prop = (typeof window.testTextAreaTemplate.innerText !== "undefined") ? "innerText" : "textContent";
window.testTextAreaTemplate[prop] = "name: <div data-bind='text: name'></div>";
ko.renderTemplate("testTextAreaTemplate", { name: 'bert' }, null, window.templateOutput);
value_of(window.templateOutput).should_contain_html("name: <div data-bind=\"text: name\">bert</div>");
},

'Anonymous template can display static content': function () {
new ko.templateSources.anonymousTemplate(window.templateOutput).text("this is some static content");
Expand Down
24 changes: 18 additions & 6 deletions src/binding/defaultBindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ ko.bindingHandlers['options'] = {
return ko.selectExtensions.readValue(node) || node.innerText || node.textContent;
});
var previousScrollTop = element.scrollTop;
element.scrollTop = 0; // Workaround for a Chrome rendering bug. Note that we restore the scroll position later. (https://github.com/SteveSanderson/knockout/issues/215)

var value = ko.utils.unwrapObservable(valueAccessor());
var selectedValue = element.value;
Expand Down Expand Up @@ -255,8 +254,7 @@ ko.bindingHandlers['options'] = {
}
}

if (previousScrollTop)
element.scrollTop = previousScrollTop;
element.scrollTop = previousScrollTop;

if (selectWasPreviouslyEmpty && ('value' in allBindings)) {
// Ensure consistency between model value and selected option.
Expand Down Expand Up @@ -426,20 +424,34 @@ ko.bindingHandlers['checked'] = {
}
};

var attrHtmlToJavascriptMap = { 'class': 'className', 'for': 'htmlFor' };
ko.bindingHandlers['attr'] = {
'update': function(element, valueAccessor, allBindingsAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()) || {};
for (var attrName in value) {
if (typeof attrName == "string") {
var 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))
var toRemove = (attrValue === false) || (attrValue === null) || (attrValue === undefined);
if (toRemove)
element.removeAttribute(attrName);
else

// In IE <= 7 and IE8 Quirks Mode, you have to use the Javascript property name instead of the
// HTML attribute name for certain attributes. IE8 Standards Mode supports the correct behavior,
// but instead of figuring out the mode, we'll just set the attribute through the Javascript
// property for IE <= 8.
if (ko.utils.ieVersion <= 8 && attrName in attrHtmlToJavascriptMap) {
attrName = attrHtmlToJavascriptMap[attrName];
if (toRemove)
element.removeAttribute(attrName);
else
element[attrName] = attrValue;
} else if (!toRemove) {
element.setAttribute(attrName, attrValue.toString());
}
}
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/subscribables/dependentObservable.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,16 @@ ko.dependentObservable = function (evaluatorFunctionOrOptions, evaluatorFunction
return dependentObservable;
};

ko.isComputed = function(instance) {
return ko.hasPrototype(instance, ko.dependentObservable);
};

var protoProp = ko.observable.protoProperty; // == "__ko_proto__"
ko.dependentObservable[protoProp] = ko.observable;

ko.dependentObservable['fn'] = {};
ko.dependentObservable['fn'][protoProp] = ko.dependentObservable;

ko.exportSymbol('dependentObservable', ko.dependentObservable);
ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
ko.exportSymbol('computed', ko.dependentObservable); // Make "ko.computed" an alias for "ko.dependentObservable"
ko.exportSymbol('isComputed', ko.isComputed);
4 changes: 2 additions & 2 deletions src/subscribables/mappingHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
});
};

ko.toJSON = function(rootObject) {
ko.toJSON = function(rootObject, replacer, space) { // replacer and space are optional
var plainJavaScriptObject = ko.toJS(rootObject);
return ko.utils.stringifyJson(plainJavaScriptObject);
return ko.utils.stringifyJson(plainJavaScriptObject, replacer, space);
};

function mapJsObjectGraph(rootObject, mapInputCallback, visitedObjects) {
Expand Down
10 changes: 7 additions & 3 deletions src/subscribables/observable.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,14 @@ ko.observable['fn'] = {
var protoProperty = ko.observable.protoProperty = "__ko_proto__";
ko.observable['fn'][protoProperty] = ko.observable;

ko.isObservable = function (instance) {
ko.hasPrototype = function(instance, prototype) {
if ((instance === null) || (instance === undefined) || (instance[protoProperty] === undefined)) return false;
if (instance[protoProperty] === ko.observable) return true;
return ko.isObservable(instance[protoProperty]); // Walk the prototype chain
if (instance[protoProperty] === prototype) return true;
return ko.hasPrototype(instance[protoProperty], prototype); // Walk the prototype chain
};

ko.isObservable = function (instance) {
return ko.hasPrototype(instance, ko.observable);
}
ko.isWriteableObservable = function (instance) {
// Observable
Expand Down
8 changes: 5 additions & 3 deletions src/templating/templateSources.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@
}

ko.templateSources.domElement.prototype['text'] = function(/* valueToWrite */) {
var tagName = this.domElement.tagName.toLowerCase(),
elemProp = tagName == "script" ? "text" : tagName == "textarea" ? "value" : "innerHTML";
if (arguments.length == 0) {
return this.domElement.tagName.toLowerCase() == "script" ? this.domElement.text : this.domElement.innerHTML;
return this.domElement[elemProp];
} else {
var valueToWrite = arguments[0];
if (this.domElement.tagName.toLowerCase() == "script")
this.domElement.text = valueToWrite;
if (elemProp != "innerHTML")
this.domElement[elemProp] = valueToWrite;
else
ko.utils.setHtml(this.domElement, valueToWrite);
}
Expand Down
16 changes: 6 additions & 10 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,17 +272,13 @@ ko.utils = new (function () {
return ko.isObservable(value) ? value() : value;
},

domNodeHasCssClass: function (node, className) {
toggleDomNodeCssClass: function (node, className, shouldHaveClass) {
var currentClassNames = (node.className || "").split(/\s+/);
return ko.utils.arrayIndexOf(currentClassNames, className) >= 0;
},
var hasClass = ko.utils.arrayIndexOf(currentClassNames, className) >= 0;

toggleDomNodeCssClass: function (node, className, shouldHaveClass) {
var hasClass = ko.utils.domNodeHasCssClass(node, className);
if (shouldHaveClass && !hasClass) {
node.className = (node.className || "") + " " + className;
node.className += (currentClassNames[0] ? " " : "") + className;
} else if (hasClass && !shouldHaveClass) {
var currentClassNames = (node.className || "").split(/\s+/);
var newClassName = "";
for (var i = 0; i < currentClassNames.length; i++)
if (currentClassNames[i] != className)
Expand Down Expand Up @@ -336,7 +332,7 @@ ko.utils = new (function () {
isIe6 : isIe6,
isIe7 : isIe7,
ieVersion : ieVersion,

getFormFields: function(form, fieldName) {
var fields = ko.utils.makeArray(form.getElementsByTagName("input")).concat(ko.utils.makeArray(form.getElementsByTagName("textarea")));
var isMatchingField = (typeof fieldName == 'string')
Expand All @@ -362,10 +358,10 @@ ko.utils = new (function () {
return null;
},

stringifyJson: function (data) {
stringifyJson: function (data, replacer, space) { // replacer and space are optional
if ((typeof JSON == "undefined") || (typeof JSON.stringify == "undefined"))
throw new Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
return JSON.stringify(ko.utils.unwrapObservable(data));
return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
},

postJson: function (urlOrForm, data, options) {
Expand Down

0 comments on commit 1f334da

Please sign in to comment.