3

I'm looking to create an unobtrusive cascading dropdown system for a website I'm working on. I'm having trouble figuring out how to get the various HtmlHelper methods to include the custom html attributes to the rendered tag, though.

Looking through the source for the built in HtmlHelper methods, they all make a call to GetUnobtrusiveValidationAttributes, which creates all the data-val-* html attributes . That's great if you need the validator attributes, but I'd like to be able to add other attributes this way without needing to alter templates and create new HtmlHelper extensions.

Is this at all possible? Am I overlooking something?

Edit

I know that all the HtmlHelper methods have an overload that accepts an object with html attributes. I'm trying to avoid this if at all possible.

Edit 2

I essentially want this to happen:

public class ViewModel
{
    [Cascading(Action="/Controller/Action")]
    public int Action { get; set; }
}

And then have the HtmlHelpers render out like:

<select data-action="/Controller/Action"></select>

But preferrably without having to write up an extension method to do it. I have no problem making my own helper method to do it, but I'm wondering if I'm missing some built in feature that already looks at random model metadata and can add html attributes.

rossisdead
  • 2,102
  • 1
  • 19
  • 30

3 Answers3

2

Seeing edit 1+2, I think you need to create your own extensions. Since you are dealing with dropdowns, you can have a look at this implementation but use custom attributes via IMetadataAware.

IMetadataAware: This interface can be implemented by an attribute class so that the attribute can add metadata to the model metadata creation process without writing a custom metadata provider. This interface is consumed by the AssociatedMetadataProvider class, so this behavior is inherited by all classes which derive from it, such as the DataAnnotationsModelMetadataProvider class.


This part is no longer useful as an answer

If you want to add custom attributes to the generated HTML, you can use the Object htmlAttributes parameter available on many helpers, for example in @Html.ActionLink().

Example with custom data-* attributes that might be used to unobtrusively initiate a modal dialog for editing user settings on javascript enabled clients. Bootstrap's modal uses something similar to this.

Note that I'm using underscores instead of dashes for the data attribute.

@Html.ActionLink(
    "Settings",
    "CreateOrUpdate",
    "User",
    new { id = "1234" },
    new {
        title = "Edit your personal settings", 
        data_show_modal = "#my-user-settings-modal"
    })

In your case, I guess you are using @Html.DropDownList(...), which takes htmlAttributes as well. Populate them as you like and let your javascript pick up the right data-* attributes.

public static MvcHtmlString DropDownList(
    this HtmlHelper htmlHelper,
    string name,
    IEnumerable<SelectListItem> selectList,
    string optionLabel,
    Object htmlAttributes
)
Community
  • 1
  • 1
Joel Purra
  • 24,294
  • 8
  • 60
  • 60
0

I use a UIHint derived attribute to deliver properties from the view model to an editor template, with the wonderful IMetadataAware interface1 I only learned about yesterday.

Note how the call to the base constructor forces the name of an editor template I have written especially to add the attributes to the actual helper as parameters. The KnownUiHints.SelectionOther constant passed to the UIHint parameter is equal to "SelectionOtherInput", the name of the editor template.

The helper parameters are then available to the helper code.

E.g. My DropDownAttribute conveys properties with the following code:

public class SelectionOtherInputAttribute : UIHintAttribute, IMetadataAware
{
    public SelectionOtherInputAttribute(string selectionElementName) : base(KnownUiHints.SelectionOther, KnownPresentationLayers.Mvc)
    {
        SelectionElementName = selectionElementName;
    }
    public SelectionOtherInputAttribute(string selectionElementName, object otherSelectionKey) : base(KnownUiHints.SelectionOther, KnownPresentationLayers.Mvc)
    {
        SelectionElementName = selectionElementName;
        ControlParameters[SelectionOtherInputControlParameterKeys.OtherSelectionKey] = otherSelectionKey;
    }
    public string SelectionElementName { get; set; }
    public object OtherSelectionKey { get; set; }
    public void OnMetadataCreated(ModelMetadata metadata)
    {
        metadata.AdditionalValues["@OtherSelectionAttrinbuteNames.SelectionElementName"] = SelectionElementName;
        metadata.AdditionalValues["@OtherSelectionAttrinbuteNames.OtherSelectionKey"] = OtherSelectionKey;
    }
}

Here is my SelectionOtherInput editor template.

@using OtherInput.Core
@{
    var meta = ViewData.ModelMetadata.AdditionalValues;
    var selectionElementName = (string)meta["@OtherSelectionAttrinbuteNames.SelectionElementName"];
    var otherSelectionKey = meta["@OtherSelectionAttrinbuteNames.OtherSelectionKey"];
    var inputElementname = ViewData.TemplateInfo.HtmlFieldPrefix;
}
@Html.SelectionOtherTextBoxFor(m => Model, selectionElementName, otherSelectionKey.ToString())

Then I use my attribute as follows in the view model:

[SelectionOtherInput("EmploymentStatusId", 1)]
public string OtherEmploymentStatus { get; set; }

This causes OtherEmploymentStatus to be rendered by the SelectionOtherInput editor template, which in turns renders my SelectionOtherTextBoxFor helper, with attributes passed as arguments.

ProfK
  • 49,207
  • 121
  • 399
  • 775
0

You can't do it without using either editor/display templates or HtmlAttributes.

The templates have that advantage that you can add tags easily in one place and have them propagate over the whole application.

linkerro
  • 5,318
  • 3
  • 25
  • 29