Wednesday, July 1, 2009

jQuery Plugins with ASP.NET Controls

After years of frustration with JavaScript and cross browser compatibility, a friend of mine introduced me to the wonderful world of jQuery. Then, after also discovering an endless amount of free plugin libraries for jQuery, I immediately went to work to develop reusable user controls for our internal development library. In this example, I will be using the qtip plugin:

1. First create an .ascx file with a placeholder.
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="Tootip.ascx.cs" Inherits="JQueryInterfaceTests.Controls.Tootip" %>

" style="max-height:<%= this.MaxHeight.ToString() %>px; overflow:auto;">




2. Next, you will need to take advantage of registering your .js files in page's ClientScript. This is important because if you drop more than one instance of your control on the page, then you only want to include your .js file once.

public class QTipControl : UserControl, INamingContainer
{
protected override void OnLoad(EventArgs e)
{
string jqPath = this.Page.ResolveClientUrl("~/Javascripts/jquery-1.3.2.js");

if (!this.Page.ClientScript.IsClientScriptIncludeRegistered(jqPath))
this.Page.ClientScript.RegisterClientScriptInclude(jqPath, jqPath);
}

// More Code Here
}


3. Also, you will need to set all attributes that will be abstracted to the properties of the user control for ease of use.

#region Events
protected override void CreateChildControls()
{
//base.CreateChildControls();
}
#endregion

#region Properties
public string MouseOverClientID
{
get;
set;
}

public int MaxHeight
{
get;
set;
}

public Position TargetPosition
{
get;
set;
}

public Position TooltipPosition
{
get;
set;
}

public Position ArrowPosition
{
get;
set;
}

public Color BorderColor
{
get;
set;
}

public Color BackgroundColor
{
get;
set;
}

///
/// If false, then tooltip hides when user unfocuses from the MouseOverClientID element.
/// If true, then tooltip remains and can be interacted with until focus on to another element on the page occurs.
///

public bool IsSticky
{
get;
set;
}
#endregion


4. Finally, you will need to write any initialization for each client instance. Also, you will need to put the content into your placeholder (notice the INamingContainer). Now here is how your OnLoad ought to look now.

[ParseChildren(true)]
public abstract class MyWrapperControl : UserControl, INamingContainer
{
protected override void OnLoad(EventArgs e)
{
string jqPath = this.Page.ResolveClientUrl("~/Javascripts/jquery-1.3.2.js");
string qtipPath = this.Page.ResolveClientUrl(ResourcePaths.JQueryQtipTootipPath);

if (!this.Page.ClientScript.IsClientScriptIncludeRegistered(jqPath))
this.Page.ClientScript.RegisterClientScriptInclude(jqPath, jqPath);

if (!this.Page.ClientScript.IsClientScriptIncludeRegistered(qtipPath))
this.Page.ClientScript.RegisterClientScriptInclude(qtipPath, qtipPath);

// Write to placeholder
using (System.IO.StringWriter sw = new System.IO.StringWriter(sb))
{
using (HtmlTextWriter htw = new HtmlTextWriter(sw))
{
if (this.Content != null)
this.Content.InstantiateIn(this.phPlaceHolder);

base.CreateChildControls();
base.Render(htw);
}
}

if (!this.Page.ClientScript.IsClientScriptBlockRegistered(this.ClientID))
this.Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
this.ClientID,
"$(document).ready(function(){" +

// Qtip Init
"$(\"#" + this.MouseOverClientID + "\").qtip({" +
"content: {text:'" + sb.ToString().Replace("'", "\\'").Replace(System.Environment.NewLine, string.Empty) + "'}" +
",position:{corner:{target:'" + this.TargetPosition + "',tooltip:'" + this.TooltipPosition + "'}}" +
",style:{tip:'" + this.ArrowPosition + "',name:'cream',background:'" + this.GetRGB(this.BackgroundColor) + "',color:'black',border:{radius:3,color:'" + this.GetRGB(this.BorderColor) + "'}}" +
(this.IsSticky ? ",hide:'unfocus'" : string.Empty) +
",show:{solo:true,effect:{type:'none',length:100}}" +
",hide:{effect:{type:'none',length:100}}" +
"});" +

"});",
true);
}
// More Code Here //
[TemplateContainer(typeof(JQueryUserControl))]
[PersistenceMode(PersistenceMode.InnerProperty)]
public ITemplate Content
{
get;
set;
}
}

That's it, enjoy!

Monday, June 29, 2009

Localization with Strongly-Typed Keys

I came across this issue while working on an MVC application in VS 2008. The project required localization to be setup such that future phases could easily incorporate languages other than English. As expected, I initially added local resources in their respective resx files.

Problem: As usual, I was thinking of improved ways to extend long term management of the application. When working with string-keyed references, I typically like to think of ways to enumerate or use static constants for strongly-typed keys instead of loosely defined strings. So I thought maybe I could build some kind of custom tool or a pre-build event executable to read through the resx xml and create code that would reflect the keyed strings. However, after some googling, I discovered that the solution was right under my nose; VS 2008 does this for me. At the top of the GUI for the resx file in VS is a dropdown menu with the label Access Modifier. When I selected Public in the dropdown, a code behind file appeared for my resx file. It had the strongly-typed key constants just as I wanted so that I could access the keys with intellisense.

For all effective purposes this seemed to work great until I added another resx file for the Spanish language. After some time looking into this, I discovered that when I changed the dropdown to public, my English resx file changed from a Content page (see Build Action field in the file's properties) to an Embedded Resource. So this means that instead of loading the resource file from IO during runtime, it will embed the file directly into my dll at compile time. However, accessing the corresponding Spanish file was not localization as usual. I needed to set the es.resx file to also have Embedded Resource in the Build Action, because localization at runtime now looks for the embedded resource files, not the IO files. There is just one thing to remember, make sure that the Access Modifier is set to No code generation for the Spanish file, don't think this will be set exactly how you set your primary language resx file with the dropdown menu in the GUI, because we already defined the keys in the primary language file.

Solution:
  1. Set the the primary resx file to Public from the Access Modifier dropdown.
  2. Set all other language resx sister files to Embedded Resource from the Build Action field in the respective file's property window.
Hope this is helpful:)