Friday, February 18, 2011

Using jQuery deferreds to cache ajax data

In this example I will demonstrate how to use jQuery deferreds to cache data from the server. This will provide the means to prevent ajax calls for data that has already been acquired. We will be making use of the jQuery.when() method. When we query the cache, our results may come from the server (first request) or from the cache (subsequent requests) and our query code will be the same for each.

Lets start with a simple example in which we have a page containing a drop down list with various categories:

<select id="categories">
<option value="1">Pikmin</option>
<option value="2">Reptiles</option>
<option value="3">Urchins</option>
</select>

When the user selects a category, a second drop down list will be populated with the values associated with the selected category. So, for instance, if the user selects "Pikmin" from the categories drop down list, we want to populate the "values" drop down list like this:

<select id="values">
<option value="1">Red</option>
<option value="2">Yellow</option>
<option value="3">Blue</option>
<option value="4">Purple</option>
<option value="5">White</option>
</select>

The set of Pikmin values may be considerably larger, so we decide to load the Pikmin values only when the user actually selects Pikmin from the category list. So for now in our html we'll start with an empty drop down list:

<select id="values"></select>

Now lets put together the jQuery code to initiate an ajax request for the values when the user selects a category:
$(function () {
    $("#categories").change(function () { LoadValues($("#categories").val()); })[0].selectedIndex = -1;
});

Here we tell jQuery to call the LoadValues() function whenever the categories drop down list is changed. We also set the selectedIndex property of the categories drop down list to -1 to indicate that nothing has been selected yet.

Lets take a look at the LoadValues() function now:
//Loads values for the selected category and populates the "values" select box with the results
function LoadValues(categoryId) {
    $.ajax({
        type: "POST",
        url: "Data.asmx/GetValues",
        data: JSON.stringify({ categoryId: categoryId }),
        contentType: "application/json",
        dataType: "json",
        success: function (msg) {
            MakeOptions(msg.d);
        }
    });
}

This ajax call sends the selected category id to the server to get the values back for a given category. Upon success, we pass the data to the MakeOptions() function which creates the option tags and places them in the values drop down list:
function MakeOptions(data) {
    //data looks like : [{ id: 1, value: 'Red' }, { id: 2, value: 'Yellow' }, { id: 3, value: 'Blue'} ...]
    var i, options = "";
    for (i = 0; i < data.length; i++) {
        options += "<option value='" + data[i].id + "'>" + data[i].value + "</option>";
    }
    $("#values").html(options);
}

Now, whenever the user selects a category, the values drop down list will be populated with the correct values for the selected category. The only problem here is that we may ask the server for the same data multiple times. For instance, if the user selects Pikmin, then selects Reptiles and then selects Pikmin again, this solution will ask the server for the Pikmin values again because we're not saving them anywhere. Let's change the LoadValues() function so that it stores data on the client once it's been fetched:
var CACHE = {};
function LoadValues(categoryId) {
    $.ajax({
        type: "POST",
        url: "Data.asmx/GetValues",
        data: JSON.stringify({ categoryId: categoryId }),
        contentType: "application/json",
        dataType: "json",
        success: function (msg) {
            CACHE[categoryId] = msg.d;
            MakeOptions(msg.d);
        }
    });
}

Now we've saved the data on the client. There are various ways we can determine whether the data we need is in the cache or not, but this is where we'll bring deferreds into play to see how useful they can be. We'll need to modify LoadValues() and our categories change handler. Lets start with LoadValues():
function LoadValues(categoryId) {
    var result = CACHE[categoryId];
    if (!result) {
        CACHE[categoryId] = $.ajax({
            type: "POST",
            url: "Data.asmx/GetValues",
            data: JSON.stringify({ categoryId: categoryId }),
            contentType: "application/json",
            dataType: "jsond",
            converters: {
                "json jsond"function (msg) {
                    return msg.hasOwnProperty('d') ? msg.d : msg;
                }
            },
            success: function (msg) {
                CACHE[categoryId] = msg;
            }
        });
        console.log(CACHE[categoryId]);
        return CACHE[categoryId];
    }
    return result;
}

Now we're looking in our cache for the data we want before we ask the server for it. If it's found in the cache, it gets returned. If it isn't found, we ask the server for the required data. Notice that I removed MakeOptions() from the success handler. That's because we now expect LoadData() to return the data (whether from cache or from the server), so we'll call MakeOptions() on the result of LoadData(). But wait! We're putting the jqXHR result of the ajax call into the cache where the data belongs. Furthermore, if we end up asking for the data from the server in the ajax call, the return value is the jqXHR. We can't pass that to MakeOptions(), can we? No, we can't but let's take a look at the modified change handler to get the full picture of what's going on:
$(function () {
    $("#categories").change(function () {
        $.when(
            LoadValues($("#categories").val())
        ).then(function (values) {
            MakeOptions(values)
        });
    })[0].selectedIndex = -1;
});

Here we can see deferreds in action. In $.when we make a call to LoadValues(). $.when waits for all its ajax methods to complete and then calls .then(). So let's walk through this. On our first call to LoadData() for Pikmin, LoadData sees that there is nothing in the cache. It then sends the ajax request and puts the resulting jqXHR into the cache. Now $.when sits around waiting for the ajax call to complete. Once it does, .then() is called with the data returned from the server as a parameter. In the ajax success handler, we store the data in the cache so that on any subsequent call to LoadData(1) the data is found and returned immediately without going to the server for it.
So why do we store the jqXHR object returned by our call to $.ajax()? The way deferreds work is that we can queue up additional callbacks on the deferred and they will all execute in order once the deferred finishes. So let's say that there's another place in our page which requires the Pikmin values. When we click a button, we want to display an unordered list of Pikmin values. If the server method to fetch the Pikmin values is long running, our user may click on this button while waiting for the values drop down list to be populated. If it's taking a while to load our Pikmin values, we certainly don't want to do it twice! So we use a deferred again for the button click:
function MakeUl(data) {
    var i, options = "";
    for (i = 0; i < data.length; i++) {
        options += "<li>" + data[i].value + "</li>";
    }
    $("#Pikmin").html(options);
}

$(function () {
    $("#categories").change(function () {
        $.when(
            LoadValues($("#categories").val())
        ).then(function (values) {
            MakeOptions(values);
        });
    })[0].selectedIndex = -1;

    //use another deferred for the button click:
    $("#loadPikmin").click(function () {
        $.when(
            LoadValues("1")
        ).then(function (values) {
            MakeUl(values);
        });
    });
});

So what does LoadData() do when the user clicks the button? It checks the cache and since we've already started an ajax call to the server, it finds and returns the jqXHR object from our previous ajax call. This causes the $.when from the button click to be enqueued behind the ajax call so that when the ajax call returns, the data can be handled by both MakeOptions() and MakeUl() without the need to talk to the server a second time.

Live demo

Wednesday, December 22, 2010

Animated scrolling background with parallax using jQuery

I wanted to distinguish the author comments on my blog in an interesting way. Here's what I came up with:


Click the image to see it live

Notice in the live view that there is a parallax effect: some of the clouds appear to float by closer than others. This is accomplished using two images with one on top of the other. The images used are these:
Background:
Transparent overlay:

Implementation

Two css classes are used, one for each image:
.byAuthor
{
color: Black;
background-image: url("http://lh4.ggpht.com/abbrev_path/sky.png");
padding: 5px;
}

.byAuthorOverlay
{
background-image: url("http://lh4.ggpht.com/abbrev_path/sky2.png");
}

Eventually our comments will take the form of two nested divs, each given one of the classes defined above with the overlay on top. For example:
<div class="byAuthor">
<div class="byAuthorOverlay">
Here is the text of the comment
</div>
</div>

Next comes the javascript. The setInterval() method is used to call a function which changes the Y offset of the background image. This is done twice: once for each image.
function scrollBackground(options) {
options.step = options.step || 1; //step property defaults to 1
options.top = options.top === undefined || Math.abs(options.top) >= options.bgImgHeight ? 0 : options.top - options.step;
options.$elements.css("background-position", "0 " + options.top + "px");
}

var mainBg = { speed: 30, bgImgHeight: 400 };
setInterval(function () { scrollBackground(mainBg); }, mainBg.speed);

var overlayBg = { speed: 50, bgImgHeight: 400 };
setInterval(function () { scrollBackground(overlayBg); }, overlayBg.speed);

scrollBackground() takes an object named "options" as an argument. Here is the definition of this object:
options = {
delta: 1, //Y delta - number of pixels by which to change the background-position each time
speed: 30, //time interval between animation steps (milliseconds)(lower is faster)
bgImgHeight: 400px, //height of the image being scrolled
$elements: $(".byAuthor") //a jquery set of elements to apply css changes to
}

setInterval() causes scrollBackground() to be called every X milliseconds. scrollBackground() calculates a new Y value for the background image and applies it. Notice that the "speed" values for each of the images is different. This causes the overlay image to scroll a little faster than the background image providing the parallax effect.

Applying to blogger

Getting this to work on blogger requires understanding of jQuery and the DOM. Because templates differ, you'll need to examine how blogger displays your comments, taking note of the DOM elements surrounding them. In my case, the DOM looks like this (trimmed for brevity):
<dt class="comment-author blog-author">Joel said... </dt>
<dd class="comment-body">
<p>
Comment Text
</p>
</dd>

I want to enclose the comment text within the two nested divs mentioned above. This is simple using jQuery:
    $(".blog-author").next().find("p").wrap('<div class="byAuthorOverlay" />');
$(".byAuthorOverlay").wrap('<div class="byAuthor" />');

That results in the following DOM layout:
<dt class="comment-author blog-author">Joel said... </dt>
<dd class="comment-body">
<div class="byAuthor" >
<div class="byAuthorOverlay" >
<p>
Comment Text
</p>
</div>
</div>
</dd>

And now, with both setInterval() functions executing at the prescribed intervals, the background images for the new divs appear to scroll upward.

Thursday, October 21, 2010

Python Import Search Order/Precedence

The Problem


I recently ran into an interesting (and very frustrating error). The error was:
ImportError: No module named apps.core.models
This error was happening to me in a django project. The package structure was as follows:

foo/
apps/
core/
models.py
auth/
foo/
models.py
views.py


In views.py I had the import statement:
from foo.apps.core.models import AModel

This was very confusing to me. Even worse was that the exact same statement worked just fine from the apps.auth.foo.models module.

The Explanation


The problem here has to do with how python handles searching for modules. You can read about it here.
To summarize python searches (in this order):
  1. The folder of the current module.
  2. Each folder listed in the PYTHONPATH.
  3. System specific folders ("set path", "export path", etc).

The Solution


The real problem I was running into was that the current folder had a foo module. So the full name that it was actually looking in was foo.apps.auth.foo.apps.core.models. To solve this I just needed to either change the package names (which really may be the best idea), or change the import statements to use a relative import path.

Something like this:
from ..core.models import AModel


Happy pythoning!

Wednesday, September 01, 2010

Using Mercurial Subrepositories

If you have any class libraries whose usage spans multiple projects, you will want to include them as subrepositories in your Mercurial repository. This allows you to have the library stored in its own repository rather than copying it into each individual project that requires it. This way, changes to the library can be reflected in all projects using the library. This article shows how to accomplish that task.

Let's say you have a class library named ClassLibrary1 which contains methods useful to several projects. Here is the code for ClassLibrary1
    public class Class1
    {
        public static string MyFunction()
        {
            return "ClassLibrary1 Version 1.0";
        }
    }

The library resides in a mercurial repository located at
\\hgStore\c\hgMain\Libraries\ClassLibrary1

After creating a new project, we set up the mercurial repository on it.
F:\My Projects\CoolApp>hg init

F:\My Projects\CoolApp>hg add
adding src\CoolApp.sln
adding src\CoolApp\CoolApp.csproj
adding src\CoolApp\Form1.Designer.cs
adding src\CoolApp\Form1.cs
adding src\CoolApp\Program.cs
adding src\CoolApp\Properties\AssemblyInfo.cs
adding src\CoolApp\Properties\Resources.Designer.cs
adding src\CoolApp\Properties\Resources.resx
adding src\CoolApp\Properties\Settings.Designer.cs
adding src\CoolApp\Properties\Settings.settings

F:\My Projects\CoolApp>

Now we clone the library into our CoolApp project repository.
F:\My Projects\CoolApp>hg clone \\hgStore\c\hgMain\Libraries\ClassLibrary1 src\ClassLibrary1
updating to branch default
9 files updated, 0 files merged, 0 files removed, 0 files unresolved

The class library is now located in the project directory structure where we want it. So now we can mark that directory as being a subrepository. We do that by creating a special file called ".hgsub".
F:\My Projects\CoolApp>echo src\ClassLibrary1 = \\hgStore\c\hgMain\Libraries\ClassLibrary1> .hgsub

In this case "src\ClassLibrary" is the path in our working directory, and of course "\\hgStore\c\hgMain\Libraries\ClassLibrary1" is the path to pull from.

Now add our special .hgsub file to version control:
F:\My Projects\CoolApp>hg add .hgsub

And commit:
F:\My Projects\CoolApp>hg commit --message="Set up subrepository for ClassLibrary1"
committing subrepository src\ClassLibrary1

F:\My Projects\CoolApp>

We're all set. Now if we clone our CoolApp project, it will include the ClassLibrary1 dependency.
F:\My Projects\>hg clone CoolApp CoolApp2
updating to branch default
pulling subrepo src\ClassLibrary1 from \\hgStore\c\hgMain\Libraries\ClassLibrary1
requesting all changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 14 changes to 9 files
12 files updated, 0 files merged, 0 files removed, 0 files unresolved

An important consideration when working with subrepositories is whether reverting to a specified revision of the project will also revert the library to the revision at the specified time.

Let's change the output of ClassLibrary1 and commit our changes.

    public class Class1

    {

        public static string MyFunction()

        {

            return "ClassLibrary1 Version 1.1"; //updated version

        }

    }


F:\My Projects\CoolApp2>hg commit --message="Changed output of ClassLibrary1"
committing subrepository src\ClassLibrary1

F:\My Projects\CoolApp2>hg push
pushing to f:\My Projects\CoolApp
pushing subrepo src\ClassLibrary1 to \\hgStore\c\hgMain\Libraries\ClassLibrary1
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files

Uh-oh. Someone thinks they found a bug in Version 1.0 of CoolApp. In order to duplicate the bug so we can understand it, we need to revert to the reported version. If our subrepository doesn't also revert to the state of ClassLibrary1 at the time of Version 1.0 we're going to have a mess on our hands. Let's try it and see what happens:
F:\My Projects\CoolApp2>hg log
changeset: 2:ef31b83178d1
tag: tip
user: Joel
date: Wed Sep 01 12:30:07 2010 -0600
summary: Changed output of ClassLibrary1

changeset: 1:a577f539790e
user: Joel
date: Wed Sep 01 12:21:30 2010 -0600
summary: Set up subrepository for ClassLibrary1

changeset: 0:d12383009bd2
user: Joel
date: Wed Sep 01 12:19:09 2010 -0600
summary: Initial revision


a577f539790e is the revision we want to work with.
F:\My Projects\CoolApp2>hg update a577f539790e
1 files updated, 0 files merged, 0 files removed, 0 files unresolved


Let's take a look at the code for ClassLibrary1

    public class Class1

    {

        public static string MyFunction()

        {

            return "ClassLibrary1 Version 1.0";

        }

    }


Perfect! It's just as we hoped.

Learn more about subrepositories in Mercurial
Have I missed something? Feedback is greatly appreciated.

Tuesday, May 18, 2010

Handy Linq/DataSet operations

Create a list from a field in the DataSet: (Query syntax)

List<uint> myList = (from row in aDataSet.Tables[0].AsEnumerable()
                     select row.Field<uint>("AColumnName")).ToList();

or: (Method Syntax)

List<uint> myList = aDataSet.Tables[0].AsEnumerable().Select(dataRow => dataRow.Field<uint>("AColumnName")).ToList();

or more generically...

IEnumerable<uint> myList = aDataSet.Tables[0].AsEnumerable().Select(dataRow => dataRow.Field<uint>("AColumnName"));



Create a Dictionary from two fields in the DataSet:

Dictionary<uint, uint> myDictionary = aDataSet.Tables[0].AsEnumerable()
    .ToDictionary(k => k.Field<uint>("KeyColumn"), v => v.Field<uint>("ValueColumn"));

Tuesday, February 23, 2010

Control.Invoke using Lambdas

Instead of using anonymous methods or delegates, you can now use a simple lambda expression. The default Control.Invoke() wont work so the best thing to do is write your own extension method. An example follows:

Extension Method


public static class ControlExtensions

{

    public static void Invoke(this Control Control, Action Action)

    {

        Control.Invoke(Action);

    }

}



Usage


this.Invoke(() => Text = "Refreshed");


Thursday, August 27, 2009

Abstract Class vs. Interface

While the differences between abstract classes and interfaces are varied, this is what helps me to remember when to use which:

An interface describes the peripheral abilities of a class, not its central identity.
An abstract class defines the core identity of its descendants.

source

Thursday, April 09, 2009

Referencing jQuery on a masterpage without breaking intellisense

If you are like me, you were pretty excited when Microsoft added support for jQuery to intellisense in visual studio. I am not going to go into how this is accomplished. If you would like details you can read Scotts blog here.

The Problem
When referencing jquery through a masterpage, you are vulnerable at runtime to the path of the content page. So if you keep your masterpages in a folder like "/Masters" but the content page you are using is in the root, a jquery path like this won't work:

<script src="../js/jquery-1.3.2.js" type="text/javascript"></script>

Intellisense works fine with this, but at runtime the path is wrong when using the content page in the root. To get the correct path at runtime we can do something like this:

<script src="<%= VirtualPathUtility.ToAbsolute("~/js/jquery-1.3.2.min.js") %>" type="text/javascript"></script>

With that reference we get the correct reference at runtime, but we get no intellisense.

The Solution
Until this problem is fixed by Microsoft, we need to use the best of both worlds. A static reference for the IDE, but a dynamic reference at runtime. To do this we use a condition on the reference that we don't want at runtime, like this:

<script src="<%= VirtualPathUtility.ToAbsolute("~/js/jquery-1.3.2.min.js") %>" type="text/javascript"></script>
<% if (this == null) { %>
    <script src="../js/jquery-1.3.2.js" type="text/javascript"></script>
<% } %>

You can replace the "this == null" with a simple "false" but you will get unreachable code warnings. You just need anything that always evaluates to false. This works great and has very little overhead in your page. I am happy that Microsoft added intellisense support for jQuery, now they just need to finish it so we don't have to use kludges like this.

Tuesday, April 07, 2009

'DropDownList' has a SelectedValue which is invalid because it does not exist in the list of items.

This error occurs when the SelectedValue property of a DropDownList is being set to a value which does not exist in the DropDownList. There are various reasons this can occur. In my case, the cause was that a record in the database was referring to a user configurable "category" that had been deleted by the user. Here are three possibilities for handling the scenario. They differ mostly in style, so pick the one that suits your taste.

All of these examples use a DropDownList set up like this:

<asp:DropDownList runat="server" ID="ddlCategory" DataSource='<%# GetCategories() %>'   
    DataTextField="Value" DataValueField="Key" AppendDataBoundItems="true">
    <asp:ListItem Text="(none)" Value="" />
</asp:DropDownList>

Option 1:



SelectedIndex='<%# Eval("CategoryID") is DBNull ? -1 : ddlCategory.Items.IndexOf(ddlCategory.Items.FindByValue(Eval("CategoryID").ToString()))  %>'



Here, I use the ternary operator to determine the index to select. If the database value is DBNull then -1 is used as the index, otherwise a search of the existing list items is performed to determine where in the list the desired value is. The great thing about this is that if the item is not DBNull but it's not in the list of items either, IndexOf() will return -1!

Option 2:



SelectedIndex='<%# GetSelectedCategoryIndex(ddlCategory, Eval("CategoryID")) %>'



This passes the DropDownList and the desired value to a method. There you can use the provided information to determine which index to use and handle any errors that may occur. The method should return an int value.

Option 3:


aspx:

OnDataBound=ddlCategory_DataBound



C#:
protected void ddlCategory_DataBound(object sender, EventArgs e)
{
    ddlCategory = sender as DropDownList;
    //set the selected index.  If the CategoryID is not in the list, set index to 0
    ddlCategory.SelectedIndex = 0;
    try
    {
        ddlCategory.SelectedValue = ((GetDataItem() as DataRowView).Row["CategoryID"]).ToString();
    }
    catch { }
}

Friday, February 13, 2009

Using 'esc' to close dynamic modal dialogs

In my previous post I described how to get a reference to a behavior when you know the target control (speaking in terms of the Ajax Control Toolkit extenders) of the extender. This is necessary to be able to call the hide() method of the ModalPopupExtender when the 'esc' key is pressed.

With this knowledge and jQuery we can get a handle on each of the ModalPopupExtender behaviors in a databound repeating control (such as GridView or Accordion.)

jQuery can be used to get a set of DOM objects by class. I want to get the set of DOM objects that represent the target controls of all the ModalPopupExtenders in the page. In order to facilitate this, I set the CssClass property of all the target controls to "mpeTarget". It's important to remember that it's possible to set multiple classes for the same element by separating them with a space, so if your target control already has a class assigned to it you can still add "mpeTarget". Your class or CssClass property would then look like CssClass="myExistingClass mpeTarget"

Now that all the target controls have been given a CssClass to distinguish them, we are ready to write some javascript.

        function pageLoad(sender, args) {
            if (!args.get_isPartialLoad()) {
                $addHandler(document, "keydown", onKeyDown);
            }
        }


This sets up the document to respond to the "keydown" event by calling a function named "onKeyDown" which is shown here:

        function onKeyDown(e) {
            if (e && e.keyCode == Sys.UI.Key.esc) {
                $(".mpeTarget").each(function() {
                    var mpe = Sys.UI.Behavior.getBehaviorsByType(this, AjaxControlToolkit.ModalPopupBehavior);                    
                    if (mpe.length > 0)
                        mpe[0].hide();
                });
            }
        }


This method checks to see if the pressed key is the 'esc' key. If it is, jQuery is used to iterate through all the DOM objects with css class "mpeTarget". Each of those DOM objects is used in a call to getBehaviorsByType() to get the behavior object and then call its hide() method.

Thursday, February 12, 2009

Getting a reference to a behavior

For my work I use databound repeating controls extensively. Of course, when using a repeating server control, child control ids are mangled by asp.net to prevent naming conflicts. To facilitate client side interaction I have begun using jQuery to get a reference to DOM elements whose names have been mangled by asp.net.

I wanted to be able to dismiss ModalPopupExtenders on my pages by pressing the escape key and the first obvious problem that manifested was that in order to call the hide() method of the ModalPopupExtender behavior, I had to have a reference to it. But because the ModalPopupExtender is a Component and not a DOM object, I cannot use jQuery to select it.

In my next post I'll demonstrate how I ultimately implemented the functionality I desired, but for now I merely wish to describe a couple of ways to get a reference to a behavior.

I used the Sys.UI.Behavior.getBehaviorsByType() method which works like this:

var mpeArray = Sys.UI.Behavior.getBehaviorsByType($get("btnCausePopup"), 
            AjaxControlToolkit.ModalPopupBehavior);


getBehaviorsByType() returns an array of behaviors. The first parameter it takes is the TargetControlID of the Ajax Control Toolkit control, or in other words, the control that is being extended by the behavior. Here's what my ModalPopupExtender looks like:

 <ajaxToolkit:ModalPopupExtender runat="server" ID="mpePopup" TargetControlID="btnCausePopup"
        PopupControlID="pnlPopup" />


The second parameter is the type of the Sys.UI.Behavior objects to find. Be sure to include the AjaxControlToolkit namespace. In this case the type is "AjaxControlToolkit.ModalPopupBehavior," not "AjaxControlToolkit.ModalPopupExtender." If you are not sure what the type is for the control you are using, one way to find it is to view the source of the page from the browser and search for the value of the TargetControlID. You will find something that looks something like this:

Sys.Application.add_init(function() {
    $create(AjaxControlToolkit.ModalPopupBehavior, {"PopupControlID":"pnlPopup","id":"mpePopup"},
       null, null, $get("btnCausePopup"));


Here, the bold text indicates the Type. Once you have the array of behaviors, iterate through the array to access each individual behavior, setting properties or calling methods at your leisure.

Another useful method in this regard is Sys.UI.Behavior.getBehaviorByName(). Its first parameter is the same as getBehaviorsByType. The second parameter is the name property of the behavior.

Wednesday, February 11, 2009

$get and $find - documented

Bookmark these links! The $get and $find client side APIs are notoriously difficult to find documentation for because their names do not lend well to googling due to the initial '$'. Here are direct links to their official documentation:

$get

$find

Also worth bookmarking is the main page of the asp.net client side reference

Wednesday, January 21, 2009

My button doesn't work (Validation in a databound repeating control)

If you include validation controls in a templated control (GridView, Repeater, Accordion) make sure to set the ValidationGroup property of the validation controls to something that will resolve to a unique value for each item. If you don't do this, buttons may appear to stop functioning as the validator they are connected with is not the one you are working with in the given row.

An example of setting the ValidationGroup to something unique to each row might be:

ValidationGroup='<%# String.Format("RecordEdit_{0}",Eval("RecordID")) >%'

Thursday, January 15, 2009

<%# Eval() %>

This post describes the difference between these two data binding constructs:

<%# Eval() %>
and
<%# DataBinder.Eval() %>

Friday, January 02, 2009

When I implemented a global.asax file and its Application_Error() event handler, I got a vague "File does not exist" error along with the error number -2147467259. This blog post helped me to discover the missing file. Essentially, add the following line to the watch list and set a break point in Application_Error() to see the missing file:

((HttpApplication)sender).Context.Request.Url

Friday, December 12, 2008

Page Loading Twice! or Data Binding Twice!

I often run into the problem of my data controls being databound twice. There are at least two causes.

1) The ObjectDataSource control has its EnableViewState property set to false. This results in the DataBind event firing twice.

2) An improperly defined html element on the page may be causing the browser to request the page multiple times. This results in the entire page lifecycle being executed repeatedly and thus resulting in multiple calls to DataBind. Two examples I have noted of improperly defined html tags are

  • An img tag with an empty src attribute
  • Others have mentioned that using the background attribute of a table cell tag (<td>) to set the color for the cell causes the browser to re-request the page. Use the bgcolor attribute instead.

Here are a couple of pages that discuss this problem.

Thursday, December 11, 2008

Login control default action problem

There is a bug in the Login control when you are using an image for the button, the default action will not be triggered. To overcome this, first wrap your Login control in a simple Panel:

<asp:Panel ID="LoginSubmitPanel" DefaultButton="" runat="server">
    <%--Login Control Here--%>
</asp:Panel>


Then add a load event to that panel to look for the unique id of the image button and assign the default action for this panel:

protected void LoginSubmitPanel_Load(object sender, EventArgs e)
{
    //Find all the controls we will need
    Login SideLogin = (sender as Login);
    Control LoginButton = (SideLogin.FindControl("LoginImageButton") as Control);
    Panel LoginSubmitPanel = (SideLoginView.FindControl("LoginSubmitPanel") as Panel);
    //We need the UniqueName under the proper context
    string btn = LoginButton.UniqueID.Remove(0, SideLoginView.UniqueID.Length + 1);
    //Assign the correct button as the default action        
    LoginSubmitPanel.DefaultButton = btn;
}


Now when you hit the enter key in the login control, the login action will be fired. Outside of the login control will follow regular default form submission.

Error list contains no line numbers

Warning! Visual Studio has a bug caused by the project path containing parentheses. It results in the error list not containing any line numbers. So after compiling, you get an error list, but you can't click the error to jump to the offending line. Not even the offending file is listed. All you can do is search your 10,000 lines of code for the error. This is particularly annoying when the error is "Semicolon expected".

By the way, a bug has been reported to the Visual Studio team and was marked "Closed (won't fix)." Thanks Microsoft!

Wednesday, December 03, 2008

Creating a Loading Page in ASP.NET

To display a waiting page while database or other lengthy work is done, you can use the Response.Write method to send text to the browser while keeping the HTTP connection alive. After your lengthy operation is complete, simply dump a javascript redirect, close the connetion and you are done.

For Example:
  Response.Write("<h3>Please wait...</h3>");
  Response.Flush();
  //Lengthy Operation
  string RedirectUrl = "<script language=javascript>window.location = \"done.aspx\";</script>";
  Response.Write(RedirectUrl);
  Response.End();

Wednesday, November 05, 2008

Changing ODS parameter at runtime causes double bind

changing a pagepropertyparameter value caused the ods to invalidate itself and forced a second database access
this was fixed by maintaining a local copy of a DatabaseAccess object and returning that on each subsequent property access (the first time, it's null and created from scratch)

Wednesday, October 01, 2008

Server tags reference

<% %> An embedded code block is server code that executes during the page's render phase. The code in the block can execute programming statements and call functions in the current page class.

<%= %> most useful for displaying single pieces of information.
CANNOT be used to assign values to properties (ie Text='<%= GetSomeText() %>' - this won't work) See here for an alternative.

<%# %> Data Binding Expression Syntax.

<%$ %> ASP.NET Expression.

<%@ %> Directive Syntax.

<%-- --%> Server-Side Comments.

Thursday, September 25, 2008

Javascript debugging

The community content on this page contains info on javascript debugging techniques

Monday, September 15, 2008

Could not load System.Web.Extensions

Could not load file or assembly 'System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35' or one of its dependencies. The system cannot find the file specified.


Solution: Download and install the Ajax library.

Friday, August 22, 2008

Failed to load viewstate

Error:
Failed to load viewstate. The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate during the previous request. For example, when adding controls dynamically, the controls added during a post-back must match the type and position of the controls added during the initial request.

One possibility is that the query that selects the count and the query that selects the fields are in disparity. The count query must return the number of rows that would be returned by an unlimited field query.

Thursday, August 21, 2008

The name xxx Does Not Exist in the Current Context

This post helped me to realize that when I made copies as backups of "infringements.aspx" and "infringements.aspx.cs" named "copy of infringements.aspx" etc, the "copy of infringements.aspx" still referenced "infringements.aspx.cs" and caused the compiler some confusion.

Wednesday, August 20, 2008

Sys$CultureInfo$_getAbbrMonthIndex

Sys.ParameterCountException: Parameter count mismatch.'
function Sys$CultureInfo$_getAbbrMonthIndex(value)

I got this error when I had two controls that used javascript to show a modal popup extender. The control which was named by the mpe as the TargetControlID would not cause this error, but the other control would. (Infringements.aspx/mpeNoticeConfirm)

This appears to be a problem with the Web Developer 1.1.6 plugin for firefox. On computers without that plugin, the error console does not display the error.

Tuesday, June 03, 2008

Page Lifecycle

Here's a thread that discusses how to use trace to view the page lifecycle in action.

This page shows the complete lifecyle including master page and controls

Wednesday, February 06, 2008

DropDownList selection problem

If the selected value of a dropdownlist is incorrect it may be that the databinding is occurring in the Page_Load every time instead of only when IsPostBack is false.

Wednesday, December 19, 2007

Handle ajax slider events

<script type="text/javascript">
<!--

function pageLoad(sender, e) {
var startslider = $find('startSliderBehavior');
startslider.add_valueChanged(onValueChanged);
onStartValueChanged(startslider, null);
}

function onValueChanged(sender, e) {
//sender is the startSliderBehavior object
time = sender.get_Value();
clientID = '<%= FindNestedControl(fvMain, "lblStartSlider") == null ? "" : FindNestedControl(fvMain, "lblStartSlider").ClientID %>'
if (clientID != "")
document.getElementById(clientID).innerHTML = ConvertIntToTimeString(time);
}
-->
</script>

<asp:TextBox ID="tbSliderStart" runat="server" Style="right: 0px" Text='<%# Bind("AssetReferenceMetaStart") %>' />

<asp:Label ID="lblStartSlider" runat="server" Style="font-size: 80%;" Text="00:00:00" />

<ajaxToolkit:SliderExtender ID="SliderExtender1" runat="server" EnableHandleAnimation="true" TargetControlID="tbSliderStart" Minimum="0"Maximum="7200" BehaviorID="startSliderBehavior" />

Wednesday, October 17, 2007

FileUpload control causes 404 error

This can occur when the size of the file being uploaded is outside the proscribed limits.
From The MSDN Article:
The default size limit is 4096 KB (4 MB). You can allow larger files to be uploaded by setting the maxRequestLength attribute of the httpRuntime element. To increase the maximum allowable file size for the entire application, set the maxRequestLength attribute in the Web.config file. To increase the maximum allowable file size for a specified page, set the maxRequestLength attribute inside the location element in Web.config.

Access the return value of an ObjectDataSource Insert method

Handle the OnInserted event of the ObjectDataSource and use the ObjectDataSourceStatusEventArgs.ReturnValue property

Thursday, October 04, 2007

Session state

I set my session state timeout to 1 minute, but after a minute has elapsed without interacting with the site, I can still browse to other pages without having to log in again. What is happening?

There is another timeout that you should set in the web.config file:
<forms timeout="1"/>

There is also an issue related to IIS where processes are recycled after being idle for a set period of time. This article explains more

Wednesday, September 05, 2007

Stack Trace - Get name of current method

System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace();
string methodName = st.GetFrame(0).GetMethod().Name;

Saturday, January 13, 2007

Find the databse ID of a gridview row

Convert.ToUInt32(gridview.DataKeys[rowIndex].Value)

getElementById with Master Pages

When using master pages and at certain other times the names of objects on an asp page get mangled to prevent name clashes. This makes it so that any javascript that references these objects is no longer able to find the element by its original name. To work around this use the following syntax with the getElementById


document.getElementById("<%= [ObjectName].ClientID %>")

Change [ObjectName] to be the name of the object you want to find.

Wednesday, October 18, 2006

Accessing the underlying data in an ObjectDataSource object

Sometimes you need to access the underlying data object of an ObjectDataSource object. An example may be that you need to change the caption of a non-bindable label based on some field in the data. It can be done by using the OnSelected event of the ObjectDataSource. The
ObjectDataSourceStatusEventArgs parameter of that event has a property called ReturnValue which points to the data object returned by the SelectMethod of the ObjectDataSource. If the SelectMethod returns a DataSet object, look through the tables and rows of the DataSet to find the desired data.

Friday, October 13, 2006

Dictionaries and null keys

C# does not let you initialize a dictionary entry by simply assigning a value to a non-existant key in the dictionary. So instead of
  mydictionary[key] = value;    
...where "key" does not yet exist in the dictionary, it is necessary to do something like this...
  if (mydictionary.ContainsKey(key))
  {
    mydictionary[key] += value;
    //or
    mydictionary[key] = value;
  }
  else
  {
    mydictionary.Add(key, value);
  }