简体   繁体   中英

My sample shopping cart is not working as expected knockout js

I am learning knockout.js. So I'm try to build a sample cart where initially a drop down will come with product data. When user select any product then corresponding price and quantity will be filled up in right input control and sub total will be calculated automatically by computed property.

Initially when I run my program then one data is showing for a cart but when I select any product then price and Qty value is not getting filled up and no sub total is showing. I am new in KO and that why some where I made the silly mistake which is not getting clear before my eyes. So please guide me where I made the mistake.

Here is my full code

 <table id="table1" cellspacing="0" cellpadding="0" border="0">
    <tr>
        <th style="width:150px">Product</th>
        <th>Price ($)</th>
        <th>Quantity</th>
        <th>Amount ($)</th>
    </tr>

    <tbody data-bind='template: {name: "orderTemplate", foreach: lines}'></tbody>
</table>

<script type="text/html" id="orderTemplate">
    <tr>
        <td><select data-bind="options: products, 
                               optionsText: 'name', 
                               value: id,
                               optionsCaption:'--Select--'">,
                               value: $parent.product
                               </select>
        </td>
        <td><span data-bind="value: price" /></td>
        <td><input data-bind="value: quantity" /></td>
        <td><span data-bind="value: subtotal" /></td>
    </tr>
</script>

<script type="text/javascript">
    var _products = [
      {
          "name": "1948 Porsche 356-A Roadster",
          "price": 53.9
          , quantity: 1
      },
      {
          "name": "1948 Porsche Type 356 Roadster",
          "price": 62.16
          , quantity: 1
      },
      {
          "name": "1949 Jaguar XK 120",
          "price": 47.25
          , quantity: 1
      },
      {
          "name": "1952 Alpine Renault 1300",
          "price": 98.58
          , quantity: 1
      },
      {
          "name": "1952 Citroen-15CV",
          "price": 72.82
          , quantity: 1
      },
      {
          "name": "1956 Porsche 356A Coupe",
          "price": 98.3
          , quantity: 1
      },
      {
          "name": "1957 Corvette Convertible",
          "price": 69.93
          , quantity: 1
      }];

    function formatCurrency(value) {
        return "$" + value.toFixed(2);
    }

    var CartLine = function () {
        var self = this;
        self.products = ko.observableArray(_products);
        self.product = ko.observable();
        self.quantity = ko.observable(1);
        self.price = ko.observable(1);
        self.subtotal = ko.computed(function () {
            return self.product() ? self.product().price * parseInt("0" + self.quantity(), 10) : 0;
        });

    };

    var Cart = function () {
        // Stores an array of lines, and from these, can work out the grandTotal
        var self = this;
        self.lines = ko.observableArray([new CartLine()]); // Put one line in by default
    };

    ko.applyBindings(new Cart());    
</script>

So there's a few issues here. First of all, your binding for the dropdown is malformed

<select data-bind="options: products, 
                           optionsText: 'name', 
                           value: id,
                           optionsCaption:'--Select--'">,
                           value: $parent.product
                           </select>

The Value field is declared inside the data-bind to the property 'id' which I don't see in the view model. The value binding outside ('value: $parent.product') binds to a valid property, but it's not inside the data-bind. It should look more like this:

<select data-bind="options: products, 
                           optionsText: 'name',
                            value: product">
                           </select>

Note: I removed the optionsCaption, because it will overwrite your 'product' object (the object with name, price, and quantity) with just the string in the optionsCaption. This messes up any bindings that rely on the 'product' object properties later.

As for the other fields you wish to display:

    <td><span data-bind="value: price" /></td>
    <td><input data-bind="value: quantity" /></td>
    <td><span data-bind="value: subtotal" /></td>

Those are currently binding to the viewModel properties price, quantity, and subtotal. None of which are changing (and the span's should be bound to 'text' not 'value'). So we need to bind those fields to the object that is getting set by the dropdown like so:

    <td><span data-bind="text: product().price" /></td>
    <td><input data-bind="value: product().quantity" /></td>
    <td><span data-bind="text: product().total" /></td>

This will display the correct values for 'price' and 'quantity'. These values are not observables, however, and will not update any computeds when they change. So we need these properties to be observable in order to get the full responsive feel from typing in the quantity field and seeing the total update. I prefer creating JS objects to hold these collections of observable properties:

function Product(data){
data = data || {};
var self = this;
self.name = ko.observable(data.name || "");
self.price = ko.observable(data.price || "0");
self.quantity = ko.observable(data.quantity || "0");
self.total = ko.computed(function(){
    return self.price() * self.quantity();
});
}

I moved the totaling function into this object as well, just for consistency.
To use this function you can call it by passing in an object with the appropriate properties or call it with nothing and it will get initialized to

{name: "", price: "0", quantity: "0"}.

This also means we have to change your list of products to use this new object, otherwise nothing in that list will have observable properties. You can just wrap 'new Product()' around the existing JS objects you were using:

var _products = [
  new Product({
      "name": "1948 Porsche 356-A Roadster",
      "price": 53.9
      , quantity: 1
  }),
  new Product({
      "name": "1948 Porsche Type 356 Roadster",
      "price": 62.16
      , quantity: 1
  }),
  new Product({
      "name": "1949 Jaguar XK 120",
      "price": 47.25
      , quantity: 1
  }),
  new Product({
      "name": "1952 Alpine Renault 1300",
      "price": 98.58
      , quantity: 1
  }),
  new Product({
      "name": "1952 Citroen-15CV",
      "price": 72.82
      , quantity: 1
  }),
  new Product({
      "name": "1956 Porsche 356A Coupe",
      "price": 98.3
      , quantity: 1
  }),
  new Product({
      "name": "1957 Corvette Convertible",
      "price": 69.93
      , quantity: 1
  })];

You can see the whole thing with all the changes I mentioned in this fiddle: http://jsfiddle.net/7e0vujf5/11/

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