Telerik ASP.NET AJAX RadGrid - Set Default Values when Adding a Record while BatchEditing

While recently working on an ASP.NET WebForms project, I wanted to improve the user interface while editing the grid. Specifically, I wanted to add what is known as "batch editing" to the UI. Batch editing is the ability for the user to make multiple changes to records -- similar to working with a spreadsheet, although much more powerful -- before saving all of the changes. No more, change a row then save. Change multiple rows; and/or insert (multiple) rows and then save.

Of course, this was not your vanilla grid with just textboxes. This grid contains:

  • 1 RadDropdownlist
  • 2 RadDateTimePickers
  • 2 RadNumericTextboxes
  • 1 Textbox in multiline mode

The business scenario is rudimentary. We have a labor force that we want to schedule. In this case, we are using the male pronoun "men" (as a generic term - don't take offense - we don't care what it's called). So, we have the phase of the project we're in represented by a dropdownlist. Start and End Date/Times. A Number of Men and Hours. And, finally, a textbox description of the Scope of Work.

The screenshot below (click to enlarge) shows the behavior we are trying to achieve.

 

Here is our definition for the Telerik RadGrid. You will notice that this definition contains multiple templated columns, each with their own Radxxx Control. This is where the fun comes in because this definition does not contain simple bound columns, initializing these values when clicking the "Add New Record" button is the complication we want to address. Also, you may notice that our templated columns contain client-side customer validators. We will cover how those are handled in a subsequent blog post.


<telerik:RadGrid ID="rgGrid" runat="server" DataSourceID="odsLaborAll" GridLines="None" Width="100%" AutoGenerateColumns="False">
            <AlternatingItemStyle Font-Bold="False" Font-Italic="False"
                Font-Overline="False" Font-Strikeout="False" Font-Underline="False"
                VerticalAlign="Top" Wrap="True" />
            <ItemStyle Font-Bold="False" Font-Italic="False" Font-Overline="False"
                Font-Strikeout="False" Font-Underline="False" VerticalAlign="Top" Wrap="True" />
            <ClientSettings AllowKeyboardNavigation="true">
                <ClientEvents OnBatchEditOpened="OnBatchEditOpened" />
            </ClientSettings>
            <MasterTableView DataSourceID="odsLaborAll" DataKeyNames="LaborId,LaborOrderId" EditMode="Batch" CommandItemDisplay="TopAndBottom" AllowAutomaticDeletes="True" AllowAutomaticUpdates="True" AllowAutomaticInserts="true">
                <BatchEditingSettings EditType="Row" OpenEditingEvent="<%$ AppSettings:BatchEditingEvent %>"  HighlightDeletedRows="True" />
                <CommandItemSettings ShowAddNewRecordButton="True"></CommandItemSettings>
                <Columns>
                    <telerik:GridTemplateColumn DataField="LaborPhase" HeaderText="Labor Phase" UniqueName="laborphase">
                       <ItemStyle VerticalAlign="top" />
                       <ItemTemplate>
                            <%# Eval("LaborPhase") %>
                        </ItemTemplate>
                        <EditItemTemplate>
                            <telerik:RadDropDownList runat="server" ID="rdlLaborPhase" DataValueField="KeyValue" DataTextField="KeyValue" DataSourceID="odsLaborPhase">
                            </telerik:RadDropDownList>
                             <br />
                       </EditItemTemplate>
                    </telerik:GridTemplateColumn>
                    <telerik:GridTemplateColumn DataField="StartDateTime" HeaderText="Start" UniqueName="startdatetime">
                         <ItemStyle VerticalAlign="top" />
                       <ItemTemplate>
                            <%# Eval("StartDateTime", "{0:MM/dd/yyyy hh:mm tt}") %>
                        </ItemTemplate>
                        <EditItemTemplate>
                            <telerik:RadDateTimePicker ID="rdpStartDateTime" DbSelectedDate='<%# Bind("StartDateTime") %>' runat="server" ShowPopupOnFocus="true" Width="200px"
                                DateInput-DateFormat="M/d/yyyy h:mm tt" DateInput-DisplayDateFormat="M/d/yyyy h:mm tt" TimeView-Interval="00:15:00" TimeView-Columns="4">
                            </telerik:RadDateTimePicker>
                            <br />
                            <asp:CustomValidator runat="server" ID="valStartDateTime" CssClass="losValidation" EnableClientScript="true" ClientValidationFunction="validateDates" ControlToValidate="rdpStartDateTime" ErrorMessage="End DT must be AFTER Start DT"></asp:CustomValidator>
                        </EditItemTemplate>
                    </telerik:GridTemplateColumn>
                    <telerik:GridTemplateColumn DataField="EndDateTime" HeaderText="End" UniqueName="enddatetime">
                         <ItemStyle VerticalAlign="top" />
                       <ItemTemplate>
                            <%# Eval("EndDateTime", "{0:MM/dd/yyyy hh:mm tt}") %>
                        </ItemTemplate>
                        <EditItemTemplate>
                            <telerik:RadDateTimePicker ID="rdpEndDateTime" DbSelectedDate='<%# Bind("EndDateTime") %>' runat="server" ShowPopupOnFocus="true" Width="200px"
                                DateInput-DateFormat="M/d/yyyy h:mm tt" DateInput-DisplayDateFormat="M/d/yyyy h:mm tt" TimeView-Interval="00:15:00" TimeView-Columns="4">
                            </telerik:RadDateTimePicker>
                            <br />
                            <asp:CustomValidator runat="server" ID="valEndDateTime" CssClass="losValidation" EnableClientScript="true" ClientValidationFunction="validateDates" ControlToValidate="rdpEndDateTime" ErrorMessage="End DT must be AFTER Start DT"></asp:CustomValidator>
                        </EditItemTemplate>
                    </telerik:GridTemplateColumn>
                    <telerik:GridTemplateColumn DataField="NumberOfMen" HeaderText="Men" UniqueName="numberofmen">
                        <ItemStyle VerticalAlign="top" />
                        <ItemTemplate>
                            <%# Eval("NumberOfMen", "{0:n0}") %>
                        </ItemTemplate>
                        <EditItemTemplate>
                            <telerik:RadNumericTextBox ShowSpinButtons="True" DbValue='<%# Bind("NumberOfMen") %>' Type="Number" DataType="System.Integer"
                                ID="rntNumberOfMen" runat="server" Width="150px" MinValue="0">
                                <NumberFormat AllowRounding="True" KeepNotRoundedValue="False" DecimalDigits="0" />
                            </telerik:RadNumericTextBox>
                            <br />
                            <asp:CompareValidator ID="valNumberOfMen" runat="server" CssClass="losValidation" ErrorMessage="Men must be greater than 0." ControlToValidate="rntNumberOfMen" ValueToCompare="0" Type="Integer" Operator="GreaterThan"></asp:CompareValidator>
                        </EditItemTemplate>
                    </telerik:GridTemplateColumn>
                    <telerik:GridTemplateColumn DataField="NumberOfHours" HeaderText="Hrs" UniqueName="numberofhours">
                        <ItemStyle VerticalAlign="top" />
                        <ItemTemplate>
                            <%# Eval("NumberOfHours", "{0:n2}") %>
                        </ItemTemplate>
                        <EditItemTemplate>
                            <telerik:RadNumericTextBox ShowSpinButtons="True" DbValue='<%# Bind("NumberOfHours") %>' Type="Number" DataType="System.Decimal"
                                ID="rntNumberOfHours" runat="server" Width="150px" MinValue="0" IncrementSettings-Step="0.5">
                                <NumberFormat AllowRounding="True" KeepNotRoundedValue="False" DecimalDigits="2" />
                            </telerik:RadNumericTextBox>
                            <br />
                            <asp:CompareValidator ID="valNumberOfHours" runat="server" CssClass="losValidation" ErrorMessage="Hours must be greater than 0." ControlToValidate="rntNumberOfHours" ValueToCompare="0" Type="Double" Operator="GreaterThan"></asp:CompareValidator>
                        </EditItemTemplate>
                    </telerik:GridTemplateColumn>
                    <telerik:GridTemplateColumn DataField="ScopeOfWork" HeaderText="Scope of Work" UniqueName="scopeofwork">
                        <ItemTemplate>
                            <%# Eval("ScopeOfWork") %>
                        </ItemTemplate>
                        <EditItemTemplate>
                            <asp:TextBox ID="txtScopeOfWork" runat="server" Text='<%# Bind("ScopeOfWork") %>' TextMode="MultiLine" Height="75px" Width="200px" />
                             <br />
                       </EditItemTemplate>
                    </telerik:GridTemplateColumn>
                     <telerik:GridBoundColumn HeaderText="LaborOrderId" DataField="LaborOrderId" ReadOnly="True" UniqueName="labororderid" Display="False" />
                   <telerik:GridButtonColumn CommandName="Delete" ConfirmText="Are you sure you want to delete this record?" ItemStyle-Width="30px" ButtonCssClass="rgDel" ConfirmTitle="Labor - Delete" Text="Delete" UniqueName="DeleteColumn" HeaderText="Delete">
                    </telerik:GridButtonColumn>
                </Columns>
            </MasterTableView>
        </telerik:RadGrid>
		

Since we want to set some default values when adding a new record, we need to start with - How do we know when the user is adding a record?

First, we have to set the RadGrid to the correct EditMode (in this case Batch). We also need to determine how the User is going to start editing (or adding) records to the grid. For that, we use the BatchEditSettings and the CommandItemSettings markup. In our case, we're pulling the OpenEditingEvent from our web.config file. We set it to Click. The CommandItemSettings markup allows us to tell the Grid that we want an Add New Record shown. Finally, we need to decide upon an event that will be used to determine if we're adding/editing a record. We'll use OnBatchEditOpened.

The shell of this markup is shown below.


<telerik:RadGrid ID="rgGrid" runat="server" DataSourceID="odsLaborAll" GridLines="None" Width="100%" AutoGenerateColumns="False">
            <ClientSettings AllowKeyboardNavigation="true">
                <ClientEvents OnBatchEditOpened="OnBatchEditOpened" />
            </ClientSettings>
            <MasterTableView DataSourceID="odsLaborAll" DataKeyNames="LaborId,LaborOrderId" EditMode="Batch" CommandItemDisplay="TopAndBottom" AllowAutomaticDeletes="True" AllowAutomaticUpdates="True" AllowAutomaticInserts="true">
                <BatchEditingSettings EditType="Row" OpenEditingEvent="<%$ AppSettings:BatchEditingEvent %>"  HighlightDeletedRows="True" />
                <CommandItemSettings ShowAddNewRecordButton="True"></CommandItemSettings>
            </MasterTableView>
        </telerik:RadGrid>
		

When OnBatchEditOpened is fired then we will call the Javascript function OnBatchEditOpened.

HOWEVER...According to the Telerik documentation regarding OnBatchEditOpened, this event does the following.

This event is fired after a cell is opened for edit. You can use it to access and modify cell values and inner controls from template columns.

So, how do we distinguish between Adding a New Record and Editing an Existing Record?

Here I needed a little help from Telerik Support (many thanks to Atilla), he supplied the following JS shell for the OnBatchEditOpened event.


function OnBatchEditOpened(sender, args) {
    var rowId = parseInt(args.get_row().id.split("__")[1]);

    var isInserting = rowId < 0; // returns true if the row's ID is negative

    if (isInserting) {
        // This is a new Row

        // Your logic when inserting

    } else {
        // Updating an existing row

        // Your logic when Updating
    }
}
		

This is a nice little piece of code. It parses out the row id of the row being fired by the event. If that row id is negative then we're adding information and not editing existing information.

A couple of additional important things to know about OnBatchEditOpened

  1. It fires once for every editable column in the grid.
  2. The name of the column that the event is firing for is available through a method call on the args parameter.
  3. The templated controls are shared (occur only once) in our Grid. Because the user can only be editing 1 row (or cell) at a time, it makes sense that Telerik wouldn't design independent controls for every row and cell.

Knowing this information, we can design the basic flow of our algorithm for accomplishing our main task.

  1. Determine if we are adding a record.
  2. If yes, then get the column name the event is firing for.
  3. Find and get a reference to the control.
  4. Set the default value for the control as required

And here is our resulting JS function based upon our algorithm.


function OnBatchEditOpened(sender, args) {
	_inBatchEditOpened = true;
	console.log('OnBatchEditOpened')
	//console.log(sender);
	//console.log(args);

    //
	// Get the rowId and determine if we're adding a record
	//
	var rowId = parseInt(args.get_row().id.split("__")[1]);
	console.log('Row = ' + rowId);
	var isInserting = rowId < 0; // returns true if the row's ID is negative

	//
	// We need the parentElement for finding the templated controls
	// And we need the name of the column that the event is firing for
	//
	var parentElement = $get("<%= rgGrid.ClientID%>");
	var colNm = args.get_columnUniqueName();
	console.log(colNm);
	var ctl;

	//
	// Now, if we're inserting then run some code based upon which column we're in
	//
	// For the column:
	//      1. Get a reference to the control by it's ID name
	//      2. Set it's default value
	//
	if (isInserting) {
		switch (colNm) {
			case "laborphase":
				ctl = $telerik.findControl(parentElement, "rdlLaborPhase");
				console.log(ctl);
				if (ctl !== null && ctl !== undefined) {
					var itemByValue = ctl.findItemByValue("Install");
					if (itemByValue !== null & itemByValue !== undefined) {
						console.log('setting Labor Phase');
						itemByValue.select();
					}
				}
				break;
			case "startdatetime":
				ctl = $telerik.findControl(parentElement, "rdpStartDateTime");
				console.log(ctl);
				if (ctl !== null && ctl !== undefined) {
					var startDateTimeValue = document.getElementById(hdnStartDateTime).value;
					console.log(startDateTimeValue);
					var myDate = Date.parse(startDateTimeValue);
					if (myDate !== null && myDate !== undefined) {
						console.log(myDate);
						ctl.set_selectedDate(myDate);
					}
				}
				break;
			case "enddatetime":
				ctl = $telerik.findControl(parentElement, "rdpEndDateTime");
				console.log(ctl);
				if (ctl !== null && ctl !== undefined) {
					var startDateTimeValue = document.getElementById(hdnStartDateTime).value;
					console.log(startDateTimeValue);
					var myDate = Date.parse(startDateTimeValue);
					if (myDate !== null && myDate !== undefined) {
						myDate = myDate.addHours(4);
						console.log(myDate);
						ctl.set_selectedDate(myDate);
					}
				}
				break;
			case "numberofmen":
				ctl = $telerik.findControl(parentElement, "rntNumberOfMen");
				console.log(ctl);
				if (ctl !== null && ctl !== undefined) {
					console.log('setting Number of Men - 1');
					ctl.set_value("1");
				}
				break;
			case "numberofhours":
				ctl = $telerik.findControl(parentElement, "rntNumberOfHours");
				console.log(ctl);
				if (ctl !== null && ctl !== undefined) {
					console.log('setting Number of Hours - 4');
					ctl.set_value("4");
				}
				break;
		}
	}
	_inBatchEditOpened = false;
}
		

A couple of notes about the defaults we are setting for these controls.

  • For the Number of Hours and Men, we are setting the defaults using "hard-coded" values - 4 Hours and 1 Man. However, these values could come from anywhere including an API call if you needed to go that route.
  • For the Start Date/Time and End Date/Time, we are pulling those values from hidden controls elsewhere on the form (not shown). These values are specific to the task that the labor references and was the main impetus for this bit of code.

This post illustrated an approach to setting default values for templated columns when adding a record using BatchEdit mode for the Telerik ASP.NET AJAX RadGrid control. It can easily be extended and refactored to handle other scenarios.

I sincerely hope that this aids someone in their development efforts. If you found value in this post, please email me as the positive feedback helps me to know that I'm adding some value and posting relevant issues.