Partial Rendering with ASP.NET MVC and jQuery

An entry about asp.net mvc | ajax | javascript Publication date 1. June 2008 13:57

Yesterday, I wrote a post detailing how easy we can invoke an ASP.NET MVC controller action from JavaScript using jQuery. Lets up the ante a bit, and see if we can't use the same approach to get some partial rendering going. To accomplish this, we'll use a fact about the RenderViewResult class that hasn't got a lot of attention so far - the fact that its View propetry can be set to point at a UserControl, not just a WebForm. When doing so, only the user control will be rendered - which sounds like exactly what we need for doing partial rendering..

Rendering User Controls

Lets say we're building a simple task list. We might have the following UserControl representing a single task:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Task.ascx.cs" Inherits="MvcApplication2.Views.Tasks.Task" %>
<div class="task">
    <div class="task_name"><%= ViewData.Name %></div>
    <div class="task_due"><%= ViewData.Due %></div>
    <div class="task_desc"><%= ViewData.Description %></div>
</div>

 

Our TaskList view may then use this to render a list of tasks:

<h1>Tasks</h1>
 
<div id="task_list">
    <% foreach(Task task in ViewData) {%>
        <%= Html.RenderUserControl("~/Views/Tasks/Task.ascx", task) %>
    <%} %>
</div>
 
<h1>Add task</h1>
 
Name: <input type="text" id="newtask_name" /><br />
Due: <input type="text" id="newtask_due" /><br />
Description: <input type="text" id="newtask_desc" /><br />
<input type="button" onclick="AddTask()" value="Add" />

 

The TaskList view is rendered by the Index action on our TaskController, which looks like this:

public ActionResult Index()
{
    Task[] tasks = // get tasks from repository
 
    return RenderView("TaskList", tasks);
}

 

Next, we need an action for creating a new task. However, we want to invoke it from JavaScript, and have it only return the markup for the new task item so that we can append it to the task list dynamically, instead of refreshing the entire page. To accomplish this, we implement it using the previously mentioned trick of the RenderViewResult class:

public ActionResult New(string name, string desc, DateTime due)
{
    Task task = new Task { Name = name, Description = desc, Due = due };
    // ...save task to repository
    return RenderView("Task", task);
}

 

Notice here that "Task" points to the Task.ascx user control, not a Web Form. When invoked, this action will render the markup for that control only.

Manipulating the DOM

Like I mentioned in yesterdays post, we can tell jQuery to handle the response from an Ajax call as html. More than that, we can use jQuery to create a DOM element for us from the returned html, and drop it anywhere in our document. The following JavaScript function accomplishes both:

function AddTask()
{
    var name = $("#newtask_name").attr("value");
    var due = $("#newtask_due").attr("value");
    var desc = $("#newtask_desc").attr("value");
 
    $.ajax(
    {
       type: "POST",
       url: "/Tasks/New",
       data: "name=" + name + "&due=" + due + "&desc= "+ desc,
       dataType: "html",
       success: function(result)
       {
            var domElement = $(result); // create element from html
            $("#task_list").append(domElement); // append to end of list        
       }
    });        
}

 

Here, we're making an Ajax call to the New action on the Tasks controller. We then create a new DOM element from the resulting html that it renders, and append it to the task list. And that's it! When the user adds a task, the following POST is sent:

Post

The following response is received from the server, which we append to the task list:

Response

If you want to take a look at a working example, you can download the project I used to demo this here.

Note: For some reason, the Ajax calls are quite slow (~1 sec) when running the demo using Visual Studio's built-in development server. Publish the site to IIS however, and things are blazingly fast (as you can see from the screenshots above) :)

Currently rated 5.0 by 8 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Invoking ASP.NET MVC Actions from JavaScript using jQuery

An entry about asp.net mvc | ajax | javascript Publication date 31. May 2008 11:27

One of the things that the MVC model is really great at, is integrating with Ajax. I blogged about a RenderJsonResult class a few weeks ago that one could use to create controller actions which render their output as Json - since then the last ASP.NET MVC framework drop also includes a JsonResult type of its own. What I didn't talk about in the mentioned post, was how to actually invoke such actions from JavaScript; enter jQuery.

Ajax with JQuery

In the crowd of JavaScript libraries out there, JQuery is probably the most unique - at least for now. Its main features are the abilities to query and manipulate the DOM using syntax that anyone familiar with CSS will feel at home with. But jQuery offers more than this - it also has an Ajax API. With this, we can easily perform a POST or GET on a controller action and handle the result it returns asynchronously.

As an example, lets assume we have a FeedbackController with an action Submit:

public ActionResult Submit(string author, string email, string comment)
{
    // logic for submitting feedback omitted
 
    return new RenderJsonResult {Result = new {success = true, message = "Thank you! We value your feedback."}};
}

 

Invoking this from JavaScript using jQuery is super easy:

$.ajax(
{
   type: "POST",
   url: "/Feedback/Submit",
   data:"author=" + author + "&email=" + authorEmail + "&comment=" + comment,
   success: function(result)
    {
        if(result.success) $("#feedback input").attr("value", ""); // clear all the input fields on success
        $("#feedback_status").slideDown(250).text(result.message); // show status message with animation
    },                
   error : function(req, status, error)
   {
        alert("Sorry! We could not receive your feedback at this time.");   
   }
});

 

We set the type of the call to POST, because we need to send some data with our request, which we use the data option to specify. jQuery allows us to provide a few callback methods that will be invoked on the success or failure of the Ajax call. If our call is successful, the success callback gets invoked, which gets passed an object representing the result of the Ajax call. jQuery is able to handle results formatted in different styles, including xml, html, script and json. You can specify the expected data type using the dataType option. Leaving it out will cause jQuery to choose the type based on the MIME type of the response, which is usually fine. There are other options which can be used; you'll find a complete description of them here. The code in the success callback uses the selectors, attributes, manipulation and effects APIs to reset the input fields and tell the user that the feedback was received.

It's simple, it's elegant - and so, so far removed from the bloated UpdatePanels of ASP.NET.

Surface Scratched

This post merely scratches the surface of what is possible when combining ASP.NET MVC with jQuery (or other Ajax APIs for that matter). Look out for more posts on this topic in the future!

Currently rated 4.2 by 18 people

  • Currently 4.222222/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

ASP.NET MVC ActionResult for Rendering JSON using JSON.NET

An entry about asp.net mvc | ajax Publication date 1. May 2008 18:03

With the latest drop of the ASP.NET MVC framework, as I've mentioned earlier, controller actions now return an ActionResult object. Out of the box, you have the choice between the RenderViewResult, ActionRedirectResult, HttpRedirectResult and EmptyResult types to do different things (they're fairly self-explanatory, no?). In the application I'm currently working on though, I have several actions which are supposed to only ever get called asynchronously from the client (using jQuery, a totally awesome JavaScript library - more on that some other time), and I want these actions to return a JSON string that can be easily consumed by the JavaScript callback handler. With the help of James Newton-Kings excellent JSON.Net library, all it took to get this working was to implement my own RenderJsonResult class:

/// <summary>
/// An action result that renders the given object using JSON to the response stream.
/// </summary>
public class RenderJsonResult : ActionResult
{
    /// <summary>
    /// The result object to render using JSON.
    /// </summary>
    public object Result { get; set; }
 
    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.ContentType = "application/json";
 
        JsonSerializer serializer = new JsonSerializer();
        serializer.Serialize(context.HttpContext.Response.Output, this.Result);
    }
}

 

My controller actions can then use the anonymous object notation of C# 3.0 to render JSON output:

return new RenderJsonResult { Result = new {status = "ok", assignedId = newItem.Id} };

 

Awesomeness! :)

Currently rated 4.3 by 6 people

  • Currently 4.333333/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Animating an Asp.Net Ajax Control Extender

An entry about asp.net | ajax Publication date 7. December 2007 22:14

Earlier this year, I wrote a blog post describing how to develop a context menu control extender using the Asp.Net Ajax control toolkit. It was a fairly basic extender, allowing you to specify a context area and an associated control that should be displayed when the user right-clicked inside that area. In this post I'll go back and enhance the extender with support for animating the context menu.

What I want to accomplish with the end result, is the ability to write markup similar to the following:

<idc:ContextMenuExtender
    ID="_cme"
    runat="server"
    ContextMenuControlID="_contextMenu"
    TargetControlID="_contextArea">
    <Animations>
        <OnShow>
            <FadeIn Duration="0.25" />
        </OnShow>
        <OnHide>
            <FadeOut Duration="0.25" />
        </OnHide>
    </Animations>
</idc:ContextMenuExtender>

If you've used any of the control extenders in the Ajax toolkit like the PopupControlExtender or UpdatePanelAnimationExtender, I'm sure you've seen that Animations tag before. It basically allows us to set up one or more storyboards for animations that should trigger on certain events - in the example above, applying a short FadeIn animation when the context menu is shown, and a short FadeOut animation when it is hidden.

The AnimationExtenderControlBase

Originally, my ContextMenuExtender inherited from the ExtenderControlBase class. The first step towards adding support for animations, is changing this to the AnimationExtenderControlBase, which will give me all the leverage I need to easily add animation support to my extender. This new base class has a few members that I'm interested in - there's the Animations property, and the SetAnimation and GetAnimation methods. As you see from the markup above, the Animations property is the one that we'll use to set up the storyboard for animations. The cool thing about this, is that it then automatically supports all the pre-built animation types that the toolkit contains - go take a look at the documentation to learn more about animations. The only thing we need to do to make it work, is to set up the different events that we want to support animation - in our case, they're OnShow and OnHide. We do this by adding similarly named properties on our extender:

[ExtenderControlProperty]
[ClientPropertyName("onShowJson")]
[Browsable(false)]
public Animation OnShow
{
get
    {
return GetAnimation(ref _onShow, "OnShow");
}
set
    {
SetAnimation(ref _onShow, "OnShow", value);
}
}
private Animation _onHide;
[ExtenderControlProperty]
[ClientPropertyName("onHideJson")]
[Browsable(false)]
public Animation OnHide
{
get
    {
return GetAnimation(ref _onHide, "OnHide");
}
set
    {
SetAnimation(ref _onHide, "OnHide", value);
}
}

As you can see, these are decorated with the ExtenderControlProperty attribute to signal that I want the values they get set to, to be transported to the client-side component. Notice also that I've used the ClientPropertyName to change the client-side target property - I'm telling the toolkit to map the server-side property OnShow/OnHide to the client-side property onShowJson/onHideJson (or rather, the set_onShowJson/set_onHideJson and get_onShowJson/get_onHideJson methods, as JavaScript has no concept of properties).

That's it for the server-side changes - the real stuff happens client-side, after all.

Animate Me

Our client-side stuff is defined in the ContextMenuBehavior object. The first thing we do, is to add the client-side properties that we told the server-side component to map the server-side OnShow and OnHide properties to:

get_onShowJson : function() 
{
return this._onShowJson;
},
set_onShowJson : function(value) 
{
this._onShowJson = value;
},
get_onHideJson : function() 
{
return this._onHideJson;
},
set_onHideJson : function(value) 
{
this._onHideJson = value;
}

Now, recall that the Animations property on the server-side extender control was just a string - so our _onShowJson and _onHideJson fields will just end up holding string representations of the animations - incidentally in a JSON format. We can use these strings to create AjaxControlToolkit.Animation objects, and we'll do that in the initialize method of the component:

initialize : function() 
{
Iridescence.Ajax.ContextMenuBehavior.callBaseMethod(this, 'initialize');
// other stuff removed for brevity...
                
if(this._onShowJson)
{
this._onShowAnim = $AA.buildAnimation(this._onShowJson, this._menuElement);            
}
if(this._onHideJson)
{
this._onHideAnim = $AA.buildAnimation(this._onHideJson, this._menuElement);
}
}

Now, if the any animations were specified, we'll have them parsed into actual Animation objects ($AA is an alias for AjaxControlToolkit.Animation) in our component, and set to target the context menu (stored in the _menuElement field), ready to be triggered whenever the context menu is shown or hidden. To trigger them, we need to modify the showMenu and hideMenu methods of the ContextMenuBehavior prototype slightly:

showMenu : function(x,y)
{    
// set flags
    this._menuVisible = true;
this._menuJustShown = true;  
this._menuElement.style.left = x + 'px';
this._menuElement.style.top = y + 'px';    
this._menuElement.style.display = '';
if(this._onHideAnim)
{
// if the hide animation is currently running, stop it
        this._onHideAnim.stop();
}
if(this._onShowAnim)
{             
this._onShowAnim.play();
}  
}

Most of this method is the same original - the only difference is that if there's an OnShow animation specified, we start playing it, first ensuring that any OnHide animation currently playing is stopped.

hideMenu : function()    
{    
if(this._onShowAnim)
{
// if show anim is currently running, stop it
        this._onShowAnim.stop();
}
if(this._onHideAnim)
{
this._onHideAnim.play();
}     
else
    {
this._hideMenuCore();
} 
this._menuVisible = false;
}

The hideMenu method is much the same - if there's an OnHide animation we start playing it, first ensuring that any OnShow animation currently playing is stopped.

Play With Me

That was a very quick guide to adding support for animations in an Asp.Net Ajax Control Extender. You can read more about using animations over at the Asp.Net Ajax Control Toolkit website.

If you want to play around with the ContextMenuExtender, click here to download a Visual Studio 2008 solution including the source code, and an example project which shows a few usage scenarios of the extender. Feel free to use and abuse it to your hearts content :)

Currently rated 4.0 by 1 people

  • Currently 4/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

A Context Menu Control Extender

An entry about asp.net | ajax | javascript Publication date 4. August 2007 13:19

One of the things I've been missing in the Ajax Control Toolkit, is a context menu extender - so I figured I'd write one myself :) Turns out it was much easier than I anticipated, and after about an hour or so I had something that works fairly well. Basically, it allows you to do something like this:

<asp:Panel
    ID="_context"
    runat="server"
    Style="background-color:#cecece; width:250px; height:250px">            
Context area            
</asp:Panel>
<asp:Panel
    ID="_menu"
    runat="server" 
Style="border:solid 1px black; background-color:White; padding:4px;">
    My Context menu!
</asp:Panel>
<iridescence:ContextMenuExtender
    ID="_cmExt"
    runat="server"
    TargetControlID="_context"
    ContextMenuControlID="_menu" /> 

The extender will then make the TargetControl float wherever the mouse is located when you right-click anywhere inside the configured ContextMenuControl.

You can get the source code at the bottom of this post - below I'll just go through the javascript code that makes it work. As I said before when talking about control extenders, I will assume that you know how to write them - if you don't, check out this tutorial for a better introduction than one I could include here :)

To hook things up, we override the initialize function:

initialize : function() 
{
Iridescence.Ajax.ContextMenuBehavior.callBaseMethod(this, 'initialize');
this._contextElement = this.get_element();
this._menuElement = $get(this._contextMenuControlID);
// style the context menu
    this._menuElement.style.display = 'none';
this._menuElement.style.position = 'absolute';
// attach event handlers
    this._onMouseDownHandler = Function.createDelegate(this, this._onMouseDown);
this._onDocumentContextMenuHandler = Function.createDelegate(this, this._onDocumentContextMenu);
this._onDocumentClickHandler = Function.createDelegate(this, this._onDocumentClick);
$addHandler(this._contextElement, 'mousedown', this._onMouseDownHandler);  
$addHandler(document, 'contextmenu', this._onDocumentContextMenuHandler);   
$addHandler(document, 'click', this._onDocumentClickHandler);
}

Here we simply get references to the Target and ContextMenuControls, and then add handlers for the events that we need.

First of all, we need to capture the mousedown event of the context area, so that we may show the context menu when the user right clicks inside it. This is handled by the _onMouseDown method:

_onMouseDown : function(e)    
{                
if (e.button == 2)
{                   
// calculate current mouse position            
        var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; 
var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft; 
// and move context menu there
        this.__menuElement.style.left = e.clientX + scrollLeft + 'px';
this.__menuElement.style.top = e.clientY + scrollTop + 'px';
this.__menuElement.style.display = '';
// set flags
        this._menuVisible = true;
this._menuJustShown = true;            
}
}

This method checks whether the right mouse button was clicked, and if so we need to figure out the current mouse position (offset by the scroll position), and position the context menu element accordingly, before showing it.

Normally, a right click would cause the browsers context menu to be displayed; we dont want that, as it would hide our custom context menu. Thus we've also hooked up to the contextmenu event of the document element, which is handled by the _onDocumentContextMenu method:

_onDocumentContextMenu : function(e)
{        
if(this._menuJustShown)
{
// when our custom context menu is showing, we want to disable the browser context menu
        e.preventDefault();
this._menuJustShown = false;
}
else if(this._menuVisible)
{        
// user right-clicks anywhere while our custom context menu is visible; hide it
        this._hideMenu();
}
}

Here, if our custom context menu was just shown, we prevent the browsers context menu from displaying by calling the preventDefault() method on the event arguments object.

The last functionality we need, is to be able to hide the context menu when the user clicks anwhere outside it after it has been shown. Above, we've solved that for when the user right-clicks - we also need to include a left-click solution. We do this by handling the click event of the document element, which is handled by the _onDocumentClick method:

_onDocumentClick : function(e)
{                                   
if(this._menuVisible && e.button != 2)
{
// user left-clicked anywhere while custom context menu is visible; hide it
        this._hideMenu();
}
}

The _hideMenu() function that both these calls is very simple - it just hides the context menu element and sets the _menuVisible flag accordingly:

_hideMenu : function()
{    
this._menuElement.style.display = 'none';
this._menuVisible = false;
}

And thats it! The only thing left is to clean up after us, which we do by overriding the dispose method:

dispose : function()     
{
// clean up
    $removeHandler(this._contextElement, 'mousedown', this._onMouseDownHandler);  
$removeHandler(document, 'contextmenu', this._onDocumentContextMenuHandler);   
$removeHandler(document, 'click', this._onDocumentClickHandler);
Iridescence.Ajax.ContextMenuBehavior.callBaseMethod(this, 'dispose');
}

Now this is a fairly simplistic context menu extender - one might want to add asynchronous loading and maybe animation support at some point - but still, it's quite impressive how simple the ASP.NET Ajax API makes it to write powerful, reusable components with very little code. Gotta love it :)

Download the complete source code here. (Updated 1. December 2007 - fixed a few bugs in the script).

Update 7. December 2007 -  Be sure to check out this post, which shows how to add animation support to the ContextMenuExtender.

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Ajax Extender for Identifying External Links

An entry about asp.net | ajax Publication date 4. April 2007 20:46

.NET slave Mads Kristensen wrote a nice post on how to identify external links using JavaScript a few days ago, over on his blog. I had been meaning to implement something similar here, and his post gave me the much needed nudge in the rigth direction - as you can see from the nice little icon next to the link to his post - but this link, which is local, does not have it. Awesome :)

I decided to take the idea a bit further than simply embedding a javascript into my pages, and implemented it as an ASP.NET Ajax ControlExtender instead. The control, which I've called ExternalHyperLinkExtender, has the following properties to configure it:

ExternalLinkCssClass
Lets you specify the CSS class that should be applied to external links. Typically, this will be a class that looks something like this:

.ExternalLink 
{
background: url(Icons/ExternalLinkIcon.png) right top no-repeat;
padding-right: 9px;
}

LocalHyperLinkRegex
Lets you specify a regular expression pattern that the extender will then use to identify local links - any link that does not match the pattern will get the ExternalLinkCssClass applied to it. If you do not specify this property, the extender will attempt to determine if the link is external by doing a string compare with the path of the document it is on instead (much like Mads original script does).

AddTargetIfNotSet
If you set this to true, the extender will set the target attribute of the hyperlink to '_blank', if it has not already been set.

A usage example then, would look something like this (taken from the code that renders the very text you are reading now):

<asp:Label
    ID="_text"
    runat="server"
    Text='<%# Eval("Text") %>' />
<iridescence:ExternalHyperLinkExtender
    ID="_textExtender"
    runat="server"
    AddTargetIfNotSet="true"
    LocalHyperLinkRegex="^http://blog\.iridescence\.no"
    ExternalLinkCssClass="ExternalLink"
    TargetControlID="_text" />

I wont go into the details of how to write an ASP.NET Ajax ControlExtender - you'll find a good walkthrough covering that over at the official ASP.NET site, but below is the javascript code that is the heart of the extender:

initialize : function() 
{
Iridescence.Ajax.ExternalHyperLinkBehavior.callBaseMethod(this, 'initialize');
// get the element we're extending
    var element = this.get_element();
// get all the anchors that are children of the element
    var links = element.getElementsByTagName('a');
for(var index in links)
{
// for each anchor, find any that link off-site
        var link = links[index];     
if(link.href && link.innerHTML && link.innerHTML.length > 0)
{       
var externalLinkFound = false;
if(this._LocalHyperLinkRegex != null && this._LocalHyperLinkRegex.length > 0)
{            
// use regex to test
                var regex = new RegExp(this._LocalHyperLinkRegex);                
externalLinkFound = !link.href.match(regex);                
}
else            
{
// do a simple string comparison to test
                externalLinkFound = !link.href.startsWith(document.location.href.substring(0, 13));
}
// if we found an external link...
            if(externalLinkFound)
{
if(this._AddTargetIfNotSet && !link.target)
{
// ...set target 
                    link.target = '_blank';     
}
if(this._ExternalLinkCssClass)
{
// ...add css class
                    link.className += " " + this._ExternalLinkCssClass;
}
}                                
}
}   
}

Finally, I'll leave you with this little nugget of sexyness: When all the major browsers (come on, IE team!) gets CSS3 support, we could solve this in a much more elegant way by using the CSS3 Selectors, as described in this post. Now that looks awesome :)

 

Update 17/6 2007

So I finally got around to fixing the bug that prevented this from working in IE6 - basically IE6 always returned 'undefined' for link.text, causing my script to skip all links. I've changed it to link.innerHTML now :)

Be the first to rate this post

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Powered by BlogEngine.NET 1.4.5.0

Welcome!

My name is Fredrik Kalseth, and this is my blog - thanks for visiting! I am fortunate enough to work with what I love for a living, and this blog is essentially the biproduct of that.

I work as a senior consultant for Capgemini, and am also an active participant in the Norwegian .NET community, as an avid attendee but also as a speaker (most recently at NNUG and MSDN Live).

As a developer, I have a wide circle of interest. My primary passion is for agile, test-driven development, with focus on best practices and clean code. That said, I also love to work on the frontend, especially with web development.

On Twitter? My handle is fkalseth. On LinkedIn? I`m there too.

NDC 2010

The conference to attend this summer happens June 16th-18th in Oslo, Norway. Are you going? Be sure to catch my talk on AOP while you're there!

 

Disclaimer

This is a personal blog; any opinions expressed here are my own and do not necessarily reflect those of my employer. All content herein is my own original creation, and as such is protected by copyright law. Unless otherwise stated, all source code posted on this blog is freely usable under the Microsoft Permissive License.

What Readers Talk About

Comment RSS