Update Panel .NET

Exploring Microsoft ASP.NET AJAX and jQuery

Archive for the ‘DOM’ Category

WebKit Bug! Default value of unchecked checkbox element is an empty string!

with one comment

If you drop a checkbox on a page, without setting the value attribute, for example:

<input type="checkbox" />

Browsers should default the value to the string “on”. To check this, add a “onclick” handler to display the value of the checkbox:

<input type="checkbox" onclick="alert(this.value);" />

Now, test this in the latest versions of Internet Explorer, Firefox, Opera, Chrome and Safari, you will notice that for the first three browsers, the value displayed for both checked and unchecked state is “on” but for Chrome (2) and Safari (4), the value displayed for the unchecked state is an empty string!

Surely this is a bug, because if you did specify a value, for example:

<input type="checkbox" value="foo" onclick="alert(this.value);" />

You will see “foo” displayed in the alert box for both checked and unchecked state on all browsers!

Written by tzkuei

June 10, 2009 at 7:59 pm

Posted in Browser, DOM, HTML

Tagged with

Setting the “value” attribute of a newly created LI element causes IE to crash

leave a comment »

The Bug

If you programmatically set the “value” attribute of a newly created <li> element, Internet Explorer (IE) versions 5, 6 and 7 will crash without fail.

Here is the HTML source to my test page:

<html>
<head>
<title>IE Crash Example</title>
</head>
<body>
<button type="button" onclick="document.createElement('li').value = null;">null - Okay</button>
<button type="button" onclick="document.createElement('li').value = 0;">0 - Okay</button>
<button type="button" onclick="document.createElement('li').value = 1;">1 - Crash</button>
<button type="button" onclick="document.createElement('li').value = '1';">'1' - Crash</button>
<button type="button" onclick="document.createElement('li').value = true;">true - Crash</button>
<button type="button" onclick="document.createElement('li').value = 'true';">'true' - Okay</button>
<button type="button" onclick="document.createElement('li').value = false;">false - Okay</button>
<button type="button" onclick="document.createElement('li').value = [];">[] - Okay</button>
<button type="button" onclick="document.createElement('li').value = [1];">[1] - Crash</button>
<button type="button" onclick="document.createElement('li').value = ['1'];">['1'] - Crash</button>
<button type="button" onclick="document.createElement('li').value = ['true'];">['true'] - Okay</button>
<button type="button" onclick="document.createElement('li').value = {};">{} - Okay</button>
<button type="button" onclick="document.createElement('li').value = {count:1};">{count:1} - Okay</button>
<button type="button" onclick="document.createElement('li').value = undefined;">undefined - Okay</button>
<button type="button" onclick="document.createElement('li').value = function(){};">function(){} - Okay</button>
</body>
</html>

Interestingly, the crash happens when the value is set to a non-zero number, or evaluates to a non-zero number!

The Workaround

As the “value” attribute of the <li>element is deprecated, I suggest you avoid using it. But if you must, attach the <li> element to the DOM before manipulating it’s attributes, i.e.

var li = document.createElement("li");
ul.appendChild(li);
li.value = 1;

If you are using jQuery:

$("
	<li/>").appendTo(ul).val(1);

But I suggest you store the value using the data function:

$("
	<li/>").appendTo(ul).data("value", 1);

N.B. The bug has been fixed in IE 8.

Written by tzkuei

April 30, 2009 at 11:20 pm

Posted in Browser, DOM, JavaScript

jQuery (ASP.NET) Validator Callout Plugin

with 33 comments

This plugin enhances the presentation of ASP.NET validator controls, by displaying the validation error message inside a (popup) callout box, like this:

Screen capture of validator callout example

This plugin is similar to the ValidatorCallout extender in the ASP.NET AJAX Control Toolkit.

Download

Download the latest release from http://plugins.jquery.com/project/updnValidatorCallout

Usage

Simply call jQuery.updnValidatorCallout.attachAll() to attach the plugin to all validators on your page.

For example:

jQuery(document).ready(function($) {
    $.updnValidatorCallout.attachAll();
});

You can style the callout box and pointer by specifying the ”calloutCssClass” and “pointerCssClass” options:

jQuery(document).ready(function($) {
    $.updnWatermark.attachAll({
        calloutCssClass: "myValidatorCallout",
        pointerCssClass: "myValidatorCalloutPointer",
    );
});

You can also style the input element and all associated labels that are in error state by specifying the “errorInputCssClass” and “errorLabelCssClass” options:

jQuery(document).ready(function($) {
    $.updnWatermark.attachAll({
        calloutCssClass: "myValidatorCallout",
        pointerCssClass: "myValidatorCalloutPointer",
        errorInputCssClass: "myValidationErrorInput",
        errorLabelCssClass: "myValidationErrorLabel" }
    );
});

Alternatively, you can simply style the default CSS classes provided by the plugin: 

.updnValidatorCallout
{
    background-color: #fcc;
    color: #900;
    padding: 5px;
    margin: -5px 0 0 10px;
    position: relative;
}
.updnValidatorCalloutPointer
{
    position: absolute;
    left: 0;
    top: 7px;
    margin: 0 0 0 -10px;
    width: 0;
    height: 0;
    border-top: 7px solid transparent;
    border-bottom: 7px solid transparent;
    border-right: 10px solid #fcc;
    border-left: 0;
}
.updnValidationErrorInput
{
    background-color: #fcc;
}
.updnValidationErrorLabel
{
    color: #900;
}

N.B. For the triangular pointer, I used a pure CSS solution as detailed in this article.

Written by tzkuei

April 19, 2009 at 9:34 pm

Posted in ASP.NET, CSS, DOM, HTML, JavaScript, jQuery

jQuery Watermark Plugin

with 12 comments

!!! Latest release 1.0.1 now available !!!

This plugin turns the tooltip text specified in the title attribute of your input element into a watermark, like this:

Screen capture of watermark plugin example

This watermark plugin can safely be used on text, password and textarea elements, as it does not alter the value of the fields, and therefore does not affect form submits or AJAX calls.

The plugin works by creating a label element using the text specified in the title attribute of each selected input element, and inserting the label element before the input element, absolutely positioned (relative to the input element’s positioning context) to appear ”behind” or “inside” the field.

Download

Download the latest release from http://plugins.jquery.com/project/updnWatermark

Usage

Specify the watermark text in the title attribute of each input element:

<input id="username" name="username" type="text" title="Please enter username" />
<input id="password" name="password" type="password" title="Please enter password" />
<textarea id="description" name="description" title="Please enter description"></textarea>

Then simply call jQuery.updnWatermark.attachAll() to attach the plugin to all text, password and textarea elements in the document.

For example:

jQuery(document).ready(function($) {
    $.updnWatermark.attachAll();
});

You can style the watermark by supplying a class via the “cssClass” option:

jQuery(document).ready(function($) {
    $.updnWatermark.attachAll({ cssClass: "myWatermark" });
});

Alternatively, just style the default “updnWatermark” class:

.updnWatermark {
    color: #999;
    font-family: Sans-Serif;
    font-size: small;
    font-style: italic;
    padding: 2px;
}

Written by tzkuei

April 17, 2009 at 9:29 pm

Posted in CSS, DOM, HTML, JavaScript, jQuery

More on getAttribute(), setAttribute() and the “value” attribute

with 3 comments

While I was preparing for my previous post, I came across this thread and a comment made by the moderator ‘jkd’ grabbed my attention:

“Another one:  input.value versus input.getAttribute(“value”). The latter should return the actual string specified in the markup of the value attribute, while input.value returns the current value of the input element.”

I was somewhat alarmed by this comment, because for over a decade (and a half?) of being a web developer, I never knew about this, and so I did some simple tests and the results were very interesting!

Here is the HTML source for the test page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</a>">
<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>" >
<head>
<title>More on getAttribute() and setAttribute()</title>
</head>
<body>
<input id="TextBox1" type="text" value="123" />
<input type="button" value="alert(element.getAttribute('value'));" onclick="alert(document.getElementById('TextBox1').getAttribute('value'));" />
<input type="button" value="alert(element.value);" onclick="alert(document.getElementById('TextBox1').value);" />
<ol>
	<li><input type="button" value="element.setAttribute('value', '456');" onclick="document.getElementById('TextBox1').setAttribute('value', '456');" /></li>
	<li>Enter 'abc' into the text box.</li>
	<li><input type="button" value="element.value = '789';" onclick="document.getElementById('TextBox1').value = '789';" /></li>
	<li><input type="button" value="element.setAttribute('value', 'xyz');" onclick="document.getElementById('TextBox1').setAttribute('value', 'xyz');" /></li>
</ol>
</body>
</html>

First, let me clarify two terms I will be using in the remaining post:

  • “value” property is element.value;
  • “value” attribute is element.getAttribute(“value”);

Open the test page in Chrome, and you should see:

Test page

Click the alert(element.getAttribute(‘value’)); button and you should see:

Initial attribute value

Click the alert(element.value); button and you should see:

Initial property value

Currently, the “value” attribute and the “value” property of the text box has the same initial value of ’123′, as specified in the HTML mark-up.

Now, follow each of the four steps:

Step 1:

Click the button to set the value of the “value” attribute to ’456′:

Step 1

Notice the text box now displays ’456′. 

Click the alert(element.getAttribute(‘value’)); button and you should see:

Step 1 attribute value

Click the alert(element.value); button and you should see:

Step 1 property value

The “value” attribute and the “value” property of the text box now has the value of ’456′.  This is what I expected.

 Step 2:

Enter ‘abc’ into the text box.  This is manually changing the value of the “value” property.

Step 2

Click the alert(element.getAttribute(‘value’)); button and you should see:

 Step 2 attribute value 

Huh?

Click the alert(element.value); button and you should see:

Step 2 property value

No, the attribute value is not the same as the property value! What is going on here?

Step 3:

Click the button to change the property value to ’789′:

Step 3

Click the alert(element.getAttribute(‘value’)); button and you should still see:

 Step 3 attribute value

Click the alert(element.value); button and you should see:

Step 3 property value

Okay, basically, we got the same behavior as in step 2.

Step 4:

Click the button to change the attribute value to ‘xyz’:

Step 4

Notice that the the text box is still displaying ’789′, which is the property value we had set in step 3.

Click the alert(element.getAttribute(‘value’)); button and you should see:

Step 4 attribute value

Click the alert(element.value); button and you should still see:

Step 4 property value

This confirms that once set, the value of the property is independent from the value of the attribute.

Wasn’t that interesting? Well, I thought it was!  Now run the test in Firefox, Opera, Safari, and Internet Explorer!!!

With Chrome, Firefox, Opera and Safari, the following behaviors were observed:

  • The “value” attribute is disconnected from the “value” property.
    (element.getAttribute(“value”) !== element.value)
  • Setting the “value” attribute does not directly affect the “value” property.
  • If the value of the “value” property has not been set, it will return the value of the “value” attribute as default.

With Internet Explorer, the following behaviors were observed:

  • The “value” attribute is connected to the “value” property.
    (element.getAttribute(“value”) === element.value)
  • Setting the “value” attribute directly affects the “value” property.
  • The value of the property is always the same as the value of the attribute.

It seems that using element.setAttribute() and element.getAttribute() methods, especially on the “value” attribute is fraught with problems.  So my conclusion is to always use element.value to ensure cross-browser compatibility!

Related Links:

Written by tzkuei

December 31, 2008 at 4:06 am

Posted in DOM, HTML, JavaScript

Tagged with ,

Use DOM Level 2 HTML

with one comment

Immediately after my previous post, I searched around the web for the best (correct and “performant”) method to access attributes of HTML elements; I mean, should I use element.setAttribute() or element.getAttribute() methods, or should I access the attributes directly using JavaScript dot notation or subscript notation?

I think the main problem is that I am old and stubborn and have always visualized HTML elements in my head as tags marked with attributes, instead of objects with methods and properties!

“The DOM Level 2 HTML extends the DOM Level 2 Core API to describe objects and methods specific to HTML documents, and XHTML documents”.

Basically, if an attribute (property) exists in the DOM Level 2 HTML specification, you can access it directly using JavaScript dot notation.  Make sure you use the correct camel-case name as JavaScript is case sensitive!

The example from my previous post should now read:

// Creates a check box
var input = document.createElement("input");
input.type = "checkbox";
input.value = option.value;
input.id = itemId;
// Creates an associated label
var label = document.createElement("label");
label.htmlFor = itemId;
label.appendChild(document.createTextNode(option.text));

Written by tzkuei

December 30, 2008 at 3:15 am

Posted in DOM, HTML, JavaScript

How to dynamically set the ‘for’ attribute of a ‘label’ tag?

with 4 comments

The main purpose of this post is to remind myself the correct way to set the “for” attribute of a label tag in JavaScript, but it may be helpful to you too.

I was working on an extender control that displays a list of check boxes with corresponding labels, and I used DOM APIs to create the elements, like this:

// Creates a check box
var input = document.createElement("input");
input.setAttribute("type", "checkbox");
input.setAttribute("value", option.value);
input.setAttribute("id", itemId);
// Creates an associated label
var label = document.createElement("label");
label.setAttribute("for", itemId);
label.appendChild(document.createTextNode(option.text));

What I found is that browsers including Chrome, Firefox, Opera and Safari correctly set the “for” attribute, whereas Internet Explorer did not!

One solution is to use element.setAttributeNode instead of element.setAttribute:

var forAttr = document.createAttribute("for");
forAttr.value = itemId;
label.setAttributeNode(forAttr);

But a much simpler solution is to use the “htmlFor“ property which can be accessed directly using JavaScript dot notation:

So instead of:

label.setAttribute("for", itemId);

Just use:

label.htmlFor = itemId;

Written by tzkuei

December 30, 2008 at 2:13 am

Posted in DOM, JavaScript

New videos from the YUI Theater

leave a comment »

Two more videos have been posted on the YUI Theater and they are well worth watching:

Douglas Crockford: “Ajax Performance”
In this video, Douglas Crockford talks about web application performance optimization.

Only speed up things that take a lot of time.
Speeding up things that take very little time will yield very little improvement.

Nicole Sullivan: “Design Fast Websites (Don’t Blame the Rounded Corners)”
In this video, Nicole Sullivan presents nine best practices for designing faster websites:

  1. Create a component library of smart objects.
  2. Use consistent semantic styles.
  3. Design modules to be transparent on the inside.
  4. Optimize images and sprites.
  5. Avoid non-standard browser fonts.
  6. Use columns rather than rows.
  7. Choose your bling carefully.
  8. Be flexible.
  9. Learn to love grids.

Written by tzkuei

December 28, 2008 at 8:29 pm

Posted in DOM, JavaScript

Tagged with , ,

Image Corners Extender Control

with one comment

The prevalent use of styled borders (e.g. rounded corners) by our designers have caused much debate amongst the developers as to which technique should be employed to achieve the desired result.  The Backgrounds and Borders Module in CSS Level 3 would be ideal, but we have to wait until that becomes ubiquitous.

In this post, I present to you an extender control I created to add image corners to <asp:Panel> controls.  The extender works by injecting additional <div> elements into the DOM and style each with a background image.

Below is the source code for the server-side extender control:

using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
[assembly: WebResource("UPDN.ImageCornersBehavior.js", "text/javascript")]
namespace UPDN
{
    ///

    /// Summary description for ImageCornersExtender
    ///

    [TargetControlType(typeof(Panel))]
    public class ImageCornersExtender : ExtenderControl
    {
        public string TopBorderImageUrl { get; set; }
        public string RightBorderImageUrl { get; set; }
        public string BottomBorderImageUrl { get; set; }
        public string LeftBorderImageUrl { get; set; }
        public string TopLeftCornerImageUrl { get; set; }
        public string TopRightCornerImageUrl { get; set; }
        public string BottomLeftCornerImageUrl { get; set; }
        public string BottomRightCornerImageUrl { get; set; }
        public string MiddleImageUrl { get; set; }
        protected override IEnumerable GetScriptDescriptors(Control targetControl)
        {
            var descriptor = new ScriptControlDescriptor(“UPDN.ImageCornersBehavior”, targetControl.ClientID);
            var imageUrls = new Dictionary();
            if (!string.IsNullOrEmpty(TopBorderImageUrl))
                imageUrls.Add(“t”, TopBorderImageUrl);
            if (!string.IsNullOrEmpty(RightBorderImageUrl))
                imageUrls.Add(“r”, RightBorderImageUrl);
            if (!string.IsNullOrEmpty(BottomBorderImageUrl))
                imageUrls.Add(“b”, BottomBorderImageUrl);
            if (!string.IsNullOrEmpty(LeftBorderImageUrl))
                imageUrls.Add(“l”, LeftBorderImageUrl);
            if (!string.IsNullOrEmpty(TopLeftCornerImageUrl))
                imageUrls.Add(“tl”, TopLeftCornerImageUrl);
            if (!string.IsNullOrEmpty(TopRightCornerImageUrl))
                imageUrls.Add(“tr”, TopRightCornerImageUrl);
            if (!string.IsNullOrEmpty(BottomLeftCornerImageUrl))
                imageUrls.Add(“bl”, BottomLeftCornerImageUrl);
            if (!string.IsNullOrEmpty(BottomRightCornerImageUrl))
                imageUrls.Add(“br”, BottomRightCornerImageUrl);
            if (!string.IsNullOrEmpty(MiddleImageUrl))
                imageUrls.Add(“m”, MiddleImageUrl);
            descriptor.AddProperty(“imageUrls”, imageUrls);
            yield return descriptor;
        }
        protected override IEnumerable GetScriptReferences()
        {
            yield return new ScriptReference(“UPDN.ImageCornersBehavior.js”, this.GetType().Assembly.FullName);
        }
    }
}

And the source code for the client-side behavior:

Type.registerNamespace("UPDN");
UPDN.ImageCornersBehavior = function(element) {
    UPDN.ImageCornersBehavior.initializeBase(this, [element]);
    // keys: t, l, b, r, tl, tr, bl, br
    this._imageUrls = null;
}
UPDN.ImageCornersBehavior.prototype = {
    initialize: function() {
        UPDN.ImageCornersBehavior.callBaseMethod(this, "initialize");
        var el = this.get_element();
        var imagesUrls = this.get_imageUrls();
        var containers = [];
        // create border elements
        if (imagesUrls["t"]) {
            var t = document.createElement("div");
            t.style.background = "url(" + imagesUrls["t"] + ") repeat-x top left";
            containers.push(t);
        }
        if (imagesUrls["r"]) {
            var r = document.createElement("div");
            r.style.background = "url(" + imagesUrls["r"] + ") repeat-y top right";
            containers.push(r);
        }
        if (imagesUrls["b"]) {
            var b = document.createElement("div");
            b.style.background = "url(" + imagesUrls["b"] + ") repeat-x bottom left";
            containers.push(b);
        }
        if (imagesUrls["l"]) {
            var l = document.createElement("div");
            l.style.background = "url(" + imagesUrls["l"] + ") repeat-y top left";
            containers.push(l);
        }
        if (imagesUrls["tl"]) {
            var tl = document.createElement("div");
            tl.style.background = "url(" + imagesUrls["tl"] + ") no-repeat top left";
            containers.push(tl);
        }
        if (imagesUrls["tr"]) {
            var tr = document.createElement("div");
            tr.style.background = "url(" + imagesUrls["tr"] + ") no-repeat top right";
            containers.push(tr);
        }
        if (imagesUrls["bl"]) {
            var bl = document.createElement("div");
            bl.style.background = "url(" + imagesUrls["bl"] + ") no-repeat bottom left";
            containers.push(bl);
        }
        if (imagesUrls["br"]) {
            var br = document.createElement("div");
            br.style.background = "url(" + imagesUrls["br"] + ") no-repeat bottom right";
            containers.push(br);
        }
        if (containers.length > 0) {
            // create content container
            var m = document.createElement("div");
            m.className = "content";
            // move child elements of target element into content container
            this._moveChildren(el, m);
            // set middle image as background of target element
            if (imagesUrls["m"]) {
                el.style.background = "url(" + imagesUrls["m"] + ") repeat top left";
            }
            containers.push(m);
            // inject containers into DOM
            el.appendChild(containers[0]);
            for (var i = 1; i < containers.length; i++) {
                containers[i - 1].appendChild(containers[i]);
            }
        }
    },
    dispose: function() {
        //Add custom dispose actions here
        UPDN.ImageCornersBehavior.callBaseMethod(this, "dispose");
    },
    _moveChildren: function(srcEl, destEl) {
        while (srcEl.hasChildNodes()) {
            var child = srcEl.childNodes[0];
            destEl.appendChild(child);
        }
    },
    get_imageUrls: function() {
        return this._imageUrls;
    },
    set_imageUrls: function(value) {
        this._imageUrls = value;
    }
}
UPDN.ImageCornersBehavior.registerClass("UPDN.ImageCornersBehavior", Sys.UI.Behavior);

To use the extender, place an <asp:Panel> control on the page:

<asp:Panel ID="Panel1" runat="server" CssClass="box1">
    This box is styled using 8 images. Lorem ipsum no etiam perfecto his, per adhuc maiorum epicurei eu. Laoreet signiferumque in mel, ne vel dicat percipit instructior. An eum veniam decore perpetua, nam laudem praesent at. Exerci accumsan contentiones est ei, appetere gloriatur et qui, ea suscipit senserit consectetuer mei.
</asp:Panel>

Then add the extender, set the TargetControlID to the ID of the <asp:Panel> control, and specify the images required to achieve your design. For this panel, I require eight images, four for the borders and four for the corners:

b bl br l r t tl tr
<updn:ImageCornersExtender ID="ImageCornersExtender1" runat="server"
    TargetControlID="Panel1"
    TopBorderImageUrl="img/box1/t.gif"
    RightBorderImageUrl="img/box1/r.gif"
    BottomBorderImageUrl="img/box1/b.gif"
    LeftBorderImageUrl="img/box1/l.gif"
    TopLeftCornerImageUrl="img/box1/tl.gif"
    TopRightCornerImageUrl="img/box1/tr.gif"
    BottomLeftCornerImageUrl="img/box1/bl.gif"
    BottomRightCornerImageUrl="img/box1/br.gif"
/>

Here is how the complete example looks like:

Image borders examples

Download the source code and example from my SkyDrive.

Written by tzkuei

November 25, 2008 at 11:04 am

Reparenting child nodes

leave a comment »

In a previous post, I suggested a quick and simple way of moving an element from one parent to another, in this post, I will present you with a reusable function that will move all child nodes from one parent element to another:

function moveChildren(srcEl, destEl) {
    while (srcEl.hasChildNodes()) {
        var child = srcEl.childNodes[0];
        destEl.appendChild(child);
    }
}

Here is an example HTML page:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "<a href="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd</a>">
<html xmlns="<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>" >
<head>
<title>Reparenting Example</title>
<style type="text/css">
div { border: solid 1px #000; padding: 10px; }
span { border: solid 1px #f00; padding: 2px; }
#Box1 { background-color: #ccc; }
#Box2 { background-color: #ffc; }
#Box3 { background-color: #ccf; }
</style>
</head>
<body>
<div id="Box1">
    <span>Box1 content</span>
    <div id="Box2">
        <span>Box2 content</span>
        <div id="Box3">
            <span>Box3 content</span>
            <span>More Box3 content</span>
        </div>
        <span>More Box2 content</span>
    </div>
    <span>More Box1 content</span>
</div>
<input type="button" value="Reparent" onclick="reparent()" />
<script type="text/javascript">
var box1 = document.getElementById("Box1");
var box2 = document.getElementById("Box2");
var box3 = document.getElementById("Box3");
function reparent() {
    moveChildren(box2, box1);
    moveChildren(box3, box1);
}
function moveChildren(srcEl, destEl) {
    while (srcEl.hasChildNodes()) {
        var child = srcEl.childNodes[0];
        destEl.appendChild(child);
    }
}
</script>
</body>
</html>

In this example, when you click the [Reparent] button, all child nodes of Box2 and Box3 will be moved to Box1.

References

Written by tzkuei

November 24, 2008 at 3:46 pm