MVC 3 + knockoutjs: adding the data-bind attribute when using EditorFor for a boolean field

StackOverflow https://stackoverflow.com/questions/7302434

  •  22-10-2019
  •  | 
  •  

Question

Using @Html.EditorFor(model =>model.IsClient), where IsClient is a boolean, renders a drop down list with Not Set, Yes and No as the options.

All well and good.

Now I want to use knockoutjs with the resulting dropdownlist, that I like, so how do I add the data-bind attribute using @Html.EditorFor, that I need for knockoutjs to work with this drop down?

I have tried:

@Html.EditorFor(model => model.IsClient, new Dictionary<string, object> { { "data-bind", "value: Account.IsClient" } })

However, this uses the object additionalViewData parameter, and it doesn't render the data-bind attribute. Which is probably quite natural, as this parameter is probably nothing to do with Html Attributes for the rendered tag.

However, can't find any reasonable documentation, and none of the other overloads look likely candidates for what I want.

TIA any suggestions.

Was it helpful?

Solution

Brad Wilson blogged about display and editor templates in ASP.NET MVC 2. So you could modify the default template for boolean and add the attributes you need (~/Views/Shared/EditorTemplates/MyTemplate.cshtml):

@{
    bool? value = null;
    if (ViewData.Model != null) 
    {
        value = Convert.ToBoolean(ViewData.Model, System.Globalization.CultureInfo.InvariantCulture);
    }

    var triStateValues = new List<SelectListItem> 
    {
        new SelectListItem 
        { 
            Text = "Not Set",
            Value = String.Empty,
            Selected = !value.HasValue 
        },
        new SelectListItem 
        { 
            Text = "True",
            Value = "true",
            Selected = value.HasValue && value.Value 
        },
        new SelectListItem 
        { 
            Text = "False",
            Value = "false",
            Selected = value.HasValue && !value.Value 
        },
    };
}

@if (ViewData.ModelMetadata.IsNullableValueType) 
{
    <!-- TODO: here you can use any attributes you like -->
    @Html.DropDownList(
        "", 
        triStateValues, 
        new { 
            @class = "list-box tri-state", 
            data_bind="value: " + ViewData.TemplateInfo.GetFullHtmlFieldName("") // you could also use ViewData.ModelMetadata.PropertyName if you want to get only the property name and not the entire navigation hierarchy name
        }
    )
} 
else 
{
    @Html.CheckBox("", value ?? false, new { @class = "check-box" })
}

and finally:

@Html.EditorFor(model => model.IsClient, "MyTemplate")

or decorate the IsClient property on your view model with the UIHint attribute:

[UIHint("MyTemplate")]
public bool? IsClient { get; set; }

and then:

 @Html.EditorFor(x => x.IsClient)

will automatically pick the custom editor template.

OTHER TIPS

Addendum for knockoutjs users:

@Darin Dimitrov's answer is great, but slightly too rigid to use with knockoutjs, where complex views may lead to viewModels that don't entirely map to the @Model parameter.

So I have made use of the object additionalViewData parameter. To access the additionalViewData parameter from your Custom EditorTemplate, see the following SO question:

Access additionalViewData from Custom EditorTemplate code

Digression: The additionalViewData param is confusing in that it does nothing with the default editor. It only comes into its own with a custom editor template.

Anyway, my amendments to Darin's code are as follows:

@if (ViewData.ModelMetadata.IsNullableValueType) 
{
    var x = ViewData["koObservablePrefix"];
    if ((x != "") && (x != null)) { x = x + "."; }
    @Html.DropDownList(
        "", 
        triStateValues, 
        new { 
            @class = "list-box tri-state", 
            data_bind="value: " + x + ViewData.TemplateInfo.GetFullHtmlFieldName("") // or you could also use ViewData.ModelMetadata.PropertyName if you want to get only the property name and not the entire navigation hierarchy name
        }
    )
} 
else 
{
    @Html.CheckBox("", value ?? false, new { @class = "check-box" })
}

Note the lines:

var x = ViewData["koObservablePrefix"];
if ((x != "") && (x != null)) { x = x + "."; }

koObservablePrefix is there so that I can add an arbitrary prefix to my viewModel ko.observable. You could do other things if you so choose.

I use the variable x as follows:

data_bind="value: " + x + ViewData.TemplateInfo.GetFullHtmlFieldName("")

That way, if I don't pass in the additionalViewData "koObservablePrefix" it all still works.

So, now I can write:

@Html.EditorFor(model => model.IsClient, "koBoolEditorFor", new { koObservablePrefix = "Account" })

that will render as:

<select class="list-box tri-state" data-bind="value: Account.IsBank" id="IsBank" name="IsBank">

Note the "value: Account.IsBank" data-bind attribute value.

This is useful if, for example, your views strongly typed model is of type Account, but in your accountViewModel for your page, you have a more complex structure, so you need to package your observables in an account object. EG:

function account(accountId, personId, accountName, isClient, isProvider, isBank) {

    this.AccountId   = ko.observable(accountId);
    this.PersonId    = ko.observable(personId);
    this.AccountName = ko.observable(accountName);
    this.IsClient    = ko.observable(isClient);
    this.IsProvider  = ko.observable(isProvider);
    this.IsBank      = ko.observable(isBank);
}

function accountViewModel() { 

    var self = this;

    this.selectedCostCentre = ko.observable('');            
    this.Account = new account(@Model.AccountId, @Model.PersonId, '@Model.AccountName', '@Model.IsClient','@Model.IsProvider', '@Model.IsBank');
              // etc. etc
}

If you don't have this kind of structure, then, the code will pick up the structure. It is just a matter of tailoring your viewModel js to this, uhmmm, flexible convention.

Hope this isn't too confusing...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top