1

How do you maintain the previous scroll position after a post using asp.net core 3.1 MVC (web application) and razor views?

How do you do this across the site?

I saw this: ASP.NET MVC3 Razor - Maintain scroll position on postback - but those did not work for me.

D.Man
  • 179
  • 1
  • 15
  • The problem: Say there's a shopping site with a view of 50 items. The use scrolls down browsing and then clicks to add one to their shopping basket/cart. But after that click, the page goes back to the top again, not to the last position the user was at. Across the site meaning, how do I achieve this for a page, and then how to implement this across the pages/view with as little code duplication. – D.Man Apr 30 '21 at 16:35
  • You're going to need to use some `javascript` for this, you can add an event listener to `window.addEventListener("DOMContentLoaded, function() { //get value of hidden input, //scroll to previous position })`. The `DOMContentLoaded` will fire the callback once the page is loaded after the post request. I'll provide enough psuedo code as answer that it should give you what you need. – Ryan Wilson Apr 30 '21 at 16:43
  • Thanks. I can't add a property to the model as it's using efcore and some entity models are in use directly in razor view and view components. Or is there a way to do this that's not too cumbersome? – D.Man Apr 30 '21 at 16:50
  • You could make it a property of your efcore models and just ignore it so it isn't tied to your DB. Please see - [does-ignoring-a-base-type-in-entity-framework-code-first-also-ignore-subclasses](https://stackoverflow.com/questions/24814875/does-ignoring-a-base-type-in-entity-framework-code-first-also-ignore-subclasses) – Ryan Wilson Apr 30 '21 at 16:58
  • Is that just using [NotMapped] ? – D.Man Apr 30 '21 at 17:00
  • Yes. `[NotMapped]` would remove any reference to the DB. [notmapped-attribute](https://www.learnentityframeworkcore.com/configuration/data-annotation-attributes/notmapped-attribute) – Ryan Wilson Apr 30 '21 at 17:04
  • Why dont you post user action with ajax and you can avoid full page reloads and maintain the scroll position... – TheMixy May 01 '21 at 07:41

3 Answers3

2

First, Create a base view model that all other models will inherit

public class BaseViewModel
{
   public int ScrollPosition { get; set; }
}

All view models that are bound that will need to remember the previous scroll position will inherit from this BaseViewModel

Second, Add a hidden input to your form used for posting:

   @using(Html.BeginForm("SomeAction", "SomeController", FormMethod.Post)
   {
      @Html.HiddenFor(x => Model.ScrollPosition)
      ....
   }

Third, inside your post method on the backend set a value to TempData:

   TempData["ScrollPosition"] = Model.ScrollPosition;

Fourth, when redirect after post, set this value to your Model for binding in your view:

   MyModel.ScrollPosition = (int)TempData["ScrollPosition"];

Fifth, use javascript to scroll to previous position after page load:

   <script type="text/javascript">
       window.addEventListener("DOMContentLoaded", function(){
           //add event listener for the scroll event and set hidden input value for posts
           document.addEventListener('scroll', function(){
              const scrollPosY = window.scrollY;
              document.querySelector('input[name="ScrollPosition"]').value = scrollPosY;
           });
           const scrollpos = document.querySelector('input[name="ScrollPosition"]').value;
           window.scrollTo(0, scrollpos); //y-axis scroll
       });
   </script>

If you would need to track x axis scroll as well as y axis, you'd need two hidden inputs for each x and y axis scroll position and change model to hold both x and y

Ryan Wilson
  • 10,223
  • 2
  • 21
  • 40
  • Where does the scroll value get set? – D.Man Apr 30 '21 at 17:08
  • @D.Man Sorry, I forgot to add that part. see updated answer and final section dealing with `javascript`, attached an event listener to the document scroll event, so that you set the current y-axis scroll value to the hidden input. – Ryan Wilson Apr 30 '21 at 17:12
  • Thanks. As the base class would be applied to models and view models, should the [NotMapped] attribute be put on the base class property or on each derived class property, i.e. each of the models/viewmodels? – D.Man Apr 30 '21 at 17:15
  • @D.Man you may have to configure this in a different way, to use the base class in your entity models, please see accepted answer: [entity-framework-core-2-0-how-to-configure-abstract-base-class-once](https://stackoverflow.com/questions/49990365/entity-framework-core-2-0-how-to-configure-abstract-base-class-once) – Ryan Wilson Apr 30 '21 at 18:00
  • Been trying this today but the scroll position value is only coming through to the back end as 0. – D.Man May 04 '21 at 10:48
  • @D.Man Have you checked that the event is firing when you scroll in the script? Have you checked to make sure the hidden input is contained in your form? Does your bound model have a property that matches the name of your hidden input? – Ryan Wilson May 04 '21 at 12:21
  • 1
    The event is firing. The problem was that the buttons don't post back the model but a set of data to another controller and uses asp-route-*. That's why it was always 0, as that data was generated server-side before any scroll events. The other controller returns a RedirectToActionResult that differs based upon the original referrer. There were a couple of other issues I had to account for relating to the partial view and multiple button instances, which needed more javascript code to get it working client-side. – D.Man May 05 '21 at 11:28
  • @D.Man I take it things are working for you now. Good job. – Ryan Wilson May 05 '21 at 12:56
  • Yes, and thank you very much for your help. Greatly appreciated. – D.Man May 05 '21 at 14:09
  • The only thing with the solution now is that the scroll position comes back via the query string. Is there another (better) way to do it when using RedirectToActionResult? – D.Man May 06 '21 at 13:53
0

Another way to do this is to submit the form with AJAX and, after a successful POST, call window.location.reload(); which will restore the scroll position.

function CreateItem() {
    var serializedForm = $('#formId').serialize();

    $.ajax({
        url: "item/create",
        type: "POST",
        data: serializedForm
    })
    .done(function () {
        window.location.reload();
    });
}
Lukas
  • 1,699
  • 1
  • 16
  • 49
0

First add the following jquery function to your page in the script tag:

$(document).scroll(function () {
    // Note scroll position.
    localStorage['pageIdentifier'] = $(document).scrollTop();
});

And then add:

$(document).ready(function () {
    // Maintain scroll position.
    $(document).scrollTop(localStorage['healthPage']);
});