2

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.

6
  • You need to use ko.mapping.fromJS() in order to have all the properties in your existing itemsJSON converted into ko.observable(). Try var itemsJSON = ko.mapping.fromJS(@Html.Raw(...)) (you may need to install the mapping plugin: knockoutjs.com/documentation/plugins-mapping.html) Commented Jan 12, 2017 at 14:27
  • No dice, Error: The argument passed when initializing an observable array must be an array, or null, or undefined. Commented Jan 12, 2017 at 14:41
  • I think I need to somehow convert it to an array of itemModel Commented Jan 12, 2017 at 14:42
  • Map to an array of models: ko.observableArray(items.map(function(data) { return new itemModel(data) })); Commented Jan 12, 2017 at 14:43
  • @user3297291, that works! Thank you! You can add as an answer if you'd like. Now I just have to figure out why my matching(arrayFirst) function is not working. But I will post another question for that. Commented Jan 12, 2017 at 14:47

1 Answer 1

1

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);
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.