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, December 8, 2010

SQL Query runs fast in MSSMS but slow as stored procedure

 

It was the weirdest thing.  I had a fairly straight-forward query that when run in my Server Management Studio editor would only take a second or 2 but after building a stored procedure with the same exact query and executing it (either from a .NET page/object or even from a T-SQL call, the stored procedure would take FOREVER to execute.

After a few hours of Google, hair-pulling and searching for a white towel to throw in, I found a solution that works 99% of the time.

 

The cause is an issue called Parameter Scanning and occurs "sometimes" in procedures that pass a parameter in.

The fix is to use internalized parameters - basically creating a new parameter inside of the stored procedure and assigning it a value of the one being passed in.

I don't know the answer as to WHY this happens and why this fix corrects it but all I know is that it does work

 

Examples:

A typical stored procedure with a single passed-parameter might be something like:

ALTER PROC [dbo].[uspREPORTING_Sales]
(@parSALESPERSONID int)
AS

SET NOCOUNT ON

SELECT SUM(GROSS_PROFIT) FROM tblSALESCONTRACTS WHERE SALESPERSON_ID = @parSALESPERSONID



This same procedure rewritten to internalize the parameter would look something like




ALTER PROC [dbo].[uspREPORTING_Sales]
(@parSALESPERSONID int)
AS

SET NOCOUNT ON

DECLARE @intSALESPERSONID int
SET @intSALESPERSONID = @parSALESPERSONID

SELECT SUM(GROSS_PROFIT) FROM tblSALESCONTRACTS WHERE SALESPERSON_ID = @intSALESPERSONID



You're basically referencing the internally-declared parameter instead of the one passed to the procedure. 



Hope this helps those who are having this problem

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.

Thursday, March 25, 2010

Avoiding "Timeout Expired" errors from long running SELECT statements

a SQLDataSource object has a default command timeout of 90 seconds. Often you may be tasked with returning large amounts of data or doing some other tedious processing on the database side that takes while. If you exceed this you'll get the pain-in-the-ass runtime exception - Sys.WebForms.PageRequestManagerServerErrorException: Timeout expired.

 error

 

To work-around this when all other methods have failed, I increase the command timeout value of the SqlDataSource object via it's SELECTING event as such:

protected void sdsYourSQLDataSource_Selecting(object sender, SqlDataSourceSelectingEventArgs e)
{
e.Command.CommandTimeout = 360;
}


The value is set in SECONDS. Hope this helps

Thursday, April 2, 2009

Populating a TreeView control from a database

I was recently tasked with creating a navigation control which displayed a list of cities and nested communities for those cities.  As the listings could change at a moments notice and I might not always be available to modify code I decided to use a TreeView control and populate it from a SQL Server table. 

While my end-product looks and acts differently than this example, I will post the mere basics for building a working product.

First a look at the data.   I am pulling information about communities located with a city so the relationship is many communities per city.

Example of the data follows.   The communityID is our primary key and will be used as the value of the selected node.

communityID communityCity                                      communityName
----------- -------------------------------------------------- --------------------------------------------------
1 Cypress Coles Crossing
2 Cypress Bridgeland
3 Cypress Stillwater Village
4 Memorial Terraces on Memorial
5 Richmond Lakes of Bella Terra



Let's drop a TreeView control into our page.  Mine is called tvNav.  I added some images to indicate the state (Collapsed or Expanded) which you see in the markup.




<body>
<form id="form1" runat="server">
<div>
<asp:TreeView ID="tvNav" runat="server"
CollapseImageUrl="~/Testimonials/images/expanded.gif"
ExpandImageUrl="~/Testimonials/images/collapsed.gif">
</asp:TreeView>
</div>
</form>
</body>



Now on the back-end we build our functions to pull the data and populate the TreeView.



The method I chose to use (for no other reason than I felt like it) was to build a DataSet of information pulled from the table.  I then iterate through this dataset for data comparison and extraction.



The first function builds a DataSet and returns it to the function that calls it.




private DataSet BuildDS()
{
string strSQL = "SELECT communityID,communityName,communityCity FROM MYTABLE WHERE communityStatus = 'A' ORDER BY communityCity, communityName";
SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["MYCONNECTIONSTRING"].ToString());
conn.Open();
SqlDataAdapter sda = new SqlDataAdapter(strSQL, conn);
DataSet ds = new DataSet();
sda.Fill(ds);
conn.Close();

return ds;
}



The workhorse:




private void BuildTree()
{
DataSet ds = BuildDS();
string cCity = "";
if (ds != null)
{

TreeNode parentnode = null;
TreeNode childnode = null;
foreach (DataRow row in ds.Tables[0].Rows)
{
if (cCity.CompareTo(row["communityCity"].ToString()) < 0)
{
parentnode = new TreeNode(row["communityCity"].ToString());
// we want parent nodes to simply expand so no postback (select) is needed
parentnode.SelectAction = TreeNodeSelectAction.Expand;
if (parentnode != null)
{
tvNav.Nodes.Add(parentnode);
}
}
cCity = row["communityCity"].ToString();
}

foreach (TreeNode tn in tvNav.Nodes)
{
foreach (DataRow row in ds.Tables[0].Rows)
{
if (tn.Text.CompareTo(row["communityCity"]) == 0)
{
childnode = new TreeNode();
childnode.Text = row["communityName"].ToString();
childnode.Value = row["communityID"].ToString();
tn.ChildNodes.Add(childnode);
}
}
tn.Collapse();
}
}
}




First thing we do is to add the Parent nodes to the Treeiew.  As the CITY is the parent in the data's relationship schema we'll use that as the value for our Parent nodes in the control. To add those nodes I iterate through each row in the dataset and compare the value of the stored City with the value of the cCity string element.  In the first run through of the loop we have "null" as the value of cCity since nothing's been assigned as the value.  After we process our actions for each iteration though we assign the current value of the City field in the dataset to the cCity element.



After our parents are populated we make another loop through our dataset to build child nodes to add to the parents.  To accomplish this we use 2 loops.  The first loop moves through the parent nodes.  The nested loop inside compares the value of the parent node text with the value of the "communityCity" data-element.  If they are the same, we know to add a child node.  If not we simply move on to the next.



Note:  If your results-set (either in your SQL statement or via filtering your Dataset) are not in the correct order, this method won't work.   if you look at the SQL query statement I've used, you see it's ordered by communityCity and then by communityName. 



Finally we call our BuildTree() function so our control is built.  I do this on the initial creation of the page so that subsequent postbacks don't create additional nodes.




protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BuildTree();   }
}

Friday, March 6, 2009

Strip HTML from TextBox inputs

Use a regular expression to search for HTML variants and convert them to "" (nothing).

 

I've placed a simple function called StripHTML in a user-class called SystemFunctions as follows:

using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Text.RegularExpressions;

public class SystemFunctions
{
public string StripHTML(string strInput)
{
return System.Web.HttpContext.Current.Server.HtmlDecode(Regex.Replace(strInput, "<(.|\n)*?>", ""));
}
}



* Make sure you add the reference to System.Text.RegularExpressions