Wednesday, June 27, 2012

ASP.NET MVC Ajax Load with Partial View and Maintaining Browser History for Ajax Call


A single page application I created where contents are in server side and with navigation from one link to other link the content are displaying in same page. As there have no redirect to any other page so Ajax loading is more applicable in such single page application. By clicking on any of the navigation link the detail content will only be requested from server. So full page is not render in such case only the detail portion of page. In such scenario Partial View of ASP.NET MVC  is suitable. The detail content will be displayed as Partial View and with Ajax call we will return only Partial View. Ajax output will be only detail content  and will be displayed to user setting as innerHTML or append inside a <div>. I used ASP.NET MVC 4 to create that application. The solution will also applicable for ASP.NET MVC 2.
From the Url of the following page you can understand Home is the Controller. DynamicContent is Action and Page1 is route parameter Id. As a single page application if I click on the navigation like in right side, only the detail content will changed. If I click on Page 2 link then the content will be “Content will be display here Page2”. Here the links are not changing and only detail content is changing with Ajax call.
image
Let create a Partial View DynamicContentPartialView.cshtml for detail content like this code:
<div>
    Content will be display here
    @ViewBag.SelectedPage
</div>



Now inside DynamicContent.cshtml partial view is rendered with RenderParial call.
<article>
    <div id="dynamicContent">      
        @{
            Html.RenderPartial("DynamicContentPartialView");
        }
    </div>
</article>
<aside>
    <ul id="dynamicActionLinks">
        <li>@Html.ActionLink("Page 1","DynamicContentPartialView","Home",new{id="Page1"},null)</li>
        <li>@Html.ActionLink("Page 2", "DynamicContentPartialView", "Home", new { id = "Page2" }, null)</li>
         <li>@Html.ActionLink("Page 3", "DynamicContentPartialView", "Home", new { id = "Page3" }, null)</li>
    </ul>
</aside>




Here in the code block DynamicContentPartialView control is displayed using RenderPartial call. And there have navigation link for Ajax call. So when first time initial loading or you refresh the page without Ajax call RenderPartial() method will be called. which will display content according the Url route value Id.

The server side code of DynamicContent Action is very simple.
  public ActionResult DynamicContent(string id)
        {
            ViewBag.SelectedPage = id;
            return View();
        }




For making it more simple the detail content is displaying only the selected route value Id.  So that we can only concentrate on main factors.

So if the Url will be like: http://localhost:1037/Home/DynamicContent/Page1 . Here Page1 is the Id route value then the output will be same as above page image.

Now in case of clicking one of right side links we will make Ajax call and we will fetch the detail content. Let say Page 2 link is clicked from right side navigation link. The action link for Page 2 is
@Html.ActionLink("Page 2", "DynamicContentPartialView", "Home", new { id = "Page2" }, null)

Here if you see the html hyperlink value after rendering ActionLink it will look like:
<a href="/Home/DynamicContentPartialView/Page2">Page 1</a>



Now from href attribute you can see Home is controller and DynamicContentPartialView is action. But we will not make redirect to DynamicContentPartialView. we will call DynamicContentPartialView with ajax call.

Javascript code for handling click on the navigation link is:
 $("#dynamicActionLinks li a").click(function () {
            var href = $(this).attr("href");
               LoadAjaxContent(href);             
            return false;
        });

 function LoadAjaxContent(href) {

        $.ajax(
            {
                url: href,
                success: function (data) {
                    $("#dynamicContent").html(data);
                }
            });

    }




Click event of <a> link is handled here in above code block. An Ajax request is made with href value of <a> (<a href="/Home/DynamicContentPartialView/Page2">Page 1</a>). So when $.ajax(fn); is called then this request goes to DynamicContentPartialView  Action inside Home Controller. The code block for DynamicContentPartialView is:
  public ActionResult DynamicContentPartialView(string id)
        {
            ViewBag.SelectedPage = id;
            return PartialView("DynamicContentPartialView");
        }




It also takes route value Id same as DynamicContent Action but here it return DynamicContentPartialView. where DynamicContent Action is returning full View. PartialView call basically write the content inside DynamicContentPartialView to the response. We will get only the content by making ajax call.
<div>
    Content will be display here
    Page2
</div>

and inside success method of $.ajax() call we set the InnerHTML of <div id=”dynamicContent”></div>.  Hence loading contents with ASP.NET MVC partial view is very easy with PartialView.

As ajax call is not redirecting to another page so now problem in case of maintaining history of browser with Url. When you will click on Page 2 navigation link Url of browser will not changed as it is a ajax request. Browser will display Url as http://localhost:1037/Home/DynamicContent/Page1 where it should be http://localhost:1037/Home/DynamicContent/Page2 but it is displaying detail content of Page2. So when we will make Ajax request we will also have to change Url of broswer. So that user can bookmark http://localhost:1037/Home/DynamicContent/Page2  also.

HTML5 browser supports history.js api which can not be applicable for non HTML5 browser. Let see the pushState() code while making ajax request.
 $("#dynamicActionLinks li a").click(function () {
            var href = $(this).attr("href");
            if (window.history && window.history.pushState) {
                LoadAjaxContent(href);
                var displayUrl = href.replace("PartialView", "");

                window.history.pushState(href, "Page Title", displayUrl);
            } else {
                $.address.value(href);

            }
            return false;
        });




Here displayUrl is used for displaying Url to the browser. Though we are making ajax request to DynamicContentPartialView Action but the browser will display http://localhost:1037/Home/DynamicContent/Page2. Now if you click on back and forward to http://localhost:1037/Home/DynamicContent/Page2. onpopstate event will raise. Let see the code :
window.onpopstate = function (event) {
        if (event.state != null)
            LoadAjaxContent(event.state);
    };

Here the state object contains  http://localhost:1037/Home/DynamicContentPartialView/Page2 as state ( first argument of pushState() method). And here we again make ajax request. You could also have detail content of Page2 inside pushState. Here I made ajax call again to fetch detail content from PartialView. But not all browser ( IE6 to IE9) does not support history.js api. So we need to check for those browser.

For non- HTML5 browser, only option for maintaining history is changing window.location.hash. I found a jquery nice plugin  Address.js. I have also used it for ajax call
$.address.value(href);



It also fire an event as like:
  $.address.change(function (event) {
            if (event.value == "/") {
                if (window.location.href.indexOf("DynamicContent") != -1) {
                    var quarylocation = window.location.href;
                    LoadAjaxContent(quarylocation.replace("DynamicContent", "DynamicContentPartialView"));
                }
                return;
            }
            try {

                LoadAjaxContent(event.value);

            }
            catch (e) {

            }
        });




We can get the value of hash with event.value.  As we did not push the state of browser when Full Page load with called with DynamicContent action . So inside the DynamicContent view  the first state is saved as:
  <div id="dynamicContent">
        <script type="text/javascript">
            if (window.history && window.history.pushState) {
                var href = window.location.href;
                var backUrl = href.replace("DynamicContent", "DynamicContentPartialView");
                window.history.pushState(backUrl, "Page Title", href);
            } else {
                $.address.value(window.location);
            }
        </script>
        @{
            Html.RenderPartial("DynamicContentPartialView");
        }
    </div>




With this call it also have the state of first load without ajax call. So when we will back to initial state it from browser history it will also make ajax request for initial state.

Here I have tried to give your same features as they get full page load but as modern web application where history is maintained with ajax call  and only the required detail content is requested from server.

My colleague Masudur Rahman also worked with me in pair programming.

Here you can download the solution with above explained code https://dl.dropbox.com/u/20275838/historybookmark.rar

Monday, June 18, 2012

Solution: Inline CSS issue and Ajax call with IE6 to IE8

 

Problem:

I have an application where I need to display html content by making Ajax call. The html content in html page with inline CSS (<style></style> tag inside html page). I have a <div> element in my main page and I make Ajax request to load the html content and set the content in <div> as innerHTML or using jQuery html(). The html content is displaying perfectly in Firefox 12 and Chrome 19 and also work nicely in IE 9. But when I made a Ajax request using IE 7 or IE 8 the html content is displaying without inline styling in page. The problem is also specified in web and is a known bug of IE  http://www.dougboude.com/blog/1/2008/05/Inline-CSS-and-Ajax-Issue-with-IE.cfm. They suggest to use CSS in global file. But my each content has different CSS style and that is not part of our application and  content can be changed any time also. So we can not replace inline CSS in another CSS file. I tried to find alternative solution of this problem  where inline CSS can be considered in IE 6 to 8 but It did not work. We also tried to refresh the DOM but failed to display content with inline style.

Solution:

jQuery has two method for inserting html content inside a <div> or other DOM elements. One is html() and another is append(). html() function of jQuery works same as setting InnerHTML inside DOM element and it replaces the previous content. But append() does not replace existing content and it appends content at the end of existing content. To replace the content empty() method should be called and just after we can call append(). It may look like both are doing same things. But append does not only set the InnerHTML inside DOM element. It do much complicated work. If you watch the append() function inside jQuery.js file you will see it first manipulates the DOM inside the html content. If you see the clean function inside jQuery then you will find it creates JQuery collection from html string. If you write your own code to create JQuery collection and append inside <div> using appendChild that will work with styling in FireFox not in IE also you will get less elements inside JQuery collection. Inside the append() function style problem is fixed but code is much complex to understand which code actually solved the problem. So if you use Jquery empty().append() function instead of html() or innerHTML for IE 6 to IE8 , you can see the content with inline CSS making Ajax call.

The simple solution for IE 6 to IE 8 is

    var url = "inline.html";
$.ajax({
url: url,
success: function (html) {
$('#content').empty();

$('#content').append(html);
},
error: function () { return null; }
});




Here is the demo of the solution.  https://dl.dropbox.com/u/20275838/testAjax/index.html