Tuesday, October 13, 2009

Custom paging in Gridview With ObjectDataSource

Many times we use paging with GridView with AllowPaging = "True" and binding the GridView with some ObjectDataSource. It actually fetches all row from the database and depending on the no of records it fetched, it generates the Pager section of the page and according to the current page index, it shows the set of record.
This approach is good for when you have limited no of record in the database. But there are many situations where there is thousands of rows in the database and you have to display only 25 records(PageSize) out of that thousands of records. So it is not good practice to fetch all record through object datasource to display only 25 records.
Here we should use custom paging to fetch only the 25 rows starting from the startindex which is
CurrentPageIndex*PageSize (As Index is 0 Based).
Calculated automatically by objectdatasource.
Here is the change you have to do

  • Set EnablePaging="True" for ObjectDataSource

  • Set The SelectMethod Property

  • Set value for following Properties of ObjecDataSource StartRowIndexParameterName, SortParameterName, MaximumRowsParameterName
    These parameters needs to be present in the method attached for selecting the result for objectdatasource.

  • Set SelectCountMethod to the name of method returning the total no of rows.


Here is my ObjectDataSourceCode


<asp:objectdatasource id="objDS" runat="server" enablepaging="True" selectmethod="SearchResult" typename="PRP.BusinessLogic.TpilTblNsefaoscripmaster" startrowindexparametername="paramStartRowIndex" sortparametername="paramSortExpression" maximumrowsparametername="paramPageSize" selectcountmethod="GetNoOfRows">
<selectparameters>
<asp:parameter name="paramSymbol" type="String">
<asp:parameter direction="Output" name="paramTotalNoOfRows" type="Int32">
</asp:parameter>
</asp:parameter>
</selectparameters>
</asp:objectdatasource>


The SearchResult & GetNoOfRows Function


public DataTable SearchResult(string paramSymbol,
int paramStartRowIndex, int paramPageSize, string paramSortExpression, out int paramTotalNoOfRows)
{
TpilTblNsefaoscripmasterHelper.SearchResult(paramSymbol, paramStartRowIndex, paramStartRowIndex + paramPageSize, paramSortExpression, ref paramTotalNoOfRows);
this.totalNoOfRows = paramTotalNoOfRows;
}
int totalNoOfRows = 0;
public int GetNoOfRows(string paramSymbol,
int paramStartRowIndex, int paramPageSize, string paramSortExpression, out int paramTotalNoOfRows)
{
paramTotalNoOfRows = totalNoOfRows;
return totalNoOfRows;
}


The parameter list for both the functions are same but the second function retuns int no of rows. But i don't have used two function call for getting the total noofrows. Inside select function i have set the totalNoOfRows parameter which is accessed inside GetNoOfRows method to get the no of rows. This works as objectdatasource calls both the method on same object instance of the class and none of the methods and the class variable is static and the select methos is called first and then the row count method is called.
Now check the stored procedure used for this purpose


Create PROCEDURE [dbo].[USP_TPIL_tbl_NSEFAOScripSelectForSearch]
@Symbol varchar(50),
@StartIndex int = 0,
@EndIndex int =25,
@SortExpression varchar(50),
@TotalNoOfRows int OUTPUT
AS
SET NOCOUNT ON
Select * from
(
SELECT m.Instrument,m.Symbol,m.SymbolDescription,ExpiryDate,
Row_Number() OVER (Order By
CASE WHEN @SortExpression='EXPIRYDATE' OR @SortExpression='EXPIRYDATE ASC' THEN m.EXPIRYDATE END DESC,
CASE WHEN @SortExpression='EXPIRYDATE DESC' THEN m.EXPIRYDATE END ASC ,
CASE WHEN @SortExpression='SYMBOL' OR @SortExpression='SYMBOL ASC' THEN m.SYMBOL END DESC,
CASE WHEN @SortExpression='SYMBOL DESC' THEN m.SYMBOL END ASC
) as RowNumber
FROM
[dbo].[TPILl_NSEFAOScripMaster] m
WHERE
(m.[Symbol] LIKE @Symbol OR @Symbol = '')
)t
Where RowNumber>@StartIndex AND RowNumber<=@EndIndex --return SELECT @TotalNoOfRows = Count(*) FROM [dbo].[TPILl_NSEFAOScripMaster] m WHERE (m.[Symbol] LIKE @Symbol OR @Symbol = '')


As i am using Sql Server 2005, i used ROW_Number() function. The value of output parameter "TotalNoOfRows " is set to the variable "totalNoOfRows" inside "SearchResult" function.
To know about how to get Row Number in Sql Server 2000 please check the bottom of this link.

Thursday, April 16, 2009

Focus the first control of your page

In all pages we have to put the focus on the first control of the page. It can be a textbox,a radio button, a checkbox, a select field, a textarea or a button. For my current project i have created a function which i have placed inside the masterpage and it will traverse through the page starting from a particular control (inside the control) and will take the first occurance of the above types of control and will put the cursor focus on the control. Below is the javascript code i used to achieve this

function selectFirstControl()
{
var objIP = getFocusableControl(document.getElementById('tdContentBody'));
if(objIP!=null)
{
objIP.focus();
return;
}
}

function getFocusableControl(parentCtrl)
{
if(!$(parentCtrl).visible())
return null;
if(parentCtrl.tagName == "INPUT")
{
if(parentCtrl.getAttribute("type")=="hidden")
{
return null;
}
return parentCtrl;
}
else
{
if(parentCtrl.tagName == "SELECT")
{
return parentCtrl;
}
}
var children = $(parentCtrl).childElements();
for(var i = 0;i<children.length;i++)
{
var resCtrl = getFocusableControl(children[i]);
if(resCtrl != null)
return resCtrl;
}
}


Call the function selectFirstControl(); from the bottom of your page.
Here i have called the function "getFocusableControl" recursively to get the control. It is checking the tagname of the node, if it is "Input" or "Select" then it is excluding the hidden fields by checking the type property for for inputs and if it is not hidden, then it is putting the focus on the control.
Iam checking if the parent control visible or not with $(parentCtrl).visible() which is a function from prototypejs to check the visibility. But this checks for the inline "display" property of the element, if "display :none" it returns false. But it cannot check if you are using the hidden property of hiding the control with stylesheet class.
If you are not using prototypejs, you can check the direct style property of the control.

Tuesday, April 7, 2009

Trigger Html/Javascript Event from Javascript

In many situation i had to trigger JavaScript Event from code. Like click on a button when something happened to post the page. It mainly comes into picture when i use UpdatePanel or using modalpopup for click on a button inside grid/list/gridview ect. This creation of event is very much dependent on the browser. Here is the code to trigger a button click event
function clickButton()
{
if( document.createEvent )
{
var evObj = document.createEvent('MouseEvents');
evObj.initMouseEvent( 'click', true, false, window, 0, 12, 345, 7, 220, false, false, true, false, 0, null );
$(' btnTransRefresh').dispatchEvent(evObj);
}
else
if( document.createEventObject )
{
$('btnTransRefresh').fireEvent('onChange');
}
}

Here i am using Prototyprjs framework, so you see the "$". You can replace those with document.getElementById('') syntax.
initMouseEvent takes the following parameters.
initMouseEvent( 'type', bubbles, cancelable, windowObject, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget )

In many scenarios we do some javascript calculation on change of value in some particular textboxes. But in some scenarios, the value in the textbox on change of which we need to calculate is done through some javascript. Like in my project i was using calender from ajax control toolkit which is inside a project specific control names PRPCalender. In some pages i had to do some calculations only when the value in calender is changed. So i could not use the javascript events associated with the calender control (ajax). So i decided to go with triggering onchange event as the textbox showing the calender control doesn't fire the onchange event as the value is changed through JavaScript.
string methodName = "prpCalenderDateChanged" + txtCalendar.ClientID;
calendarExtender.OnClientDateSelectionChanged = methodName;
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendFormat(@"
function {0}(sender,eArg)
{{
//alert('Calender Date Changed');
var tbId = '{1}';
//$(tbId).blur();
//$(tbId).focus();
if( document.createEvent )
{{
var evObj = document.createEvent('HTMLEvents');
evObj.initEvent( 'onchange', true, false);
$(tbId).dispatchEvent(evObj);
}}
else
if( document.createEventObject )
{{
$(tbId).change();
}}
}}
", methodName, txtCalendar.ClientID);
ScriptManager.RegisterStartupScript(this, this.GetType(), "textChanged" + txtCalendar.ClientID, sb.ToString(), true);
Here inside my calender control PRPCalender, i am registering a function which is unique for each calender control inside any page (i am using the client id as concatined to generate the method name) which fires the onchange event on the textbox.
calendarExtender.OnClientDateSelectionChanged = methodName;
which is called by toolkit JS when the date value is changed.
Some Useful Link Link1

Thursday, March 26, 2009

Trigger not working when importing data with SSIS package

I am very new to SSIS package. Actually i am not a hardcode SQL guy, so i never needed to work with SSIS before. In my current project i needed to import data after fetching from a server with FTP and then extract the ZIP files and insert it into respective tables. For eatracting ZIP files i used "SharpZipLib" which is open source library for dot net. Then i created a temp table in which i first inserted to remove duplicate. Remember, i created the temp table on the fly inside the "Execute SQL Task" with create table statement and after insert to the original table i deleted the table. In this scenario you have to change the "DelayValidation" property of the "Data Flow Task" to "true" (By default it is false). Otherwise you will get some validation error as the temp table in which you are going to insert data is not present now, it is created on the fly and deleted after insert is completed.
Now in my case i had a trigger which need to fire each time i instered some data into the table. Actually, i was updating the stock rate and needed to copy the rate by trigger. But to my surprise, the trigger was not firing when i insert/update the data by the SSIS. But the trigger fires when i do the update by SQL.
The problem is with the settings in "OleDB Destination" inside "Data Flow Task". Here is how i solved the issue
Right click on th "OldeDB Destination" and you will find "Show Advanced Editor" and open the Advanced editor.

In the editor check the "FastLoadOptions" which is by default "TABLOCK,CHECK_CONSTRAINTS". You need to add one more option "FIRE_TRIGGERS" . And thats it. But remember the trigger runs once for each update/insert. Not for every update/insert. So in case where we are doing batch update, you must write the trigger code in such a way that it will be able to handle the situation for multiple row sets. Here is a sample statement
CREATE TRIGGER UpdateStockRate
ON INV_tbl_StockMaster
AFTER INSERT,Update
AS

UPDATE t
SET CMP = i.LastTradedPrice
FROM INV_tbl_Holding t
JOIN INSERTED i
ON i.Symbol = t.Symbol

Friday, February 27, 2009

Reporting Service Custom Code Add Assembly

For adding assembly you have to copy the .dll assembly file to the following location "C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies" folder.

Friday, February 20, 2009

Paging in Datalist and Repeater

Datalist and Repeater doesn't provide any paging by default like gridview or datagrid. So we avoid using datagrid/repeater as much as possible. But there comes situation where using datalist or repeater is easier to maintain the design for the page. Like some product is displayed like row by row with 4 producs in a single w. There its tough to achieve (but not impossible) this design output wit datagrid/gridview. So we choose datalist making its RepeatColumns="4" RepeatDirection="Horizontal".
But paging???
So i created a control named as COUSPaging (Cous is my project name). Which contains two link buttons as "Next" and "Previous" and a textbox for goto page as below



In my control i used a public event "PageIndexChanging" which is of type GridViewPageEventHandler, so that it sends the same parameter as GridView. The event is handled from the Parent page containg the DataList.
The Paging.aspx Page Code:
<table>
<tr>
<td>
<asp:LinkButton ID="linkPrevious" CssClass="button" runat="server" Text="Previous" CommandName="Previous" OnClick="linkPrevious_Click"></asp:LinkButton>
</td>
<td>
<asp:Literal ID="litPageCount" runat="server"></asp:Literal>
<span id="spanGotpPage" runat="server">Goto Page:
<asp:TextBox Width="15" ID="txtPageNo" CssClass="inputfld" runat="server" MaxLength="3"></asp:TextBox>
<asp:Button ID="btnGo" runat="server" Text="Go" CssClass="button" OnClick="btnGo_Click" />
</span>
</td>
<td>
<asp:LinkButton ID="linkNext" CssClass="button" runat="server" Text="Next" CommandName="Next" OnClick="linkNext_Click"></asp:LinkButton>
</td>
</tr>
</table>

.Cs Page Coding:

void Controls_Paging_PreRender(object sender, EventArgs e)
{
litPageCount.Text = string.Format("{0} of {1} Pages ", SelectedPageIndex + 1, TotalNoOfPages);
if (SelectedPageIndex == TotalNoOfPages - 1)
{
linkNext.Enabled = false;
}
else
{
linkNext.Enabled = true;
}
if (SelectedPageIndex == 0)
{
linkPrevious.Enabled = false;
}
else
{
linkPrevious.Enabled = true;
}
txtPageNo.Text = (SelectedPageIndex + 1).ToString();
}
protected void btnGo_Click(object sender, EventArgs e)
{
int newPageIndex = 0;
try
{
newPageIndex = Convert.ToInt32(txtPageNo.Text) - 1;
if (newPageIndex > TotalNoOfPages - 1)
{
newPageIndex = TotalNoOfPages - 1;
}
if (newPageIndex < 0)
{
newPageIndex = 0;
}
SelectedPageIndex = newPageIndex;
PageIndexChanging(btnGo, new GridViewPageEventArgs(newPageIndex));
}
catch { }
}
public event GridViewPageEventHandler PageIndexChanging;
public int PageSize
{
set
{
ViewState["PageSize"] = value;
}
get
{
if (ViewState["PageSize"] is int)
return (int)ViewState["PageSize"];
else return 0;
}
}

public int CurrentPageIndex
{
set
{
ViewState["CurrentPageIndex"] = value;
}
private get
{
if (ViewState["CurrentPageIndex"] is int)
return (int)ViewState["CurrentPageIndex"];
else return 0;
}
}
public int StartIndex
{
get
{
return SelectedPageIndex * PageSize;
}
}

public int TotalNoOfPages
{
get
{
return (int)Math.Ceiling(TotalNoOfRows / (float)PageSize);
}
}
public long TotalNoOfRows
{
set
{
ViewState["TotalNoOfRows"] = value;
}
get
{
if (ViewState["TotalNoOfRows"] is long)
return (long)ViewState["TotalNoOfRows"];
else return 0;
}
}
public int SelectedPageIndex
{
set
{
ViewState["SelectedPageIndex"] = value;
}
get
{
if (ViewState["SelectedPageIndex"] is int)
return (int)ViewState["SelectedPageIndex"];
else return 0;
}
}
protected void linkNext_Click(object sender, EventArgs e)
{
if (SelectedPageIndex < TotalNoOfPages)
{
SelectedPageIndex = SelectedPageIndex + 1;
PageIndexChanging(sender, new GridViewPageEventArgs(SelectedPageIndex));
}
}
protected void linkPrevious_Click(object sender, EventArgs e)
{
if (SelectedPageIndex > 0)
{
SelectedPageIndex = SelectedPageIndex - 1;
PageIndexChanging(sender, new GridViewPageEventArgs(SelectedPageIndex));
}
}

The page where the control is used

Here the page size is initialized. From the cs page call the bind datalist function as

void bindDataList()
{
long totalNoOfRows;
ImageTblMasterTable dtImage = ImageTblMaster.SelectAll(-1, 1, pagingDL.StartIndex, pagingDL.PageSize, out totalNoOfRows);
pagingDL.TotalNoOfRows = totalNoOfRows;
dlImage.DataSource = dtImage;
dlImage.DataBind();
}

Here i am using custom paging. So i am sending the page start index and page size which is converted to ennIndex before the call to SP. Here is code from SP

Select @TotalNoOfRows = Count(*) FROM [dbo].[Image_tbl_Master]

Select * from
(
SELECT ROW_NUMBER() OVER (ORDER BY ImageId) as RowNumber, m.* FROM [dbo].[Image_tbl_Master] as m
)t Where
RowNumber >= @StartIndex
AND
RowNumber <= @EndIndex As i am using SQL Server 2005 i get the facility of ROW_Number() but in SQL Server 2000 or before, there is no row number. So you have to change the query like Select * from ( Select (Select Count(*) from Image_tbl_Master where ImageId<=img.ImageId )as RowNumber, img.* from Image_tbl_Master as img )t Where RowNumber >= @StartIndex
AND
RowNumber <= @EndIndex

Here row number is calculated on the fly through a subquery. See the where clause "ImageId<=img.ImageId ", it is responsible for the sorting. As i was sorting with ImageId i have used it like that.

Tuesday, January 13, 2009

Add / Insert a row to a gridview

In many cases we come across the situation where we have to add a row to a gridview. We can create a new grid view row through the GridViewRow class. But there is no property/method to a grid to add the row to the gridview. Not even the Gridview.Rows property has any methor to add/insert a row.
In a situation i had to add a footer to show the total of the fields displayed in the gridview. I knew that i could set the value in footer through RowDataBound event of GV by inspection like

if(e.Row.RowType==DataControlRowType.Footer)
{

}

here e is the GridViewRowEventArgs parameter to the event.
But i was looking for something new. I found i can create the row through GridViewRow which takes the rowindex, rowtype ... parameters.
Then i found i can cast the parent of any row of gridview to a table but cannot cast the gridview directly to table. So here is how i implemented...
::::::::::::::::::::::::::::::::::::::::::::::::::::::

gvTaxPlanning.DataSource = dv;
gvTaxPlanning.DataBind();
if (gvTaxPlanning.Rows.Count > 0)
{
GridViewRow gvr = new GridViewRow(gvTaxPlanning.Rows.Count, gvTaxPlanning.Rows.Count, DataControlRowType.Footer, DataControlRowState.Normal);
TableCell tc = new TableCell();
tc.Text = "Total";
gvr.Cells.Add(tc);
tc = new TableCell();
tc.Text = (tEQTax + tMFTax + tULTax + tBulTax + tBankTax + tAssetTax).ToString(GlobalSettings.FloatNumberFormatter);
gvr.Cells.Add(tc);
gvr.Font.Bold = true;
gvr.BackColor = System.Drawing.ColorTranslator.FromHtml("#000084");
gvr.ForeColor = System.Drawing.ColorTranslator.FromHtml("#FFFFFF");
Table tab = (Table)gvTaxPlanning.Rows[0].Parent;
tab.Rows.Add(gvr);
}

I thought that it will need the
gridView.ShowFooter = true; to show the new row i have added as it was footer and by default grid doesn't show the footer.
But not... it ws showing the footer whithout setting it to true...
And one more thing... The RowCreated event is not fired when you add this new row.

Read More http://blog.falafel.com/2007/04/12/DynamicallyAddRowsToAGridView.aspx