Skip to content

View Add Edit Delete Design Pattern

MK edited this page May 15, 2014 · 45 revisions

View / Add / Edit / Delete Design Pattern

I write a lot of web applications. In general, to me this means a front end web page containing data from a database fetched through services. Specifically, I use ASP.NET technology for the server-side functionality. Recently I started working with Knockout.js. This client-side scripting technology lays quite comfortably between the presentation layer and the data layer fulfilling the design pattern known as MVVM – Model View View-Model. I love Knockout. It’s changed the way I think about web design permanently. And this is a good thing. In this article I will be exploring one re-usable technique I've been employing to handle the very common task of Viewing, Editing, Adding, and Deleting records from a web page.


Dependencies

  1. jQuery - version 1.6.3
  2. jQuery tmpl - 1.0.0pre
  3. Knockout - version 1.3beta
  4. Knockout Mapping plugin
  5. jQuery Table Sorter plugin

Warning

This isn't a best-practice or full-description blog. This is a method I've worked out that seems to be working for me. Could there be a more efficient way of doing things? Can’t there always? So, take what I've done here with a grain of salt. I've already found spots in the approach below that scream inefficient and am working on some ways of tightening that up.

Assumptions

I tend to work on data sets that are manageable in size. I’m talking <1000 records at a time. If you are working on giant data sets with millions of records, I’m pretty sure the approach below is going to be a bit too slow. And by a bit, I mean, unmanageably slow!

Basic UI – Desired Outcome

  1. When user loads page, they are presented with a grid of records. Each grid row shows only portions of the total record.
  2. The user is presented with an Edit and Delete button on the grid.
  3. The user is also presented with an Add One button at the top of the grid.
  4. Clicking either of the Edit or Add One buttons hides the grid and displays an editor revealing all the fields of the dataset.
  5. Clicking the Delete button prompts a “confirm”, which then will proceed to removing the record from the database, and ultimately from the grid.
  6. The record editor has a CANCEL feature that allows the user to make changes on the form, but upon cancel see no changes in the grid.
  7. Clicking SAVE in the editor results in the record being submitted to the server, the editor hidden, and the grid refreshed to contain the new/updated information.

This design pattern is everywhere in my applications, so I've taken a lot of time to narrow down the most relevant bits. Of course, there are other considerations like conditional select controls and form validation, but these 7 items are the non-negotiable pieces IMHO.

0. Base Model

Before we can start building this model and HTML we will define our "base" data model. This has a ton of advantages and only really on serious drawback. As for the advantages, I'll let the rest of this article speak to that. The disadvantage is decoupling your data model from your server-side model. We will be creating a JavaScript/Knockout representation of an "object" that will need to be maintained against whatever your server needs. In my experience, when I change the data model on the server-side, I have to make changes to my client side stuff, so this isn't a big deal - but it is one more thing to remember.

We will use this base model as the initial value of our form and any time we want to insert data. So, the values we give the observables will also act as default values.

Here is a simple base model for our example.

var baseModel = function(){
    this.PersonID = ko.observable(0);
    this.FirstName = ko.observable("");
    this.LastName = ko.observable("");
    this.Email = ko.observable("");
    this.Address1 = ko.observable("");
    this.City = ko.observable("");
    this.State = ko.observable("");
}

1. Grid of Records

We need a way to fetch our data from the server. I’ll leave it to you to work out a method to get and compile valid JSON results for a collection of objects. I’m just going to show you how I fetch them in my view model. I've created a little re-usable script to fetch JSON data and return it as a JavaScript object. As with most of my JavaScript, I like to store re-usable code in its own namespace. My utility namespace is called myUtes which you will see popping up from time to time in this code.

myUtes.AjaxtoJSON = function (params) 
    var param = new Object();
    param.serviceName = params.serviceName || "";
    param.methodName = params.methodName || "";
    param.zeroValue = params.zeroValue;
    param.serviceData = params.serviceData;
    param.async = params.async || false;

    var result;

    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: myUtes.getRootUrl() + "Webservices/" + param.serviceName + ".asmx/" + param.methodName,
        data: JSON.stringify(param.serviceData),
        async: param.async,
        dataType: "json",
        success: function (data, textResponse) {
            result = $.parseJSON(data.d);
            if (param.zeroValue) {
                result.unshift(param.zeroValue);
            }
        }
    });
    return result;

The code above shouldn't look to complicated. There is one piece I've come to rely heavily on. I have a little utility JavaScript that determines the Root URL of the page using the script. This is a very nice way of making reusable code that needs to know where the page sits in the solution.

myUtes.getRootUrl = function () {
    var currentURL = document.URL;
    var rootPosition = currentURL.indexOf("/", 7);
    var relativeHomeUrl = currentURL.substring(0, rootPosition + 1);
    return relativeHomeUrl + "SolutionRootName/";
};

I should also mention that zeroValue is another little trick I like to use. Many times I will use this AjaxtoJSON function to retrieve choices used in a <select>. It is very handy to occasionally make these drop-downs contain something like "Choose One" at the top when the value is optional. However, it is too hacky for my tastes to put "Choose One" in my database. So, I use zeroValue to store a "first choice" that gets pushed in to my result. A typical call would look something like this.

            myUtes.AjaxtoJSON({
                serviceName: "PeopleServices",
                methodName: "GetCountryNames",
                serviceData: [],
                async: false,
                zeroValue: { CountryID: "0", CountryName: "Select One" }
            })

In the above example, we could call for a list of Countries and get an added element pushed in to the array with the value of "0" and a choice text of : "Select One".

OK - we should be ready to start building our view model.

var pageViewModel = {
        tablesorter: function () {
            setTimeout(function () {
                if (pageViewModel.collection().length != 0) {
                    $(".tablesorter").trigger("update");
                    var sorting = [[1, 1], [0, 0]];
                    $(".tablesorter").trigger("sorton", [sorting]);
                }
            }, 10);
        },
        collection: ko.mapping.fromJS(
            myUtes.AjaxtoJSON({
                serviceName: "PeopleService",
                methodName: "GetPeople",
                serviceData: [],
                async: false
            })
        ),
        startEdit: function (data) {
            var d;
            if (data) {
                d = data;
                this.isNew(false);
            } else {
                d = new baseModel();
                this.isNew(true);
            }

            this.selected(d);
            this.showEditor(true);
        },
        isNew: ko.observable(true),
        showEditor: ko.observable(false),
        selected: ko.observable(new baseModel()),
        cancelEdit: function () {
            this.showEditor(false);
        }
}

The model above are the essentials for displaying my grid. Let's look at each part.

        tablesorter: function () {
            setTimeout(function () {
                if (pageViewModel.collection().length != 0) {
                    $(".tablesorter").trigger("update");
                    var sorting = [[1, 1], [0, 0]];
                    $(".tablesorter").trigger("sorton", [sorting]);
                }
            }, 10);
        }

tablsorter hooks up my Table Sorter plugin. Notice that I am looking at whether any records are found or not. if(pageViewModel.collection().length != 0)

This is important because tablesorter.js does not like to trigger updates and sorts on empty <tbody> tags. Also note that the whole thing is wrapped in a timeout. This has to do with letting Knockout finish the template binding before the table is sorted and stripped.

        collection: ko.mapping.fromJS(
            myUtes.AjaxtoJSON({
                serviceName: "PeopleService",
                methodName: "GetPeople",
                serviceData: [],
                async: false
            })
        )

collection: is the heart and soul of the view model. This will hold all of the People objects called from the server and be used as the observable bound to our table rows. In this example I am not sending any serviceData in to my ajax function, but of course this is where you could add filters and search parameters, etc.

startEdit: function (data) {
            var d;
            if (data) {
                d = data;
                this.isNew(false);
            } else {
                d = new baseModel();
                this.isNew(true);
            }

            this.selected(d);
            this.showEditor(true);
        }

startEdit will be triggered in one of two ways. First - when adding a new record, and second - when editing an existing record. When we want to add a new record we will pass NOTHING to startEdit() and thus the logic above tells knockout to set our selected: observable to our baseModel(). This is extremely helpful in that our editor bindings will then reflect a clean blank record with any defaults we've defined in our baseModel.

Otherwise, we will be calling startEdit with a click event on our table row. At that time we'll pass the row data to startEdit so it will set selected: to the row data.

showEditor is a simple boolean observable that we use to toggle elements in our HTML between our editor view and read-only view.

isNew is another boolean observable that I've left in this example even though I'm not using it. I use this for forms where certain information should be suppressed when adding a new record versus editing an existing record. This comes up quite often. For instance I have one project where a person is added. After they are added I want to add more many-to-one pieces of information (like a picture, interests, etc) but I don't want to make those features available until they've committed the initial record and acquired a new Person ID.

I use isNew to show or hide portions of the editor.

selected: observable is set to our baseModel by default, but as each row is selected for editing, it is used to stored the "active" data.

cancelEdit is a function we will use as a click event on our Cancel button in the UI. Right now it just serves to toggle the views between read-only and editor, but of course you could handle other data cleanup when a user cancels editing.

Now, let's look at the grid HTML.

        <div id="details" data-bind="showHide: !showEditor()" class="hidden">
            <a href="#" data-bind="click: function() {pageViewModel.startEdit()}" class="linkButton">Add a Detail Record</a>
            <table class="tablesorter" id="listTable" style="width:auto;">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Address</th>
                        <th>Email</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody data-bind="foreach: collection, postAction: tablesorter()">
                    <tr>
                        <td data-bind="text: FirstName() + ' ' + LastName()"></td>
                        <td data-bind="text: Address1"></td>
                        <td data-bind="text: Email"></td>
                        <td>
                            <a href="#" class="linkButton" data-bind="click: function() {  pageViewModel.startEdit($data); }">Edit</a>
                            <a href="#" class="linkButton" data-bind="click: function() {  pageViewModel.doSave($data,1); }">Delete</a>
                        </td>
                    </tr>
                </tbody>
                <tfoot data-bind="visible: !pageViewModel.hasRecords()">
                    <tr>
                        <td colspan="4"><span>No People have been entered.</span></td>
                    </tr>
                </tfoot>
            </table>
        </div>

First, the whole grid is wrapped in a <div> tag with a data binding of:

data-bind="showHide: !showEditor()" class="hidden"

showHide is a a little Knockout custom binding I've added that spices up the UI with some jQuery effect. It's a simple True/False observable that uses jQuery's show() and hide() methods to manipulate the DOM element.

ko.bindingHandlers.showHide = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var val = ko.utils.unwrapObservable(valueAccessor());
        if (val) {
            $(element).show("fast");
        } else {
            $(element).hide("fast");
        }
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {

        var val = ko.utils.unwrapObservable(valueAccessor());

        if (val) { $(element).show("fast"); }
        else { $(element).hide("fast"); }
    }
};

The next piece is an "Add" button above the grid. This button contains click-event binding that will call our startEdit() function passing in NO DATA telling startEdit() that we want a blank record.

data-bind="click: function() {pageViewModel.startEdit()}"

Next we have our table itself. The <table> and <thead> have no bindings. The <tbody> is bound to the collection observable. it also has a postAction that will trigger our tablesorter logic. Note that postAction is not an existing binding; it is simply a placeholder name used to ensure that our tablesorter function is called when the bindings are evaluated.

data-bind="foreach: collection, postAction: tablesorter()"

Each cell of a <tbody> row just contains simple binding to collection elements, but the last cell contains two buttons.

  1. Edit Button - data-bind="click: function() { pageViewModel.startEdit($data); }"
    This simply calls our startEdit() function on click and passes the data attached to this row.

  2. Delete Button - data-bind="click: function() { pageViewModel.doSave($data,1); }"
    This is the first time we've seen the doSave() function referenced. I haven't talked about that one, but you can see it's passing the data from this row as well as a second parameter. This parameter we'll use to determine whether we're adding or deleting. More on that later.

And at the bottom we have our <tfoot> that is bound to whether our collection has any records or not.
data-bind="visible: !pageViewModel.hasRecords()"

So - there you have it. Step 1 :)

Now, don't go trying to implement this yet. We have several more pieces to put in place.

###2. Editor If you have been following so far, you will note that we are using a startEdit() function that sets the value of an observable called selected:. It would be great to just pass that to our editor - but there is one big UI/User Expectation problem - what about CANCEL? Users have to be able to change their mind before committing changes. If they type a few new words in a control then decide they don't want those values then we need to let them back out. Otherwise, why have Save/Cancel buttons at all?

Because of this editing convention we need a third observable: SelectedRecord. But, because this value is bound to our selected observable, we'll make it a dependantObservable.

    pageViewModel.SelectedRecord = ko.dependentObservable(function () {
        var selected = this.selected();
        var result = ko.utils.arrayFilter(this.collection(), function (item) {
            return item == selected
        });
        if (result.length != 0) {
            result = ko.toJS(result)[0];
            var observable = ko.mapping.fromJS(result);
            return observable;
        } else {
            return new baseModel();
        };
    }, pageViewModel); 

There are some critical parts here. First, note that we are making the whole thing dependent on selected's value.

var selected = this.selected();

Next we create a result variable that will be bound to an item from our collection object map.

        var result = ko.utils.arrayFilter(this.collection(), function (item) {
            return item == selected
        });

Now, when we are adding new records, result will be empty. That's OK, because we will handle that with a bit of simple logic: if (result.length != 0)

Inside this IF logic is where the magic happens. We want SelectedRecord to be an observable, but we don't want it to be the observable from our collection array. If we did that, we would get the non-cancelable behavior mentioned above. So, we'll effectively "unwrap" the result, turn it in to an object, and map it to our desire output. I've made some assumptions about your data model here that you will not have duplicate records. Thus, we only want to "top" record in our collection - ko.toJS(result)[0]

            result = ko.toJS(result)[0];
            var observable = ko.mapping.fromJS(result);
            return observable;

And of course, if we are adding a new record, we'll just return a new version of our baseModel:

return new baseModel();

In summary - we do not want to bind our Editor HTML to selected because we would have a hard time trying to cancel any changes our users make. Instead, we will bind our Editor to SelectedRecord. And it's this SelectedRecord that we'll use when sending data down to our server function.

Let's take a look now at the workhorse of this approach: doSave()

doSave: function (data, dodelete) {
            var conf = true;
            var commit = true;
            var dt = new Object();
            dt.data = ko.toJS(ko.utils.unwrapObservable(data));
            dt.DoDelete = dodelete;
            if (dodelete == 1) {
                conf = confirm("This cannot be undone.   Are you sure?");
            } else {
                commit = $("#form1").valid();
            }

            if (conf && commit) {
                ko.mapping.fromJS(myUtes.AjaxtoJSON({
                    serviceName: "PeopleServices",
                    methodName: "AlterPeopleData",
                    serviceData: dt,
                    async: false
                }), pageViewModel.collection);

                this.showEditor(false);
            };
        }

This is part of our pageViewModel and handles all the data manipulation from client-side to server-side. That includes adding, editing, and deleting records.

doSave() accepts two variables

  1. data A knockout observable that represents our baseModel with values that have been set through the UI.

  2. dodelete A binary value (1 or 0) that I use for two purposes: determine whether to obtain user confirmation and passed to my AlterPeopleData web service as a signal of intent for this record.

var conf will be used when we want to prompt the user for confirmation that the save action is OK with them. Typically I do this whenever DELETING records, but you could fire this logic whenever it makes sense for your UI.

var commit is used in conjunction with Adding/Editing records and specifically will be set to the value of my form validator. This is a very clean way of asking the UI if the form is valid. If not, all the validation errors will trigger. And if so, we can continue with our DB work.

var dt is an object I create to send to my web method. As a convention I like to send two items to my Alter methods - Data and DoDelete. My methods expect Data to be an object that matches what my server-side code expects to find for a Person object.

In the following line I set the .data portion of my dt variable to the SelectedRecord observable values.

dt.data = ko.toJS(ko.utils.unwrapObservable(data));

Notice that I am unwrapping the observable and turning it in to a plain JavaScript object. This removes all the knockout syntax and leaves me with a pretty clean object to pass to my server.

I then set the value for dt.DoDelete:

dt.DoDelete = dodelete;

Now for the logic: If the user is Deleting, let's get their permission - and if they are Editing/Adding, let's validate the form.

            if (dodelete == 1) {
                conf = confirm("This cannot be undone.   Are you sure?");
            } else {
                commit = $("#form1").valid();
            }

With that out of the way, we make sure we're OK to proceed by checking both conf and commit, then we call our web service that is expecting a data object (dt) containing a Person object and the DoDelete value.

 if (conf && commit) {
                ko.mapping.fromJS(myUtes.AjaxtoJSON({
                    serviceName: "PeopleServices",
                    methodName: "AlterPeopleData",
                    serviceData: dt,
                    async: false
                }), pageViewModel.collection);

                this.showEditor(false);
            };

Take special note of what I'm doing here - ko.mapping.fromJS(<function>, <observable array>). My web method AlterPeopleData returns my collection data with the updated values. So, using the Knockout Mapping plugin and Knockout 1.3 I can simply tell knockout to take the result of the method and overwrite the collection observable array. This array is bound to my table...and thus, my UI will be rebound with the most accurate values from the server.

Admittedly this is NOT the most efficient way of handling things. An astute observer will note that I'm retrieving ALL of the records from the server when in fact I've only altered (or inserted) one record. This is inefficient I agree. However, it does avoid issues of asynchronicity. Every time my table of result is drawn I know they are the most recent and accurate values. And for datasets less than a few hundred values, I've found this approach and the speed is manageable.

I can see a different approach where you have your service return ONLY the Person object that was committed to the database - and then using the ko.utils.arrayFilter technique find the value in your table that was changed and update it. Or if it was a new value, push the new value into your collection array.

With that, I think we are ready for the HTML portion of our editor.

       <div id="editor" data-bind="showHide: showEditor()" class="hidden">
            <fieldset style="background:#ffc;">
                <legend>Detail Editor</legend>
                <ul class="plain" data-bind="with:SelectedRecord()">
                    <li>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.doSave($data,0) ;}">Save</a>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.showEditor(false) }">Cancel</a>
                    </li>
                    <li>
                        <label>First Name</label>
                        <input type="text" class="required" name="FirstName" data-bound="value: FirstName" />
                    </li>
                    <li>
                        <label>Last Name</label>
                        <input type="text" class="required" name="LastName" data-bound="value: LastName" />
                    </li>
                    <li>
                        <label>Email</label>
                        <input type="text" class="required" name="Email" data-bound="value: Email" />
                    </li>
                    <li>
                        <label>Address</label>
                        <input type="text" class="required" name="Address1" data-bound="value: Address1" />
                    </li>
                    <li>
                        <label>City</label>
                        <input type="text" class="required" name="City" data-bound="value: City" />
                    </li>
                    <li>
                        <label>State</label>
                        <input type="text" class="required" name="State" data-bound="value: State" />
                    </li>
                    <li>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.doSave($data,0) ;}">Save</a>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.showEditor(false); }">Cancel</a>
                    </li>
                </ul>  
            </fieldset>
        </div>

As with our grid, I've wrapped the editor in a div that is dependent on the showEditor() observable. As a quick aside, I find the flashing associated with knockout-bound HTML elements is a little undesirable so I usually make my object wrappers hidden by default (class="hidden" with the css: .hidden { display:none;} ).

<div id="editor" data-bind="showHide: showEditor()" class="hidden">

Inside this wrapper we have an unordered list that will contain all our bound controls.

<ul class="plain" data-bind="with:SelectedRecord()">

On bigger forms I like to give users Save/Cancel controls at the top and bottom of the form. Here are those controls.

                    <li>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.doSave($data,0) ;}">Save</a>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.showEditor(false); }">Cancel</a>
                    </li>

Notice that the Save button uses the doSave() function passing the current $data (SelectedRow()) and a dodelete value of "0".

And each of the list items contain a bound control. For example:

                    <li>
                        <label>First Name</label>
                        <input type="text" class="required" name="FirstName" data-bound="value: FirstName" />
                    </li>

Here is the entire approach in one view.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Person Editor Sample</title>
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/jquery.tmpl.js" type="text/javascript"></script>
    <script src="js/knockout/knockout_latest.js" type="text/javascript"></script>
    <script src="js/knockout/knockout.mapping.js" type="text/javascript"></script>
    <script src="js/tablesorter.js" type="text/javascript"></script>
    
</head>
<script language="javascript">



/***********this section is pulled from my typical Utility.js include**************/
function namespace(namespaceString) {
    var parts = namespaceString.split('.'),
        parent = window,
        currentPart = '';    

    for(var i = 0, length = parts.length; i < length; i++) {
        currentPart = parts[i];
        parent[currentPart] = parent[currentPart] || {};
        parent = parent[currentPart];
    }

    return parent;
}

var myUtes = namespace("MyCompany.MyProject.MyUtes");
myUtes.getRootUrl = function () {
    var currentURL = document.URL;
    var rootPosition = currentURL.indexOf("/", 7);
    var relativeHomeUrl = currentURL.substring(0, rootPosition + 1);
    return relativeHomeUrl + "MyProject/";
};

myUtes.AjaxtoJSON = function (params) {
    var param = new Object();
    param.serviceName = params.serviceName;
    param.methodName = params.methodName;
    param.zeroValue = params.zeroValue;
    param.serviceData = params.serviceData;
    param.async = params.async || false;

    var result;

    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: myUtes.getRootUrl() + "Webservices/" + param.serviceName + ".asmx/" + param.methodName,
        data: JSON.stringify(param.serviceData),
        async: param.async,
        dataType: "json",
        success: function (data, textResponse) {
            result = $.parseJSON(data.d);
            if (param.zeroValue) {
                result.unshift(param.zeroValue);
            }
        }
    });
    return result;

};


ko.bindingHandlers.showHide = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
        var val = ko.utils.unwrapObservable(valueAccessor());
        if (val) {
            $(element).show("fast");
        } else {
            $(element).hide("fast");
        }
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel) {

        var val = ko.utils.unwrapObservable(valueAccessor());

        if (val) { $(element).show("fast"); }
        else { $(element).hide("fast"); }
    }
};

/**********************************************************************************/


var baseModel = function(){
    this.PersonID = ko.observable(0);
    this.FirstName = ko.observable("");
    this.LastName = ko.observable("");
    this.Email = ko.observable("");
    this.Address1 = ko.observable("");
    this.City = ko.observable("");
    this.State = ko.observable("");
}

var pageViewModel = {
        tablesorter: function () {
            setTimeout(function () {
                if (pageViewModel.collection().length != 0) {
                    $(".tablesorter").trigger("update");
                    var sorting = [[1, 1], [0, 0]];
                    $(".tablesorter").trigger("sorton", [sorting]);
                }
            }, 10);
        },
        collection: ko.mapping.fromJS(
            myUtes.AjaxtoJSON({
                serviceName: "PeopleService",
                methodName: "GetPeople",
                serviceData: [],
                async: false
            })
        ),
        startEdit: function (data) {
            var d;
            if (data) {
                d = data;
                this.isNew(false);
            } else {
                d = new baseModel();
                this.isNew(true);
            };
            this.selected(d);
            this.showEditor(true);
        },
        isNew: ko.observable(true),
        showEditor: ko.observable(false),
        selected: ko.observable(new baseModel()),
        cancelEdit: function () {
            this.showEditor(false);
        },
        doSave: function (data, dodelete) {
            var conf = true;
            var commit = false;
            var dt = new Object();
            dt.data = ko.toJS(ko.utils.unwrapObservable(data));
            dt.DoDelete = dodelete;
            if (dodelete == 1) {
                conf = confirm("This cannot be undone.   Are you sure?");
            } else {
                commit = $("#form1").valid();
            }

            if (conf && commit) {
                ko.mapping.fromJS(myUtes.AjaxtoJSON({
                    serviceName: "PeopleServices",
                    methodName: "AlterPeopleData",
                    serviceData: dt,
                    async: false
                }), pageViewModel.collection);

                this.showEditor(false);
            };
        }

};

pageViewModel.SelectedRecord = ko.dependentObservable(function () {
        var selected = this.selected();
        var result = ko.utils.arrayFilter(this.collection(), function (item) {
            return item == selected
        });
        if (result.length != 0) {
            result = ko.toJS(result)[0];
            var observable = ko.mapping.fromJS(result);
            return observable;
        } else {
            return new baseModel();
        };
    }, pageViewModel); 

    $(document).ready(function () {
        //configure the table sorter
        $(".tablesorter").tablesorter();
        //apply the data bindings
        ko.applyBindings(pageViewModel);

        //hook up validation
        validator = $("#form1").not(":date").validate({
            onsubmit: false,
            errorElement: "div"
        });

</script>

<body>
        <div id="details" data-bind="showHide: !showEditor()" class="hidden">
            <a href="#" data-bind="click: function() {pageViewModel.startEdit()}" class="linkButton">Add a Detail Record</a>
            <table class="tablesorter" id="listTable" style="width:auto;">
                <thead>
                    <tr>
                        <th>Name</th>
                        <th>Address</th>
                        <th>Email</th>
                        <th></th>
                    </tr>
                </thead>
                <tbody data-bind="foreach: collection, postAction: tablesorter()">
                    <tr>
                        <td data-bind="text: FirstName() + ' ' + LastName()"></td>
                        <td data-bind="text: Address1"></td>
                        <td data-bind="text: Email"></td>
                        <td>
                            <a href="#" class="linkButton" data-bind="click: function() {  pageViewModel.startEdit($data); }">Edit</a>
                            <a href="#" class="linkButton" data-bind="click: function() {  pageViewModel.doSave($data,1); }">Delete</a>
                        </td>
                    </tr>
                </tbody>
                <tfoot data-bind="visible: !pageViewModel.hasRecords()">
                    <tr>
                        <td colspan="4"><span>No People have been entered.</span></td>
                    </tr>
                </tfoot>
            </table>
        </div>
       <div id="editor" data-bind="showHide: showEditor()" class="hidden">
            <fieldset style="background:#ffc;">
                <legend>Detail Editor</legend>
                <ul class="plain" data-bind="with:SelectedRecord()">
                    <li>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.doSave($data,0) ;}">Save</a>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.showEditor(false) }">Cancel</a>
                    </li>
                    <li>
                        <label>First Name</label>
                        <input type="text" class="required" name="FirstName" data-bound="value: FirstName" />
                    </li>
                    <li>
                        <label>Last Name</label>
                        <input type="text" class="required" name="LastName" data-bound="value: LastName" />
                    </li>
                    <li>
                        <label>Email</label>
                        <input type="text" class="required" name="Email" data-bound="value: Email" />
                    </li>
                    <li>
                        <label>Address</label>
                        <input type="text" class="required" name="Address1" data-bound="value: Address1" />
                    </li>
                    <li>
                        <label>City</label>
                        <input type="text" class="required" name="City" data-bound="value: City" />
                    </li>
                    <li>
                        <label>State</label>
                        <input type="text" class="required" name="State" data-bound="value: State" />
                    </li>
                    <li>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.doSave($data,0) ;}">Save</a>
                        <a href="#" class="linkButton" data-bind="click: function() { pageViewModel.showEditor(false); }">Cancel</a>
                    </li>
                </ul>  
            </fieldset>
        </div>
</body>


###Conclusion Knockout has completely re-invented the way I think about programming web applications. When first introduced to jQuery I went crazy manipulating my DOM with jQuery selectors. Injecting table rows, coloring cells, sliding and moving pieces around as the user interacted. And while I enjoy that sort of fireworks, the real work of a web application is in coordinating the views and back-end data. KO does such a fantastic and flexible job that. And, I don't have to release all that other DOM aesthetic - just wrap it up in a re-usable custom binding.