Introduction
A set of jQuery based Ajax enabled ASP.NET controls including:
- Multi-column auto-complete
- Masked input
- Calendar drop-down
- Validation (including call-out validator)
- Spell checking
- Tab bar control.
Also include a set of helper controls making general purpose ASP.NET programming easier such as
- GridView with insert line and total section
- SqlDataSource with automatically generated insert/update statements.
Table of Contents
History
About a year ago I started to use Ajax.NET Ajax/CTP
. While Ajax CTP makes it fairly easy to add ajax functionality to your application, I had some issues with it:
- Most of my users are still using IE6. Ajax CTP has a know problem with
SELECT
and BUTTON
elements shining right through all the pop-ups. That affects all the components using popup behaivor (such as calendar and auto-complete extenders) Auto-complete
component is very inflexible. Most of the time I want my auto-complete to have multiple separate columns instead of lumping everything up together. Also doing something custom with auto-complete extender is pretty difficult: I was trying for example to show first, last name and SSN, and on user selection only populate SSN. Masked edit
component is pretty inflexible as well. For example if you have a SSN you are trying to collect, I wanted empty field to stay empty, and field with any data to have masking characters filled in. Instead by default it either removes all the masking characters, or keeps them in, in a way filling my empty fields with masked characters. - Size - even though those java-script files are cached by the browser, they can easily start from about 50K and go over 100K for behaivour.
When trying to address all those issues, I wanted to create new component without creating new framework with assiciated learning curve. Creating a date field with calendar pop-up should not require multiple tags to create an input field, popup button, calendar extender, masked edit extender, validator(s), call-out validator,... It should be something as simple as setting a property.
Attached library is my attempt to address all those problems. One of the nice things about this library is that there is no additional startup learning cost. You can literally just replace <asp:TextBox
/> with <lib:Input
/>, <asp:GridView
/> with <lib:InsertableGrid
/> and <asp:SqlDataSource
/> with <lib:TableDataSource
/>.
The library is pretty extensive and in the scope of this article it will be fairly difficult to discuss all the inner workings of the code. Instead I just want to outline main objects and interfaces.
Ajax
To replace AJAX-CTP I decided to use jQuery library. It's amazing how concise the code can become. My average java-script code is only about 6K (uncompressed) vs. about 100K for the average CTP behavior. Granted those java-script files are cached by the browser, but still the difference is huge. You are free to browse code to see how the internals are implemented. All the Ajax interactions are hidden - you don't need to do anything other then include
<asp:ScriptManager EnablePageMethods="true"/>
Sample application setup.
The sample application (default.aspx) uses MS-Sql Server sample
pubs database. The only missing piece is a spell-checker dictionary. That one assumes existence of the table called
Dictionary with one column called
Word. You can 'google' web for the files containing English dictionary, however sample should work as is, without the missing piece.
Default database
Being lazy there are some places in the library where I decided not to carry explicit connection string. So by default I will use last connection defined in the web.config file.
Utils.ConStr
property will fetch you this default connection.
Input control
The soul of the library is the Input control. The control has build in masking, watermarks, auto-complete, validation (range, compare, required and regular expression validators) and equivalent of call-out extender when validation failed.
It's derived from the TextBox and can be used just like a text box - in fact I usually just blindly replace
asp:TextBox with
cc1:Input . However it contains additional properties that can be utilized to give it more power:
DataType | Type of the control to be created. Possible values are:
String | default - just a string - no addition validation
|
---|
Date | Date value formated as mm/dd/yyyy. When clicked upon will display pop-up calendar. Will create masked input entry control, as well as regular expression and range validators. By default range is between 1/1/1900 to 1/1/2072.
<lib:Input ID="dob" runat="server" DataType="Date" />
|
---|
Time | Time value in military format. Also creates masked data entry, regular expression validator, range validator (00:00 - 23:59).
<lib:Input ID="orderTime" runat="server" DataType="Time" />
|
---|
Int | Integer value. Right justified, allows numerics only. Creates range validator. Default range is between -99,999,999 and 999,999,999.
<lib:Input ID="age" runat="server" DataType="Int" />
|
---|
Currency | Double value with 2 decimal digits. Right justified, allows numerics only. Default range is between 0 and 999,999,999.99
<lib:Input ID="age" runat="server" DataType="Currency" />
|
---|
Double | Generic double value with variable number of decimal places:
<lib:Input ID="age" runat="server" DataType="Double" />
| | | Multiline | Multi-line TextBox with spell-checker (IE only).The library doesn't come with the data nor schema. The data assumed to come from the column Word of the table Dictionary. If you are interested in having Spell-Checker working you would need to set it up yourself.
<lib:Input ID="age" runat="server" DataType="Multiline"
Rows="5" Width="150px" />
|
---|
List | Either Drop-Down list box or auto-complete TextBox based on the value of the Method property (see below).
<lib:Input ID="stores" runat="server" DataType="List"
ChkVal="SELECT stor_id, stor_name from stores"/>
|
---|
DropCheck | Multi-selection enabled list of dropdown checkboxes. The values are populated using ChkVal property (see below).
<lib:Input ID='src' runat="server" DataType="MultiChk"
Text='CP,AN' Rows='2' ChkVal='CP=CodeProject;PX=CodePlex;AN=ASP.NET' />
|
---|
|
---|
MinVal / MaxVal | Minimum and maximum value for the control. Internally RangeValidator will be generated to make sure that value falls within range. If either value starts with '#' then it's assumed to be an ID of the control to validate against and CompareValidator will be generated. However in this case only MinVal or MaxVal (not both) will be used instead of range comparison. A special value of ${CurDate} can be used to compare against current date
<lib:Input ID="qty" runat="server" DataType="Int"
MinVal="0" MaxVal="99" />
|
---|
Required | Required flag. Required field validator will be generated. Also will try to perform client side validation. |
---|
Watermark | Generate watermark text when control is empty. If the watermark is used and java-script is used to modify the value of the input control, special function val() need to be used to make sure watermark is appropriately displayed/hidden (eg:this.val('hello'); )
<lib:Input ID="name" runat="server"
Watermark="press any key to see list of authors" />
|
---|
Mask | Mask input validation. Input text has to match the mask precisely or error will occur. No masking characters are displayed when field is empty. Regular expression validator is used internally to enforce validity. Following are possible masking characters to be used:
9 | Numerics only |
---|
W | Any word (non-blank) characters |
---|
A | Alpha characters (a-z) |
---|
C | Alpha-numerics (0-9,a-z) |
---|
2 | Special validation for character to be in (0-2) range (used to validate time) |
---|
5 | Special validation for character to be in (0-5) range (used to validate time) |
---|
<lib:Input ID="phone" runat="server" Mask="999-999-9999" />
|
---|
Method | Only used for the DataType=List. If provided assumed to be a name of the PageMethod to provide Auto-complete items. Otherwise the Drop-Down control will be used and the values will be provided by ChkVal property (see below). The WebMethod needs to have following signature
[WebMethod(), ScriptMethod()]
public static string GetPayTerms(String prefixText, String contextKey) {
return "Cash\tCash is always good!!!\n"
+ Utils.Tbl2Str("SELECT distinct top 10 payterms, @p, @c from sales",
"@p", prefixText, "@c", contextKey);
}
<lib:Input ID="payTerms" runat="server"
DataType="List" Method="GetPayTerms"/>
It has to return a string. Each row is delimited by '\n' and each column is delimited by '\t'. In the simple case the return value can be something like "Male\nFemale" or "M\tMale\nF\tFemale" |
---|
OnSelect | Only used in auto-complete mode (Method has to be specified). This is client-side method to be called when user selects a value from the auto-complete list. If not provided default method looks like:
function AutoCompleteDefault(input, data) {
input.value = data[0];
}
The first argument for the method is input control to be modified, the second is a selected row converted to the array (each column is an entry). As you can see by default, the input will be set to the first column of the selected row. In here however you can also do more such as set the value of other elements (e.g.
<script runat="server">
[WebMethod(), ScriptMethod()]
public static string GetAuthors(String prefixText, String contextKey) {
return Utils.Tbl2Str(@"SELECT distinct top 10 au_lname, au_fname, phone
from authors where au_lname like @v+'%'", "@v", prefixText);
}
</script>
<script>
function SelectAuthor(inp, data) {
inp.value = data[0];
$get('<%=phone.ClientID%>').value = data[2];
}
</script>
<lib:Input ID="last" runat="server" DataType="List"
Method="GetAuthors" OnSelect='SelectAuthor'/>
<lib:Input ID="phone" runat="server" Mask="999-999-9999" />
|
---|
ChkVal | ChkVal along with the Method are used to fetch items for the list. If Method is provided then ChkVal is a parameter string to be send to the WebMethod as a contextKey. Before being send it will be split on ';' character and then all the entries starting with '#' will be assumed to be an IDs of the controls. The values of those controls will be extracted, then concatenated together and send to the server. The example of where it would be useful is pulling Models for Car maker. ChkVal then can look like 'Car;#Maker' - where Maker is an ID of the control. The contextKey on the PageMethod call will then look like 'Car;Toyota'
If Method is not specified (drop down control will be generated), then ChkVal is assumed to be either a SELECT statement (if ChkVal starts with "SELECT" word or a list of choices for the drop down.
If ChkVal is a SELECT statement, first column of the result-set is assumed to be a drop-down list value, and the second column is a drop-down's text. The data is fetched from the default database (see Database section). Example:
<lib:Input ID="store" runat="server" DataType="List"
ChkVal="SELECT stor_id, stor_name from stores"/>
If ChkVal is not a SELECT then it's assumed to be a list of items for the drop-down in item;item;...;item or in key=value;...key=value format. Example:
<lib:Input ID="hair" runat="server" DataType="List"
ChkVal="Brn=Brown;Black;Blond;Gry=Gray"/>
Note: One thing that drove me nuts about Microsoft implementation of the DropDown is how it's reacted to the bad values during the binding - all of us probably saw the exception thrown if the value to bind to not found in the drop down list. Here, if the value (Text property) is not found in the choice list, then it's just added to the end of the list. |
---|
Desc | By default validation error messages will be based on the type of validation that's failed and be generic enough without mention of the field that failed validation. When validation was caught on the client side it's not an issue since Validator call-out will be used to point to the field. However it can become confusing if validation error was caught on the back-end and validation -summary control is filled out with bunch of '"Please enter a value between 0 and 99"' messages. This property can be used to supply field description. Then same error message will look like "Please enter value between 9 and 99 for Quantity" - a little better.
Note:If Desc property is not specified, control will try to use ToolTip property for the error message. So depending on the wording of the ToolTip (if you have one) you might need to include Desc property as well.
|
---|
ErrMsg | Allows to completely customize error message entirely. However all the validation error messages will be replaced with the one provided, regardless if it's required, range or regular expression validation that's failed. |
---|
InsertableGrid
<lib:InsertableGrid ... AllowInsert="true" SelectableRow="true" OnLoadTotals="GridView1_LoadTotals">
<Columns>
<lib:BoundInput DataField="ord_num" HeaderText="Order Number" SortExpression="ord_num" />
<%-- next line creates a date column. Edit field is 80px wide --%>
<lib:BoundInput DataField="ord_date" HeaderText="Order Date" SortExpression="ord_date"
DataType="Date" Width="80px" />
<%-- next line creates 80px wide numeric column with valid data range 0-90 --%>
<lib:BoundInput DataField="qty" HeaderText="Quantity" Width="80px"
SortExpression="qty" DataType="Int" MinVal="0" MaxVal="99"/>
<%-- next line creates 50px wide autocomplete column with maxLength of 12 char.
GetPayTerms page method will be executed to fetch data --%>
<lib:BoundInput HeaderText="Terms" SortExpression="payterms" Width="50px" MaxLength="12"
DataType="List" Method="GetPayTerms" ChkVal="ChkVal Parameters"/>
<%-- next line creates drop down list box with options from ChkVal prop --%>
<lib:BoundInput HeaderText="Title" SortExpression="title_id" Width="100px"
DataType="List" ChkVal="select title_id, title from titles" />
<lib:ExtButtons />
</Columns>
<%-- create a total portion --%>
<TotalsTemplate>
<tr><td align="right" colspan='2'>Total Quantity</td>
<td align="right"><asp:Label ID="totQty" runat="server" Text="to be filled" /></td></tr>
</TotalsTemplate>
</lib:InsertableGrid>
Over here I continued the work I started some time ago (see InsertableGrid article). Some additional features were added. Overall here is a list of implemented features:
- Optionally insert row will be generated. By default insert row will use editors defined for the update row, unless
<InsertTemplate/>
is defined for the column. Footer template is not used. Appropriate events are provided for RowInserting / RowInserted
situations. - Separate
<TotalsTemplate/>
can be defined for the sub-total portion. Dynamic data can be supplied by providing LoadTotals
event handler. - Optionally GridViewRow is selectable by clicking anywere on the row.
- Changing selection will flush Insert/Edit row changes into the database (if applicable) and set selection (editing) to the new
- Optionally will create header row even if there is no data (instead of using EmptyData).
Those features can be controlled by following properties:
AlwaysHeader | Normally GridView will display EmptyDataTemplate if there is no data. By setting this property you can force it to display header line whether there is data or not |
---|
AllowInsert | Will generate auto-insert line based on the field editors specified for the regular edit rows. Please see InsertableGrid article for a insert line discussion. |
---|
SelectableRow | When set, clicking anywhere on the gird row will select a whole row. If AllowInsert is also set, then along with switching a selection it will flush data changes on the current edit/insert row, and put newly selected row in edit mode. |
---|
TotalsTemplate | Allow to specify total section. In the image above see Total Quantity field. E.g.:
<lib:InsertableGrid ... OnLoadTotals="GridView1_LoadTotals">
...
<TotalsTemplate>
<tr><td align="right" colspan='2'>Total Quantity</td>
<td align="right"><asp:Label ID="totQty"
runat="server" Text="TBD" /></td></tr>
</TotalsTemplate>
</lib:InsertableGrid>
|
---|
LoadTotals | Event generated to provide data for totals row. E.g.
protected void GridView1_LoadTotals(object sender, DataEventArgs e) {
Utils.SetCtrlText(e.cont, "totQty",
Utils.Scalar("SELECT sum(qty) from sales where stor_id=@s",
"@s", store.Text));
}
|
---|
DataError | Event generated if there was an error during the insert/update/delete operation. This way I don't need to capture 3 events to check for error code, but instead capture just one. |
---|
RowInserting | Event generated just before inserting data to the database. Will use GridViewUpdateEventArgs . |
---|
RowInserted | Event generated right after insertion is complete. Uses GridViewUpdatedEventArgs |
---|
FlushEditChanges | Method to flush edit line data (insert of update) to the datasource Usefull when you have an 'Save' button on the form and you want to make sure that whatever user last typed in is flushed to the datasource. |
---|
Additional power however come from integrating
Input
control with
InsertableGrid
So two additional classes are provided. Indirectly they are derived from the DataControlField base class and as such can be used in either InsertableGrid, GridView or FormView.
BoundInput
<lib:InsertableGrid ... AllowInsert="true" SelectableRow="true" >
<Columns>
<lib:BoundInput HeaderText="Order Number" SortExpression="ord_num" Mask="CCC-999999" />
<lib:BoundInput HeaderText="Order Date" SortExpression="ord_date" DataType="Date"
Width="80px" />
<lib:BoundInput HeaderText="Quantity" Width="80px"SortExpression="qty" DataType="Int"
MinVal="0" MaxVal="99"/>
<lib:BoundInput HeaderText="Terms" SortExpression="payterms" Width="50px" MaxLength="12"
DataType="List" Method="GetPayTerms" ChkVal="ChkVal Parameters"/>
<lib:BoundInput HeaderText="Title" SortExpression="title_id" Width="100px"
DataType="List" ChkVal="select title_id, title from titles" />
<lib:ExtButtons />
</Columns>
</lib:InsertableGrid>
The huge benefit of this class is that it allows you to bypass Item/EditTemplate
for simple things like MaxLength
validation or setting TextBox Width
. Since it exposes other properties of Input
control, it provides power to do pretty much everything Input
control does. This class for me replaced <asp:BoundField> as a default column definition. When not in edit mode, this class also will perform following:
- Right-align data for numeric columns, format data with #,### or #,###.00 format mask.
- Perform replacement of keys with values (text) from drop-down list columns (so you don't have to hand-craft select statements to fetch keys and descriptions)
Here is a list of properties this control exposes (see MSDN for additional properties exposed by asp:BoundField)
Width | Width of the Input control (TextBox width) |
---|
DataType | Field DataType. See Input control for possible data types. An additional DataType of Check is used here. Beside the fact that it is less restrictive as far as binding requirements (did you ever had to bind check box to a null value?), it also allows to minimize use of templates |
---|
InsertVisible | When cleared, insert line data entry for this field will be disabled |
---|
ReadOnly | When set, update line data entry for this field will be disabled |
---|
DataField | If not supplied, then SortExpression will be used to specify column name. |
---|
Rows | Only used for multi-line inputs - number of rows for the multi-line input control. |
---|
MaxLength | MaxLength of the input control |
---|
MinVal | See Input control for description |
---|
MaxVal | See Input control for description |
---|
Mask | See Input control for description |
---|
ChkVal | See Input control for description |
---|
Method | See Input control for description |
---|
OnSelect | See Input control for description |
---|
ExtButtons
This class implements control buttons for the row. For regular rows select and delete buttons will be generated, for the edit or insert lines it will generate update/insert and cancel buttons. When clicking Delete button, confirmation message will be generated.
TabControl
<lib:TabControl ID='tabs' runat="server">
<Tabs>
<lib:TabPage TabId='tab1' Title="Tab1" />
<!--
<lib:TabPage TabId='tab2' Title="Tab2"
Confirm='You sure you want to switch?'/>
<!--
<lib:TabPage TabId='tab3' Title='Tab3' PostBack="true" />
</Tabs>
</lib:TabControl>
Primary reason I was not satisfied with AjaxCTP tab control is that if I have a big enough form and I want to throw tabs in the middle of the FormView, it would not work. So the replacement had to have following features:
Can be optionally completely client based and not interfere with NamingContainers and such (e.g. FormView) Can be server based so if I have a lots of tabs with a lot of mark-up in them, I can load tabs only when user click on it. Tab doesn't need to be associated with panel at all, so I can use it as a replacement for a command row, e.g. <Employee Info><Work History><Save> where first two buttons will cause panel change and the last one will be a postback to save information.
The tab I come up with satisfies those two requirements and more. Here are the properties it exposes:
Tabs | Collection of TabPages. Each TabPage exposes following properties:
TabId | Optional ID of the tab control/panel. If control with this ID exists on the server then it will be used. If doesn't then it will be assumed that this is a client-side DOM id. |
---|
Title | Tab title |
---|
PostBack | If set clicking on the tab will cause postback. |
---|
Confirm | Confirmation message to be displayed before switching or postback |
---|
|
---|
TabChanged | Event generated when postback occurs as a result of user clicking on the tab. |
---|
CurrentTab PrevTab | Properties exposing current selection and previous selection. Could be used during event handler processing to perform some action or change user selection. Setting CurrentTab=PrevTab will in effect just refresh screen. |
---|
TableDataSource
This class is derived from SqlDataSource and designed to automatically generate INSERT/UPDATE/DELETE statements and all relevant parameters.
I mentioned in introduction it's not unusual for me to have to add a column or two to a table that already has over 200 columns. Updating insert and update statements and adding all the parameters to the DataSource quickly becomes real chore.
Another issue is that SqlDataSource doesn't check if data was changed or not - if you want to update the data it will update the data even if there are no changes. Most of the time if there is no changes I don't want to do an update. It becomes especially important with the way InsertableGrid
can be put in the edit mode just by clicking the line.
Yet another issue for me is to do something else with the database within the scope of the same transaction, e.g when inserting order detail record, update total in the order header table.
TableDataSource is fairly thin layer on top of the SqlDataSource. However it simplified my coding quite a bit. Here are additional properties being exposed (vs. SqlDataSource)
TableName | Table name to generate all the updates against. UPDATE/DELETE/INERT statements along with all the parameters will be generated against this table automatically, unless InsertCommand, UpdateCommand or DeleteCommand are specified - then default implementation is used. |
---|
InsertIdentity | If one of the column in the table is auto-increment column, then upon successful insert, InsertIdentity will be populated with the value of the identity value generated for the table specified by TableName . |
---|
GetView | By design Datasource is just an very thin wrapper for the DatasourceView class. DataSourceView class is the one performing heavy lifting of the changing the data in the database. Microsoft however hid the view from us. This function allows you to access the view in case you need to do data updates/inserts explicitly. |
---|
Transaction | By default SqlDataSource will close a connection used upon completion of the data access. If however you set this property prior to the data access, then the data access will be done within the scope of the transaction. I would need this when the data for the GridView is a join of two tables. Then I will have a TableDataSource for each table. The one supplied in the GridView will be called automatically and in the OnUpdated handler of that DataSource I would update second table:
protected void ds_Updated(object sender, SqlDataSourceStatusEventArgs e) {
if (e.Exception == null) {
ds1.Transaction = e.Command.Transaction;
ds1.GetView().Update(ds.Keys, ds.NewValues, ds.OldValues);
}
}
<lib:TableDataSource ... TableName="Detail" SelectCommand="SELECT * FROM Detail"
OnInserted="ds_Inserted" OnUpdated="ds_Updated">
...
</lib:TableDataSource>
|
---|
Keys, NewValues, OldValues | All those properties expose the data used for updates. See Transaction property for the example of how it those properties can be useful. |
---|
DeleteParameters UpdateParameters InsertParameters | Those are derived from the SqlDataSource and strictly speaking they are not needed. You would want to supply them when you need to force a value of certain parameter to be of a certain value (other control, session or request variable, current time). Current time is designated by use of ${CurTime} value. E.g.:
<InsertParameters>
<asp:ControlParameter ControlID="store" Name="stor_id"
PropertyName="Text" Type="String" />
<asp:SessionParameter Name="created_by"
SessionField="__LoggedInUser__" />
<asp:Parameter Name="created_on" DefaultValue="${CurTime}" />
</InsertParameters>
|
---|
Utilities module (Utils)
Utilities module provides bunch of static functions I use throughout the library. Here are some of them that I consider the most useful:
ConStr
Returns default database connection - last connection string defined in web.config file.
public static void SetCtrlText(Control nc, string name, object text)
Finds control with a certain name within naming container and sets it's value to a text. Takes care of different types of controls such as DropDowns, Labels, TextBoxes, Checkboxes... If name is null then nc text will be set.
public static DataTable GetTable(string sql, params object[] prm)
Returns DataTable for a select statement. First argument is a select statement, the rest are either:
- Connection - connection object to use for command (if not supplied uses default connection string (ConStr)
- Transaction - use connection and transaction to get data. If specified, connection associated with this transaction will be used/
- name value pairs for SQL parameters.
Example:
DataTable tbl = Utils.GetTable("SELECT distinct top 10 payterms, @p, @c from sales",
"@p", prefixText, "@c", contextKey);
public static string Tbl2Str(string sql, params object[] prm)
Same as GetTable except returned data is concatenated into one long string where rows are delimited by new-line (\n) and columns are delimited by tab (\t) - suited for the auto-complete.
public static Object Scalar(string sql, params object[] prm)
Executes sql string and returns result. If sql string starts with "SELECT" then return value is first column of the first row of the returned data. Otherwise it's a number of rows affected by INSERT/DELETE/UPDATE operation. Other arguments may be either connection, transaction or name-value pairs. (see GetTable function above)
Copyrights
I personally have no licencing limitations on the attached code. However some pieces of it (namely jQuery and Josh Bush masked edit plug-in) might have some kind of licencing restrictions. I would encourage you to double check possible restrictions associated with those components.
Summary
One of the things I noticed about jQuery library is that after using it for 15 minutes you can't help it but think how can you make you code yet a little smaller. The library I'm presenting here takes this notion into the HTML markup programming - a screen with complex enough Grid/FormView can easily be trimmed in half. You end up with less markup, and whatever left is much more maintainable. Aside from that, the javascript library included, while not as comprehensive as Ajax CTP is only a fraction in size. I hope you can have as much fun with this library as I do.
History
11/05/2008 - Initial release
11/12/2008 - Some fixes to get date localized. Still need more testing for different international settings. In particular currently week always starts on Sunday.
11/13/2008 - Small bug fix with Calendar popup. Implemented focus reset on calendar release.
11/19/2008 - Implemented data type Double. Also WebLib now will register required CSS.
12/08/2008 - Added reference to this project to the CodePlex.com.
03/09/2009 - Completely reworked validation. Addressed IE memory leak in ASP:UpdatePanel. Added new MultiCheck control type.
04/23/2008 - Fixed a bug with programmatic tab switching. Some other cleanup.
05/07/2009 - Some cleanup. Changed example to use UpdatePanel. Changed MultiChk type to DropCheck.
08/25/2009 - Fixed design mode. Added description for public properties. Switched to use some LINQ queries.
TODOs
The library is a part of the code that I use on day-to-day basis. Every now and then I have a luxury of going back and retrofitting project I have done some time ago. On average I can achieve about 50% reduction in code/markup by using this library - over long term maintenance should save me quite a number of gray hair.
While this library seems to work for me, my environment probably is fairly limited. I need to support only IE6 and English only. Therefore I can imagine some stuff not working as expected in different setups:
- Internationalization
- Other browsers (IE7 seems to work, except for spell checker seems to work on Firefox also)
- InsertableGrid validation groups - currently default page validation will be performed for the data updates.
Your donations are kindly appreciated