Search code examples
javascriptjqueryjquery-uiknockout.jsknockout-3.0

knockout-jqAutocomplete `options.value is not a function` error


I am trying to convert the knockout shoping card to a jquery AutoComplete using https://github.com/rniemeyer/knockout-jqAutocomplete.

The dropdown works, whowever after the value is selected i get: Uncaught TypeError: options.value is not a function.

My bindings are:

<tbody data-bind="foreach: lines">
  <tr>
    <td>
        <input data-bind="jqAuto: { source: $root.productData, labelProp: 'sku', value: $parent.product }" />
    </td>
    <td>
        <input data-bind="jqAuto: { source: $root.productData, labelProp: 'name', value: $parent.product }"/>
    </td>
    <td class="quantity">
        <input data-bind='visible: product, value: quantity, valueUpdate: "afterkeydown"'/>
    </td>
    <td class="price">
        <span data-bind="visible: product, value: price"> </span>
    </td>
    <td class="subTotal">
        <span data-bind="visible: product, value: subtotal"> </span>
    </td>
    <td>
        <a href="#" data-bind="click: $parent.removeLine">Remove</a>
    </td>
  </tr>
</tbody>

And my view-model is:

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

var Quote = function() {
    // Stores an array of lines, and from these, can work out the grandTotal
    var self = this;
    self.lines = ko.observableArray([new QuoteLine()]); // Put one line in by default
    self.grandTotal = ko.computed(function() {
        var total = 0;
        $.each(self.lines(), function() { total += this.subtotal() });
        return total;
    });

    self.productBeingEdited = ko.observable();

    self.editProduct = function(line) {
        console.log("self.editProduct " + line.quantity());
        self.productBeingEdited(line);
    };

    self.saveProduct = function(vm) {
        console.log("save");
    };

    self.productData = [
        {
            "man": "avaya",
            "sku" : "323",
            "name" : "1608-I Handset",
            "description": "An Avaya handset for IP Office and Aura systems."
        },
        {
            "man": "avaya",
            "sku": "324",
            "name": "1616-I Handset",
            "description": "An Avaya handset for IP Office and Aura systems."
        },
        {
            "man": "cisco",
            "sku" : "50ab",
            "name": "Cicso SIP handset",
            "description": "A Cisco handset."
        }
    ];

    self.addLine = function () { self.lines.push(new QuoteLine()) };

    self.removeLine = function(line) { self.lines.remove(line) };

    self.save = function () { /* snip */ };
};


ko.applyBindings(new Quote());

Questions are please:

  1. What i would like is to get rid of the errors!
  2. For the sku field it seems to pickup searches for name, and name field for sku i.e. 50a in the name field brings up Cisco. How can i lock this down to the labels only for that column?
  3. How would i set the Name field depending on Sku field (and vice versa) allowing me to lookup by either Sku or Name and ensure the right product() observable gets updated?

My fiddle is here: http://jsfiddle.net/m429944x/10/


Solution

  • Try change value: $parent.product to product

    http://jsfiddle.net/m429944x/11/

    Html:

    <tbody data-bind="foreach: lines">
      <tr>
        <td>
            <input data-bind="jqAuto: { source: $root.productData, labelProp: 'sku', value: product }" />
        </td>
        <td>
            <input data-bind="jqAuto: { source: $root.productData, labelProp: 'name', value: product}"/>
        </td>
        <td class="quantity">
            <input data-bind='visible: product, value: quantity, valueUpdate: "afterkeydown"'/>
        </td>
        <td class="price">
            <span data-bind="visible: product, value: price"> </span>
        </td>
        <td class="subTotal">
            <span data-bind="visible: product, value: subtotal"> </span>
        </td>
        <td>
            <a href="#" data-bind="click: $parent.removeLine">Remove</a>
        </td>
      </tr>
    </tbody>
    

    Javascript:

    var QuoteLine = function() {
        var self = this;
        self.product = ko.observable();
        self.price = ko.observable();
        self.quantity = ko.observable(1);
        self.subtotal = ko.computed(function() {
            return self.product() ? self.price() * parseInt("0" + self.quantity(), 10) : 0;
        });
    };
    
    var Quote = function() {
        // Stores an array of lines, and from these, can work out the grandTotal
        var self = this;
        self.lines = ko.observableArray([new QuoteLine()]); // Put one line in by default
        self.grandTotal = ko.computed(function() {
            var total = 0;
            $.each(self.lines(), function() { total += this.subtotal() });
            return total;
        });
    
        self.saveProduct = function(vm) {
            console.log("save");
        };
    
        // Operations
        self.addLine = function () { self.lines.push(new QuoteLine()) };
    
        self.removeLine = function(line) { self.lines.remove(line) };
    
        self.save = function () {
            var dataToSave = $.map(self.lines(),
                function(line) {
                    return line.product()
                        ? {
                            productName: line.product().name,
                            quantity: line.quantity()
                        }
                        : undefined
                });
            alert("Could now send this to server: " + JSON.stringify(dataToSave));
        };
    
        self.productData = [
            {
                "man": "avaya",
                "sku" : "323",
                "name" : "1608-I Handset",
                "description": "An Avaya handset for IP Office and Aura systems."
            },
            {
                "man": "avaya",
                "sku": "324",
                "name": "1616-I Handset",
                "description": "An Avaya handset for IP Office and Aura systems."
            },
            {
                "man": "cisco",
                "sku" : "50ab",
                "name": "Cicso SIP handset",
                "description": "A Cisco handset."
            }
        ];
    };
    
    ko.applyBindings(new Quote());