Question

When I add new pages then it add them ok, if I remove them it works also fine, but I have two problems: * When I press edit it allows me to edit the page fields with jQuery Dialog but it only works when there is just the first page. * When I add many pages and I move them using jQuery Sortable (ex. make the third page the first and so on) it works ok, but when I remove any of them it does strange things. I debugged it and I'm updating the model with a JS function. Here is the jsfiddle: http://jsfiddle.net/SuperJohn/LLndg/2/

The HTML:

<div style="width: 400px; height: 100%; margin: 10px auto;">
    <div class="pages">
    <ul id="carousel" data-bind="template: { name: 'page-icon', foreach: form.pages }"></ul>
    <ul>
        <li><a href="#" data-bind="click: designer.add.page">New</a></li>
    </ul>
    <script type="text/html" id="page-icon">
        <li>
                <a href="#" data-bind="click: $root.designer.page.id, attr: { title: $data.title }">
        <span data-bind="text: $data.title"></span></a>
        </li>
        </script>
</div>
<div>
    <div id="form" data-bind="template: { name: 'page-template', data: form.pages()[designer.pageId()] }"></div>
</div>
</div>

<script type="text/html" id="page-template">
    <div id="page">
    <h2 id="title" data-bind="text: title"></h2>
    <ul id="page-actions">
        <li><a href="#" id="editPage">Edit</a></li>
    <li><a href="#" data-bind="click: function () { $root.designer.remove.page() }">Remove</a></li>
    </ul>
    <p id="description" data-bind="text: description"></p>
    </div>
<div id="dForm-Page" title="Page">
    <fieldset>
        <legend>General</legend>
    <div>
        <label>Title</label>
        <input type="text" name="dlg-pgTitle" id="dlg-pgTitle" />
    </div>
    <div>
        <label>Instructions</label>
        <textarea name="dlg-pgDescription" id="dlg-pgDescription"></Textarea>
        </div>
        </fieldset>
</div>
</script> 

The Model (Java Script)

function PageForm(id) {
    this.id = ko.observable(id);
this.title = ko.observable('Page Title (pg.' + (id + 1) + ')');
this.description = ko.observable('Instructions or description for the page will appear here.');
}

function DesignerForm(id) {
    this.pages = ko.observableArray([new PageForm(id)]);
}

var ViewModel = function ()
{
    var self = this;
self.form = new DesignerForm(0);

self.designer = {
    "pageId": ko.observable(0),
    "add":
    {
        "page": function ()
    {
        var idx = self.form.pages().length;
        self.form.pages.push(new PageForm(idx));
        self.designer.pageId(idx);
    },
    },
    "remove":
    {
        "page": function ()
    {
        var slfDes = self.designer,
        pg = slfDes.pageId(),
        frmPgs = self.form.pages
        if (pg > 0)
            slfDes.pageId(pg - 1);
        else if (frmPgs().length == 1) {
            slfDes.add.page();
        slfDes.pageId(pg);
        }
        frmPgs.remove(frmPgs()[pg]);
        for (var i = pg; i < frmPgs().length; i++)
        {
            frmPgs()[i].id(i);
        }
    }
    },
    "page": {
        "id": function (data) { self.designer.pageId(data.id()); },
    "next": function () { self.designer.pageId(self.designer.pageId() + 1); },
    "previous": function () { self.designer.pageId(self.designer.pageId() - 1); },
    "nav": function (item)
    {
        // check for knockout bug
        if (item.id() < 999) {
            return 'page-icon';
        } else {
            return 'new-page-icon';
        }
    }
    }
};

$(function() {
    $("#dForm-Page").dialog({
        autoOpen: false,
        height: 300,
        width: 380,
        modal: true,
        buttons: {
            "Save": function() {
            $("#title").text($("#dlg-pgTitle").val());
            $("#description").text($("#dlg-pgDescription").val());
            $(this).dialog("close");    
            },
        Close: function() {
            $(this).dialog("close");
        }
    },
    close: function() {
        $(this).dialog("close");
        }
    });

    $("#editPage").click(function() {
        $("#dlg-pgTitle").val($("#title").text());
    $("#dlg-pgDescription").val($("#description").text());
    $("#dForm-Page").dialog("open");
    });  
});

$(function ()
{
    var pgToMove;
    $("#carousel").sortable({
        start: function (event, ui) {
        pgToMove = ui.item.index();
    },
    update: function (event, ui) {
        var iI = ui.item.index();
        if (iI != pgToMove) {
            var inc = (pgToMove > iI) ? 1 : -1,
            frmPgs = self.form.pages(),
            i;
        for (i = iI; i != pgToMove; i += inc)
        {
            frmPgs[i].id(i + inc);
        }
        frmPgs[i].id(iI);
        frmPgs.sort(function (a, b) { return a.id() - b.id(); });
        }
    }
    });
});
};
ko.applyBindings(new ViewModel());
Was it helpful?

Solution

Your main problem is that you are using jquery to modify the DOM directly and using jquery to register click events.

When knockout modify the DOM for you, the element on which the click event has been binded is now gone and replaced by another one.

Instead you should use the knockout click binding. And for any modifications, instead of modifying the DOM directly you should modify the model and let Knockout modify the DOM for you.

See this jsfiddle where the edit is now made with a knockout click binding. There is still a lot to change in this code but I hope it will send you in the right direction.

self.designer = {
    "pageId": ko.observable(0),
    "add":
    {
        "page": function ()
        {
            var idx = self.form.pages().length;
            self.form.pages.push(new PageForm(idx));
            self.designer.pageId(idx);
        },
    },
    "edit":
    {
        "page": function ()
        {
            $("#dForm-Page").dialog({
            autoOpen: false,
            height: 300,
            width: 380,
            modal: true,
            buttons: {
                "Save": function() {
                    $("#title").text($("#dlg-pgTitle").val());
                    $("#description").text($("#dlg-pgDescription").val());
                    $(this).dialog("close");    
                },
                Close: function() {
                    $(this).dialog("close");
                }
            },
            close: function() {
                $(this).dialog("close");
            }
        });
                $("#dlg-pgTitle").val($("#title").text());
                $("#dlg-pgDescription").val($("#description").text());
                $("#dForm-Page").dialog("open");
        },
    },        
    "remove":
    {
        "page": function ()
        {
            var slfDes = self.designer,
                pg = slfDes.pageId(),
                frmPgs = self.form.pages
            if (pg > 0)
                slfDes.pageId(pg - 1);
            else if (frmPgs().length == 1) {
                slfDes.add.page();
                slfDes.pageId(pg);
            }
            frmPgs.remove(frmPgs()[pg]);
            for (var i = pg; i < frmPgs().length; i++)
            {
                frmPgs()[i].id(i);
            }
        }
    },
    "page": {
        "id": function (data) { self.designer.pageId(data.id()); },
        "next": function () { self.designer.pageId(self.designer.pageId() + 1); },
        "previous": function () { self.designer.pageId(self.designer.pageId() - 1); },
        "nav": function (item)
        {
            // check for knockout bug
            if (item.id() < 999) {
                return 'page-icon';
            } else {
                return 'new-page-icon';
            }
        }
    }
};

And in the html:

<li><a href="#" id="editPage" data-bind="click: $root.designer.edit.page">Edit</a></li>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top