0

I have the following controller:

        public JsonResult EquipmentSelect(string term)
    {
        var equipmentSearchViewModel = new EquipmentSearchViewModel
        {
            EquipmentList = iEquipmentRepository.FindBy(t => t.equipment_name.Contains(term)
                || t.equipment_id.Contains(term)),
        };

        var filteredEquipment = equipmentSearchViewModel.EquipmentList.ToList();
        var sortableData = filteredEquipment.AsQueryable();
        var jsonData = new
        {
            rows = (
                     from m in filteredEquipment
                     select new
                     {
                         equipment_id = m.equipment_id,
                         equipment_name = m.equipment_name
                     }
                  ).ToArray()
        };

        return Json(jsonData, JsonRequestBehavior.AllowGet);
    }

and the follow js file:

$(function () {
    $('#search').click(function () {
        var searchText = $('#txtsearch').val();
        getEquipment(searchText);
    })
})

// View model declaration
var EquipmentViewModel = {
    Profiles: ko.observableArray([])
};

// Bind the equipment
bindData = function (equipment) {
    var EquipmentViewModel = {
        Profiles: ko.observableArray([])
    };
    EquipmentViewModel.Profiles(equipment);
    ko.applyBindings(EquipmentViewModel);
}

getEquipment = function (searchTerm) {
    var url = 'EquipmentSelect/Equipment';
    $.ajax({
        url: url,
        cache: false,
        contentType: 'application/json',
        data: '{"term": "' + searchTerm + '"}',
        type: "POST",
        success: function (result) {
            bindData(result.rows);
        },
        error: function (jqXHR) {
            $('#message').html(jqXHR.statusText);
        }
    });
}

and finally my view:

@{
    ViewBag.Title = "KnockoutExample";
}
<script src="~/Scripts/knockout-2.2.1.js"></script>
<script src="~/Scripts/knockout.mapping-latest.js"></script>
<script src="~/Scripts/koViewModel.js"></script>
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />
<h2>Knockout Example</h2>
<div>
    <fieldset>
        <legend>Search</legend>
        <span>Search For</span>
        <input type="text" name="txtsearch" id="txtsearch" />
        <input type="button" value="Submit" id="search"/>
    </fieldset>
</div>
<table id="myTable" class="table table-striped table-bordered table-condensed">
    <tr> 
        <th>Equipment ID</th>
        <th>Equipment Name</th>
    </tr>
    <tbody data-bind="foreach: Profiles">
        <tr">
            <td data-bind="text: equipment_id"></td>
            <td data-bind="text: equipment_name"></td>
        </tr>
    </tbody>
</table>
<p id="message"></p>

When I click the search button I get the results I am after. If I click it a second time, I get the same data but duplicated for each of the original count. For example, if the initial call returns 20 items, the second click returns 20 each of the twenty items. I need to clear my viewmodel somehow and repopulate each time the search button is clicked.

3 Answers 3

6

With knockout, I've found it easier to make the top-most view model of the page itself, including all the state and behaviour of the page.

var PageViewModel = {
    Profiles: ko.observableArray([]),
    SearchTerm: '', // observable not needed, doesn't trigger any changes
    Message: ko.observable(''),
    GetEquipment: function () {
        var self = this; // Retain scope of view model
        self.Message('Searching...');
        $.ajax({
            url: 'EquipmentSelect/Equipment',
            cache: false,
            contentType: 'application/json',
            data: ko.toJSON({ term: self.SearchTerm }),
            type: "POST",
            success: function (result) {
                self.Profiles(result.rows); // Re-set entire observable array
                self.Message('');
            },
            error: function (jqXHR) {
                self.Message(jqXHR.statusText);
            }
        });
    }
}
$(function () {
    ko.applyBindings(PageViewModel);
})

Then not only do you start to get back to object oriented principles in your JavaScript code, but the view is also bound more simply to the view model. Don't even have to define a single id attribute.

@{
    ViewBag.Title = "KnockoutExample";
}
<script src="@Url.Content("~/Scripts/knockout-2.2.1.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/koViewModel.js")" type="text/javascript"></script>
<link href="@Url.Content("~/Content/bootstrap.min.css")" rel="stylesheet" type="text/css" />
<h2>Knockout Example</h2>
<div>
    <fieldset>
        <legend>Search</legend>
        <span>Search For</span>
        <div class="input-append">
            <input type="text" data-bind="value: SearchTerm" />
            <input type="button" value="Submit" class="btn" data-bind="click: GetEquipment" />
        </div>
    </fieldset>
</div>
<table class="table table-striped table-bordered table-condensed">
    <tr> 
        <th>Equipment ID</th>
        <th>Equipment Name</th>
    </tr>
    <tbody data-bind="foreach: Profiles">
        <tr>
            <td data-bind="text: equipment_id"></td>
            <td data-bind="text: equipment_name"></td>
        </tr>
    </tbody>
</table>
<p data-bind="text: Message"></p>

There's no need for removeAll(). There's no need for jQuery style click events and id lookups if you're already using knockout bindings. And there's no need to bind your page-level view model more than once.

Sign up to request clarification or add additional context in comments.

1 Comment

Wow. I am marking this as the answer too since, as you stated, it more closely follows OOP principles and it worked perfect. I am going to use this method since the viewmodel will more closely match the view. Thanks!
2

Your javascript is almost there, but needs some minor tweaks:

$(function () {
    function onSearchClick() {
        var searchText = $('#txtsearch').val();
        getEquipment(searchText);
    }   

    // View model declaration
    var EquipmentViewModel = {
        Profiles: ko.observableArray([])
    };

    function getEquipment (searchTerm) {
        var url = 'EquipmentSelect/Equipment';
        $.ajax({
            url: url,
            cache: false,
            contentType: 'application/json',
            data: '{"term": "' + searchTerm + '"}',
            type: "POST",
            success: function (result) {
                EquipmentViewModel.Profiles(result.rows);
            },
            error: function (jqXHR) {
                $('#message').html(jqXHR.statusText);
            }
        });
    }

    $(document).ready(function(){       
        $('#search').click(onSearchClick);
        ko.applyBindings(EquipmentViewModel);
    });
})

Comments

0

Try this:

bindData.EquipmentViewModel.Profiles.removeAll() like:

    getEquipment = function (searchTerm) {
    var url = 'EquipmentSelect/Equipment';
    bindData.EquipmentViewModel.Profiles.removeAll() 
    $.ajax({
        url: url,
        cache: false,
        contentType: 'application/json',
        data: '{"term": "' + searchTerm + '"}',
        type: "POST",
        success: function (result) {
            bindData(result.rows);
        },
        error: function (jqXHR) {
            $('#message').html(jqXHR.statusText);
        }
    });
}

1 Comment

The problem with the original code is that ko.applyBindings is being called every time a search is performed. For this reason, this solution will not solve the problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.