1

I have the following view that displays gaming related data from a controller.

When the page initially loads, it hits an Index Controller that just lists all the gaming sessions ever created (100 total).

However, there is an input field, where the user can input a date, and then click a button.

When clicked, this button sends the date & time to another method called GamingSessionsByDate.

The GamingSessionsByDate method then returns new data which only contains Gaming Sessions with a start date of whatever the user entered.

Here is the view:

@model IEnumerable<GamingSessions.Session>

@{
    ViewData["Title"] = "GamingSessionsByDate";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Gaming Sessions By Date</h2>

<input type="date" name="gameSession" id="gameSession">
<input type="Submit" id="postToController" name="postToController" Value="Find" />
@section Scripts
    {
    <script type="text/javascript">
        $("#postToController").click(function () {
            var url = '@Url.Action("GamingSessionsByDate", "GameSession")'; 
            var inputDate = new Date('2019-01-23T15:30').toISOString();
            $.ajax({
                type: "POST",
                url: url,
                data: "startdate=" + inputDate,
                success: function (data) {
                    console.log("data: ", data);
                }
            });
        });

    </script>
}

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.GameName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.PlayDuration)
            </th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.GameName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.PlayDuration)
            </td>
        </tr>
        }
    </tbody>
</table>

Here is the controller that returns the gaming sessions by date:

public IActionResult GamingSessionsByDate(DateTime startdate)
{

    var response = GetGameSessionsList(startdate);

    var r = response.Results;

    return View(r);
}

By the way, I have hard-coded a date time value into the AJAX call above that I know contains 5 gaming sessions.

Please note that I am writing out the data returned from the controller in the AJAX success method.

So when I click the button, nothing happens on the screen, I just see the initially-loaded 100 gaming sessions from the call to the Index controller.

However, behind the scenes, I can see the 5 gaming sessions I need being written to the console via the console.log command in the Ajax call.

I also see the correct data when I step-through the project in Visual Studio.

So it looks like everything is working, but it appears as if the view/page is not getting refreshed.

So, how do I get that data to display on the page?

Thanks!

SkyeBoniwell
  • 6,345
  • 12
  • 81
  • 185
  • 1
    Since you don't have a form on your page... and you're making an AJAX post, you need to handle the result yourself. You'll need to replace the content of an element (`
    `) with the returned response. You can see a similar example here https://stackoverflow.com/a/19410973/2030565.
    – Jasen Jan 23 '19 at 18:30

1 Answers1

2

The XMLHttpRequest object in JavaScript (what actually makes "AJAX" requests) is what's known as a "thin client". Your web browser is a "thick client", it does more than just make requests and receives responses: it actually does stuff automatically such as take HTML, CSS, and JavaScript that's returned and "runs" them, building a DOM and rendering pretty pictures and text to your screen. A thin client, conversely, literally just makes requests and receives responses. That's it. It doesn't do anything on its own. You are responsible, as the developer, for using the responses to actually do something.

In the case here, that means taking the response you receive and manipulating the DOM to replace the list of game sessions with the different game sessions retrieved. How you do that depends on what exactly you're returning as a response from your AJAX call. It could be HTML ready to be inserted or some sort of object like JSON. In the former case, you'd literally just select some parent element in the DOM and then replace its innerHTML with the response you received. In latter case, you'd need to use the JSON data to actually build and insert elements into the DOM.

Returning straight HTML is easier, but it's also less flexible. Returning JSON gives you ultimate freedom, but it's more difficult out of the box to manipulate the DOM to display that data. That's generally the point where you want to employ a client-side framework like Vue, Angular, React, etc. All of these can create templated components. With that, you need only change the underlying data source (i.e. set the data to the JSON that was returned), and the component will react accordingly, manipulating the DOM as necessary to create the view.

I personally like to use Vue, since it has the least friction to get started with an it's almost stupidly simple to use. For example:

<div id="App">

    <input type="date" v-model="startDate" />
    <button type="button" v-on:click="filterGameSessionsByDate">Find</button>

    <table class="table">
        <thead>
            <tr>
                <th>
                    @Html.DisplayNameFor(model => model.GameName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.PlayDuration)
                </th>
            </tr>
        </thead>
        <tbody>
            <tr v-for="item in items">
                <td>{{ item.GameName }}</td>
                <td>{{ item.PlayDuration }}</td>
            </tr>
        </tbody>
    </table>

</div>

Then a bit of JS to wire it up:

(function (options) {
    let vm = new Vue({
        el: '#App",
        data: {
            items: options.items,
            startDate: null
        },
        methods: {
            filterGameSessionsByDate: function () {
                let self = this;
                $.ajax({
                    type: "POST",
                    url: options.filterByDateUrl,
                    data: "startdate=" + self.startDate,
                    success: function (data) {
                        self.items = data;
                    }
                });
            }
        }
    });
})(
    @Html.Raw(Json.Encode(new {
        items = Model,
        filterByDateUrl = Url.Action("GamingSessionsByDate", "GameSession")
    }))
 )

That may look a little funky if you're not that used to JS. I'm just using what's called a closure here: defining and calling a function in place. It takes an options param, which is being filled by the parenthesis at the bottom. Inside those, I'm creating an anonymous object that holds info I need, such as the initial items to display and the URL to get filtered results from. Then, that object is encoded into JSON and dumped to the page.

Chris Pratt
  • 232,153
  • 36
  • 385
  • 444
  • The controller is returning an entire HTML document....so I'm not sure how to deal with that because I imagine it would cause a new webpage to appear inside the already existing webpage... – SkyeBoniwell Jan 24 '19 at 15:20
  • 1
    If you're returning the whole page, then drop the AJAX and just do a standard form post. – Chris Pratt Jan 24 '19 at 15:38
  • But isn't using ajax/javascript the modern way to deal with this? I thought forms were old-fashioned – SkyeBoniwell Jan 24 '19 at 15:41
  • 1
    Not at all. The "modern" way, if there is such a thing is using a backend as merely a JSON-returning repository, and building completely client-side, using a framework such as Angular to handle your routing, views, etc. However, that has more to do with what's in vogue than being the right or even only way. Server-side apps are still completely valid and relevant. Here specifically, you have a server-side app, you're trying to treat as client-side. – Chris Pratt Jan 24 '19 at 15:47
  • Let me put it another way, I'm using .net core 2.1 MVC and razor pages. Would using a form still make the most sense in this case? Thanks! – SkyeBoniwell Jan 24 '19 at 15:49
  • 1
    Yes, absolutely. However, you can also do a hybrid app, where not every interaction involves a round-trip to the server, just the main ones, like routing to a new page. In such a scenario, you can handle bringing in the filtered game sessions via AJAX using code similar to what I posted in my answer, but for that, your endpoint needs to return JSON. If you're returning a full HTML page, there's nothing to do with that, but change the entire page in the browser, which is done via a form post, not JS. – Chris Pratt Jan 24 '19 at 15:53
  • 1
    If you want to build a client-side application, then you'll need to utilize a framework like Angular. ASP.NET Core can still be utilized on the backend, but you'll simply build an API (i.e. no Razor Pages). If you want to use Razor Pages, then you're building a server-side app, or maybe a hybrid app. With the former, you'll need to make decisions about what actions constitute changing the browser page entirely, versus just pulling a bit of new data to the already rendered page. – Chris Pratt Jan 24 '19 at 15:58
  • 1
    Take a cart as an example. You may use AJAX to do things like update item quantities, remove items, etc. But, when you're ready to checkout, you change to the checkout page entirely. – Chris Pratt Jan 24 '19 at 15:59
  • So, to make this simple, I could simply have my controller return JSON (JsonResult) instead of an IActionResult.... – SkyeBoniwell Jan 24 '19 at 16:01
  • 1
    `JsonResult` is an `IActionResult`, but yes, if you want to do something with AJAX, then you need to return JSON or an HTML fragment (partial), not a full view with layout. – Chris Pratt Jan 24 '19 at 16:04