Click here to Skip to main content
15,887,596 members
Articles / Programming Languages / Javascript

How to Achieve Progressive Disclosure UI for Non-JS Users

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
6 Feb 2023CPOL3 min read 4.1K   4  
Progressive disclosure UI on JS disabled browsers
In this post, we try to achieve progressive disclosure UI on JavaScript disabled browsers.

What Is Progressive Disclosure UI?

Progressive Disclosure UI is a user interface where the content and actions change based on user responses. For example, see the below scenario.

Based on the user response to the question — ‘ Do you have the doctor appointment?’, the UI changes dynamically. If the user response is Yes, then the next question will be ‘Is this your first visit?’. Otherwise, it will be ‘Do you want to schedule an appointment?’. Please see the flow chart below.

Based on the response to the second question, the next question/content and actions change.

Image 1

How Do You Achieve This?

In JavaScript enabled browsers, it is easy. You can use radio buttons for Yes/No and make them auto-submit true or a better approach is to use Ajax calls and load just the partial page with appropriate content and actions.

It is not the same case with JavaScript disabled browsers (non JS users).

Problems with Non JS / JavaScript Disabled Browsers

In non JS, auto submit and Ajax calls will not work. So whatever solution is discussed above will fail for non JS users. If you use radio buttons, when the user clicks on yes/no, the page updates will not happen and this will cause faulty UI. The application will not be responsive and accurate as it is expected to be in progressive disclosure. For example, see the following screen shots.

Image 2

When you choose Yes and click on Submit, the following page is displayed.

Image 3

Now choose No for the first question. Don’t click on Submit yet and notice the UI. This results in a faulty UI. Please see the flowchart above which says if the doctor appointment is not there, you need to ask the user to schedule an appointment.

Image 4

The second question should be ‘Do you want to schedule an appointment’. Compare the faulty and Correct UI below.

Image 5

In this faulty UI, the content, questions and actions are incorrect which might cause wrong entries in DB when Submit is clicked. See the example below:Image 6

Please note that all these issues are in non JS browsers (browsers where JavaScript is disabled). To test this, you can either disable JavaScript in your browser or use ‘Toggle JavaScript’ extension in Chrome.

So how do you fix this issue?

Here is the solution.

In Visual Studio 2022, create a new ASP.NET Core Web App project — NonJSProgressiveDisclosureUI:

Image 7

Image 8Image 9

Here is the final Solution Explorer:

Image 10

Add Enums folder and create new Enum YesNoType.

C#
namespace NonJSProgressiveDisclosureUI.Enums
{
    public enum YesNoType
    {
        Unknown,
        Yes,
        No
    }
}

Add New Model IndexViewModel in Models folder:

C#
using NonJSProgressiveDisclosureUI.Enums;

namespace NonJSProgressiveDisclosureUI.Models
{
    public class IndexViewModel
    {
        public YesNoType AnswerToHaveTheDoctorAppointmentQuestionIs { get; set; }

        public YesNoType AnswerToIsThisYourFirstVisitQuestionIs { get; set; }

        public YesNoType AnswerToScheduleAnAppointmentQuestionIs { get; set; }

        public bool ShowRadioButtons { get; set; }
    }
}

Update Program.cs — add caching services:

C#
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddMemoryCache();

Update Home Controller:

C#
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using NonJSProgressiveDisclosureUI.Enums;
using NonJSProgressiveDisclosureUI.Models;
using System.Diagnostics;

namespace NonJSProgressiveDisclosureUI.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly IMemoryCache _memoryCache;

        public HomeController(ILogger<HomeController> logger, IMemoryCache memoryCache)
        {
            _logger = logger;
            _memoryCache = memoryCache;            
        }

        [HttpGet]
        public IActionResult Index()
        {
            var viewModel = new IndexViewModel();
            viewModel.ShowRadioButtons = false;
            return View(viewModel);
        }

        [HttpPost]
        public IActionResult Index(IndexViewModel viewModel, string submit)
        {
            var updatedViewModel = viewModel;
            if (!viewModel.ShowRadioButtons)
            {
                updatedViewModel = SetIndexViewModel(viewModel, submit);
            }
            return View(updatedViewModel);
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, 
         Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel 
            { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }

        private IndexViewModel SetIndexViewModel
                (IndexViewModel viewModel, string submit)
        {
            var cacheKey = "doctorAppointmentKey";

            IndexViewModel cachedViewModel = 
                 (IndexViewModel)_memoryCache.Get(cacheKey) ?? viewModel;            

            switch (submit)                
            {
                case "AnswerToHaveTheDoctorAppointmentQuestionIsYes":
                    {
                        cachedViewModel.AnswerToHaveTheDoctorAppointmentQuestionIs = 
                                        YesNoType.Yes;
                        break;
                    }
                case "AnswerToHaveTheDoctorAppointmentQuestionIsNo":
                    {
                        cachedViewModel.AnswerToHaveTheDoctorAppointmentQuestionIs = 
                                        YesNoType.No;
                        break;
                    }
                case "AnswerToIsThisYourFirstVisitQuestionIsYes":
                    {
                        cachedViewModel.AnswerToIsThisYourFirstVisitQuestionIs = 
                                        YesNoType.Yes;
                        break;
                    }
                case "AnswerToIsThisYourFirstVisitQuestionIsNo":
                    {
                        cachedViewModel.AnswerToIsThisYourFirstVisitQuestionIs = 
                                        YesNoType.No;
                        break;
                    }
                case "AnswerToScheduleAnAppointmentQuestionIsYes":
                    {
                        cachedViewModel.AnswerToScheduleAnAppointmentQuestionIs = 
                                        YesNoType.Yes;
                        break;
                    }
                case "AnswerToScheduleAnAppointmentQuestionIsNo":
                    {
                        cachedViewModel.AnswerToScheduleAnAppointmentQuestionIs = 
                                        YesNoType.No;
                        break;
                    }                
                case "Submit":
                    break;
            }

            //Cache
            var cacheExpiryOptions = new MemoryCacheEntryOptions
            {
                AbsoluteExpiration = DateTime.Now.AddMinutes(20)
            };            
            _memoryCache.Set(cacheKey, cachedViewModel, cacheExpiryOptions);

            return cachedViewModel;
        }            
    }
}

Update Index.cshtml:

cshtml
@using NonJSProgressiveDisclosureUI.Enums
@model IndexViewModel

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome to Our Hospitals</h1>
        @using (Html.BeginForm("Index", "Home", FormMethod.Post))
        {
            @Html.AntiForgeryToken()

        <partial name="../_DoctorAppointment" model="Model" />

            if (Model.ShowRadioButtons)
            {
              <div style="display:flex; margin-top:35px">
                   <button type="submit" id="submit" name="submit" value="submit">
                    Submit
                   </button>                               
              </div>  
            }        
        }
</div>

Add the following views in Views > Shared folder.

__DoctorAppointment.cshtml

cshtml
@using NonJSProgressiveDisclosureUI.Enums
@model IndexViewModel

<partial name="../_DoctorAppointmentQuestion" model="Model"/>

@if(Model.AnswerToHaveTheDoctorAppointmentQuestionIs == YesNoType.Yes)
{
    <partial name="../_IsThisYourFirstVisitQuestion" model="Model" />

    @if (Model.AnswerToIsThisYourFirstVisitQuestionIs == YesNoType.Yes)
    {
        <partial name="../_SubmitIdProof" model="Model" />
    }
    else if (Model.AnswerToIsThisYourFirstVisitQuestionIs == YesNoType.No)
    {
        <partial name="../_ProvideRegistrationNumber" model="Model" />
    }
}
else if (Model.AnswerToHaveTheDoctorAppointmentQuestionIs == YesNoType.No)
{
    <partial name="../_ScheduleAnAppointmentQuestion" model="Model" />
}

__DoctorAppointmentQuestion.cshtml

cshtml
@using NonJSProgressiveDisclosureUI.Enums
@model IndexViewModel

<input asp-for="ShowRadioButtons" type="hidden" />
@{
    var buttonStyleSelected = "text-align:center; width:30%; 
        font-weight:600; color:darkslateblue; background-color:yellow";
    var buttonStyleDefault = "text-align:center; width:30%; 
        font-weight:600; color:darkslateblue";
    var buttonStyleYes = buttonStyleDefault;
    var buttonStyleNo = buttonStyleDefault;
}

<div style="text-align:left;margin-top:25px">
    Do you have the doctor appointment
</div>

@if (Model.AnswerToHaveTheDoctorAppointmentQuestionIs == YesNoType.Yes)
{
    buttonStyleYes = buttonStyleSelected;
}
else if (Model.AnswerToHaveTheDoctorAppointmentQuestionIs == YesNoType.No)
{
    buttonStyleNo = buttonStyleSelected;
}

@if (!Model.ShowRadioButtons)
{
   <div style="display:flex">
    <button style="@buttonStyleYes" type="submit" 
     id="AnswerToHaveTheDoctorAppointmentQuestionIsYes" 
     name="submit" value="AnswerToHaveTheDoctorAppointmentQuestionIsYes">Yes</button>
    <button style="@buttonStyleNo" type="submit" 
     id="AnswerToHaveTheDoctorAppointmentQuestionIsNo" 
     name="submit" value="AnswerToHaveTheDoctorAppointmentQuestionIsNo">No</button>
    </div>
}
else
{
    <div style="text-align:left;margin-top:15px">
        @Html.RadioButtonFor(m => m.AnswerToHaveTheDoctorAppointmentQuestionIs, 
        YesNoType.Yes, new { id="AnswerToHaveTheDoctorAppointmentQuestionIsYes" } )
        <label for="yes" style="width:8%">Yes</label>
        @Html.RadioButtonFor(m => m.AnswerToHaveTheDoctorAppointmentQuestionIs, 
        YesNoType.No, new { id="AnswerToHaveTheDoctorAppointmentQuestionIsNo" } )
        <label for="yes" style="width:8%">No</label>
    </div>    
}

_IsThisYourFirstVisitQuestion.cshtml

cshtml
@using NonJSProgressiveDisclosureUI.Enums
@model IndexViewModel

@{
    var buttonStyleSelected = "text-align:center; width:30%; 
        font-weight:600; color:darkslateblue; background-color:yellow";
    var buttonStyleDefault = "text-align:center; width:30%; 
        font-weight:600; color:darkslateblue";
    var buttonStyleYes = buttonStyleDefault;
    var buttonStyleNo = buttonStyleDefault;
}

<div style="text-align:left; margin-top:25px">
    Is this your first visit
</div>

@if (Model.AnswerToIsThisYourFirstVisitQuestionIs == YesNoType.Yes)
{
    buttonStyleYes = buttonStyleSelected;
}
else if (Model.AnswerToIsThisYourFirstVisitQuestionIs == YesNoType.No)
{
    buttonStyleNo = buttonStyleSelected;
}

@if (!Model.ShowRadioButtons)
{
    <div style="display:flex">
        <button style="@buttonStyleYes" type="submit" 
         id="AnswerToIsThisYourFirstVisitQuestionIsYes" name="submit" 
         value="AnswerToIsThisYourFirstVisitQuestionIsYes">Yes</button>
        <button style="@buttonStyleNo" type="submit" 
         id="AnswerToIsThisYourFirstVisitQuestionIsIsNo" name="submit" 
         value="AnswerToIsThisYourFirstVisitQuestionIsNo">No</button>
    </div>
}
else
{
    <div style="text-align:left;margin-top:15px">
        @Html.RadioButtonFor(m => m.AnswerToIsThisYourFirstVisitQuestionIs, 
         YesNoType.Yes, new { id="AnswerToIsThisYourFirstVisitQuestionIsYes" } )
        <label for="yes" style="width:8%">Yes</label>
        @Html.RadioButtonFor(m => m.AnswerToIsThisYourFirstVisitQuestionIs, 
         YesNoType.No, new { id="AnswerToIsThisYourFirstVisitQuestionIsNo" } )
        <label for="yes" style="width:8%">No</label>
    </div>
}

_ProvideRegistrationNumber.cshtml

cshtml
<div style="display:flex;margin-top:30px;">
    <label for="regNo" style="width:30%;text-align:left">
     Please provide registration number: </label>
    <input type="text" style="width:30%;" id="regNo" name="regNo">
</div>

_ScheduleAnAppointmentQuestion.cshtml

cshtml
@using NonJSProgressiveDisclosureUI.Enums
@model IndexViewModel

@{
    var buttonStyleSelected = "text-align:center; width:30%; 
    font-weight:600; color:darkslateblue; background-color:yellow";
    var buttonStyleDefault = "text-align:center; width:30%; 
    font-weight:600; color:darkslateblue";
    var buttonStyleYes = buttonStyleDefault;
    var buttonStyleNo = buttonStyleDefault;
}

<div style="text-align:left; margin-top:25px">
    Do you want to schedule an appointment
</div>

@if (Model.AnswerToScheduleAnAppointmentQuestionIs == YesNoType.Yes)
{
    buttonStyleYes = buttonStyleSelected;
}
else if (Model.AnswerToScheduleAnAppointmentQuestionIs == YesNoType.No)
{
    buttonStyleNo = buttonStyleSelected;
}

@if (!Model.ShowRadioButtons)
{
    <div style="display:flex">
        <button style="@buttonStyleYes" type="submit" 
         id="AnswerToScheduleAnAppointmentQuestionIsYes" name="submit" 
         value="AnswerToScheduleAnAppointmentQuestionIsYes">Yes</button>
        <button style="@buttonStyleNo" type="submit" 
         id="AnswerToScheduleAnAppointmentQuestionIsNo" name="submit" 
         value="AnswerToScheduleAnAppointmentQuestionIsNo">No</button>
    </div>
}
else
{
        <div style="text-align:left;margin-top:15px">
            @Html.RadioButtonFor(m => m.AnswerToScheduleAnAppointmentQuestionIs, 
            YesNoType.Yes, new { id="AnswerToScheduleAnAppointmentQuestionIsYes" } )
            <label for="yes" style="width:8%">Yes</label>
            @Html.RadioButtonFor(m => m.AnswerToScheduleAnAppointmentQuestionIs, 
            YesNoType.No, new { id="AnswerToScheduleAnAppointmentQuestionIsNo" } )
            <label for="yes" style="width:8%">No</label>
        </div>    
}

_SubmitIdProof.cshtml

cshtml
<div style="text-align:left; margin-top:30px">
    <label for="idProof" style="width:25%;text-align:left">
     Please Submit ID Proof for Registration: </label>
    <button>Browse</button>
</div>

Understanding Code

  • Home controller > SetIndexViewModel method — Based on the user selections, the model values are updated and cached.
  • All the relevant partial pages are created.
  • _DoctorAppointment.cshtml — This is where the logic to load appropriate partial pages with correct questions, content and applications is there.
  • Instead of radio buttons, buttons are used and the model values are updated in SetIndexViewModel method in Home Controller.

Solution

Now run the application and see how the new solution fixes all the issues. You may compare it with the flowchart above.

Image 11

When user selects Yes, it checks if it is the first visit.

Image 12

When user selects Yes for the second question, it asks for ID proof to register him as new user.

Image 13

When user selects No for the second question, it asks for the registration number.

Image 14

Now when the user selects No for the first question, the second question changes accordingly.

Image 15

You might have noticed that the second question has changed appropriately.

Say the user selects Yes again for the first question. The UI should retain his previous selection for second question as well, i.e., No in this case. Also, the second question is to be changed appropriately.

Image 16

So it is showing the correct UI in all the cases.

If you want to test the radio button ones, set ShowRadioButtons=true in Home Controller and run the app.

Image 17

Hope you find this interesting and useful.

History

  • 7th February, 2023: Initial version

License

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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --