dmikester1
dmikester1

Reputation: 1392

items not getting correctly added to observable array

I am creating an observable array from JSON data. Either I am creating the viewmodel wrong initially or I am adding new data to the array incorrectly. When I console.log out the array after adding to it, the newly added data is different. Here is a screenshot:enter image description here As you can see the object after brocCode=1800 is different than all the previous items in the array. I think the issue is that when I am adding a new line to the table, I am creating a new itemModel and adding that to the items list. When I am getting the JSON data and making that an observable array, I am not making an observable array of itemModels.

Here is my KnockoutJS: (specifically look at the self.addLine function and self.items = ko.observableArray(items); in itemsModel):

var itemModel = function (data) {
        var self = this;
        self.invalidItem = ko.observable(true);
        self.itemNo = ko.observable(data ? data.itemNo : '').extend( {
            required: {
                params: true,
                message: "Item no. required."
            }
        });
        self.brocCode = ko.observable(data ? data.brocCode : '').extend( {
            required: {
                params: true,
                message: "Bro code required."
            }
        });
        self.itemDesc = ko.observable(data ? data.itemDesc : '').extend( {
            required: {
                params: true,
                message: "Item desc required."
            }
        });
        self.retail = ko.observable(data ? data.retail : '').extend( {
            required: {
                params: true,
                message: "Retail required."
            }
        })
        .extend({numeric: 2});
        self.prizeNum = ko.observable(data ? data.prizeNum : '').extend( {
            required: {
                params: true,
                message: "Prize num required."
            }
        });
        self.itemOrder = ko.observable(data ? data.itemOrder : '').extend( {
            required: {
                params: true,
                message: "Item order required."
            }
        });
    }

    var itemsModel = function(items) {
        var self = this;
        self.items = ko.observableArray(items);
        //self.items = ko.mapping.fromJSON(items, itemModel);

        self.invalidItem = ko.observable(true);

        self.checkItemNo = function(data) {
            //console.log("lost focus - " + self.invalidItem());
            var itemNo = $.trim(data.itemNo());
            console.log(itemNo);
            if (itemNo != "") {
                var item = "";
                $.each(window.listOfItems, function(i, v) {
                    if (v.No.search(itemNo) != -1) {
                        item = v.Description;
                        return;
                    }
                });
                console.log(item);
                if(item != "") {
                    console.log(self.items());
                    var match = ko.utils.arrayFirst(self.items(), function(newItem) {
                        console.log("checking " + newItem.itemNo);
                        return itemNo === newItem.itemNo;
                    });
                    console.log("match: " + match);
                    if (!match) {
                        data.itemDesc(item);
                    } else { // item already entered
                        data.invalidItem(true);
                        setTimeout(function() { data.invalidItem(true); }, 1);
                        data.itemDesc("");
                        slideDownMsg("Item already entered.");
                        slideUpMsg(3000);
                    }
                } else { // invalid item #
                    console.log(data);
                    data.invalidItem(true);
                    setTimeout(function() { data.invalidItem(true); }, 1);
                    data.itemDesc("");
                    slideDownMsg("Invalid item number.");
                    slideUpMsg(3000);
                }
            }
        }

        self.submit = function() {
            //self.showErrors(true);
            if (viewModel.errors().length === 0) {
                console.log('Thank you.');
                $("#brochureForm").submit();
            }
            else {
                console.log('Please check your submission.');
                viewModel.errors.showAllMessages();
                $(".input-validation-error").first().focus();
            }
        }

        self.addLine = function() {
            var iModel = new itemModel();
            iModel.invalidItem(true);
            self.invalidItem(true);
            console.log("adding new line; it is: " + self.invalidItem());
            self.items.push( iModel );
            //setTimeout(function() { self.invalidItem(true); }, 1);
        };

        self.insertLine = function(index) {
            self.items.splice(index, 0, new itemModel() );
        };

        self.removeItem = function(item) {
            self.items.remove(item);
        };

        self.errors = ko.validation.group(self.items, { deep: true, live: true });

        self.validate = function() {
            self.errors.showAllMessages();
        }
    };

    var profitCode = function(code, desc, name) {
        this.code = code;
        this.desc = desc;
        this.name = name;
    };

    var codeModel = function(codes) {
        var self = this;
        self.availableProfitCodes = ko.observableArray([])
        self.codes = ko.observableArray(codes);
    }

    var profitItemsModel = function(items) {
        var self = this;
        self.items = ko.observableArray(items);
    }

    var combined = (function () {
        function combinedVM() {
            this.codes = ko.observable(codeModel);
            this.items = ko.observable(profitItemsModel);
            this.availableProfitCodes = codeModel.availableProfitCodes;
        }
        return combinedVM;
    })();

    ko.bindingHandlers.enterPress = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var allBindings = allBindingsAccessor();
            element.addEventListener('keydown', function (event) {
                var keyCode = (event.which ? event.which : event.keyCode);
                if (keyCode === 13 || (!event.shiftKey && keyCode === 9)) {
                    event.preventDefault();
                    //bindingContext.$root.invalidItem(false);
                    bindingContext.$root.addLine();
                    return false;
                }
                return true;
            });
        }
    };

    ko.bindingHandlers.insertPress = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var allBindings = allBindingsAccessor();
            element.addEventListener('keydown', function (event) {
                var keyCode = (event.which ? event.which : event.keyCode);
                if (keyCode === 45) {
                    event.preventDefault();
                    bindingContext.$root.insertLine(ko.unwrap(valueAccessor()));
                    return false;
                }
                return true;
            });
        }
    };

    ko.bindingHandlers.selected = {
        update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var selected = ko.utils.unwrapObservable(valueAccessor());
            if (selected) element.select();
        }
    };        

    function GetItems() {
        var itemsJSON = @Html.Raw(Json.Encode(Model.brochureItems));
        viewModel = new itemsModel(itemsJSON);
        ko.applyBindings(viewModel, $("#itemListContainer")[0]);
    }

GetItems() gets called on Document.ready.

Upvotes: 2

Views: 158

Answers (1)

user3297291
user3297291

Reputation: 23397

Your itemsJSON is an array of plain objects. Even though they might look like an itemModel (they share the same property names), there's a big difference: they do not have observable property values, nor are their values validated by your extensions.

In your addLine method, you correctly instantiate a new itemModel by calling new itemModel(). Your observable array will therefore contain a mix of plain objects and itemModel instances. This doesn't have to be a problem, but if you want to be able to easily work with the array's values, it's better to make sure they all share the same type.

You can map your initial data to the correct viewmodels by iterating over the objects in itemsJSON:

var itemModels = itemsJSON.map(function(data) {
  return new itemModel(data);
});

I'd advice you to follow naming conventions and rename itemModel to ItemModel. If it suits your style, you can create a static helper method that returns a new instance for you:

ItemModel.create = function(data) { return new ItemModel(data); };

var itemModels = itemsJSON.map(ItemModel.create);

Upvotes: 1

Related Questions