Using Knockout bindings in MVC ActionLink
Asked Answered
C

2

6

I am attempting to utilise KnockoutJS and MVC 4 in order to display a table with ActionLink definitions in the first column of the table. Displaying the data itself is extremely straight-forward and I'm not having any problem there. The problem I have is in the generation of the ActionLink's.

I have taken a look at Use MVC helpers inside jquery.tmpl templates, but the solution there does not utilise knockout templates and inserting the Url into the model object is not feasible (the app domain model objects used to create the view model will be used extensively through out the application).

The table definition:

<table>
    <tbody data-bind="template: { name: 'dmuTableDetail', foreach: tables() }"></tbody>
</table>

(tables is an observable array, hence the parens).

The knockout template definition:

<script id="dmuTableDetail" type="text/html">
    <tr>
        <td>@Html.ActionLink("Details", "Details", "DMUTableCategory", new { @Id = ??? } )</td>
        <td data-bind="text:TableId"></td>
        <td data-bind="text:TableName"></td>
    </tr>
</script>​

The View Model definition:

var PageViewModel = function () {
    self = this;

    self.tables = ko.observableArray([]);

    self.readItems = function () {
        self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
    }
}

$(document).ready(function () {
    vm = new PageViewModel();
    self.readItems('');
    ko.applyBindings(vm);
});

(the actual code performs an Ajax call to retrieve the data, but the code above also demonstrates the issue).

Regardless of what I replace the ??? with, I am unable to get the value of the TableId field to be inserted into the href.

Any help would be greatly appreciated.

Thankyou.

Cowell answered 10/8, 2012 at 2:36 Comment(0)
C
7

Thankyou Eric, you got me thinking about an anchor element and binding the href attribute.

It seems the answer is a little easier than expected (it usually is!).

The table definition: (same as original question)

<table>
    <tbody data-bind="template: { name: 'dmuTableDetail', foreach: tables() }"></tbody>
</table>

The knockout template definition: (change to the binding of the href attribute).

<script id="dmuTableDetail" type="text/html">
    <tr>
        <td><a data-bind="attr: { 'href': '@Url.Action("Details", new RouteValueDictionary() { { "Controller", "DMUTableCategory" } } )/' + TableId }">Details</a></td>
        <td data-bind="text:TableId"></td>
        <td data-bind="text:TableName"></td>
    </tr>
</script>?

The View Model definition: (same as original question)

var PageViewModel = function () {
    self = this;

    self.tables = ko.observableArray([]);

    self.readItems = function () {
        self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));
    }
}

$(document).ready(function () {
    vm = new PageViewModel();
    self.readItems('');
    ko.applyBindings(vm);
});

You dont actually need to RootValueDictionary but I've included it so people can see how to change the controller the request is sent to.

Cowell answered 10/8, 2012 at 23:21 Comment(1)
This worked for me, only I had to change TableId to TableId() to have the actual value returned.Vocative
A
3

Knockout binds completely on the client side, which is after MVC has rendered the HTML for your page and sent it back to the original browser.

If you want your Knockout template to be able to use a URL that is generated on the server, then you'll have to employ some clever strategy similar to the following:

CSHTML:

@{
    // create a dummy URL that you can use as a template
    string hrefFormat = Url.Action("Details", "DMUTableCategory", new { id = "{ID}" });
}

<script type="javascript">
    // a global string (but you can put it where ever you need to)
    var _hrefFormat = @Html.Raw(hrefFormat)
<script>

JS:

self.readItems = function () {
    self.tables(jQuery.parseJSON('[{"TableId":1001, "TableName":"Table#1"},{"TableId":1002, "TableName":"Table#2"}]'));


    // loop through the 'tables' and add a new 'href' property to each for binding
    ko.utils.arrayForEach(self.tables(), function(table){
         table.href = _hrefFormat.replace("{ID}", table.TableId);
    });
}

Your KO Tmpl where you bind the 'href' property of each table object to the a tag's href attribute:

<script id="dmuTableDetail" type="text/html">
    <tr>
        <td><a data-bind="attr: { 'href': href }">Details</a></td>
        <td data-bind="text:TableId"></td>
        <td data-bind="text:TableName"></td>
    </tr>
</script>​
Azurite answered 10/8, 2012 at 3:53 Comment(3)
Thanks Eric, but I get I am getting an error when accessing the _hrefFormat variable (_hrefFormat is not defined). After some playing around though, I managed to remove the need for the arrayForEach and changed the binding to <td><a data-bind="attr: { 'href': '@Url.Action("Details")/' + TableId }">Details</a></td>Cowell
That works as well. Did you put the _hrefFormat string in the same scope as your ViewModel?Azurite
Had a bit of trouble with that one. The View Model was created in the $(document).ready() function via a separate .js file, so was a little confused how to get the _hrefFormat in the same scope and allowing MVC to process the @HTML.Raw() function. BTW, I wasn't able to use it for another reason - the Url.Action function encodes the output, so the braces were being encoded (easily fixed by using a different character!). Once again, thankyou for your input as it did lead me onto the solution I ended up using.Cowell

© 2022 - 2024 — McMap. All rights reserved.