/// <summary> /// Create drop down menu item for property /// selected by expression using provided list item. /// </summary> /// <typeparam name="TModel">Type of model.</typeparam> /// <typeparam name="TProperty">Type of property.</typeparam> /// <param name="htmlHelper">HtmlHelper.</param> /// <param name="expression">Expression to select needed property.</param> /// <param name="listItem">List item to get text and value.</param> /// <returns>List item with radio button and span.</returns> internal static FluentTagBuilder CreateDropDownButtonMenuItem <TModel, TProperty>( this HtmlHelper <TModel> htmlHelper, Expression <Func <TModel, TProperty> > expression, SelectListItem listItem) { FluentTagBuilder menuItem = new FluentTagBuilder("li"); // Initialize span FluentTagBuilder span = new FluentTagBuilder("span") .SetInnerText(listItem.Text); // Initialize radio button string fieldName = ExpressionHelper.GetExpressionText(expression); ModelMetadata metadata = ModelMetadata.FromLambdaExpression( expression, htmlHelper.ViewData); // Get validation attributes and add hidden attribute var htmlAttributes = htmlHelper .GetUnobtrusiveValidationAttributes(fieldName); htmlAttributes.Add("hidden", "hidden"); MvcHtmlString radioButton = htmlHelper .RadioButton(fieldName, listItem.Value, listItem.Selected, htmlAttributes); // Set content of list menu item menuItem.AppendChild(span).AppendChild(radioButton); return(menuItem); }
/// <summary> /// Construct list for tree view. /// </summary> /// <typeparam name="TModel">Type of model.</typeparam> /// <typeparam name="TProperty">Type of selected property.</typeparam> /// <param name="helper">Html helper.</param> /// <param name="expression">Expression which selects property.</param> /// <param name="source">Soruce of tree view.</param> /// <returns>Tree view list as FluentTagBuilder.</returns> private static FluentTagBuilder ConstructList <TModel, TProperty>( this HtmlHelper <TModel> helper, Expression <Func <TModel, TProperty> > expression, TreeViewSource <TProperty> source) where TProperty : struct { // CSS classes string listCssClass = "tree-view-list"; string loadedCssClass = "tv-loaded"; // Construct tree view list FluentTagBuilder treeViewList = new FluentTagBuilder("ul") .AddCssClass(listCssClass); // If there is not any item return if (source == null || source.ItemList == null || source.ItemList.Count == 0) { return(treeViewList); } // If source is not empty add class treeViewList.AddCssClass(loadedCssClass); // Construct list items and add them to treeViewList foreach (TreeViewItem <TProperty> listItemSource in source.ItemList) { FluentTagBuilder listItem = helper .ConstructListItem(expression, listItemSource); treeViewList.AppendChild(listItem); } return(treeViewList); }
/// <summary> /// Construct tree view for selected property. /// </summary> /// <typeparam name="TModel">Type of model.</typeparam> /// <typeparam name="TProperty">Type of selected property.</typeparam> /// <param name="helper">Html helper.</param> /// <param name="expression">Expression which selects property.</param> /// <param name="source">Source for tree view.</param> /// <param name="sourceUrl">Url to get child items using Ajax.</param> /// <param name="searchExpression">Search expression to filter leaves.</param> /// <returns>Constructed Tree view.</returns> public static MvcHtmlString TreeViewFor <TModel, TProperty>( this HtmlHelper <TModel> helper, Expression <Func <TModel, TProperty> > expression, TreeViewSource <TProperty> source, string sourceUrl, string searchExpression) where TProperty : struct { // CSS classes string treeViewCssClass = "tree-view"; string levelCssClassFormat = "level-{0}"; string errorCssClass = string.Format("{0}__error", treeViewCssClass); string rootListCssClass = "root-list"; // Attribute keys string levelAttribute = "data-tv-level"; string forAttribute = "data-tv-for"; string sourceUrlAttribute = "data-tv-source-url"; string searchExpresionAttribute = "data-tv-search-exp"; // Get model name from expression string modelName = ExpressionHelper.GetExpressionText(expression); // Construct root list FluentTagBuilder rootList = helper.ConstructList(expression, source) .AddCssClass(string.Format(levelCssClassFormat, "0")) .AddCssClass(rootListCssClass) .MergeAttribute(levelAttribute, "0"); // Construct tree view container FluentTagBuilder treeViewContainer = new FluentTagBuilder("div") .AppendChild(rootList) .MergeAttribute(forAttribute, modelName) .MergeAttribute(sourceUrlAttribute, sourceUrl) .MergeAttribute(searchExpresionAttribute, searchExpression) .AddCssClass(treeViewCssClass); // Add error MvcHtmlString validationMessage = helper.ValidationMessageFor(expression); FluentTagBuilder errorSpan = new FluentTagBuilder("span") .AddCssClass(errorCssClass) .AppendChild(validationMessage); treeViewContainer.AppendChild(errorSpan); return(new MvcHtmlString(treeViewContainer.Render())); }
/// <summary> /// Construct list level action link container for list which will be /// managed at server side. /// </summary> /// <returns>List action container.</returns> private FluentTagBuilder ConstructServerSideListActionContainer() { if (ListActionCollection == null || ListActionCollection.Count == 0) { return(null); } FluentTagBuilder listActionContainer = new FluentTagBuilder("div"); foreach (IListEditorActionCore <TModel> action in ListActionCollection) { FluentTagBuilder listAction = action.GenerateLink(); if (listAction != null) { listActionContainer.AppendChild(listAction); } } return(listActionContainer); }
/// <summary> /// Construct editor according to configured fields. /// </summary> /// <returns>Constructed editor.</returns> private FluentTagBuilder ConstructListEditor() { // Declare and set css classes string listEditorCss = "list-editor"; string listEditorRowCss = "list-editor-row"; string listEditorRowLabelCss = "list-editor-row__label"; string listEditorRowFieldCss = "list-editor-row__field"; string listEditorDataTemplateCss = "list-editor-data__template"; string listEditorActionContainerCss = "list-editor-action-container"; string listEditorSaveActionCss = "list-editor-action__save"; string listEditorCancelActionCss = "list-editor-action__cancel"; // Initialize list editor FluentTagBuilder listEditor = new FluentTagBuilder("div") .AddCssClass(listEditorCss) .MergeAttribute("hidden", "hidden"); foreach (IFieldCore <TModel> fieldCore in FieldCollection) { // Initialize editor label FluentTagBuilder editorLabel = null; // If editor type has not been set to Hidden add label for field if (fieldCore.EditorType != EditorType.Hidden) { editorLabel = new FluentTagBuilder("div") .AddCssClass(listEditorRowLabelCss) .AppendChild(fieldCore.LabelFor()); } // Initialize editor field FluentTagBuilder editorField = new FluentTagBuilder("div") .AddCssClass(listEditorRowFieldCss) .AppendChild(fieldCore.EditorFor()); // Initialize editor row, add label and editor. FluentTagBuilder listEditorRow = new FluentTagBuilder("div") .AddCssClass(listEditorRowCss) .AppendChild(editorLabel) .AppendChild(editorField); // Add row to list editor listEditor.AppendChild(listEditorRow); } // Initialize actions FluentTagBuilder saveAction = new FluentTagBuilder("button") .MergeAttribute("type", "button") .AddCssClass(listEditorSaveActionCss) .SetInnerText(SaveButtonLabel); FluentTagBuilder cancelAction = new FluentTagBuilder("button") .MergeAttribute("type", "button") .AddCssClass(listEditorCancelActionCss) .SetInnerText(CancelButtonLabel); FluentTagBuilder actionContainer = new FluentTagBuilder("div") .AddCssClass(listEditorActionContainerCss) .AppendChild(saveAction) .AppendChild(cancelAction); // Add actions panel to list editor listEditor.AppendChild(actionContainer); // Initialize data template for adding or editing items. string dataTemplateIndex = "_template_index_"; string htmlFieldPrefix = string.Format( "{0}[{1}]", CollectionName, dataTemplateIndex); // Use BeginHtmlFieldPrefixScope instead of BeginCollectionItem, // because these items must not bound when data is posted, // and this index must not be reused by BeginCollectionItem // to keep consistency. using (Helper.BeginHtmlFieldPrefixScope(htmlFieldPrefix)) { MvcHtmlString hiddenIndexer = Helper.HiddenIndexerForModel( new { disabled = "disabled" }); FluentTagBuilder listDataRowTemplate = ConstructListDataRow(null) .AppendChild(hiddenIndexer) .AppendChild(ConstructClientSideListItemActionContainer(null)); FluentTagBuilder dataTemplate = new FluentTagBuilder("div") .MergeAttribute("hidden", "hidden") .AddCssClass(listEditorDataTemplateCss) .AppendChild(listDataRowTemplate); // Append dataTemplate to list editor. listEditor.AppendChild(dataTemplate); } return(listEditor); }
/// <summary> /// Generate page urls according to /// url generator and paging info. /// </summary> /// <param name="helper">HtmlHelper.</param> /// <param name="pagingInfo">Information about paging.</param> /// <param name="pageUrlGenerator">Url generator for pages.</param> /// <param name="htmlAttributes">Html attributes to add to links.</param> /// <returns>Generated html links.</returns> public static MvcHtmlString PageLinks( this HtmlHelper helper, PagingInfo pagingInfo, Func <int, string> pageUrlGenerator, object htmlAttributes) { if (pagingInfo == null) { throw new ArgumentNullException(nameof(pagingInfo)); } if (pageUrlGenerator == null) { throw new ArgumentNullException(nameof(pageUrlGenerator)); } string pagingContainerCss = "paging-container"; string pageLinkCss = "page-link"; string selectedLinkCss = "selected"; // Convert html attributes RouteValueDictionary attributes = HtmlHelper .AnonymousObjectToHtmlAttributes(htmlAttributes); // Initialize function to generate page link Func <int, FluentTagBuilder> constructPageLink = pageIndex => new FluentTagBuilder("a") .AddCssClass(pageLinkCss) .MergeAttribute("href", pageUrlGenerator(pageIndex)) .SetInnerText(pageIndex.ToString()); // Initialize function to constryct seperator dots Func <FluentTagBuilder> constructSeperatorDots = () => new FluentTagBuilder("span") .SetInnerText("..."); FluentTagBuilder pagingContainer = new FluentTagBuilder("div") .AddCssClass(pagingContainerCss); int lastPageIndex = pagingInfo.TotalPageCount; /// First of all define begining and ending index of page /// links which will be generated by loop. Maximum number of /// 9 pages can be generated by loop. First and last pages are added /// manually. // Maximum number of 4 pages should be rendered // before and after current page int rangeBeginIndex = Math.Max(2, pagingInfo.CurrentPage - 4); int rangeEndIndex = Math.Min(lastPageIndex - 1, pagingInfo.CurrentPage + 4); /// If there are more than 11 page links then 9 of them must be /// rendered by loop, otherwise all links must be displayed. // Get total count of page links which must be generated by loop. int totalCountOfPageLinksByLoop = Math.Min(lastPageIndex - 2, 9); /// Get current count of page links which will be generated by loop /// with current settings. For expample, if maximum page count is 100 /// and current page is 4, then two pages before current page (2, 3), /// current page itself and 4 pages after current page (5, 6, 7, 8) /// will be generated by loop. This means that total of 2 + 1 + 4 = 7 /// pages will be generated by loop, but this number must be 9. /// To accomplish this we have to add remaning pages from "before the current page" /// to the pages after current page. 4 must be generated before current page /// but this number is 2, then total of 6 pages should be generated after /// current page (5, 6, 7, 8, 9, 10). int currentPageCountByLoop = rangeEndIndex - rangeBeginIndex + 1; if (totalCountOfPageLinksByLoop > 5 && currentPageCountByLoop < totalCountOfPageLinksByLoop) { // Find remaing page count. // In the example above this is 4 - 2 = 2. int remainingPageCount = totalCountOfPageLinksByLoop - currentPageCountByLoop; // Add remaining pages to the before or after the current page. if (rangeBeginIndex == 2) { rangeEndIndex += remainingPageCount; } else if (rangeEndIndex == lastPageIndex - 1) { rangeBeginIndex -= remainingPageCount; } } // Add first page if (lastPageIndex >= 1) { FluentTagBuilder firstPage = constructPageLink(1) .MergeAttributes(attributes); if (1 == pagingInfo.CurrentPage) { firstPage.AddCssClass(selectedLinkCss); } pagingContainer.AppendChild(firstPage); // Add seperator dots there is gap between first page // and loop range begin index. if (rangeBeginIndex - 1 > 1) { pagingContainer.AppendChild(constructSeperatorDots()); } } // Add begaes in-between first and last for (int i = rangeBeginIndex; i <= rangeEndIndex; i++) { FluentTagBuilder pageLink = constructPageLink(i) .MergeAttributes(attributes); // Add selected class if needed if (i == pagingInfo.CurrentPage) { pageLink.AddCssClass(selectedLinkCss); } pagingContainer.AppendChild(pageLink); } // Add last page if needed if (lastPageIndex > 1) { // Add seperator dots if there is a gap between last page // and loop range end index. if (lastPageIndex - rangeEndIndex > 1) { pagingContainer.AppendChild(constructSeperatorDots()); } FluentTagBuilder lastPage = constructPageLink(lastPageIndex) .MergeAttributes(attributes); if (lastPageIndex == pagingInfo.CurrentPage) { lastPage.AddCssClass(selectedLinkCss); } pagingContainer.AppendChild(lastPage); } return(MvcHtmlString.Create(pagingContainer.Render())); }
/// <summary> /// Construct one row of list editor. /// </summary> /// <param name="model">Model object to construct list row according to.</param> /// <returns>Constructed row of list.</returns> private FluentTagBuilder ConstructListDataRow(TModel model) { // Declare and set css class names string listDataRowCss = "list-data-row"; string listDataFieldRowCss = "list-data-field-row"; string listDataCalptionFieldRowCss = "list-data-field-row__caption"; string listdataImageFiledRowCss = "list-data-field-row__iamge"; string listDataLabelCss = "list-data-display__label"; string listDataDisplayFieldCss = "list-data-display__field"; // Store current model for further use TModel oldModel = Helper.ViewData.Model; try { // Set model object of HtmlHelper to be able // to get correct display, editor and hidden for fields. Helper.ViewData.Model = model; // Create list data row and merge attributes FluentTagBuilder listDataRow = new FluentTagBuilder("div") .MergeAttributes(GetDataRowAttributes(model)) .AddCssClass(listDataRowCss); // Loop through each configured field and add them to the row. foreach (IFieldCore <TModel> fieldCore in FieldCollection) { /// Do not render field if provided condition is not met /// when list is managed at server side. if (BuilderType == ListEditorBuilderType.ManagedAtServerSide && !fieldCore.ShouldBeRendered(model)) { continue; } if (fieldCore.EditorType != EditorType.Hidden) { // If EditorType has not been set to Hidden, // construct normal field row // Initialize field FluentTagBuilder displayField = new FluentTagBuilder("div") .AddCssClass(listDataDisplayFieldCss); // If field should be rendered as image construct image tag // for the field otherwise costruct span for it. if (fieldCore.IsAsImage) { FluentTagBuilder iamgeField = new FluentTagBuilder("img") .MergeAttribute("src", fieldCore.Evaluate(model)); displayField.AppendChild(iamgeField); } else { FluentTagBuilder displaySpan = new FluentTagBuilder("span") .AppendChild(fieldCore.DisplayFor()); displayField.AppendChild(displaySpan); } // If this list editor will be managed at client side, // add hidden input field to be able to bind correctly. if (BuilderType == ListEditorBuilderType.ManagedAtClientSide) { displayField.AppendChild(fieldCore.HiddenFor()); } // Initialize row for label and field FluentTagBuilder fieldRow = new FluentTagBuilder("div") .AddCssClass(listDataFieldRowCss); // Add caption field row css class if needed if (fieldCore.IsAsCaption) { fieldRow.AddCssClass(listDataCalptionFieldRowCss); } // Add image filed row css class if needed if (fieldCore.IsAsImage) { fieldRow.AddCssClass(listdataImageFiledRowCss); } // Add label for field if needed. if (!fieldCore.IsWithoutLabel) { // Initialize label for field FluentTagBuilder label = new FluentTagBuilder("div") .AddCssClass(listDataLabelCss) .AppendChild(fieldCore.LabelFor()); fieldRow.AppendChild(label); } // Append display field to the row. fieldRow.AppendChild(displayField); // Add fieldRow to listEditorRow listDataRow.AppendChild(fieldRow); } else { // Initialize row for label and field FluentTagBuilder fieldRow = new FluentTagBuilder("div") .AddCssClass(listDataFieldRowCss) .AppendChild(fieldCore.HiddenFor()); // If EditorType has been set to Hidden, // just construct hidden input for field listDataRow.AppendChild(fieldRow); } } return(listDataRow); } finally { // Restore model of helper Helper.ViewData.Model = oldModel; } }
/// <summary> /// Render list editor builder as list. /// </summary> /// <remarks> /// This is implementation of RenderList(object htmlAttributes) for /// both of IServerListEditorBuilder and IClientListEditorBuilder. /// </remarks> /// <param name="htmlAttributes">Html attributes to add to list.</param> /// <returns>ListEditorBuilder as MvcHtmlString format.</returns> public MvcHtmlString RenderList(object htmlAttributes) { // Collection name must be provided for proper binding // list editor which is manged at client side if (BuilderType == ListEditorBuilderType.ManagedAtClientSide && string.IsNullOrEmpty(CollectionName)) { throw new InvalidOperationException( "Name of collection must be provided before rendering."); } // Declare and set css class names string listEditorForCss = "list-editor-for"; string listDataCss = "list-data"; string listDataListActionContainerCss = "list-action_container"; // Get html attributes RouteValueDictionary attributes = HtmlHelper .AnonymousObjectToHtmlAttributes(htmlAttributes); // Create div element and merge attributes FluentTagBuilder listEditorFor = new FluentTagBuilder("div") .MergeAttributes(attributes) .AddCssClass(listEditorForCss); // If list editor will be managed at client side we will need an editor. if (BuilderType == ListEditorBuilderType.ManagedAtClientSide) { // Construct list editor. // List editor must be constructed prior to list data. // Otherwise validation attributes will be // added to hidden input elements inside list data. listEditorFor.AppendChild(ConstructListEditor()); } // Initialize list data div FluentTagBuilder listData = new FluentTagBuilder("div") .AddCssClass(listDataCss); // Initialize list action container and add to listData if needed. FluentTagBuilder listActionContainer = null; if (BuilderType == ListEditorBuilderType.ManagedAtClientSide) { listActionContainer = ConstructClientSideListActionContainer(); } else if (BuilderType == ListEditorBuilderType.ManagedAtServerSide) { listActionContainer = ConstructServerSideListActionContainer(); } if (listActionContainer != null) { listActionContainer.AddCssClass(listDataListActionContainerCss); listData.AppendChild(listActionContainer); } if (Data != null && Data.Count() > 0) { if (BuilderType == ListEditorBuilderType.ManagedAtClientSide) { // If builder will be managed at client side we need // to add indexers to be able to bind correctly // and action buttons for managing items. foreach (TModel model in Data) { using (Helper.BeginCollectionItem(CollectionName, false)) { // Append hidden indexer for correctly binding // data and action container for the row. FluentTagBuilder listDataRow = ConstructListDataRow(model) .AppendChild(Helper.HiddenIndexerForModel()) .AppendChild(ConstructClientSideListItemActionContainer(model)); // Add listDataRow to listData listData.AppendChild(listDataRow); } } } else { // If builder will be managed at server side we need // to add links for managing items. foreach (TModel model in Data) { FluentTagBuilder listDataRow = ConstructListDataRow(model) .AppendChild(ConstructServerSideListItemActionContainer(model)); // Add listDataRow to listData listData.AppendChild(listDataRow); } } } listEditorFor.AppendChild(listData); return(new MvcHtmlString(listEditorFor.Render())); }
/// <summary> /// Render sorting controller. /// </summary> /// <param name="htmlAttributes">HtmlAttributes to add to control.</param> /// <returns>MvcHtmlString.</returns> public MvcHtmlString Render(object htmlAttributes) { if (SortingFieldCollection == null || SortingFieldCollection.Count == 0) { throw new InvalidOperationException("At list one field must be configured to enable sorting."); } string sortingControlCss = "sorting-control"; string controlFieldCss = "sorting-control-field"; string controlOrderCss = "sorting-control-order"; string controlHeaderCss = "sorting-control-header"; string controlListCss = "sorting-control-list"; string selectedCss = "selected"; // Initialize sorting field list FluentTagBuilder sortingFieldList = new FluentTagBuilder("ul") .AddCssClass(controlListCss); // Currently sorted according to this field ISortingFieldCore <TModel> currentlySortedField = null; // Loop thorugh configured fields and add links for them foreach (ISortingFieldCore <TModel> fieldCore in SortingFieldCollection) { string fieldPropertyName = fieldCore.GetPropertyName(); FluentTagBuilder link = new FluentTagBuilder("a") .MergeAttribute("href", SortFieldUrlGenerator(fieldPropertyName)) .SetInnerText(fieldCore.FieldLabel); FluentTagBuilder listItem = new FluentTagBuilder("li") .AppendChild(link); if (fieldPropertyName == Info.PropertyName) { // Store currently sorted field for further use // and add selected class to it. currentlySortedField = fieldCore; listItem.AddCssClass(selectedCss); } // Append item to list sortingFieldList.AppendChild(listItem); } // Initialize down icon FluentTagBuilder downIcon = new FluentTagBuilder("i") .AddCssClass("default-icons") .SetInnerHtml("keyboard_arrow_down"); // Initialize sorting field header FluentTagBuilder fieldHeaderSpan = new FluentTagBuilder("span") .SetInnerText(currentlySortedField.FieldLabel); FluentTagBuilder fieldHeader = new FluentTagBuilder("div") .AddCssClass(controlHeaderCss) .AppendChild(fieldHeaderSpan) .AppendChild(downIcon); // Initialize sorting field FluentTagBuilder sortingField = new FluentTagBuilder("div") .AddCssClass(controlFieldCss) .AppendChild(fieldHeader) .AppendChild(sortingFieldList); // Initialize order list FluentTagBuilder sortingOrderList = new FluentTagBuilder("ul") .AddCssClass(controlListCss); // Loop through availabel orders and create list items for them foreach (SortOrder order in Enum.GetValues(typeof(SortOrder))) { // Determine label string label = order == SortOrder.Asc ? AscendingLabel : DescendingLabel; FluentTagBuilder link = new FluentTagBuilder("a") .MergeAttribute("href", SortOrderUrlGenerator(order)) .SetInnerText(label); FluentTagBuilder listItem = new FluentTagBuilder("li") .AppendChild(link); if (order == Info.Order) { listItem.AddCssClass(selectedCss); } // Append item to list sortingOrderList.AppendChild(listItem); } // Initialize sorting order header string orderHeaderLabel = Info.Order == SortOrder.Asc ? AscendingLabel : DescendingLabel; FluentTagBuilder orderHeaderSpan = new FluentTagBuilder("span") .SetInnerText(orderHeaderLabel); FluentTagBuilder orderHeader = new FluentTagBuilder("div") .AddCssClass(controlHeaderCss) .AppendChild(orderHeaderSpan) .AppendChild(downIcon); // Initialize sorting order FluentTagBuilder sortingOrder = new FluentTagBuilder("div") .AddCssClass(controlOrderCss) .AppendChild(orderHeader) .AppendChild(sortingOrderList); // Extract htmlAttribytes RouteValueDictionary attributes = HtmlHelper .AnonymousObjectToHtmlAttributes(htmlAttributes); // Initialize sorting control, merge attributes and add controls FluentTagBuilder sortingControl = new FluentTagBuilder("div") .MergeAttributes(attributes) .AddCssClass(sortingControlCss) .AppendChild(sortingField) .AppendChild(sortingOrder); return(MvcHtmlString.Create(sortingControl.Render())); }
/// <summary> /// Create drop down button for selected property, /// with given source. /// </summary> /// <exception cref="ArgumentNullException"> /// When expression or source is null. /// </exception> /// <typeparam name="TModel">Type of model.</typeparam> /// <typeparam name="TProperty">Type of property.</typeparam> /// <param name="htmlHelper">HtmlHelper.</param> /// <param name="expression">Expression to select needed property.</param> /// <param name="source">Source of list to select from.</param> /// <param name="optionLabel">Label of drop down button if selection has not been made.</param> /// <param name="allowDefault">Allow to select default item without value.</param> /// <param name="htmlAttributes">Html attributes.</param> /// <returns>Drop down button mvc string.</returns> public static MvcHtmlString DropDownButtonFor <TModel, TProperty>( this HtmlHelper <TModel> htmlHelper, Expression <Func <TModel, TProperty> > expression, SelectList source, string optionLabel, bool allowDefault, object htmlAttributes) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } if (source == null || source.Items == null) { throw new ArgumentNullException(nameof(source)); } string dropDownCssClass = "dropdown-button"; string buttonContainerCssClass = "dropdown-button-container"; string headerCssClass = "dropdown-button-header"; string menuCssClass = "dropdown-menu"; string errorCssClass = "dropdown-button__error"; string selectedCssClass = "selected"; // Get model metadata ModelMetadata metadata = ModelMetadata .FromLambdaExpression(expression, htmlHelper.ViewData); // Convert html attributes RouteValueDictionary attributes = HtmlHelper .AnonymousObjectToHtmlAttributes(htmlAttributes); // Get modelValue string modelValue = string.Empty; if (metadata.Model != null && !string.IsNullOrEmpty(metadata.Model.ToString())) { modelValue = metadata.Model.ToString(); } // Define selected item string selectedValue = string.Empty; List <SelectListItem> listeItemSource = source .Items .Cast <SelectListItem>() .ToList(); // Check if selected value explicitly setted in the source selectedValue = listeItemSource .Where(m => m.Selected) .Select(m => m.Value) .FirstOrDefault(); /// If not explicitly set get model value as a selected value /// and set appropriate list item as selected if (string.IsNullOrEmpty(selectedValue) && !string.IsNullOrEmpty(modelValue)) { selectedValue = modelValue; SelectListItem listItem = listeItemSource .Where(m => m.Value.Equals(modelValue, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (listItem != null) { listItem.Selected = true; } } // Initialize parentDiv and merge attributes FluentTagBuilder dropDownDiv = new FluentTagBuilder("div") .MergeAttributes(attributes) .AddCssClass(dropDownCssClass); // Initialize and add css class of button container FluentTagBuilder buttonContainerDiv = new FluentTagBuilder("div") .AddCssClass(buttonContainerCssClass); // If any item has been selected // get text of that item as option label string buttonHeaderText = optionLabel; if (!string.IsNullOrEmpty(selectedValue)) { string selectedItemText = listeItemSource .Where(m => m.Value.Equals(selectedValue, StringComparison.OrdinalIgnoreCase)) .Select(m => m.Text) .FirstOrDefault(); // If selected item text found set buttonHeaderText to // this text if (!string.IsNullOrEmpty(selectedItemText)) { buttonHeaderText = selectedItemText; } } // Initialize down icon FluentTagBuilder downIcon = new FluentTagBuilder("i") .AddCssClass("default-icons") .SetInnerHtml("keyboard_arrow_down"); // Initialize button header FluentTagBuilder buttonHeader = new FluentTagBuilder("span") .AddCssClass(headerCssClass) .SetInnerText(buttonHeaderText); // Add icon and header into container buttonContainerDiv.AppendChild(downIcon).AppendChild(buttonHeader); // Append button container to content builder of dropdown dropDownDiv.AppendChild(buttonContainerDiv); // Initialzie drop down menu FluentTagBuilder menu = new FluentTagBuilder("ul") .AddCssClass(menuCssClass); // If allow default set to true add option to // select default item if (allowDefault) { FluentTagBuilder defaultItem = htmlHelper.CreateDropDownButtonMenuItem( expression, new SelectListItem { Text = optionLabel, Value = "", Selected = false }); // If model is null then add selected class to default item if (metadata.Model == null || string.IsNullOrEmpty(metadata.Model.ToString())) { defaultItem.AddCssClass(selectedCssClass); } menu.AppendChild(defaultItem); } // Loop throug list items and add them to menu foreach (SelectListItem listItem in listeItemSource) { // initialize menu item FluentTagBuilder menuItem = htmlHelper .CreateDropDownButtonMenuItem(expression, listItem); // If model equals to current list item // add selected class to menu item if (!string.IsNullOrEmpty(selectedValue) && string.Equals( listItem.Value, selectedValue, StringComparison.OrdinalIgnoreCase)) { menuItem.AddCssClass(selectedCssClass); } menu.AppendChild(menuItem); } // Append menu to dropdown dropDownDiv.AppendChild(menu); // Initialize error span and add css class MvcHtmlString validationMessage = htmlHelper.ValidationMessageFor(expression); FluentTagBuilder errorSpan = new FluentTagBuilder("span") .AddCssClass(errorCssClass) .AppendChild(validationMessage); // Append error span to dropdown dropDownDiv.AppendChild(errorSpan); return(new MvcHtmlString(dropDownDiv.Render())); }
/// <summary> /// Construct list selector according to parameters. /// </summary> /// <exception cref="ArgumentNullException"> /// When expression or source is null. /// </exception> /// <typeparam name="TModel">Type of model.</typeparam> /// <typeparam name="TProperty">Type of property.</typeparam> /// <param name="htmlHelper">HtmlHelper.</param> /// <param name="expression">Expression to select needed property.</param> /// <param name="source">Source of list to select from.</param> /// <param name="enableMultiSelect">Enable selecting multiple items at once.</param> /// <param name="htmlAttributes">Html attributes.</param> /// <returns>Constructed list selector as FluentTagBuilder.</returns> internal static FluentTagBuilder ConstructListSelector <TModel, TProperty>( this HtmlHelper <TModel> htmlHelper, Expression <Func <TModel, TProperty> > expression, SelectList source, bool enableMultiSelect, object htmlAttributes) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } if (source == null || source.Items == null) { throw new ArgumentNullException(nameof(source)); } // Determine main class string ulCssClass = enableMultiSelect ? "list-multi-selector-for" : "list-selector-for"; string contentContainerCssClass = "content-container"; string errorCssClass = string.Format("{0}__error", ulCssClass); // Get model metadata ModelMetadata metadata = ModelMetadata .FromLambdaExpression(expression, htmlHelper.ViewData); // Declare model data and get it from metadata string modelData = null; List <string> listModelData = null; if (metadata.Model != null) { if (enableMultiSelect) { listModelData = ((IEnumerable)metadata.Model) .Cast <object>() .Where(m => m != null) .Select(m => m.ToString()) .ToList(); } else { modelData = metadata.Model.ToString(); } } // Convert html attributes RouteValueDictionary attributes = HtmlHelper .AnonymousObjectToHtmlAttributes(htmlAttributes); // Construct ul tag. FluentTagBuilder ulBuilder = new FluentTagBuilder("ul") .MergeAttributes(attributes) .AddCssClass(ulCssClass); // Loop through items and add them to list foreach (SelectListItem listItem in source.Items) { // Initialize icon FluentTagBuilder icon = new FluentTagBuilder("i") .AddCssClass("default-icons") .SetInnerHtml("keyboard_arrow_right"); // Initialize span FluentTagBuilder span = new FluentTagBuilder("span") .SetInnerHtml(listItem.Text); // Initialize hidden selector field to select. // If multiselect enabled, then this field should be // checkbox, otherwise it should be radio button. string hiddenSelector; if (enableMultiSelect) { // Get model name form expression string modelName = ExpressionHelper.GetExpressionText(expression); // Get validation attributes IDictionary <string, object> validationAttributes = htmlHelper.GetUnobtrusiveValidationAttributes(modelName, metadata); // Determine if list item is checked bool isChecked = listModelData != null && listModelData.Contains(listItem.Value); FluentTagBuilder hiddenCheckBox = new FluentTagBuilder("input") .MergeAttribute("name", modelName) .MergeAttribute("id", modelName) .MergeAttribute("value", listItem.Value) .MergeAttribute("type", "checkbox") .MergeAttribute("hidden", "hidden") .MergeAttributes(validationAttributes); if (isChecked) { hiddenCheckBox.MergeAttribute("checked", "true"); } hiddenSelector = hiddenCheckBox.Render(); } else { hiddenSelector = htmlHelper .RadioButtonFor(expression, listItem.Value, new { hidden = "hidden" }) .ToHtmlString(); } // Initialize div and add elements inside. FluentTagBuilder containerDiv = new FluentTagBuilder("div") .AddCssClass(contentContainerCssClass) .AppendChild(icon) .AppendChild(span) .AppendChild(hiddenSelector); // Initialize li and set inner text FluentTagBuilder liBuilder = new FluentTagBuilder("li") .SetInnerHtml(containerDiv.Render()); // Add li to listContentBuilder ulBuilder.AppendChild(liBuilder); } // Add error MvcHtmlString validationMessage = htmlHelper.ValidationMessageFor(expression); FluentTagBuilder errorSpan = new FluentTagBuilder("span") .AddCssClass(errorCssClass) .AppendChild(validationMessage); ulBuilder.AppendChild(errorSpan); return(ulBuilder); }
/// <summary> /// Create combo date selector for date time. /// </summary> /// <exception cref="ArgumentNullException"> /// When expression is null. /// </exception> /// <typeparam name="TModel">Type of model.</typeparam> /// <param name="htmlHelper">HtmlHelper.</param> /// <param name="expression">Property selector expression.</param> /// <param name="yearRange">Range of years.</param> /// <param name="htmlAttributes">Html attributes to add to combo date.</param> /// <returns>Combo date selector.</returns> public static MvcHtmlString ComboDateFor <TModel>( this HtmlHelper <TModel> htmlHelper, Expression <Func <TModel, DateTime> > expression, IEnumerable <int> yearRange, object htmlAttributes) { if (expression == null) { throw new ArgumentNullException(nameof(expression)); } string comboDateCss = "combo-date"; string dayContainerCss = "day-container"; string monthContainerCss = "month-container"; string yearContainerCss = "year-container"; string errorCss = "combo-date__error"; string dayText = "Gün"; string monthText = "Ay"; string yearText = "İl"; // Initialize yearRange if has not been provided if (yearRange == null) { yearRange = Enumerable.Range(1900, 200); } // Get model metadata ModelMetadata metadata = ModelMetadata.FromLambdaExpression( expression, htmlHelper.ViewData); string modelName = ExpressionHelper.GetExpressionText(expression); // Append HtmlFieldPrefix if there is any string fieldPrefix = htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix; if (!string.IsNullOrEmpty(fieldPrefix)) { modelName = string.Format("{0}.{1}", fieldPrefix, modelName); } // Convert html attributes RouteValueDictionary attributes = HtmlHelper .AnonymousObjectToHtmlAttributes(htmlAttributes); // Initialize container div FluentTagBuilder comboDate = new FluentTagBuilder("div") .MergeAttributes(attributes) .AddCssClass(comboDateCss); // Initialize hidden text box for client side validation FluentTagBuilder input = new FluentTagBuilder("input") .MergeAttribute("name", modelName) .MergeAttribute("id", modelName) .MergeAttribute("type", "date") .MergeAttribute("hidden", "hidden") .MergeAttribute("readonly", "readonly"); if (metadata.Model != null) { DateTime value = Convert.ToDateTime(metadata.Model); input.MergeAttribute("value", value.ToString("yyyy-MM-dd")); } //// Get validation attributes IDictionary <string, object> validationAttributes = htmlHelper.GetUnobtrusiveValidationAttributes(modelName, metadata); // Merge validation attributes input.MergeAttributes(validationAttributes); //contentBuilder.AppendLine(input.ToString()); comboDate.AppendChild(input); // Declare date property selector Expression <Func <TModel, Int32> > datePropertySelector; // Select day property of date MemberExpression dayExpression = Expression.Property(expression.Body, "Day"); datePropertySelector = Expression.Lambda <Func <TModel, Int32> >( dayExpression, expression.Parameters); // Create drop down button for day MvcHtmlString daySelector = htmlHelper .DropDownButtonFor <TModel, int>( datePropertySelector, new SelectList(Enumerable .Range(1, 31) .Select(m => new SelectListItem { Text = m.ToString("00"), Value = m.ToString() })), dayText); // Setup day container FluentTagBuilder dayContainer = new FluentTagBuilder("div") .AddCssClass(dayContainerCss) .AppendChild(daySelector); //contentBuilder.AppendLine(dayContainer.ToString()); comboDate.AppendChild(dayContainer); // Select month property of date MemberExpression monthExpression = Expression.Property(expression.Body, "Month"); datePropertySelector = Expression.Lambda <Func <TModel, Int32> >( monthExpression, expression.Parameters); // Create drop down button for month MvcHtmlString monthSelector = htmlHelper .DropDownButtonFor <TModel, int>( datePropertySelector, new SelectList(Enumerable.Range(1, 12) .Select(r => new SelectListItem { Value = r.ToString(), Text = DateTimeFormatInfo.CurrentInfo.GetMonthName(r) })), monthText); // Setup month container FluentTagBuilder monthContainer = new FluentTagBuilder("div") .AddCssClass(monthContainerCss) .AppendChild(monthSelector); //contentBuilder.AppendLine(monthContainer.ToString()); comboDate.AppendChild(monthContainer); // Select year property of date MemberExpression yearExpression = Expression.Property(expression.Body, "Year"); datePropertySelector = Expression.Lambda <Func <TModel, Int32> >( yearExpression, expression.Parameters); // Create drop down button for month MvcHtmlString yearSelector = htmlHelper .DropDownButtonFor <TModel, int>( datePropertySelector, new SelectList(yearRange .Select(r => new SelectListItem { Text = r.ToString(), Value = r.ToString() })), yearText); // Setup year container FluentTagBuilder yearContainer = new FluentTagBuilder("div") .AddCssClass(yearContainerCss) .AppendChild(yearSelector); comboDate.AppendChild(yearContainer); // Set up error span MvcHtmlString validationMessage = htmlHelper .ValidationMessageFor(expression); FluentTagBuilder errorSpan = new FluentTagBuilder("span") .AddCssClass(errorCss) .AppendChild(validationMessage); comboDate.AppendChild(errorSpan); return(new MvcHtmlString(comboDate.Render())); }