Search code examples
asp.net-core.net-corerazorasp.net-core-mvcrendering

Just render some parts of a view and not the layout


My problem: I want to modify just some parts of the view (I don't want to reload the layout) when I click on pagination or when I change the number of items per page.

My controller:

public async Task<IActionResult> Index(int productPage, int pagesize )
{
        //await Task.Delay(5000);
        //var oldpagesize = ViewBag.pagesize ?? 15;
        //var oldpagesize = HttpContext.Session.GetInt32("pagesize");
        var firsttime = pagesize == 0;
        if(pagesize == 0)
            pagesize = 15;

        //ViewBag.pagesize = pagesize;
        //HttpContext.Session.SetInt32("pagesize", pagesize);
        
        if (productPage == 0) productPage++;
        
        var productsListViewModel = new ProductsListViewModel();

        var products = repository.Products
              .OrderBy(p => p.ProductID)
              .Skip((productPage - 1) * pagesize)
              .Take(pagesize);

        var pagingInfo = new PagingInfo
        {
            CurrentPage = productPage,
            ItemsPerPage = pagesize,
            TotalItems = repository.Products.Count()
        };

        productsListViewModel.Products = products;
        productsListViewModel.PagingInfo = pagingInfo;
        
        if (firsttime)
            return View(productsListViewModel);
        else
            return View("Index1", productsListViewModel);
}

My view index (Index1 is the same without layout - maybe I can use PartialView):

@model ProductsListViewModel
@{
    Layout = "_Layout";
}
<div class="container">
    <div class="d-flex justify-content-between">
        <partial name="Pagination" model="@Model.PagingInfo" />
        <partial name="Pagesize" model="@Model.PagingInfo" />
    </div>
</div>

<div id="listofproducts">
    @foreach (var p in Model?.Products ?? Enumerable.Empty<Product>())
    {
        <partial name="ProductSummary" model="p" />
    }
</div>

<div class="container">
    <div class="d-flex justify-content-between">
        <partial name="Pagination" model="@Model.PagingInfo" />
        <partial name="Pagesize" model="@Model.PagingInfo" />
    </div>
</div>

My layout:

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>SportsStore</title>
    <link href="/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
    <link href="/css/pagination_bs5.css" rel="stylesheet" />
    <link href="/css/waiting.css" rel="stylesheet" />
    <script language="javascript" type="text/javascript">

                    function updatePartialView(href) {
                        console.log(href);
                        //const rep = await fetch(href);
                        //const html = await rep.text;
                        //fetch("/products/page2/30").then(response => response.text()).then(data => dat1 = data);
                        var html ="";
                        fetch(href)
                            .then(response => response.text())
                            .then(data => {
                                console.log(data);
                                html = data;
                                document.getElementById('partialview').innerHTML = data;
                             })
                             .catch(error => console.error('Error loading partial view:', error));
                             
                             //waitForElementToDisplay("#partialview",function(){alert("Hi");},1000,9000);

                        //document.getElementById('partialview').innerHTML = html;
                    }

                    function setOnClick() {
                        const alls = document.querySelectorAll('.pagination-item:not(.active)');
                        alls.forEach(a => {
                          a.addEventListener('click', function(e) {
                             displayBusyIndicator();
                             updatePartialView(e.target.getAttribute("href"));
                          });
                        });
                    }
    </script>
</head>
<body onload="setOnClick()">

        <div class="bg-dark text-white p-2 d-flex justify-content-start">
            <div class="navbar-brand ml-2 w-25">SPORTS STORE</div>
            <div><partial name="_loading" /></div>
        </div>

    <div class="row m-1 p-1">
        <div id="categories" class="col-3">
            Put something useful here later

        </div>
        // here i want to just modify this part!!
        <div id="partialview" class="col-9">
            @RenderBody()
        </div>
    </div>
    @RenderSection("Scripts", required: false)
</body>
</html>

So the fetch request returns the correct data but the rendering is bad (after i modify the DOM with the command document.getElementById('partialview').innerHTML = data, it seems all parts of the layout are not present and so the libraries css and js are missing? I don't understand why....

If I do the fetch in console and then the replacement of partial view all is ok. But not in the process? I have nothing defined in viewstart

I don't see how to resolve this problem...

The first time all is ok the view is completely loaded:

firstload

and after the click on pagination

afterclick

you could see the DOM is modified .....The section head is empty ..no libraries ??

maybe the fact to capture the return View/PartialView in the fetch request is not good?

i need to do that because i am using custom and normal taghelpers.. and i think we cant render the result of view rendering in a string (i think about that, because it seems the return view has impact on the result DOM and the view is not just captured by the fetch request)


Solution

  • the problem was not an asp.problem, but more a js problem:

    1. i have forgotten the fact to click on link, even if i trap the click with an eventlistener, i have to add preventDefault() to avoid to play the href. (thanks to @pcalkins)

    2. the second problem was the fetch request which is asynchronous, so i have to finish all actions inside the promise..

    this is the fix in script js:

    <script language="javascript" type="text/javascript">
    
        function setOnClick() {
            const alls = document.querySelectorAll('.pagination-item:not(.active)');
            console.log("in setonclick");
            alls.forEach(a => a.addEventListener('click', clickHandler));
        }
    
        function clickHandler(e)
        {
            e.preventDefault();
            const href = e.target.getAttribute("href") == null ? e.target.parentElement.getAttribute("href") : e.target.getAttribute("href");
    
            const alls = document.querySelectorAll('.pagination-item');
            alls.forEach(a => a.removeEventListener('click', clickHandler));
    
            document.getElementById("loading").style.display = "block";
    
            fetch(href)
                .then(response => response.text())
                .then(data => {
                    document.getElementById("loading").style.display = "none";
                    document.getElementById('partialview').innerHTML = data;
                    setOnClick();
                    })
                    .catch(error => console.error('Error loading partial view:', error));
        }
    
    </script>
    

    the first time the setOnClick() is played by the Onload event in the body, but after, i replay the setOnClick() inside the promise..