Posts tagged JQuery

ASP.NET MVC JQuery ScriptManager

As I was developing a CMS system for a client, we had a very unique scenario (well maybe not that unique) where we had custom “parts” which you could add to a page. The “parts” were highly customisable, thus you could have 3 instances of the same “part” on one page. The html and javascript rendered by the part would conflict with each other, because they had the same id’s and registered events.

I needed something similar to the old ASP.NET Script manager and the way that old ASP.NET used the “clientId” instead of the “htmlId” when dealing with html objects. The “clientId” was a generated unique key that prevented duplicated user controls conflicting with one another.

I found quite a few implementations of ScriptManager (http://pietschsoft.com/post/2009/08/13/Simple-ScriptManager-for-ASPNET-MVC.aspx, http://aspmvccombine.codeplex.com/) for MVC, but none of them were solving my unique “htmlId” problem. If you are looking for a ScriptManager for MVC, then the two solutions I provided would be more than enough. If, however, you are in a similar situation as I am with the html controls, please read on…

I decided to combine knowledge obtained from the two ScriptManager implementations, and create my own light-weight solution addressing both problems.
Because I wanted the usage of this to be very simple, I created a fluent interface which I can use in all my parts…

This is what the end result looks like:

<% Html.ScriptManager()
     .CreateClientScript(GetUniqueId())
       .AddParam("SelectId", GetUniqueId("selectList"))
       .AddParam("SourceUrl", Url.Action("Index", "Person"))
       .Ready(() => {%>
            $.getJSON(params.SourceUrl, function(result) {
                $.populate_combobox(result, params.SelectId);
            });
      <%});%>

<% Html.ScriptManager().AddStaticMethod("populate_combobox", () =>{ %>
    jQuery.populate_combobox = function(data, selectId) {
        $.each(data, function() {
            var option = new Option(this.text, this.value);
            var dropdownList = $(selectId)[0];
            if ($.browser.msie) {
                dropdownList.add(option);
            } else {
                dropdownList.add(option, null);
            }
        });
    }
<%});%>

<div id="<%=GetUniqueId() %>_part">
 <select id="<%=GetUniqueId("selectList") %>"></select>
</div>

My “GetUniqueId()” function returns a combination of the PartId, PageId, but you can replace this with anything you fancy.
The javascript that gets generated by this is:

var ContentPart_AboutPage_82 = {
        Init: function(params) {
            $.getJSON(params.SourceUrl, function(result) {
                $.populate_combobox(result, params.SelectId);
            });
        }
}
var ContentPart_AboutPage_83 = {
        Init: function(params) {
            $.getJSON(params.SourceUrl, function(result) {
                $.populate_combobox(result, params.SelectId);
            });
       }
}
var ContentPart_AboutPage_84 = {
        Init: function(params) {
            $.getJSON(params.SourceUrl, function(result) {
                $.populate_combobox(result, params.SelectId);
            });
       }
}
$(document).ready(function() {
    ContentPart_AboutPage_82.Init({
       SelectId:'ContentPart_AboutPage_82_selectList',
       SourceUrl:'/Person'
    });
    ContentPart_AboutPage_83.Init({
       SelectId:'ContentPart_AboutPage_83_selectList',
       SourceUrl:'/Person'
    });
    ContentPart_AboutPage_84.Init({
       SelectId:'ContentPart_AboutPage_84_selectList',
       SourceUrl:'/Person'
    });
});

    jQuery.populate_combobox = function(data, selectId) {
        $.each(data, function() {
        var option = new Option(this.text, this.value);
        var dropdownList = $(selectId)[0];
        if ($.browser.msie) {
            dropdownList.add(option);
        } else {
            dropdownList.add(option, null);
        }
      });
    }

Update: I have gone even further and added the concept of “SharedVariables” to the ScriptManager where the “SourceUrl” above and even the Json request is fired only once!

Here is my ScriptManager Class:

public class ScriptManager
{
    private readonly HtmlHelper _helper;

    public IDictionary<string, ClientScript> ClientScripts
    {
       get
       {
          if (_helper.ViewContext.HttpContext.Items["ClientScripts"] == null)
          _helper.ViewContext.HttpContext.Items["ClientScripts"] = new Dictionary<string, ClientScript>();

          return (IDictionary<string, ClientScript>)_helper.ViewContext.HttpContext.Items["ClientScripts"];
       }
    }

    public IDictionary<string, Action> Methods
    {
      get
      {
         if (_helper.ViewContext.HttpContext.Items["ScriptMethods"] == null)
         _helper.ViewContext.HttpContext.Items["ScriptMethods"] = new Dictionary<string, Action>();

         return (IDictionary<string, Action>) _helper.ViewContext.HttpContext.Items["ScriptMethods"];
      }
    }

   public ScriptManager(HtmlHelper helper)
   {
       _helper = helper;
   }

   public ClientScript CreateClientScript(string key)
   {
      if(!ClientScripts.ContainsKey(key))
       ClientScripts.Add(key, new ClientScript());

      return ClientScripts[key];
   }

   public ScriptManager AddStaticMethod(string key, Action javascript)
   {
     if(!Methods.ContainsKey(key))
       Methods.Add(key, javascript);

     return this;
   }

   public void Render()
   {
     TextWriter writer = _helper.ViewContext.HttpContext.Response.Output;
     writer.WriteLine("<script type=\"text/javascript\">");

     foreach (var clientScript in ClientScripts.Keys)
     {
       writer.WriteLine("var " + clientScript + " = {");
       writer.WriteLine("        Init: function(params) {");
       ClientScripts[clientScript].Value();
       writer.WriteLine("        }");
       writer.WriteLine("}");
     }

     writer.WriteLine("$(document).ready(function() {");
     foreach(var clientScript in ClientScripts.Keys)
     {
       var client = ClientScripts[clientScript];
       writer.WriteLine("    " + clientScript + ".Init({");
       foreach (var param in client.Params)
         writer.WriteLine("       {0}:'{1}'{2}", param.Key, param.Value, IsLast(client.Params, param.Key) ? string.Empty : ",");
         writer.WriteLine("    });");
       }
     writer.WriteLine("});");
     foreach(var method in Methods.Values)
       method();
     writer.WriteLine("</script>");
   }

   private static bool IsLast(IDictionary<string, string> parameters, string key)
   {
     var keys = parameters.Keys.ToList();
     return keys.Count > 0 && keys[keys.Count-1] == key;
   }
}

Here is the ClientScript class:

public class ClientScript
{
   private readonly IDictionary<string, string> _parameters = new Dictionary<string, string>();
   private Action _script;

   public IDictionary<string, string> Params
   {
     get { return _parameters; }
   }

   public ClientScript AddParam(string key, string value)
   {
     Params.Add(key, value);
     return this;
   }

   public ClientScript Ready(Action script)
   {
     _script = script;
     return this;
   }

   public Action Value
   {
     get
     {
       return _script;
     }
   }
}

Here is my extension method:

public static ScriptManager ScriptManager(this HtmlHelper helper)
{
    return new ScriptManager(helper);
}

And then you only need to add this at the end of your master page:

<% Html.ScriptManager().Render(); %>

jLinq – LINQ for JSON

jLinq

Sometimes I stumble accross something and immediately think “where has this been all my life?”. Finding jLinq is one of those moments.
I love Language Integrated Query (LINQ) and have used it for the last couple of years. With LINQ2NHibernate, LINQ2Lucene, LINQ2Amazon and even LINQ2Twitter emerging, it is very clear that LINQ is here to stay.

Enter jLinq from Hugoware

$.getJSON("/Person/All", function(data) {
  var results = jLinq.from(data.users)
      .startsWith("first", "a")
      .orEndsWith("y")
      .orderBy("admin", "age")
      .select();
});

You can view the basics of jLinq from this screencast.
We used jLinq to solve a very complex join operation on our JSON object and found it to be a very simple task…

  var results = jLinq.from(data.users)
    .join(data.locations, "location", "locationId", "id")
    .equals("location.state", "texas")
    .orderBy("location.city")
    .select(function(r) {
    return {
       fullname:r.first + " " + r.last,
       city:r.location.city,
       state:r.location.state
    };
});

jLinq is also very extensible and creating your own extension methods couldnt be easier…

  jLinq.extend({
    name:"startsWithLetterP",
    type:"query",
    count:0,
    method:function(q) {
        return q.helper.match(q.value, /^p/);
    }}); 

//use the new method
var results = jLinq.from(data.users)
    .startsWithLetterP("first")
    .select();

So why wait another minute, head off to experiment here right now!

Update Form Fields with JSON result in ASP.NET MVC

Wouldn’t it be nifty to receive a JSON result object from AJAX and it automagically updates your form fields? Those who said “No” leave now!
I had to find a way to iterate through my JSON properties and, if I named the fields the same as the JSON properties, update them all in one go.

Lets use a simple “Person” model and have a look at the ASP.NET MVC action:

public ActionResult GetPerson(int id)
{
    var person = Context.GetPerson(id);
    return Json(new
                    {
                        success = true,
                        model = new
                                    {
                                        person.Id,
                                        person.Name,
                                        person.Surname,
                                        person.Age,

                                    }
                    });
}

As you can see this returns a DTO (data transfer object) as JSON result and is ready to be consumed via AJAX…

$.getJSON("/Person/1", function(data) {
    if (data.success) {
        $.updateForm(data.model);
    } else {
        alert("An error occurred");
    }
});

All we then need is the magical “$.updateForm” method that contains all the voodoo:

jQuery.updateForm = function(model) {
   // we iterate through all the properties
   for (var property in model) {
        // evaluate the expression to get the value
        var propertyValue = eval("model." + property);
        // and update the field
        $("#" + property).val(propertyValue);
    }
};

Currenty this will work fine for textboxes and textareas as this was all the functionality I needed for now. If anyone extends this for “select”, “checkbox”, “radiobutton” etc… please share with us so I can update this post.

Kaboom! – JQuery MVVM Framework for the web

Screenshot“Kaboom!” is one of those things you stumble upon and intrigues you from the start. It probably needs more work (not to mention documentation), but certainly gives you a peak at what I believe could be the next big thing in the ASP.NET MVC world. Having just finished a CMS system where I used a very rich JQuery client, I found this a very probable next step. Already using “jquery.forms” to submit all my forms via ajax POST and using “$.getJSON” for all my GET actions, my views started to get pretty lean. I thought, what if I had a JQuery ViewModel that would handle my UI commands and handle all my communication with my controller? A quick search lead me to “Kaboom!”.

The first thing I worried about was testability. “QUnit” (http://docs.jquery.com/QUnit) seems to be a very powerful testing framework that would probably give me more coverage than what I had before! UnitTesting – Check :)

So lets look at one of the samples in the codeplex download, specifically the Asp.Net MVC sample…

We start with a “Person” Model:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

And a “PersonController” class which has a Search action:

public class PersonsController : Controller
{
    public JsonResult Search(string searchString)
    {
        List persons = new List();
        persons.Add(new Person { FirstName = "Kozin", LastName = "Osot" });
        persons.Add(new Person { FirstName = "Setesyci", LastName = "Rynaugh" });
        persons.Add(new Person { FirstName = "Atheck", LastName = "Garash" });

        return Json(persons
            .Where(p => p.FirstName.ToLower().Contains(searchString.ToLower()) ||
                p.LastName.ToLower().Contains(searchString.ToLower())).ToList());
    }
}

Then we create a “SearchViewModel” js file and put this in our “ViewModels” folder like so:

var SearchViewModel = {
    Initialize: function(args, callback) {
        Kaboom.register("Search", SearchViewModel.Search);
        SearchViewModel.SearchResults = new Array();
        callback();
    },

    Ready: function() {
        SearchViewModel.Search();
    },

    SearchString: '',
    SearchResults: null,
    Search: function() {
        $.getJSON('/Person/Search',// You can set this as a hidden field on your View with 'Url.Action(..' and simply do $('#searchSource').val()
            { searchString: SearchViewModel.SearchString },
            SearchViewModel.PopulateSearchResults);
    },

    PopulateSearchResults: function(data) {
        SearchViewModel.SearchResults = data;
        Kaboom.notify(SearchViewModel, "SearchResults");
    }
}

And our “Search” view would hook up to our view model like so…

<head runat="server">
    <title>Search</title>
    <% string version = DateTime.Now.Ticks.ToString(); %>
    <script type="text/javascript" src="../../ViewModels/Persons/SearchViewModel.js?id=<%= version %>"></script>
    <script type="text/javascript" src="Scripts/jquery-1.3.2.js?id=<%= DateTime.Now.Ticks.ToString() %>"></script>
    <script type="text/javascript" src="Scripts/json2.js?id=<%= DateTime.Now.Ticks.ToString() %>"></script>
    <script type="text/javascript" src="Scripts/kaboom.js?id=<%= DateTime.Now.Ticks.ToString() %>"></script>

</head>
<body>
    Quick start shows how to communicate with an aspMVC controller.....<br />
    <input type="hidden" id="viewmodel" viewmodel="SearchViewModel" debug="0" />
    <div id="Debug"></div>
    Search:<br />
    <input type="text" bindto="SearchString" mode="TwoWay" /><br />
    <input type="button" value="Search" command="Search" />
    <br />
    <table bindto="SearchResults">
        <thead>
            <tr>
                <th>First Name</th>
                <th>Last Name</th>
                <th>Options</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>$FirstName</td>
                <td>$LastName</td>
                <td><a href="#" onclick="alert('$FirstName')">Delete</a></td>
            </tr>
        </tbody>
    </table>
</body>

You can bind all your view actions to commands.

<input type="button" command="Save" value="Bound to Save command via jQuery(element).click()" /><br /><br />
<input type="button" command="Save" trigger="dblclick" value="Bound to Save command via jQuery(element).dblclick()" /><br /><br />
<input type="button" command="Save" trigger="blur" value="Bound to Save command via jQuery(element).blur()" /><br /><br />
<input type="button" command="Save" trigger="customaction" value="Bound to Save command via jQuery(element).customaction()" /><br /><br />

… and it has support for other controls and more complex bindings.

<select bindto="Person.Salutation"
         datatextfield="Name"
         datavaluefield="Id"
         datasourceid="Salutations"
         onbind="ProgrammaticallyBindIt" >
   <option value="0">[select]</option>
</select>

There are loads of other examples, including binding to Tables, Divs, Spans, Checkboxes etc. You can download the framework here.

Get the contents of an IFrame via JQuery (Wymeditor)

I recently found myself in a scenario where I needed to copy the contents of an iframe to another. Madness you say? Hold on… there is a method to my madness. I am using Wymeditor to help manage my html fields/properties on a form. The client requested that the user should be able to import recently entered contents in a similar field on the same form. When these contents are “imported” only a certain amount of characters should be used. Almost like a short version (synopsis) of another field.

What does this have to do with Iframes I hear you ask? Well, Wymeditor uses an Iframe to box its functionality and mask it as a textarea.
After much searching I came up with the following:

$("#importFromFull").click(function(e) {
        e.preventDefault();
        var text = $("#postBodyTab .wym_iframe iframe")
            .contents()
            .find('body')
            .html();

        $("#longSynopsisTab .wym_iframe iframe")
            .contents()
            .find('body')
            .html(text.substr(0, 255));
    });

Thus, the function for retrieving the contents of any iframe is:

$("#myIframe")
            .contents()
            .find('body')
            .html();

(Please note, this will only work if both iframes are on the same domain. There are security issues for manipulating an iframe’s content)

Splitting your dynamic unordered list into columns

The easiest way to split your unordered list into columns is by assigning css classes.

<ul>
<li class="left">Item 1</li>
<li class="left">Item 2</li>
<li class="right_first">Item 3</li>
<li class="right">Item 4</li>
</ul>
.left {
margin-left: 0px;
}

.right {
margin-left: 50%;
}

.right_first {
margin-top: -40px;
}

If you know the size of the list then you can play around with the top margin of the “right_first” class. In our case, we do not know the size of the list, so we would have to do some black magic…

// First we calculate some variables
// the minimum column length (items / 2) at which to split into columns
var columnSplitLength = 7;
// the current would be column length
var currentColumnLength = Math.round(items.length / 2);
// is the items an odd or even length
var isOdd = (items.length > 2) != currentColumnLength;
// should we split the columns?
var shouldSplitColumns = currentColumnLength > columnSplitLength; 

var count = 1; var myLiHeight = 13; // height of each ListItem

$.each(items, function() {
    var className = shouldSplitColumns &&  (count > currentColumnLength) ? "right":"left";
    if(count == currentColumnLength+1)
        className = "right_first";
    var resultRow = $.stringFormat("<li class='{0}'>{1}</li>", [className, this.name]);
    $(resultRow).appendTo("#myUlList");
    count++;
});

// If the list length is odd, we have to add a phantom item to the right to balance
if(isOdd) {
 $("<li class='right'></li>").appendTo("#myUlList");
count++;
}
// Lastly, reset the first item of the right column to the top
$(".right_first").css("margin-left", "50%");
$(".right_first").css("margin-top", "-"+count*myLiHeight+"px");

Populating an ordered- or unordered list with JQuery via JSON

Loading dynamic data into an ordered- or unordered list cannot be simpler.

First we create a template for each LI row:

var listRow = "<li id=\"{0}\">{1}</li>";

Then we call the ajax method and handle the JSON results:

// Now using my $.stringFormat method
// (see tag 'string format')
// we append the template with the correct values to the ul or ol
$.getJSON("http://somedomain/getsomedata", function(data, status) {
   $.each(data, function() {
   var listRowPopulated = $.stringFormat(listRow, [this.id, this.title]);
      $(listRowPopulated).appendTo("#someul");
    });
});

JQuery function to populate a select box

Something you might be repeating while building a user interface is populating a select box with JSON results. Here is a quick demonstration on a very simple way to do this. Put the ‘populateSelect’ method in a global .js file and use it throughout your project as ‘$.populateSelect(args…)’

ASP.NET MVC Action Method

public ActionResult GetUsers()
{
     var data = Service.QueryUsers()
           .Select(x => new {value = x.Id, text = x.Name});

      return Json(data);
}

Usage:

var url = "<%=Url.Action("GetUsers", "UserController") %>";

$.getJSON(url, function(data, textstatus) {
        $.populateSelect("#someSelectBox", data);
});

Global Method:

// JQuery Method
jQuery.populateSelect = function(selectId, data) {
    $.each(data, function() {
        var option = new Option(this.text, this.value);
        var dropdownList = $(selectId)[0];
        if ($.browser.msie) {
            dropdownList.add(option);
        } else {
            dropdownList.add(option, null);
        }
    });
};

Removing all Items from a select list is just as simple:

$("#mySelectList")
   .find("option")
   .remove();

And lastly how to Append or Prepend items to the list:

// Add options to the end of a select
$("#mySelectList")
    .append("");

// Add options to the start of a select
$("#mySelectList")
    .prepend("");

Turn your hyperlinks into ajax calls

When designing a rich user interface with JQuery it is very useful to replace default hyperlink functionality with ajax calls. The first method will turn the <a> functionality into an ajax call using the “href” attribute as source. The second method will do the same, but will ask “Are you sure?” first (very usefull for deletes etc…).

The ajaxReload var can then be set to a function that would handle the ajaxCall. These methods would usually be located in a ‘common.js’ file.

// GLOBAL VARS
var ajaxReload = function() { };

$(".ajaxCall").live("click", function(e) {
    e.preventDefault();
    $.get($(this).attr("href"), ajaxReload);
});

$(".yesNoAjaxCall").live("click", function(e) {
    e.preventDefault();
    if (confirm('Are you sure?')) {
        $.get($(this).attr("href"), ajaxReload);
    }
});

The reason behind using ‘live’ instead of ‘bind’ or ‘click’ is because I want to bind all future loaded ‘a’ tags too. Usually ‘bind’ or ‘click’ will only find the static ‘a’ tags that are available on page load.

JQuery C# String.Format equivalent

When I am working with strings in JQuery I sometimes miss the ol’ “String.Format” in C#. I decided to create a quick global method that makes life just a little bit easier!

Usage:

$.stringFormat("{0} {1}!", ["Hello", "world"]);

Method:

jQuery.stringFormat = function(format, arguments) {
    var str = format;
    for (i = 0; i < arguments.length; i++) {
        str = str.replace('{' + i + '}', arguments[i]);
    }
    return str;
};