Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KnockoutJS with IE8, occasional problems with Stringify?

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:

  1. It's not consistently happening
  2. It doesn't happen to all IE8 users
  3. We can't consistently reproduce it
  4. The structure of our model that we're serializing doesn't appear matter
  5. The jscript.dll is the current version for IE8
like image 272
reallyJim Avatar asked Mar 04 '13 16:03

reallyJim


2 Answers

I was also experiencing this issue. Digging deeper I found a few things:

  • It was only failing occasionally, I found this by running the code in the console enter image description here
  • The code in the 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).
  • Removing the try finally revealed a cannot parse bindings message.

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.

like image 166
Daniel Imms Avatar answered Nov 15 '22 07:11

Daniel Imms


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.

like image 41
Paul Manzotti Avatar answered Nov 15 '22 08:11

Paul Manzotti