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);
  }