A number of our users are still on IE8. Some of them occasionally are reporting problems when trying to post data to our servers (via a big button labeled "SAVE").
There is a script error that IE8 shows, which is: Unexpected call to method or property access, always pointing to the same line in the KnockoutJS 2.2.0 (debug, for now) library, line 450, which is as follows:
return JSON.stringify(ko.utils.unwrapObservable(data), replacer, space);
The method in my code that is at the root of the stack trace where this happens is this:
self.saveSingle = function (onSuccess, onFailure) {
ko.utils.arrayForEach(self.days(), function (day) {
day.close();
});
var jsonData = ko.toJSON(self);
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: applicationLocation + "/api/assignmentapi/save",
data: jsonData,
success: function (data) {
self.status(data.Status);
self._isDirty(false);
ko.utils.arrayForEach(self.days(), function (day) {
day.clean();
});
if (onSuccess)
onSuccess();
},
error: function (data) {
onFailure();
},
dataType: "json"
});
};
We do strip out a number of properties that are not necessary to our POST as we convert the object to JSON, using this approach: http://www.knockmeout.net/2011/04/controlling-how-object-is-converted-to.html
OurType.prototype.toJSON = function () {
var copy = ko.toJS(this);
delete copy.someUnneededProperty1;
delete copy.someUnneededProperty2;
delete copy.someUnneededProperty3;
delete copy.someUnneededProperty4;
return copy;
}
When it fails, it fails consistently on the line
var jsonData = ko.toJSON(self);
Now here comes the real mess:
I was also experiencing this issue. Digging deeper I found a few things:
data-bind
was trowing an exception except the message was being swallowed due to IE8 gobbling up the message when using a try {} finally {}
block (without catch
).When I started to get close to figuring out the issue (digging deep into the knockout code) it seemed to disappear in front of my eyes. This is the section of code it was failing on, catching the exception at the end of the code:
ko.utils.extend(ko.bindingProvider.prototype, {
'nodeHasBindings': function(node) {
switch (node.nodeType) {
case 1: return node.getAttribute(defaultBindingAttributeName) != null; // Element
case 8: return ko.virtualElements.virtualNodeBindingValue(node) != null; // Comment node
default: return false;
}
},
'getBindings': function(node, bindingContext) {
var bindingsString = this['getBindingsString'](node, bindingContext);
return bindingsString ? this['parseBindingsString'](bindingsString, bindingContext, node) : null;
},
// The following function is only used internally by this default provider.
// It's not part of the interface definition for a general binding provider.
'getBindingsString': function(node, bindingContext) {
switch (node.nodeType) {
case 1: return node.getAttribute(defaultBindingAttributeName); // Element
case 8: return ko.virtualElements.virtualNodeBindingValue(node); // Comment node
default: return null;
}
},
// The following function is only used internally by this default provider.
// It's not part of the interface definition for a general binding provider.
'parseBindingsString': function(bindingsString, bindingContext, node) {
try {
var bindingFunction = createBindingsStringEvaluatorViaCache(bindingsString, this.bindingCache);
return bindingFunction(bindingContext, node);
} catch (ex) {
throw new Error("Unable to parse bindings.\nMessage: " + ex + ";\nBindings value: " + bindingsString);
}
}
});
But yea, it stopped becoming reproducible so I came up with a hack that I tested and works earlier, just retrying the data parsing. So this:
data-bind="value: ko.computed(function(){return ko.toJSON(appViewModel.model()[0])})"
Became this:
data-bind="value: ko.computed(function(){while (true) { try { var json = ko.toJSON(appViewModel.model()[0]); return json; }catch(e){}}})"
Yes, it's very yucky, but it seems to do the trick until our users no longer need IE8 or the Knockout issue is fixed.
I have no idea if this will fix it, but you can use the mapping plugin to go between JS and JSON:
var mapping = {
'ignore': ["propertyToIgnore", "alsoIgnoreThis"]
}
var viewModel = ko.mapping.toJS(data, mapping);
Taken from my answer to this question
I'd give this a try and see if it helps, as there's nothing obviously wrong in your approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With