0

I want to create a generic checkbox list view model and so I got this:

public class ChckboxListViewModel<T>
{
    public List<CheckboxViewModel<T>> CheckboxList { get; set; }
    public IEnumerable<T> SelectedValues
    {
        get { return CheckboxList.Where(c => c.IsSelected).Select(c => c.Value); }
    }

    public ChckboxListViewModel()
    {
        CheckboxList = new List<CheckboxViewModel<T>>();
    }
}

public class CheckboxViewModel<T>
{
    public string Label { get; set; }
    public T Value { get; set; }
    public bool IsSelected { get; set; }

    public CheckboxViewModel(string i_Label, T i_Value, bool i_IsSelected)
    {
        Label = i_Label;
        Value = i_Value;
        IsSelected = i_IsSelected;
    }
}

It is used by a different view model to represent filters of different statuses:

public class FaultListFilters
{
    public string SearchKeyword { get; set; }
    public ChckboxListViewModel<Fault.eFaultStatus> StatusFilter { get; set; }

    public FaultListFilters()
    {
        SearchKeyword = null;
        StatusFilter = new ChckboxListViewModel<Fault.eFaultStatus>();

        StatusFilter.CheckboxList.Add(new CheckboxViewModel<Fault.eFaultStatus>(FaultManagementStrings.OpenStatus,Fault.eFaultStatus.Open,true));
        StatusFilter.CheckboxList.Add(new CheckboxViewModel<Fault.eFaultStatus>(FaultManagementStrings.InProgressStatus, Fault.eFaultStatus.InProgress, true));
        StatusFilter.CheckboxList.Add(new CheckboxViewModel<Fault.eFaultStatus>(FaultManagementStrings.ClosedStatus, Fault.eFaultStatus.Close, false));

    }
}

Now I can't find the right way to display the editors or to create an editor template for that kind of a view model because it is Generic.

I don't want o create a separate editor template for ChckboxListViewModel<int> and then another for ChckboxListViewModel<Fault.eFaultStatus> and so on..

Is it even a goose idea to use generics in this case?

Is there another way to represent and display a check-box list in MVC?

I have done the following but the modle is not binding for some reason:

@using (Html.BeginForm("FaultManagement", "Faults", FormMethod.Get, null))
{

    for (int i=0 ; i<Model.FaultListFilters.StatusFilter.CheckboxList.Count() ; i++)
    {
        @Html.HiddenFor(m => m.FaultListFilters.StatusFilter.CheckboxList[i].Value)
        @Html.CheckBoxFor(m => m.FaultListFilters.StatusFilter.CheckboxList[i].IsSelected)
        @Html.LabelFor(m=> m.FaultListFilters.StatusFilter.CheckboxList[i].IsSelected,Model.FaultListFilters.StatusFilter.CheckboxList[i].Label)
    }

    <input type="submit" />
}

2 Answers 2

2

Is it even a goose idea to use generics in this case?

Don't think it is.

Is there another way to represent and display a check-box list in MVC?

I would write a custom HTML helper:

public static class HtmlExtensions
{
    public static IHtmlString CheckboxListFor<TModel>(
        this HtmlHelper<TModel> html, 
        Expression<Func<TModel, IEnumerable<string>>> ex, 
        IEnumerable<string> possibleValues)
    {
        var metadata = ModelMetadata.FromLambdaExpression(ex, html.ViewData);
        var availableValues = (IEnumerable<string>)metadata.Model;
        var name = ExpressionHelper.GetExpressionText(ex);
        return html.CheckboxList(name, availableValues, possibleValues);
    }

    private static IHtmlString CheckboxList(this HtmlHelper html, string name, IEnumerable<string> selectedValues, IEnumerable<string> possibleValues)
    {
        var result = new StringBuilder();

        foreach (string current in possibleValues)
        {
            var label = new TagBuilder("label");
            var sb = new StringBuilder();

            var checkbox = new TagBuilder("input");
            checkbox.Attributes["type"] = "checkbox";
            checkbox.Attributes["name"] = name;
            checkbox.Attributes["value"] = current;
            var isChecked = selectedValues.Contains(current);
            if (isChecked)
            {
                checkbox.Attributes["checked"] = "checked";
            }

            sb.Append(checkbox.ToString());
            sb.Append(current);

            label.InnerHtml = sb.ToString();
            result.Append(label);
        }
        return new HtmlString(result.ToString());
    }
}

Then you could have a view model:

public class FaultListFiltersViewModel
{
    public IEnumerable<string> SelectedStatusFilters { get; set; }
    public IEnumerable<string> AvailableStatusFilters
    {
        get
        {
            return new[] { "Label 1", "Label 2", "Label 3" }
        }
    }
}

and inside the view you could use the helper:

@Html.CheckBoxListFor(x => x.SelectedStatusFilters, Model.AvailableStatusFilters)
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks, I'll actually do the same for Radio Buttons and drop-downs ! yo helped me a lot.
Just a comment i think that IEnumerable<string> availableValues would need to be IEnumerable<string> selectedValues for clarity purposes ..
Feel free to edit my post and name the local variables as you like.
So I have made a lot of edit to support custom Key,Value pairs from the example you gave me and i wish to share it with everyone .. what would be the best practice here ? should i add another answer or should i add an edit to your post ?
I have added an edit to your post, with all the relevant code examples
|
0

Here is another implementation that will better support bootstrap button-group labels (as it requires them to be seperated) and enum type selected values.

    public static IHtmlString CheckboxListFor<TModel, TKey>(this HtmlHelper<TModel> helper, Expression<Func<TModel, IEnumerable<TKey>>> ex, Dictionary<TKey, string> i_PossibleOptions, object i_LabelHtmlAttributes)
        where TKey : struct, IConvertible
    {
        var metadata = ModelMetadata.FromLambdaExpression(ex, helper.ViewData);
        var selectedValues = (IEnumerable<TKey>)metadata.Model;
        var name = ExpressionHelper.GetExpressionText(ex);
        return helper.CheckboxList(name, selectedValues, i_PossibleOptions, i_LabelHtmlAttributes);
    }

    private static IHtmlString CheckboxList<TKey>(this HtmlHelper helper, string name, IEnumerable<TKey> i_SelectedValues, Dictionary<TKey, string> i_PossibleOptions, object i_LabelHtmlAttributes)
        where TKey : struct, IConvertible
    {
        if (!typeof(TKey).IsEnum) throw new ArgumentException("T must be an enumerated type");

        var result = new StringBuilder();

        foreach (var option in i_PossibleOptions)
        {
            var label = new TagBuilder("label");
            label.MergeAttributes(new RouteValueDictionary(i_LabelHtmlAttributes));
            label.Attributes["for"] = string.Format("{0}",option.Key.ToString());
            label.InnerHtml = option.Value;

            var checkbox = new TagBuilder("input");
            checkbox.Attributes["type"] = "checkbox";
            checkbox.Attributes["name"] = name;
            checkbox.Attributes["id"] = string.Format("{0}", option.Key.ToString());
            checkbox.Attributes["value"] = option.Key.ToString();

            bool isChecked = ((i_SelectedValues != null) && (i_SelectedValues.Contains(option.Key)));
            if ( isChecked )
            {
                checkbox.Attributes["checked"] = "checked";
            }

            result.Append(checkbox);
            result.Append(label);
        }
        return new HtmlString(result.ToString());
    }

And then the View Model looks like that:

public class FaultListFilters
{
    [Display(ResourceType = typeof(FaultManagementStrings), Name = "SearchKeyword")]
    public string SearchKeyword { get; set; }
    public Dictionary<Fault.eFaultStatus, string> PossibleFaultStatuses 
    {
        get 
        {
            var possibleFaultStatuses = new Dictionary<Fault.eFaultStatus, string>();
            possibleFaultStatuses.Add(Fault.eFaultStatus.Open, FaultManagementStrings.OpenStatus);
            possibleFaultStatuses.Add(Fault.eFaultStatus.InProgress, FaultManagementStrings.InProgressStatus);
            possibleFaultStatuses.Add(Fault.eFaultStatus.Close, FaultManagementStrings.ClosedStatus);
            return possibleFaultStatuses;
        }
    }
    public IEnumerable<Fault.eFaultStatus> SelectedFaultStatuses { get; set; }

    public FaultListFilters()
    {
        SearchKeyword = null;
        SelectedFaultStatuses = new[] { Fault.eFaultStatus.Open, Fault.eFaultStatus.InProgress };
    }
}

and the usage remains the same (except i have added the label html attributes)

    <div class="btn-group">
    @Html.CheckboxListFor(m => m.FaultListFilters.SelectedFaultStatuses, Model.FaultListFilters.PossibleFaultStatuses, new { Class="btn"})
</div>

Comments

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.