1
votes

In my application I am trying to integrate bootstrap and knockout into MVC4. I have two drop-down controls from where the user can select an item and then I use jQuery to populate the textbox next to that. This works without issue when I use ViewBag and a @foreach loop but when I try to use knockout observables I am having an issue.

I am able to see the data from my ViewModel show up in the drop down control but it will not update that value in the text box. Is there a special data-bind attribute I should be using?

Some code...

<div class="container">
    <div class="col-sm-7 well">
        <form class="form-inline" action="#" method="get">
            <div class="input-group col-sm-8">
                <input class="form-control" value="" placeholder="Work Section" name="q" type="text">
                <div class="input-group-btn">
                    <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">Select <span class="caret"></span></button>
                    <ul class="dropdown-menu">
                        <li data-bind="foreach: Names">
                            <a href="#" data-bind="text: Name, value: Name"></a>
                        </li>
                    </ul>
                    <input name="category" class="category" type="hidden">
                </div>
            </div>
            <div class="input-group col-sm-8">
                <input class="form-control item" value="" placeholder="Select a Color" name="color" type="text">
                <div class="input-group-btn">
                    <button type="button" class="btn btn-default dropdown-toggle item" data-toggle="dropdown">Select <span class="caret"></span></button>
                    <ul class="dropdown-menu">
                        @foreach (var item in ViewBag.Colors)
                        {
                            <li>
                                <a href="#">@item</a>
                            </li>
                        }
                    </ul>
                    <input name="category" class="category" type="hidden">
                </div>
            </div>

The issue is with the Names foreach as the Colors is working fine.

I am using this to find the control on the page and take the selected item and place that in the input control...I have verified this now only works on the Colors dropdown.

$(function () {
        $(".dropdown-menu li a").click(function () {
            $(this).parents(".input-group").find('.form-control').text($(this).text());
            $(this).parents(".input-group").find('.form-control').val($(this).text());
        });
    });

I have tried both approaches below to no avail. Let's start with the first response....

I placed the following in my tag at the top of the page

$(function ViewModel() {
    alert('Here');  // To test if the code generating the model is executed
    this.Names = [{ name: "Person 1", name: "Person 2" }];

    this.selectedName = ko.observable();
    this.clickName = function (name) { this.selectedName = name; }
});

Next I placed a new at the top of my page:

<p>Current selection is <span data-bind="text: selectedName"></span></p>

Lastly, I placed this code in the bootstrap dropdown:

<ul class="dropdown-menu" data-bind="foreach: Names">
    <li class="dropdown">
        <a href="#" data-bind="text: Name, value: Name, click: clickName(Name)"></a>
    </li>
</ul>

The drop down is empty and the div never gets updated.

enter image description here

OK, after taking the ViewModel I created and integrating the click: function() {$root.Name(Name);} I am now able to see my value update as I hoped.

Here is my view model code

$(document).ready(function () {
    function ViewModel() {
        var self = this;
        self.Name = ko.observable("");

        var Names = {
            Name: self.Name
        };

        self.Name = ko.observable();
        self.Names = ko.observableArray([{ Name: "Brian" }, { Name: "Jesse" }, {Name: "James"}]);
    }

    var viewModel = new ViewModel();
    ko.applyBindings(viewModel);
});
2
I tried the above approach and modified my code above, please take a look.user2146538

2 Answers

1
votes

The dropdowns created by Bootstrap are not really controls as such, it's just fancy CSS. As a result, the Bootstrap dropdown selection needs to be handled manually. This is where Knockout really helps.

So based on your code above, I've created a simple jsFiddle.

The dropdown would look similar to this (note the position of the foreach binding is different to the example in the question, on the ul rather than the li):

    <ul class="dropdown-menu" data-bind="foreach: Names">
        <li class="dropdown">
            <a href="#" data-bind="text: Name, value: Name, click: function() {$root.selectedName(Name);}"></a>
        </li>
    </ul>

This assumes a view model similar to:

var ViewModel = function() {
    var self = this;

    //Properties
    self.selectedName = ko.observable("NONE");
    self.Names = ko.observableArray([
        { Name:"Name1" },
        { Name:"Name2" },
        { Name:"Name3" },
    ]);    

}

So by using the click binding, it's possible to feed the user selection back into the Knockout view model. Of course, instead of a function in the view ( the function() {$root.selectedName(Name);} bit), it would ideally be a method ViewModel, but I thought it was clearer to show it this way first.

1
votes

assuming your view model currently looks like

function ViewModel() {
  this.Names = [{name: x}, ...];
}

you should expand as follows

function ViewModel() {
  this.Names = [{name: x}, ...];
  this.selectedName = ko.observable();
  this.clickName = function(name) { this.selectedName(name); }
}

Then you would put data-bind="text: selectedName" on your text field, and data-bind="click: clickName(Name)" on your link. No need for jQuery selectors, so you can decouple your data interactions from the DOM structure.