Shell page technique in SPA.

SPA Shell Page.png

The above image is a shell/master page in a Single Page Application. every other partial will be shown in the Partial content container and it’s title will be shown in the Partial title area. This title is menu text, in general. When a user clicks on a menu item, it’s icon will spin as preloader.

This page will contain no mark-up in Partial content area. When this master loads, it calls another action method to load it’s default partial, on document.ready event-

var PartialTitle = $("#PartialTitle");
function LoadDefaultContent() {
    //If any error, return this msg.
    var msg = '<div class="notice outer"> \
                            <div class="note note-danger">  \
                            <button type="button" class="close">×</button>   \
                                <strong>Notice!</strong> Something is wrong. Please reload the page.  \
                            </div>  \
                        </div>';


    $.ajax({
        url: '@ViewBag.BaseUrl' + 'Dashboard/DefaultContent?sid='+ sid +'&uid='+ userId +'',
        async: true,
        beforeSend: function () {
            // $("#PartialContent").fadeOut("slow").empty();
            //$('#PartialContent').fadeTo('slow', 0);
        },
        success: function (data) {
            console.log(data);
            if (data=='loginexpired') {
                window.location = '@ViewBag.BaseUrl' + 'Login';
            }
            else {
                if (data == 'error') {
                    $('#PartialContent').html(msg).fadeIn("slow");
                }
                else {
                    $('#PartialContent').html(data).fadeIn("slow");
                }
            }
        },
        error: function (request, status, error) {
            $('#PartialContent').html(msg).fadeIn("slow");
        },
        complete: function () {
        }
    });
}

LoadDefaultContent();

Let’s have a look on the html where partials are being injected-


<div class="content" >
    <div class="outer">
        <div class="inner">
            <div class="page-header">
                <!--this h5 will be changed dynamically-->
                <h5 id="PartialTitle">Dashboard </h5>                   
            </div>
            <div class="body">
                <div class="container" id="PartialContent">
                    <!--partials will inject here -->
                </div>
            </div>
        </div>
    </div>
</div>
// A typical action method looks like-
public ActionResult PartialContent()
{

    //Collecting data from query string
    if (Request.QueryString["sid"] == null) //sid = SessionGuid,
    {
        return Json("loginexpired", JsonRequestBehavior.AllowGet);
    }

    //user id
    if (Request.QueryString["uid"] == null)
    {
        return Json("loginexpired", JsonRequestBehavior.AllowGet);
    }

    string strEncryptedSessionGuid = Request.QueryString["sid"].ToString();
    string sessionGuid = this.DeCrypt(strEncryptedSessionGuid); //Decrypt SessionId
    string strEncryptedUserId = Request.QueryString["uid"];
    string strDecryptedUserId = this.DeCrypt(strEncryptedUserId);
    int userId = Convert.ToInt32(strDecryptedUserId);
    //Collecting data from query string

    try
    {

        //ViewBag (Common)
        ViewBag.BaseUrl = this.BaseUrl;
        ViewBag.EncryptedSessionGuid = strEncryptedSessionGuid;
        ViewBag.UserId = userId;
        ViewBag.EncryptedUserId = this.EnCrypt(userId.ToString());
        //ViewBag (Common)
    }
    catch (Exception Ex)
    {
        LogException(Ex);
        return Json("error", JsonRequestBehavior.AllowGet);
    }

    return View("PartialContent");
}
<!-- PartialContent sample mark-up -->

<div class="block well">
    <div class="navbar">
        <div class="navbar-inner">
            <h5>Line chart</h5>
        </div>
    </div>
    <div class="body">
        <ul class="midnav">
            <li><a href="#" title="Add an article"><img src="~/Assets/images/icons/color/order-149.png" alt="" /><span>New tasks</span></a></li>
            <li><a href="#" title="Upload files"><img src="~/Assets/images/icons/color/issue.png" alt="" /><span>My invoices</span></a></li>
            <li><a href="#" title="Add something"><img src="~/Assets/images/icons/color/hire-me.png" alt="" /><span>Add users</span></a><strong>17</strong></li>
            <li><a href="#" title="Messages"><img src="~/Assets/images/icons/color/donate.png" alt="" /><span>Donations</span></a></li>
            <li><a href="#" title="Check statistics"><img src="~/Assets/images/icons/color/config.png" alt="" /><span>Parameters</span></a></li>
        </ul>
    </div>
</div>

<div class="block well">
    <div class="navbar">
        <div class="navbar-inner">
            <h5>Line chart</h5>
        </div>
    </div>
    <div class="body">
        gfdg
    </div>
</div>
<!-- Now explaining the navigation menu mark-up technique-->
<ul class="navigation">
    <li class="active">
        <a class="handle-link" href="@(ViewBag.BaseUrl)Controller/View/ActionMehtod?sid=1&uid=2" data-content="Menu without sub-menu"><img src="dashboard.png" />Menu without sub-menu</a>
    </li>

    <li class="has-sub-menu">
        <a href="#" title=""><img src="form-elements.png" />Menu with sub-menu</a>
        <ul>
            <li>
                <a class="handle-link sub-menu" href="@(ViewBag.BaseUrl)Controller/ActionMehtod?sid=1&uid=2" data-content="Sub 1">Sub 1</a>
            </li>
            <li>
                <a class="handle-link sub-menu" href="@(ViewBag.BaseUrl)Controller/ActionMehtod?sid=1&uid=2" data-content="Sub 2">Sub 2</a>
            </li>
            <li>
                <a class="handle-link sub-menu" href="@(ViewBag.BaseUrl)Controller/View/ActionMehtod?sid=1&uid=2" data-content="Sub 3">Sub 3</a>
            </li>
        </ul>
    </li>
</ul>

1. handle-link class- To identify the link which is handled by jquery and to submit via ajax.
2. sub-menu class- To identify wheather it's a sub-menu.
3. data-content attribute- It's value will be used to show the title in Partial title area mark-up.
//When a user clink on menu (.handle-link), it'll be handled in JQuery-

$('a.handle-link').click(function (e) {
    e.preventDefault();
    var currentMenuItem = $(this);
    var currentLinkAddress = $(currentMenuItem).attr('href');
    var currentLI = currentMenuItem.closest('li');
    var dataContent = currentMenuItem.attr('data-content');
    PartialTitle.html(dataContent);
    var img; //variable to hold currently displaying preloader.

    //Check this is sub-menu
    if (currentMenuItem.hasClass('sub-menu'))
    {  //If this is a sub-menu item

        var currentUL = currentLI.closest('ul');
        var parentLI = currentUL.closest('li');

        //Checking whether it has 'current' class
        if (currentLI.hasClass('current')) 
        { //if it has 'current' class 
            //This is current and its top li is also active. So no need to do any class removing code.
            //But user may want to reload this page. So, show the preloader
            //Showing preloader
            var firstAnchor = parentLI.find('a:first');
            img = firstAnchor.find('img');
            img.addClass('imageee');
            //Showing preloader
        } //if it has 'current' class
        else 
        { //if it does not have 'current' class

            //But it might be the sub-menu under the same menu. So check it now.
            if (parentLI.hasClass('active'))
            { //If it's under same parent menu
                //No need to check whether another element has 'imageee' class
                //Just show the preloader
                var firstAnchor = parentLI.find('a:first');
                img = firstAnchor.find('img');
                img.addClass('imageee');

                // So, no need to change active. But change only 'current' class
                currentUL.find('li').each(function (i) {
                    if ($(this).hasClass('current')) {
                        $(this).removeClass('current');
                    }
                });
                currentLI.addClass('current');
            } //If it's under same parent menu
            else
            { //If parent li does not have 'active' class
                //But other menu/sub-menu may have 'active' class or display preloader. So check it.
                var baseUl = parentLI.closest('ul');
                baseUl.find('li').each(function (i) {
                    if ($(this).hasClass('active')) {
                        $(this).removeClass('active');

                        //Hide all other (if any) preloader
                        var otherAnimatingImage = $(this).find('img');
                        if (otherAnimatingImage.hasClass('imageee')) {
                            otherAnimatingImage.removeClass('imageee')
                        }
                        //Hide all other (if any) preloader

                        if ($(this).hasClass('has-sub-menu')) {
                            $(this).find('li').each(function (i) {
                                if ($(this).hasClass('current')) {
                                    $(this).removeClass('current');
                                }
                            });
                        }
                    }
                });

                parentLI.addClass('active');
                currentLI.addClass('current');
                //Show preloader
                var firstAnchor = parentLI.find('a:first');
                img = firstAnchor.find('img');
                img.addClass('imageee');
                //Show preloader
            } //If parent li does not have 'active' class
        } //If it does not have 'current' class
        //Checking whether it has 'current' class
    } //If this is a sub-menu item
    else
    { //If this is not sub-menu
        var baseUl = currentLI.closest('ul');
        baseUl.find('li').each(function (i) {
            if ($(this).hasClass('active')) {

                //Hide all other (if any) preloader
                var otherAnimatingImage = $(this).find('img');
                if (otherAnimatingImage.hasClass('imageee')) {
                    otherAnimatingImage.removeClass('imageee')
                }
                //Hide all other (if any) preloader

                $(this).removeClass('active');
                if ($(this).hasClass('has-sub-menu')) {
                    $(this).find('li').each(function (i) {
                        if ($(this).hasClass('current')) {
                            $(this).removeClass('current');
                        }
                    });
                }
            }
        });

        currentLI.addClass('active');

        img = currentMenuItem.find("img");
        img.addClass('imageee');
    } //If this is not sub-menu
    //Check this is sub-menu

    //If any error, show this msg.
    var msg = '<div class="notice outer"> \
                            <div class="note note-danger">  \
                            <button type="button" class="close">×</button>   \
                                <strong>Notice!</strong> Something is wrong. Please reload the page.  \
                            </div>  \
                        </div>';

    //Finally, ajax call to load partial
    $.ajax({
        url: currentLinkAddress,
        async: true,
        beforeSend: function () {
            // $("#PartialContent").fadeOut("slow").empty();
            //$('#PartialContent').fadeTo('slow', 0);
        },
        success: function (data) {
            console.log(data);

            if (data == 'loginexpired') {
                window.location = '@ViewBag.BaseUrl' + 'Login';
            }
            else {
                if (data == 'error') {
                    $('#PartialContent').html(msg).fadeIn("slow");
                }
                else {
                    $('#PartialContent').html(data).fadeIn("slow");
                }
            }
        },
        error: function (request, status, error) {
            $('#PartialContent').html(msg).fadeIn("slow");
        },
        complete: function () {
            img.removeClass('imageee');
        }
    });
    //Finally, ajax call to load partial
});
/* imageee style. This will spin the menu icon to transform it as preloader */

.imageee {
    margin: -60px 0 0 -60px;
    -webkit-animation: spin 2s linear infinite;
    -moz-animation: spin 2s linear infinite;
    animation: spin 2s linear infinite;
}

@@-moz-keyframes spin {
    100% {
        -moz-transform: rotate(360deg);
    }
}

@@-webkit-keyframes spin {
    100% {
        -webkit-transform: rotate(360deg);
    }
}

@@keyframes spin {
    100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
    }
}
//You may store common javascript variables in the shell page script tag- 

<script>
   var sid = '@ViewBag.EncryptedSessionGuid';
   var userId = '@ViewBag.EncryptedUserId';
</script>

Hope this post will let you help to understand SPA technique. Thank you.

Advertisements

Menu navigation technique in SPA

Untitled-1

Navigation/Menu demands some extra atttention in a single page application. Since, master/shell page does not refresh, so it requires to mark an menu item active and other inactive (i.e. add/remove styles).

And, finally laod the partial content.

/*CSS to spin the preloader image*/
.imagee {
    overflow: hidden;
    transition-duration: 0.8s;
    transition-property: transform;
}

.imagee:hover {
    transform: rotate(360deg);
}

.imageee {  
     margin: -60px 0 0 -60px;
    -webkit-animation: spin 2s linear infinite;
    -moz-animation: spin 2s linear infinite;
    animation: spin 2s linear infinite;
}

@-moz-keyframes spin {
    100% {
        -moz-transform: rotate(360deg);
    }
}

@@-webkit-keyframes spin {
    100% {
        -webkit-transform: rotate(360deg);
    }
}

@@keyframes spin {
    100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
    }
}
<ul class="navigation">
    <li class="active">
        <a class="handle-link" href="index.html" title=""><img src="dashboard.png" />Dashboard</a>
    </li>

    <li class="has-sub-menu">
        <a href="#" title=""><img src="form-elements.png" />Multiple<strong>3</strong></a>
        <ul>
            <li>
                <a class="handle-link sub-menu" href="forms.html" >One</a>
            </li>
            <li>
                <a class="handle-link sub-menu" href="two.html">Two</a>
            </li>
            <li><a class="handle-link sub-menu" href="three.html" >Three</a></li>
        </ul>
    </li>
    <li >
        <a class="handle-link" href="index.html"><img src="dashboard.png" />Single
        </a> 
    </li>

    <li class="has-sub-menu">
        <a href="#"><img src="elements.png"/>This is Multiple also<strong>2</strong></a>
        <ul>
            <li>
                <a class="handle-link sub-menu" href="forms.html" >One</a>
            </li>
            <li>
                <a class="handle-link sub-menu" href="form_wizards.html" title="">Two</a>
            </li>
        </ul>
    </li>
</ul>
$('a.handle-link').click(function (e) {
    e.preventDefault();
    var currentMenuItem = $(this);
    var currentLinkAddress = $(currentMenuItem).attr('href');
    var currentLI = currentMenuItem.closest('li');

    var img; //variable to hold currently displaying preloader.

    //Check this is sub-menu --->
    if (currentMenuItem.hasClass('sub-menu'))
    {  //If this is a sub-menu item ---->

        var currentUL = currentLI.closest('ul');
        var parentLI = currentUL.closest('li');

        //Checking whether it has 'current' class ---->
        if (currentLI.hasClass('current')) 
        { //if it has 'current' class -->
            //This is current and its top li is also active. So no need to do any class removing code.
            //But user may want to reload this page. So, show the preloader
            //Showing preloader--->
            var firstAnchor = parentLI.find('a:first');
            img = firstAnchor.find('img');
            img.addClass('imageee');
            //Showing preloader---<
        } //if it has 'current' class ---<
        else 
        { //if it does not have 'current' class --->

            //But it might be the sub-menu under the same menu. So check it now.
            if (parentLI.hasClass('active'))
            { //If it's under same parent menu--->
                //No need to check whether another element has 'imageee' class
                //Just show the preloader
                var firstAnchor = parentLI.find('a:first');
                img = firstAnchor.find('img');
                img.addClass('imageee');

                // So, no need to change active. But change only 'current' class
                currentUL.find('li').each(function (i) {
                    if ($(this).hasClass('current')) {
                        $(this).removeClass('current');
                    }
                });
                currentLI.addClass('current');
            } //If it's under same parent menu ---<
            else
            { //If parent li does not have 'active' class --->
                //But other menu/sub-menu may have 'active' class or display preloader. So check it.
                var baseUl = parentLI.closest('ul');
                baseUl.find('li').each(function (i) {
                    if ($(this).hasClass('active')) {
                        $(this).removeClass('active');

                        //Hide all other (if any) preloader--->
                        var otherAnimatingImage = $(this).find('img');
                        if (otherAnimatingImage.hasClass('imageee')) {
                            otherAnimatingImage.removeClass('imageee')
                        }
                        //Hide all other (if any) preloader---<

                        if ($(this).hasClass('has-sub-menu')) {
                            $(this).find('li').each(function (i) {
                                if ($(this).hasClass('current')) {
                                    $(this).removeClass('current');
                                }
                            });
                        }
                    }
                });

                parentLI.addClass('active');
                currentLI.addClass('current');
                //Show preloader--->
                var firstAnchor = parentLI.find('a:first');
                img = firstAnchor.find('img');
                img.addClass('imageee');
                //Show preloader ----<
            } //If parent li does not have 'active' class ---<
        } //If it does not have 'current' class ---<
        //Checking whether it has 'current' class ----<
    } //If this is a sub-menu item ----<
    else
    { //If this is not sub-menu ---->
        var baseUl = currentLI.closest('ul');
        baseUl.find('li').each(function (i) {
            if ($(this).hasClass('active')) {

                //Hide all other (if any) preloader--->
                var otherAnimatingImage = $(this).find('img');
                if (otherAnimatingImage.hasClass('imageee')) {
                    otherAnimatingImage.removeClass('imageee')
                }
                //Hide all other (if any) preloader--->

                $(this).removeClass('active');
                if ($(this).hasClass('has-sub-menu')) {
                    $(this).find('li').each(function (i) {
                        if ($(this).hasClass('current')) {
                            $(this).removeClass('current');
                        }
                    });
                }
            }
        });

        currentLI.addClass('active');

        img = currentMenuItem.find("img");
        img.addClass('imageee');
    } //If this is not sub-menu ---->
    //Check this is sub-menu --->

    //Finally, ajax call to load partial ---->
    $.ajax({
        url: currentLinkAddress,
        async: true,
        beforeSend: function () {
            // $("#PartialContent").fadeOut("slow").empty();
            //$('#PartialContent').fadeTo('slow', 0);
        },
        success: function (data) {
            //  $('#PartialContent').fadeTo('slow', 0).slideUp()
            $('#PartialContent').html(data).fadeIn("slow");
        },
        error: function (request, status, error) {
            //  alert("Error-  Request: " + request + ", Status: " + status + ", Error: " + error);
            var msg = '<div class="notice outer"> \
                            <div class="note note-danger">  \
                            <button type="button" class="close">×</button>   \
                                <strong>Notice!</strong> Something is wrong. Please reload the page.  \
                            </div>  \
                        </div>';
            $('#PartialContent').html(msg).fadeIn("slow");

        },
        complete: function () {
            img.removeClass('imageee');
        }
    });
    //Finally, ajax call to load partial ----<
});


Handle link in SPA to load partial

<div id="page-wrapper">

</div>


$('a.handle-link').click(function (e) {
    e.preventDefault();

    var currentLink = $(this).attr('href');
    $.ajax({
        url: currentLink,
        async: true,
        beforeSend: function () {
            $("#page-wrapper").fadeOut("slow").empty();
        },
        success: function (data) {
            $('#page-wrapper').hide().html(data).fadeIn("slow");
        },
        error: function (request, status, error) {
            alert("Error-  Request: " + request + ", Status: " + status + ", Error: " + error);
        },
        complete: function () {
            // $('div.sidenav').unblock();
        }
    });
});