剃刀页面和Bootstrap - 延迟加载选项卡

选项卡式接口是管理大量信息呈现成单独的面板的好方法,其中每个面板的数据都是自己的意义,并且只有一个面板一次可查看。浏览器中的选项卡是一个很好的例子。从剃刀页面开发人员的角度来看,选项卡对于在业务应用程序中控制复杂数据的显示特别有用。

从一开始,开发人员被教导到最少的数据库数量 只呼叫仅获取视图数据所必需的那些。呀,在一个 选项卡接口,视图仅包含第一个选项卡的内容:

用户可能只需要在该示例中看到联系人。如果你获得了 所有其他标签的数据不必要地,这可能会损害您的应用程序 表现。需要资源来从数据库中提取数据, 为其他选项卡生成HTML,并在浏览器中呈现。如果是用户 认为你的申请迟缓,他们很快就会变得沮丧, 导致申请被拒绝的可能性更大。

该解决方案仅需按需加载其他选项卡的数据 - 模式 被称为懒惰的装载。

以下示例说明了剃刀页面内的模式 应用。首先,这里是一个PageModel,获得了一个产品 database:

public class TabsModel : PageModel
{
    private readonly IOrderService orderService;
 
    public TabsModel(IProductService productService)
    {
        this.productService = productService;
    }
 
    [BindProperty(SupportsGet = true)]
    public int ProductId { get; set; } = 1;
    public Product Product { get; set; }

    public async Task OnGetAsync()
    {
        Product = await productService.GetProductAsync(ProductId);
    }
}

以下是使用Bootstrap的选项卡接口:

<h1 class="display-4">Lazy Loading Tabs From Database</h1>
<h3>@Model.Product.ProductName</h3>
<input type="hidden" asp-for="Product.ProductId" />
<ul class="nav nav-tabs" id="myTab" role="tablist">
    <li class="nav-item">
        <a class="nav-link active" id="product-tab" data-toggle="tab" href="#product" aria-controls="product" aria-selected="true">Details</a>
    </li>
    <li class="nav-item">
        <a class="nav-link" id="supplier-tab" data-toggle="tab" href="#supplier" aria-controls="supplier" aria-selected="false">Supplier</a>
    </li>
    <li class="nav-item">
        <a class="nav-link" id="orders-tab" data-toggle="tab" href="#orders" aria-controls="orders" aria-selected="false">Orders</a>
    </li>
</ul>
<div class="tab-content p-3 border-right border-left">
    <div class="tab-pane fade show active" id="product" role="tabpanel" aria-labelledby="product-tab">
        <dl class="row">
            <dt class="col-sm-2">Quantity Per Unit</dt><dd class="col-sm-10">@Model.Product.QuantityPerUnit</dd>
            <dt class="col-sm-2">Unit Price:</dt><dd class="col-sm-10">@Model.Product.UnitPrice</dd>
            <dt class="col-sm-2">Units In Stock:</dt><dd class="col-sm-10">@Model.Product.UnitsInStock</dd>
            <dt class="col-sm-2">Units On Order:</dt><dd class="col-sm-10">@Model.Product.UnitsOnOrder</dd>
        </dl>
    </div>
    <div class="tab-pane fade" id="supplier" role="tabpanel" aria-labelledby="supplier-tab"></div>
    <div class="tab-pane fade" id="orders" role="tabpanel" aria-labelledby="orders-tab"></div>
</div>

选项卡式接口由无序列表生成(虽然您没有 have to use a ul element) with the classes nav and nav-tabs 应用。每个列表项都形成实际标签,以及 列表项中的锚元素用于生成选项卡标签,并 to control selection. There are three tabs here, one of which has the active class applied to its anchor element. All this does is to apply 标签的不同风格。

In this example, the data-toggle attribute is used to 标签之间的声明控制切换。你可以删除这个 如果愿意,属性和写入代码以显示和隐藏选项卡。内容是 placed in divs with a tab-pane class inside a div with a tab-content class. This combination of classes is used to control visibility of the active tab content. The first tab pane also has fade, show and active classes. You use active to set the default tab. The fade class is used to animate the display of the content. The show class is used with fade 使内容默认可见。第一个选项卡的内容是 在服务器上生成并形成初始视图的一部分。另一个标签 窗格是emtpy。它们将按需加载。

管理按需加载HTML内容的最简单方法是使用部分 结果服务器上并使用Ajax调用它们。所以下一步是改变 PageModel通过添加两种新方法来为两个选项卡生成HTML:

public class TabsModel : PageModel
{
    private readonly IOrderService orderService;
    private readonly IProductService productService;
    private readonly ISupplierService supplierService;
    public TabsModel(IOrderService orderService, IProductService productService, ISupplierService supplierService)
    {
        this.orderService = orderService;
        this.productService = productService;
        this.supplierService = supplierService;
    }
 
    [BindProperty(SupportsGet = true)]
    public int ProductId { getset; } = 1;
    public Product Product { getset; }
    public async Task OnGetAsync()
    {
        Product = await productService.GetProductAsync(ProductId);
    }
 
    public async Task<PartialViewResult> OnGetSupplierAsync()
    {
        var supplier = await supplierService.GetSupplierForProduct(ProductId);
        return Partial("_SupplierDetails", supplier);
    }
        
    public async Task<PartialViewResult> OnGetOrdersAsync()
    {
        var details = await orderService.GetOrdersForProduct(ProductId);
        return Partial("_OrdersByProduct", details);
    }
}

OnGetSupplierAsync 调用获得的服务方法 来自数据库的供应商的详细信息,然后将数据传递给部分 page, returning the generated HTML in the response. The second method, OnGetOrdersAsync goes through the same process to obtain orders for 产品。这里是 _ ordersbyProduct. partial:

@model List<OrderDetails>
 
<table class="table-sm table">
    <thead class="thead-light">
        <tr>
            <th>Customer</th>
            <th>Date</th>
            <th>Total Ordered</th>
            <th>Total Value</th>
        </tr>
    </thead>
    @foreach (var order in Model.OrderByDescending(o => o.Order.OrderDate))
    {
        <tr>
            <td>@order.Order.Customer.CompanyName</td>
            <td>@order.Order.OrderDate.ToShortDateString()</td>
            <td>@order.Quantity</td>
            <td>@(order.UnitPrice * order.Quantity)</td>
        </tr>
    }
</table>

Finally, here is a client side script that fires in response to shown.bs.tab, which is a custom Bootstrap jQuery事件显示选项卡后触发:

@section scripts{
    <script>
        var supplierLoaded = false;
        var ordersLoaded = false;
        var productid = $('#Product_ProductId').val();
        $(function () {
            $('a[data-toggle="tab"]').on('shown.bs.tab'function (e) {
                switch ($(e.target).attr('aria-controls')) {
                    case "supplier":
                        if (!supplierLoaded) {
                            $('#supplier').load(`/tabs/supplier?productid=${productid}`)
                            supplierLoaded = true;
                        }
                        break;
                    case "orders":
                        if (!ordersLoaded) {
                            $('#orders').load(`/tabs/orders?productid=${productid}`)
                            ordersLoaded = true;
                        }
                        break;
                }
            });
        });
    </script>
}

在脚本开头声明的布尔值用于检测 是否已经加载了特定选项卡。这 aria-controls 单击的选项卡的属性用于确定哪个标签是 点击了。如果该选项卡尚未加载,则jQuery load 函数用于调用标签的正确名为Handler方法,以及 将Ajax调用的响应放入Tab窗格中。

如果您愿意使用纯javascript(即no no jquery),这里是相同的 救息,使用fetch API进行AJAX调用:

@section scripts{
    <script>
        var supplierLoaded = false;
        var ordersLoaded = false;
        var productid = document.getElementById('Product_ProductId').value;
        load = function (url, el) {
            fetch(url)
                .then((response) => {
                    return response.text();
                })
                .then((result) => {
                    document.getElementById(el).innerHTML = result;
                });
        }
        document.querySelectorAll('a[data-toggle="tab"]').forEach(el => el.addEventListener('click', (e) => {
            switch (e.target.getAttribute('aria-controls')) {
                case "supplier":
                    if (!supplierLoaded) {
                        load(`/tabs/supplier?productid=${productid}`'supplier');
                    }
                    supplierLoaded = true;
                    break;
                case "orders":
                    if (!ordersLoaded) {
                        load(`/tabs/supplier?productid=${productid}`'orders')
                        ordersLoaded = true;
                    }
                    break;
            }
        }));
    </script>
}

Note that the event handler is now the click event, not shown.bs.tab, because you can't use addEventListener with 自定义jQuery事件。

概括

此演示表明您不必加载复杂的所有数据 ui一举。如果何时何时,您可以使用延迟加载仅加载数据 必需的。 Bootstrap提供了您可以挂钩的自定义事件 使用jQuery方法使用标签。但它同样易于使用 如果您更喜欢非jQuery解决方案。