Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Having Fun with HTML 5 History API – Part One

3.67/5 (3 votes)
18 Jul 2016CPOL2 min read 8.6K  
Having fun with HTML5 History API - Part One
<g:plusone count="false" href="http://blog.andrei.rinea.ro/2016/07/18/having-fun-with-html-5-history-api-part-one/" size="small">

Have you ever come across a web page with a few fields, let’s say cascading drop downs, you know like these:

Image 1

… and after carefully filling in the fields you submit, and you are taken to the next page. Only to find out that you made a mistake and you push the back button in your browser. Guess what, now (almost) all your fields are reset and you have to begin all over again. Frustrating.

Apart from that, I have noticed that “infinite scrolling” has really taken off in a lot of sites, some news site (for example this one – at least at the time of the writing) even change the URL as you scroll down into the next article.

And then I wondered, how could I use this gimmick into solving the first issue (i.e., losing fields information after pressing back) and I came up with two possible solutions to this. I will showcase a small PoC for each one in the next two parts.

First let’s consider the following minimal web project (ASP.NET MVC but since this is about JavaScript, the back end doesn’t really matter) with two drop-downs. The first dropdown will let you choose a make of automobiles and the second one will let you choose a model from that particular make. In the end, you press continue and you are directed to a new page:

01

02

03

Things start to go sideways if you press back in the last stage. You will be taken back to the selector page, the browser will do its best to restore the values but it will only succeed in setting back the make, the model restoration will not work:

04

=============

The client side (HTML markup, CSS and JavaScript lumped together in one file :P ) looks like so:

CSS
@model HistoryApiSample.Models.SelectorViewModel
@{
    ViewBag.Title = "Selector";
}

<style>
    .lbl {
        display: inline-block;
        width: 210px;
        text-align: right;
    }
</style>
<h2>Car select</h2>

@using (Html.BeginForm())
{
    <div id="divMake">
        @Html.LabelFor(m => m.SelectedMake, new { @class = "lbl" })
        @Html.DropDownListFor(m => m.SelectedMake, Model.Makes, " -- please select a make -- ")
    </div>

    <div id="divModel" style="display: none;">
        @Html.LabelFor(m => m.SelectedModel, new { @class = "lbl" })
        @Html.DropDownListFor(m => m.SelectedModel, Model.Models, " -- please select a model -- ")
    </div>

    <input type="submit" value="Continue" />
}

@section scripts
{
    <script type="text/javascript">
        $(function () {
            $("#SelectedMake").change(makeChanged);
            makeChanged();
        });

        function makeChanged() {
            var selectedMake = $("#SelectedMake").val();
            if (selectedMake === "") {
                $('#divModel').hide();

            } else {
                $.ajax({
                    cache: true,
                    type: 'GET',
                    url: '@Url.Action("GetModelsByMake")',
                    data: { 'makeId': selectedMake },
                    success: function (data) {
                        var models = $("#SelectedModel");
                        models.empty();
                        models.append($("<option></option>").attr
                        ("value", "").text(" -- please select a model -- "));
                        $.each(data, function (key, val) {
                            models.append($("<option></option>").attr
                            ("value", val.Id).text(val.Text));
                        });
                        $('#divModel').show();
                    },
                    error: function (xhr, ajaxOptions, error) {
                        alert(error);
                        $('#divModel').hide();
                    }
                });
            }
        }
    </script>
}

The controller code:

C#
// ...
[HttpGet]
public ActionResult Selector()
{
    var model = new SelectorViewModel
    {
        Makes = new[]
        {
            new SelectListItem { Value = "VW",  Text = "Volkswagen" },
            new SelectListItem { Value = "AUD", Text = "Audi"       },
            new SelectListItem { Value = "SEA", Text = "Seat"       },
            new SelectListItem { Value = "SKD", Text = "Skoda"      }
        },
        Models = Enumerable.Empty<SelectListItem>()
    };
    return View(model);
}

[HttpPost]
public ActionResult Selector(SelectorViewModel viewModel)
{
    return RedirectToAction("ShowSelection", new
    {
        makeId = viewModel.SelectedMake,
        modelId = viewModel.SelectedModel
    });
}

[HttpGet]
public ActionResult ShowSelection(string makeId, string modelId)
{
    string makeName = null;
    string modelName = null;
    switch (makeId)
    {
        case "VW": makeName = "Volkswagen"; break;
        case "AUD": makeName = "Audi"; break;
        case "SEA": makeName = "Seat"; break;
        case "SKD": makeName = "Skoda"; break;
    }
    switch (modelId)
    {
        case "TIG": modelName = "Tiguan"; break;
        case "TRG": modelName = "Touareg"; break;
        case "A4": modelName = "A4"; break;
        case "A6": modelName = "A6"; break;
        case "LEO": modelName = "Leon"; break;
        case "IBZ": modelName = "Ibiza"; break;
        case "FAB": modelName = "Fabia"; break;
        case "OCT": modelName = "Octavia"; break;
    }
    var viewModel = new SelectionViewModel
    {
        Make = makeName,
        Model = modelName
    };
    return View(viewModel);
}

[HttpGet]
public ActionResult GetModelsByMake(string makeId)
{
    object data;
    switch (makeId)
    {
        case "VW":
            data = new[]
            {
                new DataItem { Id = "TIG", Text = "Tiguan"  },
                new DataItem { Id = "TRG", Text = "Touareg" }
            };
            break;
        case "AUD":
            data = new[]
            {
                new DataItem { Id = "A4", Text = "A4"  },
                new DataItem { Id = "A6", Text = "A6" }
            };
            break;
        case "SEA":
            data = new[]
            {
                new DataItem { Id = "LEO", Text = "Leon"  },
                new DataItem { Id = "IBZ", Text = "Ibiza" }
            };
            break;
        case "SKD":
            data = new[]
            {
                new DataItem { Id = "FAB", Text = "Fabia"  },
                new DataItem { Id = "OCT", Text = "Octavia" }
            };
            break;
        default:
            return HttpNotFound();
    }
    return Json(data, JsonRequestBehavior.AllowGet);
}

private class DataItem
{
    public string Id { get; set; }
    public string Text { get; set; }
}

Finally, the viewmodels:

C#
public class SelectorViewModel
{
    [Display(Name = "select a make")]
    public string SelectedMake { get; set; }
    public IEnumerable<SelectListItem> Makes { get; set; }

    [Display(Name = "select a model")]
    public string SelectedModel { get; set; }
    public IEnumerable<SelectListItem> Models { get; set; }
}

public class SelectionViewModel
{
    public string Make { get; set; }
    public string Model { get; set; }
}

In the next part, we’ll explore the simplest way to fix this issue, rewriting history (sounds dramatic, but it isn’t :P ).

Stay tuned! ;)

Image 9

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)