jump to navigation

ScriptArguments: An easy way to programmatically pass arguments to script from codebehind January 13, 2012

Posted by codinglifestyle in AJAX, ASP.NET, C#, CodeProject, Javascript.
Tags: , , , , ,
1 comment so far

During my on-going adventures AJAXifying a crusty old business app I have been using a methodology by which most client events are setup in codebehind. The reason for this is I have easy access to my client ids, variables, and resources in codebehind. By constructing the script function calls at this stage, I can avoid messy and fragile in-line code. What I am endeavouring to do is remove all script from the markup itself. So instead of having MyPage.aspx with script mixed with markup I have MyPage.js and all functions here. Separate js files avoid fragile in-line code which only fails at runtime, can’t be refactored, and doesn’t play as nice with the debugger. Besides, separation of markup and script is good!

The downside to setting up all this script in the codebehind is it didn’t take long for the number of arguments to grow and become unruly. My script function signature looked like this:

function fnAddressChange(ddId, labelId, checkId, sameAsId, hidSelectId, hidSameAsId, onSelectEvent)

And in the codebehind I had this:

string selectArgs     = string.Format("'{0}', '{1}', '{2}', '{3}', '{4}', '{5}'", _DropDownAddress.ClientID, _LabelAddress.ClientID, _RowSameAs.ChildClientID, (SameAs && _SameAsAddress != null) ? _SameAsAddress.LabelControl.ClientID : "-1", _HiddenSelectedID.ClientID, _HiddenSameAs.ClientID);

string selectScript   = string.Format("fnAddressSelect({0}); ", selectArgs);
string changeScript   = string.Format("fnAddressChange({0}, '{1}'); ", selectArgs, OnClientSelect);

We can see selectArgs is getting out of control. Not only is it getting ridiculous to add more to it, the function signature in script is getting huge and the ordering is easier to mess up. So I came up with this solution:

ScriptArguments args = new ScriptArguments ();
args.Add("ddId", _DropDownAddress.ClientID);
args.Add("labelId", _LabelAddress.ClientID);
args.Add("checkId", _RowSameAs.ChildClientID);
args.Add("sameAsId", (SameAs && _SameAsAddress != null) ? _SameAsAddress.LabelControl.ClientID : "-1");
args.Add("hidSelectId", _HiddenSelectedID.ClientID);
args.Add("hidSameAsId", _HiddenSameAs.ClientID);

Not only is the codebehind cleaner but I don’t have to worry about string.Format or the order in which I add arguments in. The resulting script generated is:

args.ToString()
"{ ddId : 'ctl00__ContentMain__ControlOrderSoldTo__AddressSoldTo__DropDownAddress', labelId : 'ctl00__ContentMain__ControlOrderSoldTo__AddressSoldTo__LabelAddress', checkId : 'ctl00__ContentMain__ControlOrderSoldTo__AddressSoldTo__RowSameAs_FormField_CheckBox', sameAsId : '-1', hidSelectId : 'ctl00__ContentMain__ControlOrderSoldTo__AddressSoldTo__HiddenSelectedID', hidSameAsId : 'ctl00__ContentMain__ControlOrderSoldTo__AddressSoldTo__HiddenSameAs' }"

This is a javascript Object with a property per key set to the corresponding value. So in script I only need to take in one argument, the argument object. I can then access every piece of information inserted in to ScriptArguments via the correct key:

function fnAddressIsReadOnly(args) {
     alert(args.ddId);
     alert(args.labelId);
}

Will alert me with “ctl00__ContentMain__ControlOrderSoldTo__AddressSoldTo__DropDownAddress” and “ctl00__ContentMain__ControlOrderSoldTo__AddressSoldTo__LabelAddress”.

The great thing is how simple this was to implement:

public class ScriptArguments : Dictionary<string, string>
{
    public override string ToString()
    {
        StringBuilder script = new StringBuilder("{ ");
        this.Keys.ToList().ForEach(key => script.AppendFormat("{0} : '{1}', ", key, this[key]));
        script.Remove(script.Length - 2, 2);
        script.Append(" }");
        return script.ToString();
    }
}

This simple class solves a simple problem. I hope you find it useful.

Advertisement

Pass a Name Value Pair Collection to JavaScript August 8, 2011

Posted by codinglifestyle in ASP.NET, CodeProject, Javascript.
Tags: , ,
1 comment so far

In my crusade against in-line code I am endevouring to clean up the script hell in my current project. My javascript is littered these types of statements:

var hid = <%=hidSelectedItems.ClientId%>;
var msg = <%=GetResourceString('lblTooManyItems')%>;

Part of the cleanup is to minimize script on the page and instead use a separate .js file. This encourages me to write static functions which take in ids and resources as parameters, allows for easier script debugging, and removes all in-line code making maintenance or future refactoring easier.

While moving code to a proper .js file is nice there are times we might miss the in-line goodness. Never fear, we can build a JavaScript object containing properties for anything we might need with ease. This equates to passing a name/value pair collection to the JavaScript from the code behind. Take a look at this example:

    ScriptOptions options = new ScriptOptions();
    options.Add("ok", GetResourceString("btnOK"));
    options.Add("oksave", GetResourceString("btnOkSave"));
    options.Add("cancel", GetResourceString("btnCancel"));
    options.Add("viewTitle", GetResourceString("lblAddressEditorView"));
    options.Add("editTitle", GetResourceString("lblAddressEditorEdit"));
    options.Add("createTitle", GetResourceString("lblAddressEditorCreate"));
    options.RegisterOptionsScript(this, "_OptionsAddressEditorResources");

Here we’re using the ScriptOptions class to create an object called _OptionsAddressEditorResources we can access in our script. Now let’s see these options in use:

function fnAddressEditDialog(address, args) {
    //Define the buttons and events
    var buttonList = {};
    buttonList[_OptionsAddressEditorResources.ok]     = function() { return fnAddressEditOnOk(jQuery(this), args); };
    buttonList[_OptionsAddressEditorResources.oksave] = function() { return fnAddressEditOnOkSave(jQuery(this), args); };
    buttonList[_OptionsAddressEditorResources.cancel] = function() { jQuery(this).dialog("close"); };

    //Display the dialog
    jQuery("#addressEditorDialog").dialog({
        title: _OptionsAddressEditorResources.editTitle,
        modal: true,
        width: 535,
        resizable: false,
        buttons: buttonList
    });
}

Above we see the jQuery dialog using the resources contained within the _OptionsAddressEditorResources object.

So this seems simple but pretty powerful. Below is the ScriptOptions class which simply extends a Dictionary and writes out the script creating a named global object. Good luck cleaning up your script hell. Hopefully this will help.

    /// <summary>
    /// Class for generating javascript option arrays
    /// </summary>
    public class ScriptOptions : Dictionary<string, string>
    {
        /// <summary>
        /// Adds the control id to the options script
        /// </summary>
        /// <param name="control">The control.</param>
        public void AddControlId(WebControl control)
        {
            this.Add(control.ID, control.ClientID);
        }

        /// <summary>
        /// Registers all the key/values as an options script for access in the client.
        /// </summary>
        /// <param name="page">The page</param>
        /// <param name="optionsName">Name of the options object</param>
        public void RegisterOptionsScript(Page page, string optionsName)
        {
            if (!page.ClientScript.IsStartupScriptRegistered(page.GetType(), optionsName))
            {
                StringBuilder script = new StringBuilder(string.Format("var {0} = new Object();", optionsName));
                this.Keys.ToList().ForEach(key => script.Append(string.Format("{0}.{1}='{2}';", optionsName, key, this[key])));
                page.ClientScript.RegisterStartupScript(page.GetType(), optionsName, script.ToString(), true);
            } 
        }
    }

Currency TextBox with Javascript April 24, 2009

Posted by codinglifestyle in Javascript.
Tags: , , , ,
1 comment so far

This took me a long time so here it is for prosperity.  I started by looking at Ajax’s masked edit field in order to enforce a simply currency field.  This was a waste of time and not up for the job.  I accomplished it with javascript instead.  Very simple, no symbols, just #####.## with the cents being optional. 

Here is the script:

/*

**  Returns the caret (cursor) position of the specified text field.

**  Return value range is 0-oField.length.

*/

function doGetCaretPosition(oField) {

 

    // Initialize

    var iCaretPos = 0;

 

    // IE Support

    if (document.selection) {

 

        // Set focus on the element

        oField.focus();

 

        // To get cursor position, get empty selection range

        var oSel = document.selection.createRange();

 

        // Move selection start to 0 position

        oSel.moveStart(‘character’, -oField.value.length);

 

        // The caret position is selection length

        iCaretPos = oSel.text.length;

    }

 

    // Firefox support

    else if (oField.selectionStart || oField.selectionStart == ‘0’)

        iCaretPos = oField.selectionStart;

 

    // Return results

    return (iCaretPos);

}

 

function fnCurrencyOnly(o) {

    var sValue = o.value;

    var sKey = String.fromCharCode(window.event.keyCode);

    var range = document.selection.createRange();

    var pos = doGetCaretPosition(o);

 

    if (range.text == sValue) {

        sValue = sKey;

    } else {

        sValue = sValue.substr(0, pos) + sKey + sValue.substr(pos);

    }

    var re = new RegExp(“^\\d+(?:(\\.|\\,)\\d{0,2})?$”);

    if (!sValue.match(re))

        window.event.returnValue = false;

}

 

    <div>

        <input type=”text” onkeypress=”fnCurrencyOnly(this);” />

    </div>

 

I’m sure sure who the original author is, but I found the script for doGetCaretPosition() here: http://blog.vishalon.net/index.php/javascript-getting-and-setting-caret-position-in-textarea/

Enjoy!

Cascading Dropdown Lists with jQuery (parent / child select options) March 9, 2009

Posted by codinglifestyle in ASP.NET, C#, CodeProject, Javascript, jQuery.
Tags: , , , , , , , ,
add a comment

 

Recently I was tasked with merging 3 similar screens in to one.  I took stock of the commonalities and decided a Category and Subcategory dropdown list would suffice.   As you’d expect the contents of the Subcategory dropdown depend on the selected Category.  Obviously I wanted to avoid postbacks so I first looked to the Ajax Control Toolkit’s CascadingDropDown extender to link the parent to the child.  In my case, there were only a dozen subcategories so a webservice seemed like overkill.

After attending Tech-Ed 2008 and seeing Microsoft throwing its weight behind jQuery I decided to have a look.  So what follows is my first foray in to jQuery and I’m quite pleased with the results.

First, we define an enum in the codebehind which can be used for bitwise operations.

public enum Category

{

Invoice     = 1,

Order       = 2,

Shipment    = 4   //Fourth item would be 8, then 16, 32, …

}

Next I wanted to be able to use the enum to specify the Category (only specifying one) and Subcategory items (bitwise OR any combination).

_DropDownCategory.Items.Add(GenerateListItem(“Orders”, Category.Order, false));

_DropDownCategory.Items.Add(GenerateListItem(“Invoices”, Category.Invoice, false));

_DropDownCategory.Items.Add(GenerateListItem(“Shipments”, Category.Shipment, false));

_DropDownSubcategory.Items.Add(GenerateListItem(“Purchase Orders”, Category.Invoice | Category.Order | Category.Shipment, false));

_DropDownSubcategory.Items.Add(GenerateListItem(“Quote Number”, Category.Order, false));

_DropDownSubcategory.Items.Add(GenerateListItem(“Customer Project Number”, Category.Shipment, true));

The enum was converted to a number and added as a custom attribute to the new list item in GenerateListItem():

li.Attributes.Add(“Category”, ((int)eCat).ToString());

Enter jQuery, first we’re going to need to keep a copy of all the available Subcategory items.  Each time we change the Category we will be showing a subset of this array which we must keep separate in memory.

var optSubCat = null;

$(function() {

//Make a copy in memory of all the subcat options

optSubCat = $(“#_DropDownSubcategory”).children().clone();

//Default subcat to “Orders”

ddSubcategoryUpdate(2);

});

Then we need an event to fire when the Category is changed.

$(function() {

$(“#_DropDownCategory”)

.bind(“change”, function(event) {

var eCat = this[this.selectedIndex].attributes(“Category”).value;

ddSubcategoryUpdate(eCat);

});

});

Note how jQuery allows us to wire up DOM events after the fact to our .NET controls.  Here we add an onchange event dynamically rather than injecting this via the codebehind which forces us to put javascript code in the wrong place.  All we’re doing is pulling the value of the enum from the selected Category and then passing it to a function which will update the Subcategory items.

function ddSubcategoryUpdate(eCat) {

//Remove all items from drop down

$(“#_DropDownSubcategory”).children().remove();

//For each subcat item: test if it belongs in eCat, if so add it

$(optSubCat).each(function() {

if (($(this).attr(“Category”) & eCat) > 0) {

$(“#_DropDownSubcategory”).append($(this).clone()[0]);

}

});

}

And finally, in the if statement we see we test each Subcategory item previously saved in memory to see if it matches the enum value of the Category.

What we’ve ended up with is a very powerful yet simple mechanism for cascading dropdowns using enums in our codebehind.  It’s easier to wire up than what the Ajax Control Toolkit provides, its all script so the UI looks good, and while this could have been done in straight javascript it demonstrates the power and simplicity of the jQuery library.

Numeric input only for a HtmlInputField January 12, 2006

Posted by codinglifestyle in ASP.NET, Javascript.
Tags: , ,
1 comment so far

Sometimes a validator control is overkill, especially when you are dynamically creating all your web controls and just want a simple restriction.  Below is a little javascript to require numeric input only:

HtmlInputField myField = new HtmlInputField();

myField.Attributes.Add(“onkeyup”, “this.value=this.value.replace(/[^\\d]*/gi,\”\”);”);

Exposing C# controls as Active-X and the effects on developing with the download cache January 5, 2006

Posted by codinglifestyle in C#, Javascript, Winform.
Tags: , , , ,
add a comment

A C# assembly can be easily exposed as an Active-X control using the object tag like so:

<OBJECT id=”Bob” classid=”url to assembly#full name of control></OBJECT>

So for example my assembly is located in a virtual directory on server VM2003S2:88 called MyObject.  MyObject has a namespace of Chris.ActiveX.Controls and the class is MyControl.  My object tag would be: http://vm2003s2:88/myobject.dll#chris.activex.controls.mycontrol

This complete I can now use javascript to talk to “Bob” via MyControl’s public methods.  This is all well and good however you may experience frustration in seeing your updates during development.

The assembly is automatically pulled down to the download cache which is located in c:\windows\assembly\downloads on Win2003.  Unfortunately, its not smart enough to read version strings and realize a newer copy is available on the server.  Therefore you must clear this cache using:

gacutil /cdl

But that’s not all!  A copy is also made in your temporary internet files.  You won’t be able to simply delete it using del from the post-build event as Window’s treats the directory as special.  So either write a small program utilizing the API to delete your assembly from the cache or use one of the miriad of privacy utilities to clean this for you.  I found CleanUp! which can be quietly executed from the command line and added to my post build event.  The result is the assembly is pulled every time (on the development machine) so you see your changes with each build.

Here is a good example explaining Embedded Windows User Controls into Internet Explorer:

http://www.devhood.com/tutorials/tutorial_details.aspx?tutorial_id=187