Leanne
Leanne

Reputation: 687

Knockout js add a new list

I am trying to create a form with a list which adds a list dynamically!

However I just can't get my add function working and it keep complains that my list is not defined.

Here is my viewmodel in jave script

function DeliveryCategoryViewModel() {
    var self = this;
    self.DeliveryCategory = ko.observable(new DeliveryCategory(1, "new Name", [new DeliveryOption("first"), new DeliveryOption("second")]));

    self.addDeliveryOptions = function () {
        self.DeliveryOptions.push(new DeliveryOption("new Option"));
    }

    self.removeDeliveryOptions = function (option) {
        self.DeliveryCategory.remove(option);
    }

}

and these are actual model which holds the data

function DeliveryCategory(id, name, option) {
    this.Id = id;
    this.Name = name;
    this.DeliveryOptions = ko.observableArray(option);

}

function DeliveryOption(name) {


    this.Id = "2";
        this.Name = name;
}


$(document).ready(function () {

    ko.applyBindings(new DeliveryCategoryViewModel());
});

This is my form

<div id="newDelCategory">
        <input data-bind="value:DeliveryCategory().Id" type="hidden" />
        <label class="newDelCategoryLabel">New delivery category name: </label>
    <input type="text" data-bind="value:DeliveryCategory().Name" class="newDelCategoryText" id="newDelCategoryText" placeholder="Delivery category name" />
</div>

<div id="addOption">
    <a id="addOptionLink" href="#" data-bind="click:addDeliveryOptions" class="link">+Add delivery option</a>
</div>

<div id="deliveryOptionContent" data-bind="foreach: DeliveryCategory().DeliveryOptions">
    <div class="newDelOption">
        <input data-bind="value:$data.Id" type="hidden" />
        <div class="divider"></div>

        <label class="newDelOptionLabel">New delivery option name: </label>
        <input type="text" data-bind="value:$data.Name" class="categoryName" id="newDelOptionText" placeholder="Delivery option name" />
        <a id="removeOptionLink" data-bind="click:$parent.removeDeliveryOptions" class="link removeOptionLink">Remove</a>
    </div>
</div>

When I try to click click:addDeliveryOptions, it return on Firebug console.

TypeError: self.DeliveryCategory.DeliveryOptions is undefined
self.DeliveryCategory.DeliveryOptions.push(new DeliveryOption("new Option"));

I tried different things such as click:$root.addDeliveryOptions, and also tried to add addDeliveryOptions function as a prototype (e.g. DeliveryCategory.prototype.addDeliveryOptions(...)) and still getting Typeerror...

Is it because DeliveryOptions is not initialised? I expected that it would be when DeliveryCategory is initialised from the DeliveryCategoryViewModel() ...

Any idea? Thanks!

Upvotes: 0

Views: 46

Answers (1)

Jeff
Jeff

Reputation: 2443

Small Oversight. Easy Fix.

You were calling push and remove off the observable array from the view model but it does not exists as a direct member of the view model.

This is because you never add the observable array directly to this view model. You use a constructor to create an object to observe with DeliveryCategory. One of the properties on that object is an observable array DeliveryOptions. To get access to the observable array from this scope, you have to evaluate DeliveryCategory to get access to it's property DeliveryOptions before you run any array methods. So, instead of this:

self.addDeliveryOptions = function () {
    self.DeliveryOptions.push(new DeliveryOption("new Option"));
}

self.removeDeliveryOptions = function (option) {
    self.DeliveryCategory.remove(option);
}

The Solution:

self.addDeliveryOptions = function() {
    self.DeliveryCategory().DeliveryOptions.push(new DeliveryOption("new Option"));
}

self.removeDeliveryOptions = function(option) {
    self.DeliveryCategory().DeliveryOptions.remove(option);
}

See the snippet below

function DeliveryCategoryViewModel() {
  var self = this;
  self.DeliveryCategory = ko.observable(new DeliveryCategory(1, "new Name", [new DeliveryOption("first"), new DeliveryOption("second")]));
  self.addDeliveryOptions = function() {
    self.DeliveryCategory().DeliveryOptions.push(new DeliveryOption("new Option"));
  }

  self.removeDeliveryOptions = function(option) {
    self.DeliveryCategory().DeliveryOptions.remove(option);
  }

}

function DeliveryCategory(id, name, option) {
  this.Id = id;
  this.Name = name;
  this.DeliveryOptions = ko.observableArray(option);

}

function DeliveryOption(name) {


  this.Id = "2";
  this.Name = name;
}


$(document).ready(function() {

  ko.applyBindings(new DeliveryCategoryViewModel());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="newDelCategory">
  <input data-bind="value:DeliveryCategory().Id" type="hidden" />
  <label class="newDelCategoryLabel">New delivery category name:</label>
  <input type="text" data-bind="value:DeliveryCategory().Name" class="newDelCategoryText" id="newDelCategoryText" placeholder="Delivery category name" />
</div>

<div id="addOption">
  <a id="addOptionLink" href="#" data-bind="click:addDeliveryOptions" class="link">+Add delivery option</a>
</div>

<div id="deliveryOptionContent" data-bind="foreach: DeliveryCategory().DeliveryOptions">
  <div class="newDelOption">
    <input data-bind="value:$data.Id" type="hidden" />
    <div class="divider"></div>

    <label class="newDelOptionLabel">New delivery option name:</label>
    <input type="text" data-bind="value:$data.Name" class="categoryName" id="newDelOptionText" placeholder="Delivery option name" />
    <a href="#" id="removeOptionLink" data-bind="click:$parent.removeDeliveryOptions" class="link removeOptionLink">Remove</a>
  </div>
</div>

Upvotes: 1

Related Questions