Showing posts with label AJAX. Show all posts
Showing posts with label AJAX. Show all posts

Tuesday, November 29, 2011

Clunky old Javascript input mask

 

I was working with the MaskedEditExtender recently to control input into some textboxes but got fed up with the “quirks” of it – namely the inability of it to select() the value of the textbox as well as weirdness of the masks in general.   For instance, if you were working with a currency mask set up as #,###.## and blurred off of the control and then went back to it (focus) your cursor would appear at the left-most character instead of the right-most.   This required an extra click to edit the value if you didn’t feel like retyping the entire thing.

Be that as it may, I still liked the ability to limit the keys used on the inputs and used a trusty old JavaScript function to achieve just that.  

My function uses passed variables to determine which keycodes to allow and what not to.   It’s a little clunky but it does the trick.  Simply specify the keys you’ll allow (by passing a “1) and which ones will be ignored (passing any other value besides “1”)

function LimitInput(NumbersOK, DecimalPointOK, CommasOK, QuotesOK, AlphaOK, SpacesOK, DateTimeSymbolsOK) {
x = window.event.keyCode;

// 0-9 on top row keys as well as num-pad
if (NumbersOK == 1 && ((x >= 48 && x <= 57) || (x >= 96 && x <= 105))) return true;

// decimal point and period key
if (DecimalPointOK == 1 && (x == 190 || x == 110)) return true;

//commas
if (CommasOK == 1 && x == 188) return true;

//single and double quotation marks
if (QuotesOK == 1 && x == 222) return true;

//letters only - upper and lower
if (AlphaOK == 1 && x >= 65 && x <= 90) return true;

// spacebar
if (SpacesOK == 1 && x == 32) return true;

// the dash, colon and forward-slash symbols
if (DateTimeSymbolsOK == 1 && (x == 191 || x == 109 || x == 16)) return true;

//always allow Backspace, Delete, TAB and Left/Right arrow keys
if (x == 46 || x == 8 || x == 9 || x == 37 || x == 39) return true;

return false;
}



Usage  Simply wire-up a call to this function from the control’s onkeydown event as such:


TextBox1.onkeydown = return LimitInput(#,#,#,#,#,#,#);


Example:  Wiring up a textbox “TextMoney” in code-behind so it only accepts currency-formatted input would look like


TextMoney.Attributes.Add("onkeydown", "return LimitInput(1,1,1,0,0,0,0);");


TIP:  Always remember to include “return” when making the call or else it won’t work Smile

Monday, November 14, 2011

Client-side disable/enable AJAX DropDownExtender using Javascript

I was recently adding a series of controls to an form which utilized the DropDownExtender to display an OptionList attached to a TextBox.  A requirement of this form was that the control-values could be overridden by the user checking a checkbox on the form.   Sounds simple enough, right?   

Now the fun stuff.   The form is presented in a ModalPopup and I don’t want to introduce any lag to the form by way of PostBacks and no additional weight by adding UpdatePanels so I decided to use Javascript to Enable and Disable the extender.

Advantages:   Fast, simple code, no post-back.  No lag

Disadvantages:  No more hover-arrow on the control.  The hover-arrow is defaulted to show during MouseOver to indicate that the control can “drop down”.  In testing this resolution, I was able to mimic the disabling (by not showing it) but not the enabling of it.  If anyone knows a workaround for this other than completely wiping the control and then recreating, please let me know.

First – remove the hover event of the extender.   Bye-bye drop-arrow…

$find("MyDropdownExtenderID").hover = function () { };


This will cause Nothing to happen when you hover over the control. No highlighting, no dropdown arrow. Nothing.

Change the Active state by simply modifying the extender’s behaviors:

To “disable”:



document.getElementById("MyTextboxBeingExtendedID").disabled = false;
$find("MyDropdownExtenderID")._showPopup = function () { $find("MyDropdownExtenderID")._dropPopupPopupBehavior.hide(); };


To “enable”:

document.getElementById("MyTextboxBeingExtendedID").disabled = true;

$find("MyDropdownExtenderID")._showPopup = function () { $find("MyDropdownExtenderID")._dropPopupPopupBehavior.show(); };

The first line disables/enables the textbox that serves as the TargetControlID for the extender.


The second line redefines the _showPopup event to act as defined in the “hide” event of the popup’s “show” behavior (when the list shows) – meaning the control specified by the DropdownControlID of the extender will either be seen or not be seen.

Wednesday, April 28, 2010

AutoCompleteExtender using Application-Cached Data

A recent project required me to build a search function that looked for existing customers by name.  Our company has thousands of customers so simply putting the list into a dropdownlist or bulleted list was not feasible.  I decided to build a simple textbox input search function which was wired up with the AJAX AutoCompleteExtender.  The user would enter a name of a customer and the list would filter down until either a match was made or it wasn't. 

Working with the extender is quite simple.  You add a textbox control to your form, associate the extender with it and point the extender to a webservice.  Each character entered into the textbox triggers a webmethod of the webservice which queries data and returns results as a string array [].

On the backend the webservice would execute a simple query against a table on our SQL Server based on the contents of the text entered in the textbox.  As more and more people use this function however, the traffic and performance of all of this activity becomes a concern.   Since the data being queried against rarely changed (names of customers), I decided to utilize an application cache which the webmethod would use as the basis of finding matches via the extender.

The original setup of my webmethod was quite simple.   It would execute a query against the database that returned X amount of rows (to limit the number shown in the autocomplete results).  These rows (containing 1 column) would be inserted into a string array and returned to the extender for display.

Original code:

[WebMethod]
public string[] GetCustomers(string prefixText, int ResultsReturned)
{
string strSQL = "SELECT TOP " + ResultsReturned.ToString() + " CUSTOMER_NAME + ' - ' + CUSTOMER_STATUS FROM tblCUSTOMERS ";
strSQL += "WHERE REPLACE(REPLACE(UPPER(CUSTOMER_NAME),'.',''),',','') LIKE '%" + prefixText.ToUpper().Replace(".", "").Replace(",", "") + "%' ";
strSQL += "ORDER BY CUSTOMER_NAME";
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["myConn"].ToString());
conn.Open();
SqlDataAdapter sda = new SqlDataAdapter(strSQL, conn);
DataSet ds = new DataSet();
sda.Fill(ds);
conn.Close();
string[] items = new string[ds.Tables[0].Rows.Count];
int i = 0;
foreach (DataRow dr in ds.Tables[0].Rows)
{
items.SetValue(dr[0].ToString(), i);
i++;
}
ds.Dispose();
sda.Dispose();
conn.Dispose();

return items;
}

To utilize caching I had to determine the best scope to use.  If the data being cached was different or would change depending on the user accessing that cache, I would use a Session-level cache.   If the data was the same across the board, regardless of the user, an Application cache is the best bet.   Since this was the case, I went the application route. 


Implementing a cached data source takes a little extra coding since I now have to filter my data from a persistant (sortof) dataset as opposed to requerying the database and building a new dataset each time.  The first step is to add a constructor that can build a basic, unfiltered dataset (using a DataTable) and add it into the Application.Cache space as such:


private DataTable GetCustomersDT()
{
DataTable dt = (DataTable)HttpContext.Current.Cache["CustomersDT"];
if (dt == null)
{
string strSQL = "SELECT CUSTOMER_NAME, CUSTOMER_STATUS FROM tblCUSTOMERS";

SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["myConn"].ToString());
conn.Open();
SqlDataAdapter sda = new SqlDataAdapter(strSQL, conn);
DataSet ds = new DataSet();
sda.Fill(ds);
dt = ds.Tables[0];
HttpContext.Current.Cache.Insert("CustomersDT", dt, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5));
sda.Dispose();
ds.Dispose();
conn.Close();
}
else
{
dt = (DataTable)HttpContext.Current.Cache["CustomersDT"];
}
return dt;
}

Now that we have a Cached DataTable containing the data we'll use to filter for our extender, lets look at the WebMethod that utilizes it:


[WebMethod]
public string[] GetCustomersFromCache(string prefixText, int ResultsReturned)
{
DataTable dt = GetCustomersDT();
DataView dv = new DataView(dt);
dv.RowFilter = "CUSTOMER_NAME LIKE '%" + prefixText.ToUpper().Replace(".", "").Replace(",", "") + "%'";
dv.Sort = "CUSTOMER_NAME";
int rowcount = dv.Count;
int counter = 5;
if (rowcount < 5)
{
counter = rowcount;
}
string[] items = new string[counter];

int x = 0;
foreach (DataRowView drv in dv)
{
if (x < counter)
{
items.SetValue(drv[0].ToString(), x);
x++;
}
else
{
break;
}

}
return items;
}

And there you have it.   You'll see that the first line of the WebMethod calls GetCustomersDT() which returns the DataTable used for filtering our results.  This DataTable will either exist in the Application's Cache or be created and then added to the Cache.    This should save many, many round-trips to the database.

Wednesday, September 17, 2008

Conditional ModalPopupExtender as UpdateProgress control

I'm a big fan of Matt Berseth's blog and his articles are some of the most clear, concise ones out there when dealing with UI elements and webcontrols for ASP.NET.   I've happily employed his ModalPopup as an AJAX Progress Indicator example in a project I'm working on but ran into a small hitch.

Matt's Indicator envelops the beauty of simplicity in that it simply calls the ModalPopupExtender's show() and hide() events when the page's requestmanager receives a request from the Scriptmanager.

One of my pages contains a fairly dense GridView which reflects data that's changed quite often.  As such, I've encapsulated this GridView in an UpdatePanel control and added a Timer control to it with a tick of 20000.   I wanted to show the Modal Progress Indicator when users changed filtering options (controls) for this Gridview but didn't want it to show on postback from the Timer.  After a bit of searching it was as simple as pulling the name of the control that triggered the async call and managing the reaction.

 

<form id="form1" runat="server">
<cc1:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</cc1:ToolkitScriptManager>
<script type="text/javascript" language="javascript">
// register for our events
Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(beginRequest);
Sys.WebForms.PageRequestManager.getInstance().add_endRequest(endRequest);
function beginRequest(sender, args)
{
if(args.get_postBackElement().id.toString() != 'NameOfMyTimerControl')
{
// show the popup
$find('NameOfMyPopupExtender').show();
}
}
function endRequest(sender, args)
{
// hide the popup
$find('NameOfMyPopupExtender').hide();
}
</script>


Now my Progress Indicator only shows when users interact with the page and NOT when the timer refreshes the data.