简体   繁体   中英

Observable Array changes are not reflecting on first click - KnockoutJS

I have a observable array for checkbox list and I am handling checked event using an observable array.

Now when I make a change, the values of array update instantaneously but it does not reflect it on the DOM. but when I click on some other item, the change reflects.

In short, Change of array values is not reflected on first click but on the second click, it does.

Here is my code:

        /*--- Factory Objects ---*/
    var ModelFactory = function () {
        this.ModelTitle = ko.observable();
        this.ModelId = ko.observable();
        this.FamilyId = ko.observable();
    }
    var SeriesFactory = function (SeriesTitle, SeriesId) {
        this.SeriesTitle = ko.observable();
        this.SeriesId = ko.observable();
    };
    var OptionsFactory = function (SelectionFlag, OptionTitle, OptionId, OptionPrice, OptionType, ModelId) {
        var self = this;
        this.OptionTitle = ko.observable();
        this.OptionId = ko.observable();
        this.OptionType = ko.observable();
        this.OptionPrice = ko.observable();
        this.ModelId = ko.observable();
        this.SelectionFlag = ko.computed(function () {
            return self.OptionType() === "0" ? self.OptionId() : null;
        });
        this.FormattedOptionPrice = ko.computed(function () {
            return "USD " + self.OptionPrice();
        });
    };
    /*--- Factory Objects End ---*/
    /*--- ViewModel ----*/
    function OpportunityViewModel() {
        var self = this;
        self.SeriesList = ko.observableArray([]);
        self.ModelList = ko.observableArray([]);
        self.OptionList = ko.observableArray([]);
        self.SelectedOptionsList = ko.observableArray([]);
        self.LoadSeries = function () {
            self.ClearFactory(self.SeriesList);
            for (var i = 0; i < familyList.length; i++) {
                self.SeriesList.push(new SeriesFactory().SeriesId(familyList[i].Id).SeriesTitle(familyList[i].FamilyTitle));
            }
        };
        self.LoadModels = function (seriesId) {
            self.ClearFactory(self.ModelList);
            for (var i = 0; i < productList.length; i++) {
                if (seriesId() === productList[i].FamilyId) {
                    self.ModelList.push(new ModelFactory().ModelId(productList[i].Id).ModelTitle(productList[i].ModelTitle).FamilyId(productList[i].FamilyId));
                }
            }
        };
        self.SeriesClick = function (seriesObject, event) {
            if (seriesObject !== null && typeof seriesObject !== 'undefined') {
                self.LoadModels(seriesObject.SeriesId);
            }
        };
        self.ModelClick = function (modelObject, event) {
            if (modelObject !== null && typeof modelObject !== 'undefined') {
                self.LoadOptions(modelObject.ModelId);
            }
        };
        self.LoadOptions = function (modelId) {
            self.ClearFactory(self.OptionList);
            for (var i = 0; i < optionList.length; i++) {
                if (modelId() === optionList[i].ModelId) {
                    self.OptionList.push(new OptionsFactory()
                        .OptionId(optionList[i].Id)
                        .OptionTitle(optionList[i].OptionTitle)
                        .OptionPrice(optionList[i].OptionPrice)
                        .OptionType(optionList[i].OptionType)
                        .ModelId(optionList[i].ModelId)
                        );
                    if (optionList[i].OptionType === "0") {
                        self.SelectedOptionsList.push(optionList[i].Id);
                    }
                }
            }
        };
        self.SelectedOptions = function (selectedItem) {
            console.clear();
            for (var i = 0; i < self.SelectedOptionsList().length; i++) {
                if (self.SelectedOptionsList()[i] === selectedItem.OptionId()) {
                    self.SelectedOptionsList.remove(selectedItem.OptionId());
                    console.log(JSON.stringify(self.SelectedOptionsList()));
                    return;
                }
            }
            self.SelectedOptionsList.push(selectedItem.OptionId());
            console.log(JSON.stringify(self.SelectedOptionsList()));
        };
        self.ClearFactory = function (observableFactory) {
            observableFactory([]);
        };
    }
    /*--- View Model End ---*/
    /*--- Init --- */
    $(function () {
        var opportunityVM = new OpportunityViewModel();
        opportunityVM.LoadSeries();
        ko.applyBindings(opportunityVM);
    });
    /*--- Init End ---*/

and the template

 <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#productSearch">
    Launch Modal
</button>
<div class="modal fade" id="productSearch" role="dialog" aria-labelledby="productDialogTitle">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                <h4 class="modal-title" id="productDialogTitle">Product Selection</h4>
            </div>
            <div class="modal-body">
                <div class="row">
                    <div class="col-md-4">
                        <div class="panel panel-primary">
                            <div class="panel-heading">
                                <h3 class="panel-title">Series Selection</h3>
                            </div>
                            <div class="panel-body">
                                <ul data-bind="foreach: SeriesList" id="ulFamilyList">
                                    <li><a class="btn btn-link series" data-bind="text: SeriesTitle, attr: {Id: SeriesId}, click: $parent.SeriesClick"></a></li>
                                </ul>
                            </div>
                            <div class="panel-footer">

                            </div>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="panel panel-primary">
                            <div class="panel-heading">
                                <h3 class="panel-title">Model Selection</h3>
                            </div>
                            <div class="panel-body">
                                <ul id="ulModelList" data-bind="foreach: ModelList">
                                    <li><a class="btn btn-link productModel" data-bind="text: ModelTitle, attr:{Id: ModelId}, click: $parent.ModelClick"></a></li>
                                </ul>
                            </div>
                            <div class="panel-footer">

                            </div>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="panel panel-primary">
                            <div class="panel-heading">
                                <h3 class="panel-title">Option Selection</h3>
                            </div>
                            <div class="panel-body">
                                <table class="table table-stripped table-hover table-bordered" id="ulOptionList" data-bind="foreach: OptionList">
                                    <tr class="optionListRow">
                                        <td>
                                            <input type="checkbox" data-bind="value: OptionId, click: $parent.SelectedOptions, checked: $parent.SelectedOptionsList" />
                                            <span data-bind="text: '&nbsp' + OptionTitle()"></span>
                                        </td>
                                        <td>
                                            <p data-bind="text: FormattedOptionPrice"></p>
                                        </td>
                                    </tr>
                                </table>
                            </div>
                            <div class="panel-footer">

                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
                <button type="button" class="btn btn-primary">Save changes</button>
            </div>
        </div>
    </div>
</div>

What am I doing wrong? Also I am just another rookie and just got started with KnockoutJS. I would appreciate if experts here also optimize this code and show some of their skills improving it.

Thanks :)

UPDATE

Here is some test data that I am using.

        familyList = [
        { FamilyTitle: "3 Series", Id: "9e55c0ae-37d0-0d89-808f-acccfb58562f" },
        { FamilyTitle: "5 Series", Id: "3d9d6640-a8b5-b4ed-56d2-ef9d1d6f8544" },
        { FamilyTitle: "7 Series", Id: "c395c655-f302-852a-ccca-1a5e9bf10e0e" },
        { FamilyTitle: "X Series", Id: "3e31300f-1dcb-1d50-f796-c58c76cb4ab2" }
    ];
    productList = [
        { ModelTitle: "325i", Id: "e1d17dbf-3142-4bde-89bd-830caa09eb93", FamilyId: "9e55c0ae-37d0-0d89-808f-acccfb58562f" },
        { ModelTitle: "525i", Id: "9ac7da72-5224-6046-3913-c2700c486682", FamilyId: "3d9d6640-a8b5-b4ed-56d2-ef9d1d6f8544" },
        { ModelTitle: "X5", Id: "e64d76e4-73f8-36e5-9eca-27166bcc890b", FamilyId: "3e31300f-1dcb-1d50-f796-c58c76cb4ab2" },
        { ModelTitle: "X6", Id: "b104e806-5652-a21b-0180-a8956246a045", FamilyId: "3e31300f-1dcb-1d50-f796-c58c76cb4ab2" },
        { ModelTitle: "X3", Id: "b3447f56-a51b-a451-29de-26b4b3fd98e9", FamilyId: "3e31300f-1dcb-1d50-f796-c58c76cb4ab2" },
        { ModelTitle: "530i xDrive", Id: "30595eef-d0a7-a234-dc7f-6670a588c00e", FamilyId: "3d9d6640-a8b5-b4ed-56d2-ef9d1d6f8544" },
        { ModelTitle: "740Li", Id: "f19aed7c-f2a8-6777-4ed2-93ef2b717ac3", FamilyId: "c395c655-f302-852a-ccca-1a5e9bf10e0e" },
        { ModelTitle: "325Li xDrive", Id: "eaabc23e-d040-34ee-1e59-b1f6b9bc3cff", FamilyId: "9e55c0ae-37d0-0d89-808f-acccfb58562f" },
        { ModelTitle: "M3", Id: "e200ffba-e70c-e743-e8b5-8dd39061bd7d", FamilyId: "9e55c0ae-37d0-0d89-808f-acccfb58562f" },
        { ModelTitle: "M5", Id: "3d37e4b7-61be-a467-d4b9-aba784dea17b", FamilyId: "3d9d6640-a8b5-b4ed-56d2-ef9d1d6f8544" },
        { ModelTitle: "320i", Id: "398ee891-4847-5f29-b7c9-b98249d6b582", FamilyId: "9e55c0ae-37d0-0d89-808f-acccfb58562f" },
        { ModelTitle: "550i Sedan", Id: "49e7ca96-d801-ff2c-c112-245b1707e6c1", FamilyId: "3d9d6640-a8b5-b4ed-56d2-ef9d1d6f8544" },
        { ModelTitle: "750Li xDrive", Id: "9193cc24-58a8-cb8b-c7d4-f10eddb110b6", FamilyId: "c395c655-f302-852a-ccca-1a5e9bf10e0e" },
    ];
    optionList = [
        { Id: "option01", ModelId: "e1d17dbf-3142-4bde-89bd-830caa09eb93", OptionPrice: "120", OptionType: "0", OptionTitle: "325i Moonroof" },
        { Id: "option02", ModelId: "e1d17dbf-3142-4bde-89bd-830caa09eb93", OptionPrice: "150", OptionType: "1", OptionTitle: "325i 19\" Alloy Wheels" },
        { Id: "option03", ModelId: "e1d17dbf-3142-4bde-89bd-830caa09eb93", OptionPrice: "180", OptionType: "1", OptionTitle: "325i Remote Keyless Entry" },
        { Id: "option04", ModelId: "e1d17dbf-3142-4bde-89bd-830caa09eb93", OptionPrice: "200", OptionType: "0", OptionTitle: "325i Power Locks" },
        { Id: "option05", ModelId: "e1d17dbf-3142-4bde-89bd-830caa09eb93", OptionPrice: "520", OptionType: "0", OptionTitle: "325i 6 Speed Automatic" },
        { Id: "option06", ModelId: "e1d17dbf-3142-4bde-89bd-830caa09eb93", OptionPrice: "600", OptionType: "1", OptionTitle: "325i Pedal Shifters" },
        { Id: "option07", ModelId: "e1d17dbf-3142-4bde-89bd-830caa09eb93", OptionPrice: "540", OptionType: "1", OptionTitle: "325i Heated Front and Back Seats" },
        { Id: "option06", ModelId: "f19aed7c-f2a8-6777-4ed2-93ef2b717ac3", OptionPrice: "690", OptionType: "1", OptionTitle: "740Li Touring Package" },
        { Id: "option09", ModelId: "f19aed7c-f2a8-6777-4ed2-93ef2b717ac3", OptionPrice: "960", OptionType: "0", OptionTitle: "740Li Illuminating" },
        { Id: "option10", ModelId: "9ac7da72-5224-6046-3913-c2700c486682", OptionPrice: "600", OptionType: "0", OptionTitle: "525i 20\" Alloy Wheels" },
        { Id: "option11", ModelId: "9ac7da72-5224-6046-3913-c2700c486682", OptionPrice: "320", OptionType: "0", OptionTitle: "525i Tech Package" },
        { Id: "option12", ModelId: "9ac7da72-5224-6046-3913-c2700c486682", OptionPrice: "542", OptionType: "1", OptionTitle: "525i Premium Package" },
        { Id: "option13", ModelId: "9ac7da72-5224-6046-3913-c2700c486682", OptionPrice: "520", OptionType: "1", OptionTitle: "525i Sports Package" },
        { Id: "option14", ModelId: "9ac7da72-5224-6046-3913-c2700c486682", OptionPrice: "540", OptionType: "0", OptionTitle: "525i Heated Back Seats" },
        { Id: "option15", ModelId: "9ac7da72-5224-6046-3913-c2700c486682", OptionPrice: "770", OptionType: "1", OptionTitle: "525i Smart Cruise Control" },
        { Id: "option16", ModelId: "e64d76e4-73f8-36e5-9eca-27166bcc890b", OptionPrice: "670", OptionType: "1", OptionTitle: "X5 Moonroof" },
        { Id: "option17", ModelId: "e64d76e4-73f8-36e5-9eca-27166bcc890b", OptionPrice: "450", OptionType: "0", OptionTitle: "X5 Blind Spot Detection" },
        { Id: "option18", ModelId: "b3447f56-a51b-a451-29de-26b4b3fd98e9", OptionPrice: "340", OptionType: "0", OptionTitle: "X3 Heated Seats" },
        { Id: "option19", ModelId: "9193cc24-58a8-cb8b-c7d4-f10eddb110b6", OptionPrice: "550", OptionType: "0", OptionTitle: "750Li Smart Stop System" },
        { Id: "option20", ModelId: "9193cc24-58a8-cb8b-c7d4-f10eddb110b6", OptionPrice: "870", OptionType: "0", OptionTitle: "750Li Blind Spot Detection" },
        { Id: "option21", ModelId: "e200ffba-e70c-e743-e8b5-8dd39061bd7d", OptionPrice: "230", OptionType: "0", OptionTitle: "M3 6 Cylinder Direct Injecton Engine" },
        { Id: "option22", ModelId: "e200ffba-e70c-e743-e8b5-8dd39061bd7d", OptionPrice: "450", OptionType: "1", OptionTitle: "M3 8 Speed Automatic Transmission" },
        { Id: "option23", ModelId: "e200ffba-e70c-e743-e8b5-8dd39061bd7d", OptionPrice: "650", OptionType: "0", OptionTitle: "M3 6 Speed Manual Transmission" },
        { Id: "option24", ModelId: "3d37e4b7-61be-a467-d4b9-aba784dea17b", OptionPrice: "980", OptionType: "1", OptionTitle: "M5 Automatic Moonroof" },
    ];

**UPDATE 2 **

Here I put everything on JSFiddle

https://jsfiddle.net/hhkrjus3/

I solved it. The click event was preventing the selection. I do not know why, but when I removed the click event, it worked. The workaround I chose is: I created a computed flag in Factory Method. It would be very helpful if someone will answer why binding click event was preventing the results form being reflected on the DOM.

Thanks everyone :)

when dealing with checkboxes, you should use the "checked" binding. http://knockoutjs.com/documentation/checked-binding.html

This will probably solve your problem :)

EDIT:I was abit to fast there, i see you alrdy are using the checked binding. But you are also using the value binding. You should only use one of them, checked in this case

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM