Improving UX in web pages (ASP.MVC 4, jQuery Accordion and Knockout.JS)

October 24, 2018
5 min read
Improving UX in web pages (ASP.MVC 4, jQuery Accordion and Knockout.JS)
I. Existing problem

In one of the pages in a Web Application project, an admin user has the option to set up a list of questions for a form template, which will later be used in the application by regular users, to submit a list of answers specific to that form. The question configuration was looking like below:

Improving UX in web pages (ASP.MVC 4, jQuery Accordion and Knockout.JS)

The first problem with that configuration, as it can be seen, is the fact that we can have plenty of questions in that form (in this case 30). In the screenshot above only 3 can be seen, imagine the page/form with 30 questions. It’s 10 times bigger.

Another problem is the fact that you must pay a fair amount of attention to notice where a question ends and where the next one starts.

Although there is no clear indication anywhere, each question has an order, which makes this the third problem. Closely related to this, the fourth problem, is the fact that if you decide to change the order of a question in the form template, you have to do that by manually deleting the questions (by clicking the X button next to the question name) and create them again in the order you want.

II. Solution

The Accordion

Improving UX in web pages (ASP.MVC 4, jQuery Accordion and Knockout.JS)
As you can see, most of the problems are solved. Accordion enforces only one/none active item/tab open at a given moment in time, for a great UX. Header information is comprised of the order index and the text of the question. If needed, these can be further enhanced with other information.
III. The best thingof all

The drag and drop question reordering 

The watch before drag and drop:
 Improving UX in web pages. ASSIST Software - Ion Balan - The watch before drag and drop

Drag

Improving UX in web pages (ASP.MVC 4, jQuery Accordion and Knockout.JS)

Drop

Improving UX in web pages (ASP.MVC 4, jQuery Accordion and Knockout.JS)

The watch after drag and drop:

Improving UX in web pages (ASP.MVC 4, jQuery Accordion and Knockout.JS)
IV. Technical solution

1. jQuery accordion

var newOrder = new Array();

$(document).ready(function () {
    $("#accordion").accordion({
        collapsible: true,
        header: "> div > h3",
        icons: false,
        autoFill: true,
        active: false,
        header: 'h3'
    }).sortable({
        axis: "y",
        handle: "h3",
        stop: function (event, ui) {
            newOrder = new Array();
            $('div.questions > div.accordionPanel > input.orderId').each(function () {
                //get the id
                var id = $(this).attr("value");
                newOrder.push(id);
            });
            appViewModel.refresh(newOrder)
        }
    });

    $('#addQuestionLink').click(function () {
        appViewModel.addQuestion(uuidv4(), "", newQuestionsJSON.Type);
        $('.questions').accordion("refresh");
        $("#accordion").accordion({ active: -1 });
        return false;
    });
});
$('#accordion').on('accordionactivate', function (event, ui) {
    if (ui.newPanel.length) {
        $('#accordion').sortable('disable');
    } else {
        $('#accordion').sortable({
            enable: 'enable',

        });
    }
});

2. Knockout view model

function AppViewModel() {
    var self = this;

    self.questionList = ko.observableArray();

    self.addQuestion = function (qid, qOrder, title, qIsMandatory) {

        var question = {
            id: ko.observable(qid),
            order: ko.observable(qOrder),
            isMandatory: ko.observable(qIsMandatory),
            questionText: ko.observable(title),
            questionType: ko.observable(qType)

        };
        var delayedQuestionType = ko.pureComputed(question.questionType).extend({ throttle: 50 });
        delayedQuestionType.subscribe(function (value) {
            $('.questions').accordion("refresh");
        });
        self.questionList.push(question);
    };

    self.removeQuestion = function () {
        self.questionList.remove(this);
    }
    self.refresh = function (newOrder) {
        var newList = [];
        newOrder.forEach(function (id, index) {
            var item = ko.utils.arrayFirst(self.questionList(), function (item) {
                return item.id() === id;
            });
            if (item != null) {
                item.order(index);
                newList.push(item);
            }
        });
        self.questionList(newList);
    }
    self.setQuestions = function (questions) {
        questions.forEach(function (question, index) {
            self.addQuestion(question.Id, question.Order, question.Text,question.IsMandatory, question.Type)
        });
    }
}
3. The ASP .NET MVC display templates for the list of questions (partially included, as an example only)

 The list itself:
@model List<MyWebApplication.ViewModels.QuestionViewModel>

@{
    Layout = null;
}

<div class="panel-body">
    <div id="accordion" class="questions form-template-questions">
        @Html.EditorFor(model=>model[0],"Question")
    </div>
</div>

The list item (QuestionViewModel):

<!-- ko foreach: questionList -->
    <div class="accordionPanel namePathRoot">
        @Html.HiddenFor(model => model.Id, new { data_bind = "value: id(), namePath: true" ,@class = "orderId"})
        @Html.HiddenFor(model => model.Order, new { data_bind = "value: order(), namePath: true" })
        <h3 data-bind="text: $index() + 1 + '. ' + questionText()"><span class="required" data-bind="visible: isRequired() ">*</span></h3>
        <div style="-ms-align-content: stretch; -webkit-align-content:stretch; align-content:stretch">
            <div class="input-group">
                @Html.TextBoxFor(x => x.Text, new { maxLength = 400, @class = "form-control", data_bind = "value: questionText, namePath: true" })
                <div class="action input-group-addon">
                    <a href="#" data-bind="click: $parent.removeQuestion" class="remove glyphicon glyphicon-remove" title="@MyWebApplication.Resources. Manage.Tooltip_Question_Delete"></a>
                </div>
                @Html.ValidationMessageFor(x => x.Text)
            </div>
            <div class="form-group">
                @Html.LabelFor(x=>x.IsMandatory)
                @Html.CheckBoxWithNamePathBinding("IsMandatory",Model.IsMandatory, new { data_bind = "checked: isMandatory, namePath: true" } )
            </div>
            <div class="questionType form-group">
                @Html.LabelFor(x=>x.Type)
                @{
                    @Html.EnumDropDownListFor(x => x.Type, typeof(MyWebApplication. Domain.QuestionType), new { @class = "form-control", data_bind = "value: questionType, namePath: true" }, typeof(MyWebApplication.Resources.Enums), false)
                }
            </div>
V. Some of the challenges

a. The items reordering – as it can be seen, it’s done when an item stops being dragged, by calling appViewModel.refresh(newOrder). Then the Knockout binding makes sure that the new order is bound into the ASP.NET MVC model.

b.Knockout support for collection of objects – this is not supported, instead it’s done through custom binding ‘namePath’(namePath :true). You can find more information if you access this page.
VI. Conclusion

It was a good exercise to combine all these 3 technologies, ASP.NET MVC, jQuery and Knockout.JS. Many other things can be accomplished in a similar manner, however if you have complex objects or if Knockout.JS was not one of your original choices while your application evolved, then things can easily get complicated. However, if you do have simple objects, or you are working on new features, then Knockout.JS can help provide a significant improvement to the UX, as well as helping keep your code clean and sane, by taking away some uninteresting concerns (like binding – or passing values from client to server side) on client side.  
Finally, each piece of technology can make our lives easier only if used correctly. It is up to us to make sure that that happens.

If you enjoyed reading this, you can also check my previous article:

Share on:

Want to stay on top of everything?

Get updates on industry developments and the software solutions we can now create for a smooth digital transformation.

* I read and understood the ASSIST Software website's terms of use and privacy policy.

Frequently Asked Questions

ASSIST Software Team Members

See the past, present and future of tech through the eyes of an experienced Romanian custom software company. The ASSIST Insider newsletter highlights your path to digital transformation.

* I read and understood the ASSIST Software website's terms of use and privacy policy.

Follow us

© 2024 ASSIST Software. All rights reserved. Designed with love.