Search code examples
knockout.jsknockout-mapping-pluginmaster-detail

knockout dynamic master details


In the quest of Dynamic Create, Read,Update, Delete through KnockoutJS

part of the continuation of a question with fellow expert (dynamic column and rows with knockoutjs)

<script type="text/javascript">
     var columnDefs = [{
        "$id": "1",
            "ColumnName": "Id",
            "system_type_id": 56,
            "primaryCol": 1
    }, {
        "$id": "2",
            "ColumnName": "Inv_Id",
            "system_type_id": 231,
            "primaryCol": 0
    }, {
        "$id": "3",
            "ColumnName": "ACX_No",
            "system_type_id": 175,
            "primaryCol": 0
    }, {
        "$id": "4",
            "ColumnName": "ACX_Name",
            "system_type_id": 175,
            "primaryCol": 0
    }, {
        "$id": "5",
            "ColumnName": "S_No",
            "system_type_id": 175,
            "primaryCol": 0
    }, {
        "$id": "27",
            "ColumnName": "Acc_Class",
            "system_type_id": 231,
            "primaryCol": 0
    }, {
        "$id": "28",
            "ColumnName": "Direction",
            "system_type_id": 231,
            "primaryCol": 0
    }];

    var rowDefs = [{
        "$id": "1",
            "Id": 1,
            "Inv_Id": "PV0001-1",
            "ACX_No": "6",
            "ACX_Name": "ABC",
            "S_No": "5",
            "Acc_Class": "Local",
            "Direction": "Two-Way"
    }, {
        "$id": "2",
            "Id": 2,
            "Inv_Id": "PV0002-1",
            "ACX_No": "3",
            "ACX_Name": "CKD",
            "S_No": "6",
            "Acc_Class": "Local",
            "Direction": "Two-Way"
    }];

    function HeaderViewModel(data, parent) {
        var self = this,
            nbsp = String.fromCharCode(160);

        // observables
        ko.mapping.fromJS(data, {}, self);
        //self.isVisible = ko.observable(true);

        // computeds
        self.isVisible = ko.computed(function () {
            //console.log('data.ColumnName.indexOf(\'SYS\'):',  data.ColumnName.indexOf('SYS'));
            return data.ColumnName.indexOf('SYS') >= 0 ? false : true;
        });
        self.HeaderName = ko.computed(function () {
            return data.ColumnName.replace('_', nbsp);
        });
        self.SystemField = ko.computed(function () {
            return data.ColumnName.indexOf('SYS');
        });
        self.Visible = ko.computed(function () {
            //console.log('data.ColumnName.indexOf(\'SYS\'):', data.ColumnName.indexOf('SYS'));
            return data.ColumnName.indexOf('SYS') >= 0 ? false : true;
        });
    }
    // ---------------------------------------------------------------------------

    function RowViewModel(data, parent) {
        var self = this,
            nbsp = String.fromCharCode(160);

        // observables
        ko.mapping.fromJS(data, {}, self);

        // computeds
        self.HrefDetailsPage = ko.computed(function () {
            return parent.invtype + '/' + data.Id;
        });

        self.selectedItem = ko.observable();
        self.setSelectedItem = function () {
            console.dir(this);
            self.selectedItem(this);
        };
        self.getDetails = function (details) { 
            self.selectedItem(details);
            //console.dir(self.selectedItem);
            return details; // self.selectedItem();
        }
    }
    // ---------------------------------------------------------------------------

    function ViewModel() {
        var self = this;

        // raw values
        self.invtype = "@ViewBag.invtype"; //"Pavement";

        // observables
        self.columns = ko.observableArray();
        self.rows = ko.observableArray();

        // computeds
        self.visibleColumns = ko.computed(function () {
            return ko.utils.arrayFilter(self.columns(), function (column) { 
                return column.isVisible;
            });
        });

        // methods 

        self.load = function () {
            $.when(
            // use two plain $.get() calls to your API resources here
            // the $.post() is required only for the mock-up in jsFiddle
            $.post("/echo/json/", {
                json: JSON.stringify(columnDefs)
            }),
            $.post("/echo/json/", {
                json: JSON.stringify(rowDefs)
            }))
                .then(function (columnResponse, rowResponse) {
                var columnDefs = columnResponse[0],
                    rowDefs = rowResponse[0],
                    columnMapping = {
                        key: function (data) {
                            return ko.utils.unwrapObservable(data.ColumnName);
                        },
                        create: function (options) {
                            return new HeaderViewModel(options.data, self);
                        }
                    },
                    rowMapping = {
                        key: function (data) {
                            return ko.utils.unwrapObservable(data.Id);
                        },
                        create: function (options) {
                            return new RowViewModel(options.data, self);
                        }
                    };

                ko.mapping.fromJS(columnDefs, columnMapping, self.columns);
                ko.mapping.fromJS(rowDefs, rowMapping, self.rows);
            });

            return self;
        };
    }
    // ---------------------------------------------------------------------------

    ko.applyBindings(new ViewModel().load());


      </script>  

//HTML - UI

   <div class="details" data-bind="visible:rows.selectedItem">
     <div data-bind="with: rows.selectedItem">
            details data.......
            <pre data-bind="text: JSON.stringify(ko.toJSON($data))"></pre>
            <input data-bind="value:Id" />

            <!-- TODO dynamicly output label and data from the viewmodel -->

            <b>ID:</b> <span data-bind="text: Id"></span>
            <b>ACX Name:</b> <span data-bind="text:ACX_Name"></span>
            <b>Inv ID:</b> <span data-bind="text:ACX_Name"></span>

      </div>
    </div>
    <div data-bind="visible: rows.selectedItem == null">
        No selection was made
    </div>
    <div id="listPlaceholder"> 
        <table>
        <thead>
            <tr>
                <th>Link</th>
                <!-- ko foreach: $root.visibleColumns -->
                <th data-bind="text: HeaderName, visible: Visible"></th>
                <!-- /ko -->
            </tr>
        </thead>
        <tbody data-bind="foreach: $root.rows">
            <tr data-bind="click: $data.setSelectedItem">
                <td><a href="" data-bind="attr: {href: HrefDetailsPage}">Details</a></td>
                <!-- ko foreach: $root.visibleColumns --> 
                <td data-bind="text: $parent[ColumnName()], visible: Visible"></td>
                <!-- /ko -->
            </tr>
        </tbody>
        </table>

    </div>

with above code i am seing the data at self.setSelectedItem = function () { console.dir(this); self.selectedItem(this); };

i would like to output this data in details at the section

sandbox : (http://jsfiddle.net/sigibb/tq55u/)


Solution

  • The selectedItem property was misplaced so move it to the ViewModel class. Now if you click on a row its details will be shown a the top.

    See fiddle

    function ViewModel() {
         var self = this;
         self.selectedItem = ko.observable(null);
    }
    <div data-bind="with: selectedItem">test <pre data-bind="text: JSON.stringify(ko.toJSON($data))"></pre>
        <input data-bind="value:Id" /> <b>ID:</b>  <span data-bind="text: Id"></span>
    </div>
    

    Edit:

    Sorry SiGagan. When I saw the detail div the top of the html.I thought that you want only one detail for all rows. I updated the fiddle in order to have a master and detais view.

    See new Fiddle

    I hope it helps.