Esempio n. 1
0
        /// <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;
        }
Esempio n. 2
0
        /// <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));
        }
Esempio n. 3
0
        /// <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));
        }
Esempio n. 4
0
        /// <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);
        }
Esempio n. 5
0
        /// <summary>
        /// Generate a &lt;fast-select&gt; element for the <paramref name="expression"/>.
        /// </summary>
        /// <param name="optionLabel">Optional text for a default empty &lt;fast-option&gt; element.</param>
        /// <param name="selectList">
        /// A collection of <see cref="SelectListItem"/> objects used to populate the
        /// &lt;fast-select&gt; element with &lt;optgroup&gt; and &lt;fast-option&gt; 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 &lt;fast-option&gt; elements
        /// to select. If <c>null</c>, selects &lt;fast-option&gt; 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 &lt;fast-select&gt; element.
        /// </param>
        /// <param name="htmlAttributes">
        /// An <see cref="object"/> that contains the HTML attributes for the &lt;fast-select&gt; element. Alternatively, an
        /// <see cref="IDictionary{String, Object}"/> instance containing the HTML attributes.
        /// </param>
        /// <returns>A new <see cref="TagBuilder"/> describing the &lt;fast-select&gt; element.</returns>
        /// <remarks>
        /// <para>
        /// Combines <see cref="TemplateInfo.HtmlFieldPrefix"/> and <paramref name="expression"/> to set
        /// &lt;fast-select&gt; 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);
        }