February 2015 - Development Simply Put

A blog simplifies main concepts in IT development and provides tips, hints, advices and some re-usable code. "If you can't explain it simply, you don't understand it well enough" -Albert Einstein

  • Development Simply Put

    If you can't explain it simply, you don't understand it well enough.

    Read More
  • Integrant

    Based in the U.S. with offshore development centers in Jordan and Egypt, Integrant builds quality custom software since 1992. Our staff becomes an extension of your team to eliminate the risks of software development outsourcing and our processes...

    Read More
  • ITWorx

    ITWorx is a global software professional services organization. Headquartered in Egypt, the company offers Portals, Business Intelligence, Enterprise Application Integration and Application Development Outsourcing services to Global 2000 companies.

    Read More
  • Information Technology Institute

    ITI is a leading national institute established in 1993 by the Information and Decision Support Centre.

    Read More

2015-02-08

Knockout Advanced Tree Library & Control





http://knockoutadvancedtree.byethost10.com


All code samples used on this post can be downloaded from here


This post shows you how to fully implement a tree control using knockout. It was taken into consideration while writing the code to separate the business-related code from the core implementation as far as possible without adding too much complexity to the whole solution.

You can take this library as a base which you can modify to be adapted to your needs. Feel free to do it and for sure if you have any comments or ideas I am all ears.


Supported Features
  1. Flat input data
  2. Dynamic node object properties
  3. Sorting by node object properties
  4. Searching by node object properties
    1. By like patterns (case sensitive/insensitive) or whole words
    2. By regular expressions
    3. Expanding to matching nodes
    4. Highlighting matching nodes
  5. Expand/Collapse
  6. Adding nodes
  7. Extensibility

Note:
The demo page may take quite time to fully load and the tree to appear because I meant to load a quite number of nodes to test the tree under such conditions and not only happy scenarios. The tree in the demo will load 1230 nodes.


Note:
This post is not meant to be a knockout tutorial. It assumes that you have experience with knockout and that's why I am not going to explain every line of code. Most of the code is business irrelative and dependent except for a few lines which will be highlighted with inline comments on the code.

Note:
The tree structure and styling used in this solution is built on the jsTree plugin. jsTree is jquery plugin, that provides interactive trees. It is absolutely free, open source and distributed under the MIT license. jsTree is easily extendable, themable and configurable, it supports HTML & JSON data sources and AJAX loading. It has several great features but in this solution we just used its themes.


Due to the huge amount of code I will not post the code here but you have download the code from this link.
You can also check the live demo on this link.


That's it. Hope you find this useful.


http://knockoutadvancedtree.byethost10.com



2015-02-02

Knockout Datagrid With Sorting, Paging And Searching




www.googledrive.com/host/0BxGPghTC6VdBfmVlRzg3d0xuSW1lVVRReXUwYlZDbFl6dTdhaUdRazJwb2kxNzVDRllVbTg


All code samples used on this post can be downloaded from here.


If you are interested into having a quick recap on the paging concept you can check the post Paging Concept - The Main Equations To Make It Easy first.


This post shows you how to fully implement a data grid with sorting, paging and searching features. It was taken into consideration while writing the code to separate the business-related code from the core implementation as far as possible without adding too much complexity to the whole solution.

You can take the code below as a base which you can modify to be adapted to your needs. Feel free to do it and for sure if you have any comments or ideas I am all ears.


Note:
This post is not meant to be a knockout tutorial. It assumes that you have experience with knockout and that's why I am not going to explain every line of code. Most of the code is business irrelative and dependent except for a few lines which will be highlighted with inline comments on the code.


index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
  <title>Development Simply Put | DataGrid With Sorting, Paging And Searching</title>
  
  <link rel="stylesheet" type="text/css" href="css/layout.css"/>
  <link type="text/css" rel="stylesheet" href="js/jtable.2.4.0/themes/metro/green/jtable.min.css"/>
  
  <script type="text/javascript" src="js/Vendor/jquery-1.11.0.min.js"></script>
  <script type="text/javascript" src="js/Vendor/jquery-ui-1.9.2.js"></script>
  <script type="text/javascript" src="js/Vendor/jquery.layout-1.3.0.js"></script>
  <script type="text/javascript" src="js/Vendor/jquery.paginate.js"></script>
  <script type="text/javascript" src="js/Vendor/KO/knockout-3.2.0.js"></script>
  <script type="text/javascript" src="js/Vendor/KO/knockout.mapping-latest.js"></script>
  <script type="text/javascript" src="js/DecimalSignedTextBoxWithLimit.js"></script>
  <script type="text/javascript" src="js/Common.js"></script>
  <script type="text/javascript" src="js/CommonViewModelsDefinitions.js"></script>
  <script type="text/javascript" src="js/Employees.js"></script>
    </head>
 
 <body>
  <div id="EmployeesMainDiv"></div>
 </body>
</html>

Employees_Template.html
<div class="jtable-main-container">
 <div class="jtable-title">
  <div class="jtable-title-text">
            Employees
  </div>
     <div class="jtable-toolbar">
   <span class="jtable-toolbar-item jtable-toolbar-item-add-record" style="vertical-align:middle!important;">
    <span class="jtable-toolbar-item-text">
     Highlight  
     <input type="text" style="width:200px;" data-bind="value: $root.SearchText, valueUpdate: 'afterkeydown'" />
    </span>
   </span>
      <!-- ko if:  $root.WithPaging() -->
    <span class="jtable-toolbar-item jtable-toolbar-item-add-record" style="vertical-align:middle!important;">
     <span class="jtable-toolbar-item-text">
      Page Size 
      <input type="text" style="width:50px;" data-bind="value: $root.PageSize, isUnSignedIntegerTextBox: true" />
     </span>
    </span>
   <!-- /ko -->
  </div> 
 </div>
    <table class="jtable">
  <thead>
   <tr>
    <th class="jtable-column-header jtable-column-header-sortable" data-bind="click: function () { $root.SetEmployeesSorting('Id'); }, css: { 'jtable-column-header-sorted-asc': $root.SortingColumn() == 'Id' && $root.SortingDirection() == 'asc', 'jtable-column-header-sorted-desc': $root.SortingColumn() == 'Id' && $root.SortingDirection() != 'asc' }">
     <div class="jtable-column-header-container">
      <span class="jtable-column-header-text">
       Id
      </span>
     </div>
    </th>
    <th class="jtable-column-header jtable-column-header-sortable" data-bind="click: function () { $root.SetEmployeesSorting('Name'); }, css: { 'jtable-column-header-sorted-asc': $root.SortingColumn() == 'Name' && $root.SortingDirection() == 'asc', 'jtable-column-header-sorted-desc': $root.SortingColumn() == 'Name' && $root.SortingDirection() != 'asc' }">
     <div class="jtable-column-header-container">
      <span class="jtable-column-header-text">
       Name
      </span>
     </div>
    </th>
   </tr>
  </thead>
  <tbody id="EmployeesTableBody">
   <!-- ko if:  $data.Employees().length > 0 -->
    <!-- ko template: {name: 'EmployeesTemplate', foreach: $data.Employees, as: 'employee'}-->
    <!-- /ko -->
   <!-- /ko -->
   <!-- ko ifnot:  $data.Employees().length > 0 -->
    <tr class="jtable-data-row jtable-row-odd">
     <td colspan="2">
      No employees found
     </td>
    </tr>
   <!-- /ko -->
  </tbody>
 </table>
    <!-- ko if:  $data.Employees().length > 0 -->
        <div style="display:block;" id="EmployeesPager" class="page_navigation"></div>
    <!-- /ko -->
</div>

<script id="EmployeesTemplate" type="text/html">
    <tr class="jtable-data-row" data-bind="css: {'not-found-on-search': !$root.SearchTextExistsOnRow(employee), 'jtable-row-even': ($index() % 2) == 0, 'jtable-row-odd': ($index() % 2) != 0 }">
        <td>
            <label data-bind="highlightThis: $root.SearchText(), text: employee.Id(), shortenAt: AppSettings.ShortenAt, moreText: AppSettings.ShortenMoreText, lessText: AppSettings.ShortenLessText"></label>
        </td>
  <td>
            <label data-bind="highlightThis: $root.SearchText, text: employee.Name(), shortenAt: AppSettings.ShortenAt, moreText: AppSettings.ShortenMoreText, lessText: AppSettings.ShortenLessText"></label>
        </td>
    </tr>
</script>

CommonViewModelsDefinitions.js
var AppSettings = new Object();
AppSettings.PageSize = 5;
AppSettings.ShortenAt = 15;
AppSettings.ShortenMoreText = "more";
AppSettings.ShortenLessText = "less";


function EmployeesViewModelDefinition(settings) {
    var self = this;

    self.IsNullOrUndefinedOrEmpty = function (obj) {
        return (typeof (obj) == 'undefined' || undefined == obj || null == obj || '' == obj.toString().trim());
    };

    self.finalSettings = {
        WithPaging: (self.IsNullOrUndefinedOrEmpty(settings.WithPaging)) ? false : settings.WithPaging,
        PageSize: (self.IsNullOrUndefinedOrEmpty(settings.PageSize)) ? AppSettings.PageSize : settings.PageSize,
        ActualCurrentPageNumber: (self.IsNullOrUndefinedOrEmpty(settings.ActualCurrentPageNumber)) ? 1 : settings.ActualCurrentPageNumber,
        ActualNumberOfPages: (self.IsNullOrUndefinedOrEmpty(settings.ActualNumberOfPages)) ? 1 : settings.ActualNumberOfPages,
        ActualTotalNumberOfRows: (self.IsNullOrUndefinedOrEmpty(settings.ActualTotalNumberOfRows)) ? 0 : settings.ActualTotalNumberOfRows,
        ActualCurrentPageRowsCount: (self.IsNullOrUndefinedOrEmpty(settings.ActualCurrentPageRowsCount)) ? 0 : settings.ActualCurrentPageRowsCount,
        PagerDomElementId: (self.IsNullOrUndefinedOrEmpty(settings.PagerDomElementId)) ? null : settings.PagerDomElementId,
        SortingColumn: (self.IsNullOrUndefinedOrEmpty(settings.SortingColumn)) ? 'Id' : settings.SortingColumn,
        SortingDirection: (self.IsNullOrUndefinedOrEmpty(settings.SortingDirection)) ? 'asc' : settings.SortingDirection,
        WithPagingUpdatedDelegate: (self.IsNullOrUndefinedOrEmpty(settings.WithPagingUpdatedDelegate)) ? function (currentSettings) { } : settings.WithPagingUpdatedDelegate,
        PageSizeUpdatedDelegate: (self.IsNullOrUndefinedOrEmpty(settings.PageSizeUpdatedDelegate)) ? function (currentSettings) { } : settings.PageSizeUpdatedDelegate,
        ActualCurrentPageNumberUpdatedDelegate: (self.IsNullOrUndefinedOrEmpty(settings.ActualCurrentPageNumberUpdatedDelegate)) ? function (currentSettings) { } : settings.ActualCurrentPageNumberUpdatedDelegate,
        SortingColumnUpdatedDelegate: (self.IsNullOrUndefinedOrEmpty(settings.SortingColumnUpdatedDelegate)) ? function (currentSettings) { } : settings.SortingColumnUpdatedDelegate,
        SortingDirectionUpdatedDelegate: (self.IsNullOrUndefinedOrEmpty(settings.SortingDirectionUpdatedDelegate)) ? function (currentSettings) { } : settings.SortingDirectionUpdatedDelegate
    };
 
    self.GetFinalSettings = function () {
        for (var prop in self) {
            if (self.finalSettings.hasOwnProperty(prop)) {
                self.finalSettings[prop] = self[prop]();
            }
        }

        return self.finalSettings;
    };

    self.SynchronizeSettings = function (source, destination) {
        for (var prop in destination) {
            if (destination.hasOwnProperty(prop)) {
                if (typeof (source[prop]) != 'undefined' && null != source[prop]) {
                    destination[prop] = source[prop];
                }
            }
        }
    };
 
 self.SearchText = ko.observable();
 
    self.private_WithPaging = ko.observable(self.finalSettings.WithPaging);
    self.WithPaging = ko.computed({
        read: function () {
            return self.private_WithPaging();
        },
        write: function (value) {
            self.private_WithPaging(value);

            if (typeof (self.finalSettings.WithPagingUpdatedDelegate) != 'undefined' && null != self.finalSettings.WithPagingUpdatedDelegate) {
                self.finalSettings.WithPagingUpdatedDelegate(self.GetFinalSettings());
            }
        },
        deferEvaluation: true
    });

    self.private_PageSize = ko.observable(self.finalSettings.PageSize);
    self.PageSize = ko.computed({
        read: function () {
            return self.private_PageSize();
        },
        write: function (value) {
            if (typeof (value) == 'undefined' || null == value || '' == value) {
                value = AppSettings.PageSize;
            }

            self.private_PageSize(value);

            if (typeof (self.finalSettings.PageSizeUpdatedDelegate) != 'undefined' && null != self.finalSettings.PageSizeUpdatedDelegate) {
                self.finalSettings.PageSizeUpdatedDelegate(self.GetFinalSettings());
            }
        },
        deferEvaluation: true
    });

    self.private_PagerDomElementId = ko.observable(self.finalSettings.PagerDomElementId);
    self.PagerDomElementId = ko.computed({
        read: function () {
            return self.private_PagerDomElementId();
        },
        write: function (value) {
            self.private_PagerDomElementId(value);
        },
        deferEvaluation: true
    });

    self.private_SortingColumn = ko.observable(self.finalSettings.SortingColumn);
    self.SortingColumn = ko.computed({
        read: function () {
            return self.private_SortingColumn();
        },
        write: function (value) {
            if (value == self.private_SortingColumn()) {
                self.private_SortingDirection(self.ToggleSortingDirection(self.SortingDirection()));
            }
            else {
                self.private_SortingColumn(value);
            }

            if (!self.WithPaging()) {
                self.SortEmployees();
            }
            else if (typeof (self.finalSettings.SortingColumnUpdatedDelegate) != 'undefined' && null != self.finalSettings.SortingColumnUpdatedDelegate) {
                self.finalSettings.SortingColumnUpdatedDelegate(self.GetFinalSettings());
            }
        },
        deferEvaluation: true
    });

    self.ToggleSortingDirection = function (currentDirection) {
        var finalDirection;

        if (currentDirection.toLowerCase().indexOf('asc') > -1) {
            finalDirection = 'desc';
        }
        else {
            finalDirection = 'asc';
        }

        return finalDirection;
    };

    self.private_SortingDirection = ko.observable(self.finalSettings.SortingDirection);
    self.SortingDirection = ko.computed({
        read: function () {
            return self.private_SortingDirection();
        },
        write: function (value) {
            self.private_SortingDirection(self.ToggleSortingDirection(self.SortingDirection()));

            if (!self.WithPaging()) {
                self.SortEmployees();
            }
            else if (typeof (self.finalSettings.SortingDirectionUpdatedDelegate) != 'undefined' && null != self.finalSettings.SortingDirectionUpdatedDelegate) {
                self.finalSettings.SortingDirectionUpdatedDelegate(self.GetFinalSettings());
            }
        },
        deferEvaluation: true
    });

    self.SetEmployeesSorting = function (sortingColumn) {
        self.SortingColumn(sortingColumn);
    };

    self.SortEmployees = function () {
        self.Employees.sort(self.SortEmployeesComparer);
    };

    self.SortEmployeesComparer = function (a, b) {
        var result = 0;

        var sortingColumn = self.SortingColumn();
        var sortingDirection = self.SortingDirection();

        var valA = a[sortingColumn]();
        var valB = b[sortingColumn]();

        if (isNumeric(valA) && isNumeric(valB)) {
            valA = valA.toString().toLowerCase().replace(',', '');
            valB = valB.toString().toLowerCase().replace(',', '');

            if (parseFloat(valA) == parseFloat(valB)) {
                result = 0;
            }
            else if (parseFloat(valA) > parseFloat(valB)) {
                result = 1;
            }
            else {
                result = -1;
            }
        }
        else {
            if (valA == valB) {
                result = 0;
            }
            else if (valA > valB) {
                result = 1;
            }
            else {
                result = -1;
            }
        }

        if (sortingDirection.toLowerCase().indexOf('asc') == -1) {
            result = result * -1;
        }

        return result;
    };

    self.DataBind = function (JSEmployees, actualNumberOfPages, actualTotalNumberOfRows, actualCurrentPageNumber, actualCurrentPageRowsCount) {
        var retrievedEmployees = ko.mapping.fromJS(JSEmployees);

        self.Employees.removeAll();

        for (var i = 0; i < retrievedEmployees().length; i++) {
            var Employee = (retrievedEmployees())[i];
            self.Employees.push(Employee);
        }

        self.finalSettings.ActualNumberOfPages = actualNumberOfPages;
        self.finalSettings.ActualTotalNumberOfRows = actualTotalNumberOfRows;
        self.finalSettings.ActualCurrentPageNumber = actualCurrentPageNumber
        self.finalSettings.ActualCurrentPageRowsCount = actualCurrentPageRowsCount;
        self.BuildPager();
    };

    self.DataBindFromSettings = function (JSEmployees, newSettings) {
        var finalSettings = self.GetFinalSettings();
        self.SynchronizeSettings(newSettings, finalSettings);
        self.DataBind(JSEmployees, finalSettings.ActualNumberOfPages, finalSettings.ActualTotalNumberOfRows, finalSettings.ActualCurrentPageNumber, finalSettings.ActualCurrentPageRowsCount);
    };

    self.BuildPager = function () {
        if (!self.IsNullOrUndefinedOrEmpty(self.PagerDomElementId()) && self.WithPaging() && self.Employees().length > 0 && self.finalSettings.ActualNumberOfPages > 1) {
            $("#" + self.PagerDomElementId()).show();

            $("#" + self.PagerDomElementId()).paginate({
                count: self.finalSettings.ActualNumberOfPages,
                start: self.finalSettings.ActualCurrentPageNumber,
                display: 11,
                border: false,
                text_color: 'rgba(255, 255, 255, 1)',
                background_color: 'rgba(255, 255, 255, 0)',
                text_hover_color: '#FB6E52',
                background_hover_color: '#FFFFFF',
                images: false,
                onChange: function (newPageNumber) {
                    if (newPageNumber != self.finalSettings.ActualCurrentPageNumber) {
                        self.finalSettings.ActualCurrentPageNumber = newPageNumber;

                        if (typeof (self.finalSettings.ActualCurrentPageNumberUpdatedDelegate) != 'undefined' && null != self.finalSettings.ActualCurrentPageNumberUpdatedDelegate) {
                            self.finalSettings.ActualCurrentPageNumberUpdatedDelegate(self.GetFinalSettings());
                        }
                    }
                }
            });
        }
        else {
            $("#" + self.PagerDomElementId()).hide();
        }
    };
 
 
 //Define your own entities array here
 self.Employees = ko.observableArray();
 
 //Include your own entities columns here
 self.SearchTextExistsOnRow = function(employee) {
  if(self.IsNullOrUndefinedOrEmpty(self.SearchText())) {
   return true;
  }
  else {
   return (employee.Id().toString().toLowerCase().indexOf(self.SearchText().toLowerCase())) != -1
   || (employee.Name().toString().toLowerCase().indexOf(self.SearchText().toLowerCase())) != -1;
  }  
 };
}

Common.js
jQuery.fn.highlight = function (str, className) {
    var regex = new RegExp(str, "gi");
    return this.each(function () {
        $(this).contents().filter(function() {
            return this.nodeType == 3 && regex.test(this.nodeValue);
        }).replaceWith(function() {
            return (this.nodeValue || "").replace(regex, function(match) {
                return "<span class=\"" + className + "\">" + match + "</span>";
            });
        });
    });
};

function isBound(elemenId) {
    var result = false;
    var x = ko.dataFor($("#" + elemenId)[0]);

    if (typeof (x) != 'undefined' & null != x) {
        result = true;
    }

    return result;
};

function IsNullOrUndefinedOrEmpty(obj) {
    return (typeof (obj) == 'undefined' || undefined == obj || null == obj || '' == obj);
}

function isNumber(evt) {
    evt = (evt) ? evt : window.event;
    var charCode = (evt.which) ? evt.which : evt.keyCode;
    if (charCode > 31 && (charCode < 48 || charCode > 57)) {
        return false;
    }
    return true;
}

function numberWithCommas(x) {
    var parts = x.toString().split(".");
    parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    return parts.join(".");
}

function getMaxOccurance(array) {
    if (array.length == 0)
        return null;
    var modeMap = {};
    var maxEl = array[0], maxCount = 1;
    for (var i = 0; i < array.length; i++) {
        var el = array[i];
        if (modeMap[el] == null)
            modeMap[el] = 1;
        else
            modeMap[el]++;
        if (modeMap[el] > maxCount) {
            maxEl = el;
            maxCount = modeMap[el];
        }
    }
    return maxEl;
}

function isNormalInteger(str) {
    var n = ~~Number(str);
    return String(n) === str && n > 0;
}

/*-----------------------------------KO Extenders-------------------------------*/
ko.bindingHandlers.isDecimalSignedTextBoxWithLimit = {
    update: function (element, valueAccessor, allBindings) {
        // First get the latest data that we're bound to
        var value = valueAccessor();

        // Next, whether or not the supplied model property is observable, get its current value
        var valueUnwrapped = ko.unwrap(value);

        // Grab some more data from another binding property
        var noOfDigitsBeforeDecimal = allBindings.get('noOfDigitsBeforeDecimal') || 10;
        var noOfDigitsAfterDecimal = allBindings.get('noOfDigitsAfterDecimal') || 10;
        var defaultValue = allBindings.get('defaultValue') || 0;
        var invalidInputCallack = allBindings.get('invalidInputCallack') || null;

        // Now manipulate the DOM element
        if (valueUnwrapped == true) {
            $(element).on("keypress", function () {
                return checkSignedDecimalWithLimit(this, noOfDigitsBeforeDecimal, noOfDigitsAfterDecimal);
            });

            $(element).on("keyup", function () {
                checkPasteSigned(this, noOfDigitsBeforeDecimal, noOfDigitsAfterDecimal, defaultValue, function (elem) { invalidInputCallack(elem); }, false);
            });

            $(element).on("change", function () {
                checkPasteSigned(this, noOfDigitsBeforeDecimal, noOfDigitsAfterDecimal, defaultValue, function (elem) { invalidInputCallack(elem); }, false);
            });

            $(element).on("blur", function () {
                checkPasteSigned(this, noOfDigitsBeforeDecimal, noOfDigitsAfterDecimal, defaultValue, function (elem) { invalidInputCallack(elem); }, true);
            });
        }
    },
    init: function (element, valueAccessor, allBindings) {
        var noOfDigitsBeforeDecimal = allBindings.get('noOfDigitsBeforeDecimal') || 10; // 10 is default duration unless otherwise specified
        var noOfDigitsAfterDecimal = allBindings.get('noOfDigitsAfterDecimal') || 10; // 10 is default duration unless otherwise specified

        adjustZerosAfterDecimalPoint(element, noOfDigitsAfterDecimal);
        adjustNumbersBeforeDecimalPoint(element, noOfDigitsBeforeDecimal);
    }
};

ko.bindingHandlers.isDecimalUnSignedTextBoxWithLimit = {
    update: function (element, valueAccessor, allBindings) {
        // First get the latest data that we're bound to
        var value = valueAccessor();

        // Next, whether or not the supplied model property is observable, get its current value
        var valueUnwrapped = ko.unwrap(value);

        // Grab some more data from another binding property
        var noOfDigitsBeforeDecimal = allBindings.get('noOfDigitsBeforeDecimal') || 10;
        var noOfDigitsAfterDecimal = allBindings.get('noOfDigitsAfterDecimal') || 10;
        var defaultValue = allBindings.get('defaultValue') || 0;
        var invalidInputCallack = allBindings.get('invalidInputCallack') || null;

        // Now manipulate the DOM element
        if (valueUnwrapped == true) {
            $(element).on("keypress", function () {
                return checkUnSignedDecimalWithLimit(this, noOfDigitsBeforeDecimal, noOfDigitsAfterDecimal);
            });

            $(element).on("keyup", function () {
                checkPasteUnSigned(this, noOfDigitsBeforeDecimal, noOfDigitsAfterDecimal, defaultValue, function (elem) { invalidInputCallack(elem); }, false);
            });

            $(element).on("change", function () {
                checkPasteUnSigned(this, noOfDigitsBeforeDecimal, noOfDigitsAfterDecimal, defaultValue, function (elem) { invalidInputCallack(elem); }, false);
            });

            $(element).on("blur", function () {
                checkPasteUnSigned(this, noOfDigitsBeforeDecimal, noOfDigitsAfterDecimal, defaultValue, function (elem) { invalidInputCallack(elem); }, true);
            });

            $(element).on("focus", function () {
                var value = $(this).val();
                var formattedDefaultValue = formatZerosAfterDecimalPoint(defaultValue.toString(), noOfDigitsAfterDecimal);
                formattedDefaultValue = formatNumbersBeforeDecimalPoint(formattedDefaultValue, noOfDigitsBeforeDecimal);

                if (value == formattedDefaultValue) {
                    $(this).val('');
                }
            });
        }
    },
    init: function (element, valueAccessor, allBindings) {
        var noOfDigitsBeforeDecimal = allBindings.get('noOfDigitsBeforeDecimal') || 10; // 10 is default duration unless otherwise specified
        var noOfDigitsAfterDecimal = allBindings.get('noOfDigitsAfterDecimal') || 10; // 10 is default duration unless otherwise specified

        adjustZerosAfterDecimalPoint(element, noOfDigitsAfterDecimal);
        adjustNumbersBeforeDecimalPoint(element, noOfDigitsBeforeDecimal);
    }
};

ko.bindingHandlers.isUnSignedIntegerTextBox = {
    update: function (element, valueAccessor, allBindings) {
        // First get the latest data that we're bound to
        var value = valueAccessor();

        // Next, whether or not the supplied model property is observable, get its current value
        var valueUnwrapped = ko.unwrap(value);

        // Grab some more data from another binding property
        var defaultValue = allBindings.get('defaultValue') || 0;

        // Now manipulate the DOM element
        if (valueUnwrapped == true) {
            $(element).on("keypress", function (event) {
                return isNumber(event)
            });

            $(element).bind("paste", function (e) {
                e.preventDefault();
            });

            $(element).on("focus", function () {
                var value = $(this).val();

                if (value == defaultValue) {
                    $(this).val('');
                }
            });

            $(element).on("blur", function () {
                var value = $(this).val();

                if (null == value || '' == value) {
                    $(this).val(defaultValue);
                }
            });
        }
    }
};

ko.bindingHandlers.shortenAt = {
    update: function (element, valueAccessor, allBindings) {
        // First get the latest data that we're bound to
        var value = valueAccessor();

        // Next, whether or not the supplied model property is observable, get its current value
        var valueUnwrapped = ko.unwrap(value);

        // Grab some more data from another binding property
        var moreText = allBindings.get('moreText') || "more";
        var lessText = allBindings.get('lessText') || "less";

        // Now manipulate the DOM element
        if (valueUnwrapped > 0) {
            var content = $(element).text();
            if (content.length > valueUnwrapped) {
                var con = content.substr(0, valueUnwrapped);
                var hcon = content.substr(valueUnwrapped, content.length - valueUnwrapped);
                var txt = con + '<span class="dots">...</span><span class="morecontent"><span>' + hcon + '</span>&nbsp;&nbsp;<a href="" class="moretxt">' + moreText + '</a></span>';
                $(element).html(txt);

                $(element).find(".moretxt").click(function () {
                    if ($(this).hasClass("sample")) {
                        $(this).removeClass("sample");
                        $(this).text(moreText);
                    } else {
                        $(this).addClass("sample");
                        $(this).text(lessText);
                    }
                    $(this).parent().prev().toggle();
                    $(this).prev().toggle();
                    return false;
                });
            }
        }
    }
};

ko.bindingHandlers.highlightThis = {
    update: function (element, valueAccessor, allBindings) {
        // First get the latest data that we're bound to
        var value = valueAccessor();

        // Next, whether or not the supplied model property is observable, get its current value
        var valueUnwrapped = ko.utils.unwrapObservable(value);

        // Grab some more data from another binding property
        //var moreText = allBindings.get('moreText') || "more";
        //var lessText = allBindings.get('lessText') || "less";

        // Now manipulate the DOM element
        if (typeof(valueUnwrapped) != 'undefined' && null != valueUnwrapped && valueUnwrapped.toString() != '') {
   if (!$(element).is("[backup]")) {
    $(element).attr("backup", $(element).html());
   }
   
   var backup = $(element).attr("backup");
   $(element).html(backup);
   $(element).highlight(valueUnwrapped, "highlighted-search-text");
        }
  else {
   var backupAttr = $(this).attr('backup');

   if ($(element).is("[backup]")) {debugger;
    var backup = $(element).attr("backup");
    $(element).html(backup);
   }
  }
    }
};
/*-----------------------------------KO Extenders-------------------------------*/

Employees.js
var EmployeesViewModel = null;
 
function ResetEmployeesView() {
    EmployeesViewModel = new EmployeesViewModelDefinition({
        SortingColumnUpdatedDelegate: GetEmployeesPagedOrdered,
        SortingDirectionUpdatedDelegate: GetEmployeesPagedOrdered,
        ActualCurrentPageNumberUpdatedDelegate: GetEmployeesPagedOrdered,
        PageSizeUpdatedDelegate: GetEmployeesPagedOrdered,
        WithPaging: true,
        PagerDomElementId: 'EmployeesPager'
    });
}

function GetEmployeesPagedOrdered(currentSettings, callback) {
 EmployeesViewModel.SearchText("");

    var pageNumber = currentSettings.ActualCurrentPageNumber;
 var pageSize = currentSettings.PageSize;
 var orderByPropertyName = currentSettings.SortingColumn;
 var sortingDirection = currentSettings.SortingDirection;
 
 var token = GetEmployeesPagedOrderedByAjax(pageSize, pageNumber, orderByPropertyName, sortingDirection);
 
 if(null != token) {
  var employees = token.Employees;
  var actualNumberOfPages = token.ActualNumberOfPages;
  var actualTotalNumberOfRows = token.ActualTotalNumberOfRows;
  var actualCurrentPageNumber = token.ActualCurrentPageNumber;
  var actualCurrentPageRowsCount = token.ActualCurrentPageRowsCount;

  var newSettings = {
   ActualNumberOfPages: actualNumberOfPages,
   ActualTotalNumberOfRows: actualTotalNumberOfRows,
   ActualCurrentPageNumber: actualCurrentPageNumber,
   ActualCurrentPageRowsCount: actualCurrentPageRowsCount
  };

  $("#EmployeesMainDiv").load('KOTemplates/Employees_Template.html',
   function () {
    ko.cleanNode($("#EmployeesMainDiv")[0]);
    if (!isBound('EmployeesMainDiv')) {
     ko.applyBindings(EmployeesViewModel, $("#EmployeesMainDiv")[0]);
    }

    EmployeesViewModel.DataBindFromSettings(employees, newSettings);
    
    if (typeof (callback) != 'undefined' && null != callback) {
     callback();
    }
   }
  );
 }
};

//This is the method resonsible for retrieving employees from the back-end by sorting and paging.
//The implementation of this method differs from one system to another depending on the back-end and the server-side framework and language.
//The most important point to notice here is the "Paging Calculations" section in the method below.
//This section should be taken into consideration and implemented. 
function GetEmployeesPagedOrderedByAjax(pageSize, pageNumber, orderByPropertyName, sortingDirection) {
 var resultToken = null;
 
 var AllEmployees = new Array();
 AllEmployees.push({Id:1, Name:"Employee 1"});
 AllEmployees.push({Id:2, Name:"Employee 2"});
 AllEmployees.push({Id:3, Name:"Employee 3"});
 AllEmployees.push({Id:4, Name:"Employee 4"});
 AllEmployees.push({Id:5, Name:"Employee 5"});
 AllEmployees.push({Id:6, Name:"Employee 6"});
 AllEmployees.push({Id:7, Name:"Employee 7"});
 AllEmployees.push({Id:8, Name:"Employee 8"});
 AllEmployees.push({Id:9, Name:"Employee 9"});
 AllEmployees.push({Id:10, Name:"Employee 10"});
 AllEmployees.push({Id:11, Name:"Employee 11"});
 AllEmployees.push({Id:12, Name:"Employee 12"});
 AllEmployees.push({Id:13, Name:"Employee 13"});
 AllEmployees.push({Id:14, Name:"Employee 14"});
 AllEmployees.push({Id:15, Name:"Employee 15"});
 AllEmployees.push({Id:16, Name:"Employee 16"});
 AllEmployees.push({Id:17, Name:"Employee 17"});
 AllEmployees.push({Id:18, Name:"Employee 18"});
 
 if(AllEmployees.length > 0) {
  AllEmployees.sort(function (a, b) {
   var result = 0;
   var valA = a[orderByPropertyName];
   var valB = b[orderByPropertyName];
   
   if (isNumeric(valA) && isNumeric(valB)) {
    valA = valA.toString().toLowerCase().replace(',', '');
    valB = valB.toString().toLowerCase().replace(',', '');

    if (parseFloat(valA) == parseFloat(valB)) {
     result = 0;
    }
    else if (parseFloat(valA) > parseFloat(valB)) {
     result = 1;
    }
    else {
     result = -1;
    }
   }
   else {
    if (valA == valB) {
     result = 0;
    }
    else if (valA > valB) {
     result = 1;
    }
    else {
     result = -1;
    }
   }

   if (sortingDirection.toLowerCase().indexOf('asc') == -1) {
    result = result * -1;
   }

   return result;
  });
  
  //-------------------------------------------------Paging Calculations-------------------------------------------------
  if(pageSize <= 0) {
   pageSize = AllEmployees.length;
  }
  
  var maxNumberOfPages = Math.max(1, Math.ceil(parseFloat(parseFloat(AllEmployees.length) / parseFloat(pageSize))));
  
  if(pageNumber < 1) {
   pageNumber = 1;
  }
  else if(pageNumber > maxNumberOfPages) {
   pageNumber = maxNumberOfPages;
  }
  
  var pageIndex = pageNumber - 1;
  var firstItemIndex = pageIndex * pageSize;
  var lastItemIndex = (pageIndex * pageSize) + (pageSize - 1);
  var actualNumberOfPages = maxNumberOfPages;
  var actualTotalNumberOfRows = AllEmployees.length;
  var actualCurrentPageNumber = pageNumber;
  
  if(lastItemIndex > AllEmployees.length) {
   lastItemIndex = AllEmployees.length - 1;
  }
  
  var actualCurrentPageRowsCount = lastItemIndex - firstItemIndex;
  //-------------------------------------------------Paging Calculations-------------------------------------------------
  
  resultToken = new Object();
  resultToken.Employees = AllEmployees.slice(firstItemIndex, lastItemIndex + 1);
  resultToken.ActualNumberOfPages = actualNumberOfPages;
  resultToken.ActualTotalNumberOfRows = actualTotalNumberOfRows;
  resultToken.ActualCurrentPageNumber = actualCurrentPageNumber;
  resultToken.ActualCurrentPageRowsCount = actualCurrentPageRowsCount;
 }
 
 return resultToken;
}

function BindGrid(callback) {
    GetEmployeesPagedOrdered(EmployeesViewModel.GetFinalSettings(), callback);
};

$(document).ready(function(){
 ResetEmployeesView();
 BindGrid();
});

Layout.css
body {
    font-family: Arial, Helvetica, sans-serif;
    background: grey no-repeat center center;
    min-width: 980px;
    font-size: 12px;
    line-height: normal;
    color: black;
}

* {
    padding: 0;
    outline: none;
    border: none;
    text-align: left;
    list-style: none;
}

.not-found-on-search {
 display: none!important;
}

.highlighted-search-text {
 background-color: #ff2;
}

table thead tr th, table tbody tr td {
    vertical-align: top;
}

a, a:hover, a:visited {
    text-decoration: none;
    outline: none;
    cursor: pointer;
}

a:hover, a:focus, a:active {
 color: black /*c4c4c4*/;
 text-decoration: none;
}

::selection {
    background: #666;
    color: black;
    text-shadow: none;
}

html, body {
    height: 100%;
}

.jPaginate {
    float: right;
    height: 34px;
    position: relative;
    color: #a5a5a5;
    font-size: small;
    width: auto;
    padding-right: 65px;
    margin-top: 10px;
}

.jPaginate a {
 line-height: 15px;
 height: 18px;
 cursor: pointer;
 padding: 2px 5px;
 margin: 2px;
 float: left;
}

.jPag-control-back {
    position: absolute;
    left: 0px;
}

.jPag-control-front {
    position: absolute;
    top: 0px;
}

.jPaginate span {
    cursor: pointer;
}

ul.jPag-pages {
    float: left;
    list-style-type: none;
    margin: 0px 0px 0px 0px;
    padding: 0px;
}

ul.jPag-pages li {
 display: inline;
 float: left;
 padding: 0px;
 margin: 0px;
 font-weight: bold;
}

ul.jPag-pages li a {
 float: left;
 padding: 2px 5px;
}

span.jPag-current {
    cursor: default;
    background-color: rgba(251, 235, 235, 0.38);
    line-height: 15px;
    height: 18px;
    padding: 2px 5px;
    margin: 2px;
    float: left;
}

table tr.jtable-row-odd td, table tr.jtable-data-row td {
    border-bottom: 1px solid #ddd;
    border-right: 1px solid #ddd;
    line-height: 30px;
}


Note:
The table structure and styling used in this solution is built on the jTable plugin. jTable is a jQuery plugin that is used to create AJAX based CRUD tables without coding HTML or Javascript. It has several great features but in this solution we just used its themes. In this solution we used the green metro theme but it provides other themes which you can choose from.



That's it. Again, all code samples used on this post can be downloaded from here.
Hope you find this usefull.