About Us

Working with events in jquery 1.7

by Travis Ellis 19. June 2012 00:22

jQuery is a great javascript library that makes it easy to do many common tasks in a web application. One of the most frequent things you need to do is wire up events to your DOM elements. Prior to jQuery 1.7, you had a few different ways to accomplish this:

Option 1: Using the Bind method

<script type="text/javascript">
    $("a.add").bind("click", function(event) {
        alert("You clicked a link.");
    });
</script>

The above code will attach a click event handler to all anchor elements that have the class “add”. Similar to this method, jQuery provides some helper methods to attach these events in a more concise syntax. When attaching the '”click” event, you can use the click() method, etc.:

<script type="text/javascript">
    $("a.add").click(function(event) {
        alert("You clicked a link.");
    });
</script>

Option 2: Using Live/Delegate

The problem with using the bind method is that it does not add event handlers to dynamically added DOM elements. In order to do this you have to use the live/delegate syntax:

<script type="text/javascript">
    $("a.add").live("click", function(event) {
        alert("You clicked a link.");
    });

    $("#parent").delegate("a.add", "click", function(event) {
        alert("You clicked a link.");
    });
</script>

This attaches a click event handler to all anchor elements that have the class "add" similar to bind. The difference, however; is that any elements that are added to the DOM dynamically will also have the event handler attached. To demonstrate the problem you can visit the following pages (you may need to click the Run button to run the example):

Adding events with the bind method
Adding events with the live function.
Adding events with the delegate function.

You’ll notice that in the first example, when you click the initial paragraph it will add another paragraph directly after it, however; if you click one of the dynamically created paragraphs they will not add additional paragraphs. This is different from the second example where clicking on the dynamically generated paragraph will also cause a new paragraph to be created. I am using delegate in this example but it also works with the live method. The only difference between delegate and live is performance. The live method will search the entire DOM for elements where as the delegate method can search specific parts of the DOM using a selector.

Option 3: Using the on method in jQuery 1.7

So why is there a need for another way to wire up events? Simplicity. In jQuery 1.7 they have made it even easier to do. They have added a new method that will allow you to wire up events like bind and live/delegate all using a single method. Instead of having to remember the syntax of three different methods, you can now use the following:

<script type="text/javascript">
    $("#parent").on("click", "a.add", function(event) {
        alert("You clicked a link");
    });
</script>

This syntax is very similar to live and it works just like delegate, but it performs better than both of them. There is also a corresponding method called off that you can use to disable the events:

<script type="text/javascript"> 
    $("#parent").off("click", "a.add");
</script>

Adding events with the on method

Creating a Cascading Dropdown in ASP.net MVC 3 and jQuery

by Travis Ellis 5. August 2011 09:00

One of the common tasks that comes up when developing web applications is working with dependent data. If you have a form in your application that asks the user to select the Make and Model of their car, then you need to refresh the list of Models every time the Make changes. In ASP.net Web Forms this was commonly done by setting the AutoPostBack property of the DropDownList control and creating a SelectedIndexChanged event handler. The problem with this approach is that the page does a full postback, which can often be inefficient and can appear slow to the user. Fortunately, a better method exists in ASP.net MVC that is easy to accomplish with jQuery.

In order to show you how to create a cascading dropdown I am going to create a very simple web application. It will have a single page that will have two dropdowns, one for a list of States and one for a list of Cities. The list of Cities will change whenever the State changes. Here is the main controller for our application:

public class HomeController : Controller
{
    private ILocationRepository locationRepository = new LocationRepository();

    public ActionResult Index()
    {
        var model = new IndexViewModel();

        model.AvailableStates = new SelectList(locationRepository.GetStates(), "Abbreviation", "Name");
        model.AvailableCities = new SelectList(locationRepository.GetCities(), "Id", "Name");

        return View(model);
    }
}

public class IndexViewModel
{
    public IndexViewModel()
    {
        AvailableStates = new SelectList(Enumerable.Empty<State>(), "Abbreviation", "Name");
        AvailableCities = new SelectList(Enumerable.Empty<City>(), "Id", "Name");
    }

    public string State { get; set; }
    public int City { get; set; }

    public SelectList AvailableStates { get; set; }
    public SelectList AvailableCities { get; set; }
}

The view for our page will be extremely simple. It will consist of the two dropdown lists needed to display the data and a bit of javascript used to retrieve the data.

@model CascadingDropdowns.Models.IndexViewModel
@using (Html.BeginForm())
{
    <div class="editor-label">
        @Html.LabelFor(m => m.State)
    </div>
    <div class="editor-field">
        @Html.DropDownListFor(m => m.State, Model.AvailableStates, new { style = "width: 150px" })
    </div>
    
    <div class="editor-label">
        @Html.LabelFor(m => m.City)
    </div>
    <div class="editor-field">
        @Html.DropDownListFor(m => m.City, Model.AvailableCities, new { style = "width: 150px" })
    </div>
    
    <p>
        <input type="submit" value="Submit" />
    </p>
}

I am holding off on posting the javascript code until I post the code to retrieve the locations. The view is pretty simple. It defines a label and a dropdown for both the State and City property of our model. It uses the AvailableStates and AvailableCities properties as the data source for the dropdowns. It also contains a Submit button to POST the form, but this example won't be doing anything when the form is posted.

Here is the controller we will use to retrieve our location data. It has two action methods that both return JSON formatted data. The first method will return a list of all states. The other method will retrieve all of the cities based on the state abbreviation that is passed in.

public class LocationsController : Controller
{
    private ILocationRepository locationRepository = new LocationRepository();

    [HttpPost]
    public ActionResult States()
    {
        var states = locationRepository.GetStates();
            
        return Json(new SelectList(state, "Id", "Name"));
    }

    [HttpPost]
    public ActionResult Cities(string abbreviation)
    {
        var cities = locationRepository.GetCities(abbreviation);

        return Json(new SelectList(cities, "Abbreviation", "Name")); 
    }
}

Here is the corresponding repository class that will retrieve the data. I am going to hard code the data to simplify the example, although it would be easy to hook up this code to a database using your favorite data access tool such as Entity Framework or LINQ to SQL.

public class LocationRepository : ILocationRepository
{
    public IQueryable<State> GetStates()
    {
        return new List<State>
        {
            new State { Abbreviation = "NE", Name = "Nebraska" },
            new State { Abbreviation = "NC", Name = "North Carolina" }
        }.AsQueryable();
    }

    public IQueryable<City> GetCities(string abbreviation)
    {
        var cities = new List<City>();
 
        if (abbreviation == "NE")
        {
            cities.AddRange(new List<City> {
                new City { Id = 1, Name = "Omaha" },
                new City { Id = 2, Name = "Lincoln" }
            });
        }
        else if (abbreviation == "NC")
        {
            cities.AddRange(new List<City> {
                new City { Id = 3, Name = "Charlotte" },
                new City { Id = 4, Name = "Raleigh" }
            });
        }

        return cities.AsQueryable();
    }
}

public interface ILocationRepository
{
    IQueryable<State> GetStates();
    IQueryable<City> GetCities(string abbreviation);
}

Now that we have a controller action that will return the data we need, it is time to wrap it all up with our jQuery code. You might have noticed that our controller actions have been marked with the [HttpPost] attribute. This will prevent the browser from caching the data, and MVC doesn't allow JSON GET requests by default. Here is the jQuery code:

<script type="text/javascript" src="/Scripts/jquery-1.5.1.min.js"></script>
<script type="text/javascript">
    function getCities(abbr) {
        $.ajax({
            url: "@Url.Action("Cities", "Locations")",
            data: {abbreviation: abbr},
            dataType: "json",
            type: "POST",
            error: function() {
                alert("An error occurred.");
            },
            success: function(data) {
                var items = "";
                $.each(data, function(i, item) {
                    items += "<option value=\"" + item.Value + "\">" + item.Text + "</option>";
                });

                $("#City").html(items);
            }
        });
    }

    $(document).ready(function(){
        $("#State").change(function() {
            var abbr = $("#State").val();

            getCities(abbr);
        });
    });
</script>

That is all the code that is required to get a cascading dropdown. The jQuery code sets up an event handler that will run whenever the selected item is changed, making an AJAX call that is expecting JSON formatted data. When the AJAX call successfully completes it iterates over the collection of objects building an <option> element for each one and sets the HTML of the dropdown to the new list of items. It would be trivial to replace the code in the repository to retrieve data from a database as well, but I will leave that as an exercise for the reader.