Search code examples
knockout.jsknockout-2.0single-page-application

UI not updating with ko.utils.arrayFilter


When I update my ko.computed, the UI does not update. I have a click event that fires a method called selectThing. selectThing updates computed, which in turn should update my UI (it doesn't). I am new to knockout, so it is probably a concept I am missing about how computeds work. Here is part of my view with the call to selectThing :

                                         <tbody data-bind="foreach: myCertificates">
                                        <tr style="cursor: pointer" data-bind="click: $parent.selectThing, css: { highlight: $parent.isSelected() == $data.lwCertID }  ">
                                            <td>
                                                <ul style="width: 100%">
                                                    <b><span data-bind="    text: clientName"></span>&nbsp;(<span data-bind="    text: clientNumber"></span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span data-bind="    text: borrowBaseCount"></span>&nbsp;Loan(s)&nbsp;</b>
                                                    <br />
                                                    Collateral Analyst:&nbsp;<span data-bind="    text: userName"></span>
                                                    <br />
                                                    Certificate:&nbsp;<span data-bind="text: lwCertID"></span>&nbsp;&nbsp;Request&nbsp;Date:&nbsp;<span data-bind="    text: moment(requestDate).format('DD/MMM/YYYY')"></span>
                                            </td>
                                        </tr>
                                    </tbody>

selectThing should update this part of the view-

                     <table id="Table2" style="width: 100%;" border="0">
                     <tr>
                        <td>Length: <span data-bind="text: CertificateDetailsToShow().length"></span>
                            <table id="Table3" border="0" class="table table-hover" width="100%">
                                <tbody data-bind="foreach: CertificateDetailsToShow">
                                    <tr id="Tr1" style="cursor: pointer">
                                        <td>
                                            <ul style="width: 100%">
                                                <b>Loan:&nbsp;<span data-bind="text: LoanNum"></span>&nbsp;(<span data-bind="    text: CurrType"></span>)</b><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Collateral Balance:&nbsp;<span data-bind="    text: CollanteralBalance"></span><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Sales/Additinal:&nbsp;<span data-bind="    text: SalesAdditions"></span><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Discounts:&nbsp;<span data-bind="    text: Discounts"></span><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Credit Memos:&nbsp;<span data-bind="    text: CreditMemos"></span><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Non AR Cash:&nbsp;<span data-bind="    text: NonARCash"></span><br />

                                        </td>
                                    </tr>
                                </tbody>

                            </table>
                        </td>
                    </tr>
                </table>

Here is the viewmodel code with the selectThing method :

         var vm = {
        activate: activate,
        allCertificates: allCertificates,
        myCertificates: myCertificates,
        CertificateDetails: CertificateDetails,
        CertificateDetailsToShow: CertificateDetailsToShow, 
        title: 'Certificate Approvals',
        SelectMyCerts: SelectMyCerts,
        SelectAllCerts: SelectAllCerts,
        theOptionId: ko.observable(1),
        serverOptions: serverOptions,
        serverSelectedOptionID: serverSelectedOptionID,
        SortUpDownAllCerts: SortUpDownAllCerts,
        isSelected: isSelected,
        selectThing: function (row, event) {
            filter = row.lwCertID;
            CertificateDetailsToShow = ko.utils.arrayFilter(CertificateDetails(), function (CertD) {
                        return CertD.CertificateID == filter;
                    });

            isSelected(row.lwCertID);
        }

    };

CertificateDetailsToShow is populated correctly from CertificateDetails observablearray, but the UI does not update. How can I fix this?

Here is all the code :

viewmodel :

 define(['services/logger', 'durandal/system', 'durandal/plugins/router', 'services/CertificateDataService'],
function (logger, system, router, CertificateDataService) {
    var allCertificates = ko.observableArray([]);
    var myCertificates = ko.observableArray([]);
    var isSelected = ko.observable();
    var serverSelectedOptionID = ko.observable();
    var filter = ko.observable(0);
    var CertificateDetails = ko.observableArray([]);
    var CertificateDetailsToShow = ko.computed(function () {
        GetCertificateDetails();

        return ko.utils.arrayFilter(CertificateDetails(), function (CertD) {
            return CertD.CertificateID == filter;
        });
    }, this);

    var serverOptions = [
    { id: 1, name: 'Certificate', OptionText: 'lwCertID' },
    { id: 2, name: 'Client Name', OptionText: 'clientName' },
    { id: 3, name: 'Client Number', OptionText: 'clientNumber' },
    { id: 4, name: 'Request Date', OptionText: 'requestDate' },
    { id: 5, name: 'Collateral Analyst', OptionText: 'userName' }
    ];

    var activate = function () {
        // go get local data, if we have it
        return SelectAllCerts(), SelectMyCerts(), CertificateDetailsToShow();
    };


    var vm = {
        activate: activate,
        allCertificates: allCertificates,
        myCertificates: myCertificates,
        CertificateDetails: CertificateDetails,
        CertificateDetailsToShow: CertificateDetailsToShow, 
        title: 'Certificate Approvals',
        SelectMyCerts: SelectMyCerts,
        SelectAllCerts: SelectAllCerts,
        theOptionId: ko.observable(1),
        serverOptions: serverOptions,
        serverSelectedOptionID: serverSelectedOptionID,
        SortUpDownAllCerts: SortUpDownAllCerts,
        isSelected: isSelected,
        selectThing: function (row, event) {
            filter = row.lwCertID;
            CertificateDetailsToShow = ko.utils.arrayFilter(CertificateDetails(), function (CertD) {
                        return CertD.CertificateID == filter;
                    });

            isSelected(row.lwCertID);
        }

    };



    serverSelectedOptionID.subscribe(function () {
        var sortCriteriaID = serverSelectedOptionID();
        allCertificates.sort(function (a, b) {
            var fieldname = serverOptions[sortCriteriaID - 1].OptionText;

            if (a[fieldname] == b[fieldname]) {
                return a[fieldname] > b[fieldname] ? 1 : a[fieldname] < b[fieldname] ? -1 : 0;
            }

            return a[fieldname] > b[fieldname] ? 1 : -1;

        });

    });

    return vm;


    function GetCertificateDetails() {
        return CertificateDataService.getCertDetails(CertificateDetails);
    }

    function SortUpDownAllCerts() {
        allCertificates.sort();
    }


    function SelectAllCerts() {
        return CertificateDataService.getallCertificates(allCertificates);
    }

    function SelectMyCerts() {
        return CertificateDataService.getMyCertificates(myCertificates);
    }

});

data service module :

 define(['services/logger', 'durandal/system'],
function (logger, system) {
    var certificateModel = function (clientID, lwCertID, requestDate, userName, statusDescription, statusCode, statusDesc, ceoUserName, clientName, clientNumber, borrowBaseCount, advRequestCount, certType) {
        var self = this;
        self.clientID = ko.observable(clientID);
        self.lwCertID = ko.observable(lwCertID);
        self.requestDate = ko.observable(requestDate);
        self.userName = ko.observable(userName);
        self.statusDescription = ko.observable(statusDescription);
        self.statusCode = ko.observable(statusCode);
        self.statusDesc = ko.observable(statusDesc);
        self.ceoUserName = ko.observable(ceoUserName);
        self.clientName = ko.observable(clientName);
        self.clientNumber = ko.observable(clientNumber);
        self.borrowBaseCount = ko.observable(borrowBaseCount);
        self.advRequestCount = ko.observable(advRequestCount);
        self.certType = ko.observable(certType);
    };

    var certificateDETAILSModel = function (ToBeProcessedDate, CertType, CertCollID, CertificateID, LoanNumberTypeAndCurrencyCombined, LoanType, CurrType, CollanteralBalance, SalesAdditions,
        CreditMemos, CashRemovals, NonDilutiveAdjustment, Discounts, NonARCash, DilutiveAdjustment, LWCertCollsComments, StatusCode, CertLoanID, Modified, 
        LoanNum, EffectiveDate, RepWireNumber, Advance, ModifiedDate, DDAAccountName, LWCertLoansComments, Comment) {
        var self = this;
        self.ToBeProcessedDate = ko.observable(ToBeProcessedDate);
        self.CertType = ko.observable(CertType);
        self.CertCollID = ko.observable(CertCollID);
        self.CertificateID = ko.observable(CertificateID);
        self.LoanNumberTypeAndCurrencyCombined = ko.observable(LoanNumberTypeAndCurrencyCombined);
        self.LoanType = ko.observable(LoanType);
        self.CurrType = ko.observable(CurrType);
        self.CollanteralBalance = ko.observable(CollanteralBalance);
        self.SalesAdditions = ko.observable(SalesAdditions);
        self.CreditMemos = ko.observable(CreditMemos);
        self.CashRemovals = ko.observable(CashRemovals);
        self.NonDilutiveAdjustment = ko.observable(NonDilutiveAdjustment);
        self.Discounts = ko.observable(Discounts);
        self.NonARCash = ko.observable(NonARCash);
        self.DilutiveAdjustment = ko.observable(DilutiveAdjustment);
        self.LWCertCollsComments = ko.observable(LWCertCollsComments);
        self.StatusCode = ko.observable(StatusCode);
        self.CertLoanID = ko.observable(CertLoanID);
        self.Modified = ko.observable(Modified);
        self.LoanNum = ko.observable(LoanNum);
        self.EffectiveDate = ko.observable(EffectiveDate);
        self.RepWireNumber = ko.observable(RepWireNumber);
        self.Advance = ko.observable(Advance);
        self.ModifiedDate = ko.observable(ModifiedDate);
        self.DDAAccountName = ko.observable(DDAAccountName);
        self.LWCertLoansComments = ko.observable(LWCertLoansComments);
        self.Comment = ko.observable(Comment);
    };

    //var getCertDetails = function (certificateDetailsObservable, source) {
    //    var dataObservableArray = ko.observableArray([]);
    //    $.ajax({
    //        type: "POST",
    //        dataType: "json",
    //        url: "/api/caapproval/CertDtlsByID/",
    //        data: source,
    //        async: false,
    //        success: function (dataIn) {
    //            var newJ = $.parseJSON(dataIn);
    //            ko.mapping.fromJSON(dataIn, {}, dataObservableArray);

    //        },
    //        error: function (error) {
    //            jsonValue = jQuery.parseJSON(error.responseText);
    //            //jError('An error has occurred while saving the new part source: ' + jsonValue, { TimeShown: 3000 });
    //        }

    //    });
    //    return dataObservableArray;
    //}
    var getCertDetails = function (certificateDetailsObservable) {
        var dataObservableArray = ko.observableArray([]);
        var newJ;
        $.ajax({
            type: "POST",
            dataType: "json",
            url: "/api/caapproval/CertDtlsByID/",
            data: '{}',
            async: false,
            success: function (dataIn) {
                newJ = $.parseJSON(dataIn);

                certificateDetailsObservable([]);

                newJ.forEach(function (p) {
                    var certificateDtls = new certificateDETAILSModel(p.toBeProcessedDate, p.certType, p.certCollID, p.certificateID,
                        p.loanNumberTypeAndCurrencyCombined, p.loanType, p.currType, p.collanteralBalance, p.salesAdditions,
                        p.creditMemos, p.cashRemovals, p.nonDilutiveAdjustment, p.discounts, p.nonARCash, p.dilutiveAdjustment, p.lWCertCollsComments, p.statusCode, p.certLoanID, p.modified,
                        p.loanNum, p.effectiveDate, p.repWireNumber, p.advance, p.modifiedDate, p.dDAAccountName, p.lWCertLoansComments, p.comment);
                    certificateDetailsObservable.push(certificateDtls);
                });


            },
            error: function (error) {
                jsonValue = jQuery.parseJSON(error.responseText);
                //jError('An error has occurred while saving the new part source: ' + jsonValue, { TimeShown: 3000 });
            }

        });
        return certificateDetailsObservable(newJ);
    }

        var getallCertificates = function (CertificatesObservable) {
            $.getJSON('/api/caapproval', function (data) {

                CertificatesObservable([]);
                data.forEach(function (p) {
                    var certificate = new certificateModel(p.clientID, p.lwCertID, p.requestDate, p.userName, p.statusDescription, p.statusCode, p.statusDesc, p.ceoUserName, p.clientName, p.clientNumber, p.borrowBaseCount, p.advRequestCount, p.certType);
                    CertificatesObservable.push(certificate);
                });
                return CertificatesObservable(data);
            });
        }

        var getMyCertificates = function (CertificatesObservable) {
            $.getJSON('/api/caapproval/myCert', function (data) {

                CertificatesObservable([]);
                data.forEach(function (p) {
                    var certificate = new certificateModel(p.clientID, p.lwCertID, p.requestDate, p.userName, p.statusDescription, p.statusCode, p.statusDesc, p.ceoUserName, p.clientName, p.clientNumber, p.borrowBaseCount, p.advRequestCount, p.certType);
                    CertificatesObservable.push(certificate);
                });
                return CertificatesObservable(data);
            });
        }

    var dataservice = {
        getallCertificates: getallCertificates,
        getMyCertificates: getMyCertificates,
        getCertDetails: getCertDetails
    };
    return dataservice;

});

view :

 <section>

<br />
<br />
<br />
<table border="0" style="width: 100%">
    <tr>
        <td style="width: 50%">
            <h2 data-bind="text: title"></h2>
            <ul class="nav nav-tabs" style="width: 100%">
                <li class="active">
                    <a id="btnMyCert" href="#MyCert" data-toggle="tab">
                        <i class="icon-align-center"></i>&nbsp;&nbsp;My Certificates&nbsp;
                    </a>
                </li>
                <li>
                    <a id="btnAll" href="#AllCert" data-toggle="tab">
                        <i class="icon-align-center"></i>&nbsp;&nbsp;All Pending&nbsp;
                    </a>
                </li>

            </ul>
            <div class="ToolBox" style="height: 30px; width: 100%">
                <b>&nbsp;&nbsp;Sort By:&nbsp;</b>
                <select id="ddlSortBy" style="margin-top: 0px; height: 24px; width: 160px !important"
                    data-bind="value: serverSelectedOptionID, options: serverOptions, optionsText: 'name', optionsValue: 'id'">
                </select>&nbsp;&nbsp;<img data-bind="click: SortUpDownAllCerts" src="/Content/images/updownarrow.bmp" style="padding-bottom: 4px; cursor: pointer; vertical-align: middle;" />
            </div>

            <div class="tab-content">
                <div id="MyCert" class='tab-pane active' style="height: 400px; width: 100%; overflow-x: hidden; overflow-y: auto;">
                    <div class="btn-group">
                    </div>
                    <table id="tblCert" style="width: 100%;" border="0">
                        <tr>
                            <td>
                                <table id="tblMyCert" border="0" class="table table-hover" width="100%">
                                    <tbody data-bind="foreach: myCertificates">
                                        <tr style="cursor: pointer" data-bind="click: $parent.selectThing, css: { highlight: $parent.isSelected() == $data.lwCertID }  ">
                                            <td>
                                                <ul style="width: 100%">
                                                    <b><span data-bind="    text: clientName"></span>&nbsp;(<span data-bind="    text: clientNumber"></span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span data-bind="    text: borrowBaseCount"></span>&nbsp;Loan(s)&nbsp;</b>
                                                    <br />
                                                    Collateral Analyst:&nbsp;<span data-bind="    text: userName"></span>
                                                    <br />
                                                    Certificate:&nbsp;<span data-bind="text: lwCertID"></span>&nbsp;&nbsp;Request&nbsp;Date:&nbsp;<span data-bind="    text: moment(requestDate).format('DD/MMM/YYYY')"></span>
                                            </td>
                                        </tr>
                                    </tbody>
                                </table>
                            </td>
                        </tr>
                    </table>
                </div>
                <div id="AllCert" class='tab-pane' style="height: 400px; width: 100%; overflow-x: hidden; overflow-y: auto;">
                    <table id="Table1" style="width: 100%;" border="0">
                        <tr>
                            <td>
                                <table id="tblAllCert" border="0" class="table table-hover" width="100%">
                                    <tbody data-bind="foreach: allCertificates">
                                        <tr id="AllCertRow" style="cursor: pointer" data-bind="click: $parent.selectThing, css: { highlight: $parent.isSelected() == $data.lwCertID }">
                                            <td>
                                                <ul style="width: 100%">

                                                    <b><span data-bind="    text: clientName"></span>&nbsp;(<span data-bind="    text: clientNumber"></span>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span data-bind="    text: borrowBaseCount"></span>&nbsp;Loan(s)&nbsp;</b>
                                                    <br />
                                                    Collateral Analyst:&nbsp;<span data-bind="    text: userName"></span>
                                                    <br />
                                                    Certificate:&nbsp;<span data-bind="text: lwCertID"></span>&nbsp;&nbsp;Request&nbsp;Date:&nbsp;<span data-bind="    text: moment(requestDate).format('DD/MMM/YYYY')"></span>
                                            </td>
                                        </tr>
                                    </tbody>
                                </table>
                            </td>
                        </tr>
                    </table>
                </div>
            </div>
        </td>
        <td style="width: 50%">
            <br /><br /><br /><br /><br />
            <div id="Div1" class='tab-pane' style="height: 400px; width: 100%; overflow-x: hidden; overflow-y: auto;">
                <table id="Table2" style="width: 100%;" border="0">
                     <tr>
                        <td>Length: <span data-bind="text: CertificateDetailsToShow().length"></span>
                            <table id="Table3" border="0" class="table table-hover" width="100%">
                                <tbody data-bind="foreach: CertificateDetailsToShow">
                                    <tr id="Tr1" style="cursor: pointer">
                                        <td>
                                            <ul style="width: 100%">
                                                <b>Loan:&nbsp;<span data-bind="text: LoanNum"></span>&nbsp;(<span data-bind="    text: CurrType"></span>)</b><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Collateral Balance:&nbsp;<span data-bind="    text: CollanteralBalance"></span><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Sales/Additinal:&nbsp;<span data-bind="    text: SalesAdditions"></span><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Discounts:&nbsp;<span data-bind="    text: Discounts"></span><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Credit Memos:&nbsp;<span data-bind="    text: CreditMemos"></span><br />
                                                &nbsp;&nbsp;&nbsp;&nbsp;Non AR Cash:&nbsp;<span data-bind="    text: NonARCash"></span><br />

                                        </td>
                                    </tr>
                                </tbody>

                            </table>
                        </td>
                    </tr>
                </table>
            </div>

        </td>
    </tr>



</table>


Solution

  • Your selectThing shouldn't write to ko.computed. They are supposed to be read only (unless specified otherwise). So what your function does is overwriting var CertificateDetailsToShow with static data so it's not observable anymore and therefore knockout doesn't take it under consideration when updating UI.

    That's how you missed the concept and answers your question.

    To fix this either make var CertificateDetailsToShow an ko.observable and in selectThing just put new value in it: CertificateDetailsToShow( certificateID ) or as you already saving it in isSelected(row.lwCertID) use it in your ko.computed by defining filter (and don't overwrite it):

    var filter = isSelected()
    return ko.utils.arrayFilter(CertificateDetails(), function (CertD) {
                return CertD.CertificateID == filter;
           });
    

    Depends what you need. Also looking for your value twice is not optimal. And ko.utils.arrayFilter can return more than one Certificate so it is a place for bug in the future. Even if IDs are unique I would go with ko.utils.arrayFirst because arrayFilter iterates always through whole array.