views:

119

answers:

3

I have an asp.net MVC app. One of the forms posts json data to a public method (not an action, perhaps it will be later) on the controller. This works great in IE 8. However, it does not work in Firefox 3.5.

The view is a list of jquery sortable objects inside of a form. Here is a stripped down version of the form:

<form class="cmxform" id="UserForm" method="post" action="/Home/Index"> 
//...the <li> objects that are sortable
<input type="submit" value="Save Changes" onclick="SaveLinks();" />
</form>

Here is the javascript to fire when the button is clicked. /Home/ProcessLinks is a public method in the controller, and Visible and Invisible is a parameter being passed to the method:

 function SaveLinks() {
        var VisibleList = document.getElementById('sortable1');
        var InvsibleList = document.getElementById('sortable2');

        for (var i = 0; i < VisibleList.childNodes.length; i++) {
            var link = {};
            link.id = VisibleList.childNodes[i].childNodes[1].innerText;
            link.title = VisibleList.childNodes[i].childNodes[2].innerText;
            link.description = VisibleList.childNodes[i].childNodes[3].innerText;
            link.url = VisibleList.childNodes[i].childNodes[4].innerText;
            link.order = i + 1;

            $.post("/Home/ProcessLinks/Visible", $.toJSON(link), function(data, testStatus) {
                /*This is where the user can be notified that the item was saved successfully*/
                //alert(link.id + " has been updated");
                window.location.reload();
            }, "text");
        }

        for (var i = 0; i < InvsibleList.childNodes.length; i++) {
            var link = {};
            link.id = InvsibleList.childNodes[i].childNodes[1].innerText;
            link.title = InvsibleList.childNodes[i].childNodes[2].innerText;
            link.description = InvsibleList.childNodes[i].childNodes[3].innerText;
            link.url = InvsibleList.childNodes[i].childNodes[4].innerText;
            link.order = i + 1;

            $.post("/Home/ProcessLinks/Invisible", $.toJSON(link), function(data, testStatus) {
                /*This is where the user can be notified that the item was saved successfully*/
                //alert(link.id + " has been updated");
                window.location.reload();
            }, "text");
        }
    }

It is my belief that the above method does not get triggered when in Firefox, as the breakpoints I place with Firebug don't get hit.

For fun, here is my serverside function:

    public string ProcessLinks(string id)
    {
        string Type = id;
        string json = Request.Form[0];

        var serializer = new DataContractJsonSerializer(typeof(JsonObject));
        var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(json));
        JsonObject item = (JsonObject)serializer.ReadObject(memoryStream);
        memoryStream.Close();

        return "hello";
    }

And my custom class JsonObject:

[DataContract]
public class JsonObject
{
        [DataMember]
        internal int id { get; set; }

        [DataMember]
        internal string title { get; set; }

        [DataMember]
        internal string description { get; set; }

        [DataMember]
        internal string order { get; set; }

        [DataMember]
        internal string url { get; set; }
}

Do you have any idea what I'm doing wrong? I can't seem to nail it down.

+4  A: 

Firefox does not support innerText. Use innerHTML instead.

Another possible reason is the following HTML:

<div>
    <span>foo</span>
    <span>bar</span>
</div>

will have the following structure in IE

div
   |---span(childNode[0])
   |       |
   |       '---text---foo
   |
   '---span(childNode[1])
           |
           '---text---bar

while in all other browsers including firefox it will have the following structure:

div
   |---text(childNode[0])---newline and tabs
   |
   |---span(childNode[1])
   |       |
   |       '---text---foo
   |
   |---text(childNode[2])---newline and tabs
   |
   '---span(childNode[3])
           |
           '---text---bar

This ridiculous behavior is mandated by the W3C spec. So IE is technically wrong here.

Now, I notice in your code you make assumptions about childNode indexing without checking element.nodeName. That could be where the problem lies.

slebetman
although innerHTML too is not a part of the W3C DOM specification
Xinus
Done. The app still doesn't work as I don't believe it even gets to that function in Firefox.
splatto
+1 nice explanation of the dom structure differences.
Jared
Regarding your edit: I'm fairly certain my problem is the function doesn't even get triggered when I use Firefox. Breakpoints I set in Firebug do not get hit. If the childNode indexing is the problem here then my breakpoints are being skipped for some reason
splatto
I echo what Jared said, nice explanation. I will look into this but I feel while that may be _an_ issue, it is not the _current_ issue
splatto
Hmm.. in that case it's strange indeed. I can't see anything else wrong with your code. What's your DOCTYPE? Also try deleting the body of SaveLinks and replace it with an alert to see that gets triggered.
slebetman
+3  A: 

Update: after looking at the code you uploaded I removed the <input type="submit... button from the form and added a button tag outside the form like this:

</form>
<button onclick="SaveLinks();">Save Changes</button>

After click this I got an error in FireFox:

"VisibleList.childNodes[i].childNodes[1] is undefined"

To correct this I took the code from jerjer's answer ( i had to modify it a bit ) and came up with the following SaveLinks() method that works in FF.

function SaveLinks() {
    $('#sortable1 li').each(function(i, item) {
        var divs = $('div:not(.imagecontainer)', this);

        var link = {
            id: $(divs[0]).text(),
            title: $(divs[1]).text(),
            description: $(divs[2]).text(),
            url: $(divs[3]).text(),
            order: i + 1
        };

        $.post("/Home/ProcessLinks/Visible", $.toJSON(link), function(data, testStatus) {
            /*This is where the user can be notified that the item was saved successfully*/
            alert(link.id + " has been updated");
            //window.location.reload();
        }, "text");
    });

    $('#sortable2 li').each(function(i, item) {
        var divs = $('div:not(.imagecontainer)', this);

        var link = {
            id: $(divs[0]).text(),
            title: $(divs[1]).text(),
            description: $(divs[2]).text(),
            url: $(divs[3]).text(),
            order: i + 1
        };

        $.post("/Home/ProcessLinks/Invisible", $.toJSON(link), function(data, testStatus) {
            /*This is where the user can be notified that the item was saved successfully*/
            alert(link.id + " has been updated");
            //window.location.reload();
        }, "text");
    });
    return false;
}
Jared
The reason I post each object rather than the list order is because properties of the objects may be edited before posted. I will refactor once I get this proof of concept working
splatto
Yes, it is working. I will upload a zip file of the solution for you if you wish to see what I'm doing.
splatto
I've uploaded the solution files to http://splatto.net/content/sort.zip.
splatto
Thanks Jared, that works beautifully. I do see what my problem was in the jquery, including inexperience :) I didn't even think about the input type=submit being an issue!
splatto
+1  A: 

Since you are using jquery, you might as well use the jquery's shorthand for getting/setting text/html.

for instance getting text content:

link.id = $(VisibleList.childNodes[i].childNodes[1]).text();
link.title = $(VisibleList.childNodes[i].childNodes[2]).text();
link.description = $(VisibleList.childNodes[i].childNodes[3]).text();
link.url = $(VisibleList.childNodes[i].childNodes[4]).text();
link.order = i + 1;

getting html content:

link.id = $(VisibleList.childNodes[i].childNodes[1]).html();
link.title = $(VisibleList.childNodes[i].childNodes[2]).html();
link.description = $(VisibleList.childNodes[i].childNodes[3]).html();
link.url = $(VisibleList.childNodes[i].childNodes[4]).html();
link.order = i + 1;

This is the jquery equivalent:

$('#sortable1 li').each(function(){
    var $li = $(this);   
    var link = {};
    link.id = $li.children(1).text();
    link.title =$li.children(2).text();
    link.description = $li.children(3).text();
    link.url = $li.children(4).text();
    link.order = i + 1;

    $.post("/Home/ProcessLinks/Visible", $.toJSON(link), function(data, testStatus) {
        /*This is where the user can be notified that the item was saved successfully*/
        //alert(link.id + " has been updated");
        window.location.reload();
    }, "text");
});
jerjer