/// <inheritdoc /> public override void Init(TagHelperContext context) { if (For is null) { // Informs contained elements that they're running within a targeted <select/> element. context.Items[typeof(SelectTagHelper)] = null; return; } // Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient. // IHtmlGenerator will enforce name requirements. if (For.Metadata is null) { throw new InvalidOperationException(NoProvidedMetadata( $"<{DesignSystem.ToString().ToLowerInvariant()}-select>", ForAttributeName, nameof(IModelMetadataProvider), For.Name)); } // Base allowMultiple on the instance or declared type of the expression to avoid a // "SelectExpressionNotEnumerable" InvalidOperationException during generation. // Metadata.IsEnumerableType is similar but does not take runtime type into account. Type realModelType = For.ModelExplorer.ModelType; allowMultiple = typeof(string) != realModelType && typeof(IEnumerable).IsAssignableFrom(realModelType); this.currentValues = Generator.GetCurrentValues(ViewContext, For.ModelExplorer, For.Name, allowMultiple); // Whether or not (not being highly unlikely) we generate anything, could update contained <option/> // elements. Provide selected values for <option/> tag helpers. CurrentValues?currentValues = this.currentValues is null ? null : new CurrentValues(this.currentValues); context.Items[typeof(SelectTagHelper)] = currentValues; }
/// <summary> /// Generate a <c>text-field</c> element. /// </summary> /// <param name="viewContext">The <see cref="ViewContext"/>.</param> /// <param name="inputType">The <see cref="InputType"/>.</param> /// <param name="modelExplorer">The <see cref="ModelExplorer"/>.</param> /// <param name="expression">The expression.</param> /// <param name="value">The value.</param> /// <param name="format">The format.</param> /// <param name="htmlAttributes">The html attributes.</param> /// <param name="designSystem">The <see cref="DesignSystem"/> to apply to the component.</param> public TagBuilder GenerateTextField( ViewContext viewContext, ModelExplorer modelExplorer, InputType inputType, string expression, object value, string?format, object?htmlAttributes, DesignSystem designSystem) { IDictionary <string, object>?htmlAttributeDictionary = GetHtmlAttributeDictionaryOrNull(htmlAttributes); return(GenerateInputElement( viewContext, inputType, modelExplorer, expression, value, modelExplorer is null && value is null, false, true, format, designSystem, htmlAttributeDictionary)); }
/// <summary> /// Generate <c>checkbox</c> element. /// </summary> /// <param name="viewContext">The <see cref="ViewContext"/>.</param> /// <param name="modelExplorer">The <see cref="ModelExplorer"/>.</param> /// <param name="expression">The expression.</param> /// <param name="isChecked">If the input is checked.</param> /// <param name="htmlAttributes">The html attributes.</param> /// <param name="designSystem">The <see cref="DesignSystem"/> to apply to the component.</param> public TagBuilder GenerateCheckBox( ViewContext viewContext, ModelExplorer modelExplorer, string expression, bool?isChecked, object?htmlAttributes, DesignSystem designSystem) { // CheckBoxFor() case. That API does not support passing isChecked directly. Debug.Assert(isChecked.HasValue is false); IDictionary <string, object>?htmlAttributeDictionary = GetHtmlAttributeDictionaryOrNull(htmlAttributes); if (bool.TryParse(modelExplorer.Model.ToString(), out var modelChecked)) { isChecked = modelChecked; } if (isChecked.HasValue && htmlAttributeDictionary is not null) { // Explicit isChecked value must override "checked" in dictionary. htmlAttributeDictionary.Remove("checked"); } // Use ViewData only in CheckBox case (metadata null) and when the user didn't pass an isChecked value. return(GenerateInputElement( viewContext, InputType.CheckBox, modelExplorer, expression, "true", modelExplorer is null && isChecked.HasValue is false, isChecked ?? false, false, null, designSystem, htmlAttributeDictionary)); }
/// <summary> /// Generate an input element. /// </summary> /// <param name="viewContext">The <see cref="ViewContext"/>.</param> /// <param name="inputType">The <see cref="InputType"/>.</param> /// <param name="modelExplorer">The <see cref="ModelExplorer"/>.</param> /// <param name="expression">The expression.</param> /// <param name="value">The value.</param> /// <param name="useViewData">Whether to use view data.</param> /// <param name="isChecked">If the input is checked.</param> /// <param name="isExplicitValue">Whether this is an explicit value.</param> /// <param name="format">The format.</param> /// <param name="htmlAttributeDictionary">The html attributes.</param> private TagBuilder GenerateInputElement( ViewContext viewContext, InputType inputType, ModelExplorer modelExplorer, string expression, object value, bool useViewData, bool isChecked, bool isExplicitValue, string?format, DesignSystem designSystem, IDictionary <string, object>?htmlAttributeDictionary) { var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression); if (IsFullNameValid(fullName, htmlAttributeDictionary) is not true) { throw new ArgumentException( FieldNameCannotBeNullOrEmpty(typeof(IHtmlHelper).FullName, nameof(IHtmlHelper.Editor), typeof(IHtmlHelper <>).FullName, nameof(IHtmlHelper <object> .EditorFor), "htmlFieldName"), nameof(expression)); } TagBuilder tagBuilder; var valueParameter = FormatValue(value, format); var usedModelState = false; // Element tag name prefix is its design system name var prefix = designSystem.ToString().ToLowerInvariant(); if (inputType is InputType.CheckBox) { var modelStateWasChecked = GetModelStateValue(viewContext, fullName, typeof(bool)) as bool?; tagBuilder = new TagBuilder($"{prefix}-checkbox") { TagRenderMode = TagRenderMode.EndTag }; if (modelStateWasChecked.HasValue) { isChecked = modelStateWasChecked.Value; usedModelState = true; } if (usedModelState != true) { if (GetModelStateValue(viewContext, fullName, typeof(string)) is string modelStateValue) { isChecked = string.Equals(modelStateValue, valueParameter, StringComparison.Ordinal); usedModelState = true; } } if (usedModelState != true && useViewData) { isChecked = EvalBoolean(viewContext, expression); } if (isChecked) { tagBuilder.MergeAttribute("checked", "checked"); } tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue); } else { tagBuilder = new TagBuilder($"{prefix}-text-field") { TagRenderMode = TagRenderMode.SelfClosing }; AddPlaceholderAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression); AddMaxLengthAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression); if (inputType is InputType.Password) { if (value is not null) { tagBuilder.MergeAttribute("value", valueParameter, true); } } else { var attributeValue = (string?)GetModelStateValue(viewContext, fullName, typeof(string)); if (attributeValue is null) { attributeValue = modelExplorer is null && value is null ? EvalString(viewContext, expression, format) : valueParameter; } tagBuilder.MergeAttribute("value", attributeValue, true); } } if (string.IsNullOrEmpty(fullName) is not true) { tagBuilder.MergeAttribute("name", fullName, true); } tagBuilder.MergeAttributes(htmlAttributeDictionary); var inputTypeString = GetInputTypeString(inputType); tagBuilder.MergeAttribute("type", inputTypeString); NameAndIdProvider.GenerateId(viewContext, tagBuilder, fullName, IdAttributeDotReplacement); // If there are any errors for a named field, we add the CSS attribute. if (viewContext.ViewData.ModelState.TryGetValue(fullName, out ModelStateEntry? entry) && entry.Errors.Count > 0) { tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName); } AddValidationAttributes(viewContext, tagBuilder, modelExplorer, expression); return(tagBuilder); }
/// <summary> /// Generate a <fast-select> element for the <paramref name="expression"/>. /// </summary> /// <param name="optionLabel">Optional text for a default empty <fast-option> element.</param> /// <param name="selectList"> /// A collection of <see cref="SelectListItem"/> objects used to populate the /// <fast-select> element with <optgroup> and <fast-option> elements. /// If <c>null</c>, finds this collection at <c>ViewContext.ViewData[expression]</c>. /// </param> /// <param name="currentValues"> /// An <see cref="ICollection{String}"/> containing values for <fast-option> elements /// to select. If <c>null</c>, selects <fast-option> elements based on <see cref="SelectListItem.Selected"/> values in <paramref name="selectList"/>. /// </param> /// <param name="allowMultiple"> /// If <c>true</c>, includes a <c>multiple</c> attribute in the generated HTML. /// Otherwise generates a single-selection <fast-select> element. /// </param> /// <param name="htmlAttributes"> /// An <see cref="object"/> that contains the HTML attributes for the <fast-select> element. Alternatively, an /// <see cref="IDictionary{String, Object}"/> instance containing the HTML attributes. /// </param> /// <returns>A new <see cref="TagBuilder"/> describing the <fast-select> element.</returns> /// <remarks> /// <para> /// Combines <see cref="TemplateInfo.HtmlFieldPrefix"/> and <paramref name="expression"/> to set /// <fast-select> element's "name" attribute. Sanitizes <paramref name="expression"/> to set element's "id" /// attribute. /// </para> /// <para> /// See <see cref="GetCurrentValues"/> for information about how the <paramref name="currentValues"/> /// collection may be created. /// </para> /// </remarks> ///<inheritdoc/> public TagBuilder GenerateSelect( ViewContext viewContext, ModelExplorer modelExplorer, string?optionLabel, string expression, IEnumerable <SelectListItem> selectList, ICollection <string>?currentValues, bool allowMultiple, object?htmlAttributes, DesignSystem designSystem ) { // Element tag name prefix is its design system name var prefix = designSystem.ToString().ToLowerInvariant(); var fullName = NameAndIdProvider.GetFullHtmlFieldName(viewContext, expression); IDictionary <string, object>?htmlAttributeDictionary = GetHtmlAttributeDictionaryOrNull(htmlAttributes); if (IsFullNameValid(fullName, htmlAttributeDictionary) is false) { throw new ArgumentException( FieldNameCannotBeNullOrEmpty( typeof(IHtmlHelper).FullName, nameof(IHtmlHelper.Editor), typeof(IHtmlHelper <>).FullName, nameof(IHtmlHelper <object> .EditorFor), "htmlFieldName"), nameof(expression)); } // If we got a null selectList, try to use ViewData to get the list of items. if (selectList is null) { selectList = GetSelectListItems(viewContext, expression); } modelExplorer ??= ExpressionMetadataProvider.FromStringExpression(expression, viewContext.ViewData, metadataProvider); // Convert each ListItem to an <option> tag and wrap them with <optgroup> if requested. IHtmlContent listItemBuilder = GenerateGroupsAndOptions(optionLabel, selectList, currentValues, prefix); var tagBuilder = new TagBuilder($"{prefix}-select"); tagBuilder.InnerHtml.SetHtmlContent(listItemBuilder); tagBuilder.MergeAttributes(htmlAttributeDictionary); NameAndIdProvider.GenerateId(viewContext, tagBuilder, fullName, IdAttributeDotReplacement); if (string.IsNullOrEmpty(fullName) is false) { tagBuilder.MergeAttribute("name", fullName, replaceExisting: true); } if (allowMultiple) { tagBuilder.MergeAttribute("multiple", "multiple"); } // If there are any errors for a named field, we add the css attribute. if (viewContext.ViewData.ModelState.TryGetValue(fullName, out ModelStateEntry entry)) { if (entry.Errors.Count > 0) { tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName); } } AddValidationAttributes(viewContext, tagBuilder, modelExplorer, expression); return(tagBuilder); }