private TableNode FooterContainer(string cssClass)
        {
            TableNode container = new TableNode("div");

            container.Element.AddCssClass("float-right");
            container.Element.AddCssClass(cssClass);

            return(container);
        }
        private TableNode CreateAndAppend(string tag, TableNode parent, IEnumerable <string> cssClasses = null)
        {
            TableNode node = new TableNode(tag);

            parent.InnerContent.Add(node);
            if (cssClasses != null)
            {
                this.AddCssClasses(cssClasses, node.Element);
            }

            return(node);
        }
        private void FilteringLinkTemplate(TableNode container)
        {
            if (_config.Columns.Any(c => c.Value.Filtering.Threshold > 0))
            {
                TableNode filterLink   = this.CreateAndAppend("a", container);
                TableNode linkTemplate = this.CreateAndAppend("input", container);
                string    url          = $"{_config.Update.Url}?{this.CommonQueryAttrs()}{this.SortQueryAttrs()}{this.PagingQueryAttrs()}";

                this.SetupAjaxAttrs(filterLink.Element);
                filterLink.Element.Attributes.Add("id", "FilterLink");
                linkTemplate.Element.Attributes.Add("id", "FilterLinkTemplate");
                linkTemplate.Element.Attributes.Add("type", "hidden");
                linkTemplate.Element.Attributes.Add("value", url);
            }
        }
        private TableNode NavCtrl(TableNode container, string iconLib, string icon, string navClass, bool enabled = true)
        {
            TableNode nav = this.CreateAndAppend("a", container, new [] { "btn", "btn-default", navClass });

            if (!enabled)
            {
                nav.Element.AddCssClass("disabled");
            }

            if (iconLib != null)
            {
                this.CreateAndAppend("span", nav, new [] { iconLib, icon });
            }

            return(nav);
        }
        private TableNode Table()
        {
            TableNode table = new TableNode("table");

            Element = table.Element;
            this.BaseConfig((ConfigBase)_config, "table", "table-");
            //this.AddAttribute("id", _config.Id, table.Element);
            //this.AddAttribute("name", _config.Name, table.Element);
            //table.Element.AddCssClass("table");
            table.Element.AddCssClass(_config.Dark ? "table-dark" : null);
            table.Element.AddCssClass(_config.Striped ? "table-striped" : null);
            table.Element.AddCssClass(_config.Bordered ? "table-bordered" : null);
            table.Element.AddCssClass(_config.HoverState ? "table-hover" : null);
            table.Element.AddCssClass(_config.Small ? "table-sm" : null);
            //this.AddCssClasses(_config.CssClasses, table.Element);

            return(table);
        }
        private void ManualFiltering(ColumnConfig config, PropertyInfo propInfo, TableNode filter)
        {
            TableNode input = this.CreateAndAppend("input", filter);

            input.Element.Attributes.Add("type", "text");
            input.Element.Attributes.Add("data-filter-prop", propInfo.Name);
            input.Element.Attributes.Add("data-filter-threshold", config.Filtering.Threshold.ToString());
            input.Element.AddCssClass("form-control");
            this.AddCssClasses(config.Filtering.CssClasses, input.Element);
            if (_tableState.Filters.ContainsKey(propInfo.Name))
            {
                input.Element.Attributes.Add("value", _tableState.Filters[propInfo.Name].Value);
            }
            if (propInfo.Name == _tableState.CurrentFilter)
            {
                // Filter input should be focused.
                input.Element.Attributes.Add("data-filter-focus", string.Empty);
            }
        }
Example #7
0
 public TableNode(string element, TableNode inner)
     : this(new TagBuilder(element))
 {
     InnerContent.Add(inner);
 }
        private void Footer(TableNode table)
        {
            if (_config.Paging.PageSize > 0 || !string.IsNullOrEmpty(_config.Footer.Text))
            {
                List <TableNode> footerContent = new List <TableNode>();
                int entityCount = _model.Entities.Count();
                int pageCount   = _config.Paging.PageSize > 0
                    ? entityCount / _config.Paging.PageSize + (entityCount % _config.Paging.PageSize > 0 ? 1 : 0)
                    : 0;

                if (_config.Paging.PageInfo)
                {
                    TableNode container = this.FooterContainer("NavInfoContainer");

                    this.CreateAndAppend("span", container).Element.InnerHtml.Append($"{_tableState.Page}/{pageCount}");
                    footerContent.Add(container);
                }

                // Paging.
                if (_config.Paging.PageSize > 0 && entityCount > _config.Paging.PageSize)
                {
                    TableNode firstContainer = this.FooterContainer("NavBtnContainer");
                    TableNode prevContainer  = this.FooterContainer("NavBtnContainer");
                    TableNode nextContainer  = this.FooterContainer("NavBtnContainer");
                    TableNode lastContainer  = this.FooterContainer("NavBtnContainer");
                    TableNode first          = this.NavCtrl(firstContainer, _config.Paging.IconLib, _config.Paging.FirstCssClass, "NavFirst", _tableState.Page > 1);
                    TableNode prev           = this.NavCtrl(prevContainer, _config.Paging.IconLib, _config.Paging.PreviousCssClass, "NavPrevious", _tableState.Page > 1);
                    TableNode next           = this.NavCtrl(nextContainer, _config.Paging.IconLib, _config.Paging.NextCssClass, "NavNext", pageCount > _tableState.Page);
                    TableNode last           = this.NavCtrl(lastContainer, _config.Paging.IconLib, _config.Paging.LastCssClass, "NavLast", pageCount > _tableState.Page);

                    this.SetupAjaxAttrs(first.Element, this.PagingLinkQueryAttrs(1));
                    this.SetupAjaxAttrs(prev.Element, this.PagingLinkQueryAttrs(_tableState.Page - 1));
                    this.SetupAjaxAttrs(next.Element, this.PagingLinkQueryAttrs(_tableState.Page + 1));
                    this.SetupAjaxAttrs(last.Element, this.PagingLinkQueryAttrs(pageCount));

                    if (_config.Paging.DirectPageAccess)
                    {
                        TableNode container      = this.FooterContainer("NavAccessContainer");
                        TableNode pages          = this.CreateAndAppend("select", container);
                        TableNode pageSelector   = this.CreateAndAppend("a", container);
                        string    pageSelectorId = Guid.NewGuid().ToString();

                        pages.Element.Attributes.Add("data-pageselector-id", $"#{pageSelectorId}");
                        for (int pageNumber = 1; pageNumber <= pageCount; pageNumber++)
                        {
                            TableNode page = this.CreateAndAppend("option", pages);

                            page.Element.InnerHtml.Append(pageNumber.ToString());
                            page.Element.Attributes.Add("value",
                                                        $"{_config.Update.Url}?{this.CommonQueryAttrs()}{this.FilterQueryAttrs()}{this.PagingLinkQueryAttrs(pageNumber)}");
                            if (pageNumber == _tableState.Page)
                            {
                                page.Element.Attributes.Add("selected", "");
                            }
                        }

                        pageSelector.Element.Attributes.Add("id", pageSelectorId);
                        this.SetupAjaxAttrs(pageSelector.Element);
                        footerContent.Add(container);
                    }

                    footerContent.Add(lastContainer);
                    footerContent.Add(nextContainer);
                    footerContent.Add(prevContainer);
                    footerContent.Add(firstContainer);
                }

                // Text.
                if (!string.IsNullOrEmpty(_config.Footer.Text))
                {
                    TableNode container  = new TableNode("div");
                    TableNode footerText = this.CreateAndAppend("span", container);

                    container.Element.AddCssClass("FooterTextContainer");
                    footerText.Element.AddCssClass("FooterText");
                    footerText.Element.InnerHtml.Append(_config.Footer.Text);
                    footerContent.Add(container);
                }

                if (footerContent.Any())
                {
                    TableNode footer   = this.CreateAndAppend("tfoot", table);
                    TableNode row      = this.CreateAndAppend("tr", footer);
                    TableNode content  = this.CreateAndAppend("td", row);
                    int       colCount = _entity.GetType().GetProperties()
                                         .Count(pi => !_config.Columns.ContainsKey(pi.Name) || _config.Columns[pi.Name].Visible);

                    content.Element.Attributes.Add("colspan", colCount.ToString());
                    this.AddContextualState(content.Element, _config.Footer.State, "table-");
                    this.AddCssClasses(_config.Footer.CssClasses, content.Element);
                    content.InnerContent.AddRange(footerContent);
                }
            }
        }
        private void PrepopulatedFiltering(ColumnConfig config, PropertyInfo propInfo, TableNode filter,
                                           IEnumerable <string> filterValues)
        {
            TableNode  dropDown      = this.CreateAndAppend("div", filter);
            TagBuilder dropDownBtn   = new TagBuilder("button");
            TagBuilder dropDownMenu  = new TagBuilder("ul");
            TagBuilder dropDownCaret = new TagBuilder("span");

            dropDown.Element.AddCssClass("dropdown");
            dropDown.Element.InnerHtml.AppendHtml(dropDownBtn);
            this.AddCssClasses(config.Filtering.CssClasses, dropDownMenu);
            dropDownBtn.AddCssClass("btn");
            dropDownBtn.AddCssClass("btn-default");
            dropDownBtn.AddCssClass("dropdown-toggle");
            dropDownBtn.Attributes.Add("type", "button");
            dropDownBtn.Attributes.Add("data-toggle", "dropdown");
            dropDownBtn.Attributes.Add("aria-haspopup", "true");
            dropDownBtn.Attributes.Add("aria-expanded", "false");
            if (_tableState.Filters.ContainsKey(propInfo.Name))
            {
                // Currently selected filter value.
                dropDownBtn.InnerHtml.Append(_tableState.Filters[propInfo.Name].Value);
            }
            dropDownBtn.InnerHtml.AppendHtml(dropDownCaret);
            dropDown.Element.InnerHtml.AppendHtml(dropDownMenu);
            dropDownMenu.AddCssClass("dropdown-menu");
            dropDownCaret.AddCssClass("caret");
            this.AddFilterSelection(dropDownMenu, propInfo.Name, "&nbsp;", true);
            foreach (var filterValue in filterValues)
            {
                this.AddFilterSelection(dropDownMenu, propInfo.Name, filterValue);
            }
        }
        private void Header(TableNode table)
        {
            if (_config.Columns.Values.Any(c => !string.IsNullOrEmpty(c.Header)))
            {
                TableNode header    = this.CreateAndAppend("thead", table);
                TableNode headerRow = this.CreateAndAppend("tr", header);
                TableNode filterRow = _config.Columns.Any(c => c.Value.Filtering.Threshold > 0 || c.Value.Filtering.Prepopulated)
                    ? this.CreateAndAppend("tr", header)
                    : null;
                IDictionary <string, IEnumerable <string> > filterValues = new Dictionary <string, IEnumerable <string> >();

                // For each column configured for prepopulated filtering, retrieve the possible
                // filter values.
                this.IterateProperties(_entity, (propInfo, config) =>
                {
                    if (config?.Filtering.Prepopulated != null)
                    {
                        List <object> values = _model.Entities
                                               .Select(ExpressionHelper.PropertyExpr <T>(propInfo.Name)).Distinct().ToList();
                        filterValues.Add(propInfo.Name, values.Select(v => v.ToString()).OrderBy(v => v));
                    }
                });

                // Columns.
                this.IterateProperties(_entity, (propInfo, config) =>
                {
                    TableNode headerCol = this.CreateAndAppend("th", headerRow);
                    TableNode filter    = filterRow != null ? this.CreateAndAppend("td", filterRow) : null;

                    if (config != null)
                    {
                        headerCol.Element.InnerHtml.Append(config.Header);
                        this.AddCssClasses(config.CssClasses, headerCol.Element);

                        // Sorting.
                        if (config.SortState.HasValue)
                        {
                            TableNode sortAsc  = this.CreateAndAppend("a", headerCol, new [] { "SortIcon" });
                            TableNode sortDesc = this.CreateAndAppend("a", headerCol, new [] { "SortIcon" });
                            TableNode ascIcon  = this.CreateAndAppend("span", sortAsc);
                            TableNode descIcon = this.CreateAndAppend("span", sortDesc);

                            ascIcon.Element.AddCssClass(_config.Sorting.IconLib);
                            ascIcon.Element.AddCssClass(_config.Sorting.AscendingCssClass);
                            descIcon.Element.AddCssClass(_config.Sorting.IconLib);
                            descIcon.Element.AddCssClass(_config.Sorting.DescendingCssClass);
                            this.SetupAjaxAttrs(sortAsc.Element,
                                                $"&sort={propInfo.Name}&asc=True{this.PagingQueryAttrs()}");
                            this.SetupAjaxAttrs(sortDesc.Element,
                                                $"&sort={propInfo.Name}&asc=False{this.PagingQueryAttrs()}");
                            if (propInfo.Name == _tableState.SortProp ||
                                (config.SortState != SortState.None && _tableState.SortProp == null))
                            {
                                bool ascending = propInfo.Name == _tableState.SortProp
                                    ? _tableState.AscSort
                                    : config.SortState == SortState.Ascending;

                                sortAsc.Element.AddCssClass(ascending ? "ActiveSort" : null);
                                sortDesc.Element.AddCssClass(!ascending ? "ActiveSort" : null);
                            }
                        }

                        // Filtering.
                        if (config.Filtering.Threshold > 0)
                        {
                            this.ManualFiltering(config, propInfo, filter);
                        }
                        else if (config.Filtering.Prepopulated)
                        {
                            this.PrepopulatedFiltering(config, propInfo, filter, filterValues[propInfo.Name]);
                        }
                    }
                });
            }
        }
        private void Body(TableNode table)
        {
            TableNode         body = this.CreateAndAppend("tbody", table);
            IList <RowConfig> rows = _config.Rows;

            if (!rows.Any())
            {
                IQueryable <T> entities = _model.ProcessedEntities;

                if (!_model.Processed)
                {
                    KeyValuePair <string, ColumnConfig> initialFilterColumn = _config.Columns
                                                                              .FirstOrDefault(c => c.Value.Filtering.Initial != null);
                    KeyValuePair <string, ColumnConfig> initialSortColumn = _config.Columns
                                                                            .FirstOrDefault(c => c.Value.SortState.HasValue);

                    // Initial rendering of the table, apply initial filteringm sorting and paging.
                    if (initialFilterColumn.Key != null)
                    {
                        Expression <Func <T, bool> > whereExpr = ExpressionHelper.EqualsExpr <T>(initialFilterColumn.Key,
                                                                                                 initialFilterColumn.Value.Filtering.Initial);

                        entities = entities.Where(whereExpr);
                    }
                    entities = _config.Paging.PageSize > 0
                        ? entities.Take(_config.Paging.PageSize)
                        : entities;
                    if (initialSortColumn.Key != null)
                    {
                        var sortExpr = ExpressionHelper.PropertyExpr <T>(initialSortColumn.Key);

                        entities = initialSortColumn.Value.SortState == SortState.Ascending
                            ? entities.OrderBy(sortExpr)
                            : entities.OrderByDescending(sortExpr);
                    }
                }

                // No row configuration has been performed.
                // Create rows from the entities.
                foreach (var row in entities.Select(e => new RowConfig(e)))
                {
                    rows.Add(row);
                }
            }


            // Rows.
            foreach (RowConfig rowConfig in rows)
            {
                TableNode row = this.CreateAndAppend("tr", body);

                this.AddContextualState(row.Element, rowConfig.State, "table-");
                this.AddCssClasses(rowConfig.CssClasses, row.Element);
                if (rowConfig.NavigationUrl != null)
                {
                    row.Element.Attributes.Add("style", "cursor: pointer");
                    row.Element.Attributes.Add("onclick", $"window.location.href = '{rowConfig.NavigationUrl}'");
                }
                else if (!string.IsNullOrEmpty(_config.RowClick) || !string.IsNullOrEmpty(rowConfig.RowClick))
                {
                    string jsCall = !string.IsNullOrEmpty(rowConfig.RowClick)
                        ? rowConfig.RowClick
                        : $"{_config.RowClick}(this)";

                    row.Element.Attributes.Add("style", "cursor: pointer");
                    row.Element.Attributes.Add("onclick", jsCall);
                }

                // Cells.
                this.IterateProperties(rowConfig.Entity, (property, columnConfig) =>
                {
                    CellConfig cellConfig = rowConfig.CellConfigs.ContainsKey(property.Name)
                        ? rowConfig.CellConfigs[property.Name]
                        : null;
                    TableNode cell   = this.CreateAndAppend("td", row);
                    string cellValue = property.GetValue(rowConfig.Entity)?.ToString();

                    if (cellValue != null)
                    {
                        if (columnConfig != null && columnConfig.Filtering.Prepopulated &&
                            columnConfig.Filtering.Links)
                        {
                            TagBuilder filterLink = new TagBuilder("a");

                            filterLink.AddCssClass("FilterLink");
                            filterLink.InnerHtml.Append(cellValue?.ToString());
                            filterLink.Attributes.Add("href", "#");
                            this.SetupAjaxAttrs(filterLink, $"&filter[]={property.Name}&filter[]={cellValue}&filter[]={true}", property.Name);
                            cell.Element.InnerHtml.AppendHtml(filterLink);
                        }
                        else
                        {
                            cell.Element.InnerHtml.Append(cellValue);
                        }
                    }

                    if (cellConfig != null)
                    {
                        this.AddContextualState(cell.Element, cellConfig.State, "table-");
                        this.AddCssClasses(cellConfig.CssClasses, cell.Element);
                    }
                });
            }
        }