Search code examples
knockout.jsko.observablearray

knockoutjs - How can I populate an empty observable array in response to user action (anchor link click)


I'm trying to add elements to an empty observable array in response to a user selection (the user clicks on a link to add that element to observable array)

Here is my ko script code:

function mfProduct(data) {
    var self = this;
    self.MfProductId = data.MfProductId;
    self.MfProductName = data.MfProductName;
};

function mfProductViewModel() {
    // Data
    var self = this;
    self.ProdId = ko.observable();
    self.ProdName = ko.observable();
    self.selectedProducts = ko.observableArray([]);

    // Operations
    self.addProduct = function () {
        if (self.ProdId() != '' && self.ProdId() != undefined) {
            self.selectedProducts.push(new mfProduct({ MfProductId: self.ProdId(), MfProductName: self.ProdName() }));
            self.ProdId("");
            self.ProdName("");
        }
    };

    self.removeProduct = function (mfProduct) {
        self.selectedProducts.remove(mfProduct);
    };
};

Here is my view markup:

<div class="row">
<div id="selectedProducts" class="col-sm-12" data-bind="foreach: selectedProducts">
    <div class="row" style="border: 1px solid red;">
        <div class="col-sm-10"><strong data-bind="text: MfProductName"></strong></div>
        <div class="col-sm-2"><a href="#" data-bind="click: removeProd"><i class="glyphicon glyphicon-remove-circle"></i></a></div>
    </div>
</div>
<div class="form-group">
    <div class="col-sm-3">
        Select product
    </div>
    <div class="col-sm-offset-3">
        <input type="hidden" id="selectedProdId" data-bind="value: ProdId" value="">
        <input type="hidden" id="selectedProdName" data-bind="value: ProdName" value="">
        <div id="searchResults" class="ddlContents">
            <ul>
                <li><a id="00001" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">Milky Way</a></li>
                <li><a id="00002" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">Bocadin</a></li>
                <li><a id="00003" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">Carlos V</a></li>
                <li><a id="00004" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">Snickers</a></li>
                <li><a id="00005" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">Crunch</a></li>
                <li><a id="00006" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">Power Milch</a></li>
                <li><a id="00007" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">Toblerone</a></li>
                <li><a id="00008" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">KitKat</a></li>
                <li><a id="00009" href="#" onmouseover="javascript:markSelected(this);" data-bind="click: addProduct">Cacao</a></li>
            </ul>
        </div>
    </div>
</div>

I added the onmouseover event handler to update the observable properties for productId and productName since I don't know how to send parameters to the click binding (something like javascript:addProduct(id, name);)

Here is the javascript code for that matter:

$(document).ready(function () {
    ko.applyBindings(new mfProductViewModel());
});

function markSelected(anchor) {
    // Update the value of the hidden fields with id and Text coming from the "moused over" link.
    $("#selectedProdId").val(anchor.id);
    $("#selectedProdName").val(anchor.innerText);
};

My intention is to click on any of the links in the unordered list, add the selected product in the mfProductViewModel.selectedProducts observable collection, and have the Product Name displayed in the <div id="selectedProducts"> section of the view.

Have any of you faced this kind of functionality using KnockOut.js?

Thank you very much for sharing your knowledge.


Solution

  • The available products that can be selected are also a kind of data and therefore belong into the ViewModel, and not the View (HTML file).

    function mfProductViewModel() {
        // Data
        var self = this;
        self.selectedProducts = ko.observableArray([]);
    
        self.products = [
            new mfProduct({ MfProductId: '00001', MfProductName: 'Milky Way' }),
            new mfProduct({ MfProductId: '00002', MfProductName: 'Bocadin' }),
            // ...
        ];
    
        // Operations
        self.addProduct = function(data) {
            // add a copy to allow that the same item is added more than once
            self.selectedProducts.push(new mfProduct(data));
        };
    
        // ...
    };
    

    and then simply render the list with knockout:

    <ul data-bind="foreach: products">
        <li><a href="#" data-bind="text: MfProductName, click: $root.addProduct"></a></li>
    </ul>
    

    Slightly simplified example: http://jsfiddle.net/vp9Lqxkp/