Sort listview using column headings in the LayoutTemplate
I am able to sort a basic list view using asp:SqlDataSource and setting the list view property DataSourceID by pointing it to the asp:SqlDataSource ID. I am having an issue when sorting when not using the asp:SqlDataSource and just DataBinding from the code behind.
SqlDataSource Example:
<asp:ListView ID="ContactsListView" DataSourceID="ContactsDataSource" runat="server">
<LayoutTemplate>
<table width="640px" runat="server">
<tr class="header" align="center" runat="server">
<td>
<asp:LinkButton runat="server" ID="SortByFirstNameButton" CommandName="Sort" Text="First Name" CommandArgument="FirstName" />
</LayoutTemplate>
....
</asp:ListView>
<asp:SqlDataSource ID="ContactsDataSource" runat="server"
ConnectionString="<%$ ConnectionStrings:MainConnString %>"
SelectCommand="SELECT * FROM TableName">
</asp:SqlDataSource>
DataBind Example:
<asp:ListView ID="ContactsListView" DataSourceID="ContactsDataSource" runat="server">
<LayoutTemplate>
<table width="640px" runat="server">
<tr class="header" align="center" runat="server">
<td>
<asp:LinkButton runat="server" ID="SortByFirstNameButton" CommandName="Sort" Text="First Name" CommandArgument="FirstName" />
</LayoutTemplate>
....
</asp:ListView>
protected void Page_Load(object sender, EventArgs e)
{
String SQL = "SELECT * FROM Customer";
SqlDataAdapter da= new SqlDataAdapter(SQL, ConnStr);
DataSet ds = new DataSet();
da.Fill(ds);
ContactsListView.DataSource = ds.Tables[0];
ContactsListView.DataBind();
}
Both code samples populate the list view, but the second example data binding does not work for sorting. With the first example, the sorting just works with the added asp:LinkButton in the LayoutTemplate adding the CommandName="sort" and setting the CommandArugment="ColumnName", but it does not work with the second example.
Can anyone please explain why and how to get the sorting working using the code behind DataBind method?
Thanks!
I solved my issue.
I added an event to handle the sorting. The event grabs the command name (Data column) and passes it and the sorting direction into a function which will make another call to the database sort the returned results and rebind to the List View.
I had to create a view state to hold the List View Sort Direction because for some reason, the onsorting event handler kept saying that the sort direction was ascending.
Front End
<asp:ListView ID="ContactsListView" OnSorting="ContactsListView_Sorting" runat="server">
<LayoutTemplate>
<table width="640px" runat="server">
<tr class="header" align="center" runat="server">
<td>
<asp:LinkButton runat="server" ID="SortByFirstNameButton" CommandName="Sort" Text="First Name" CommandArgument="FirstName" />
</LayoutTemplate>
....
</asp:ListView>
Back End
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
BindContacts(string.Empty);
}
}
protected SortDirection ListViewSortDirection
{
get
{
if (ViewState["sortDirection"] == null)
ViewState["sortDirection"] = SortDirection.Ascending;
return (SortDirection)ViewState["sortDirection"];
}
set { ViewState["sortDirection"] = value; }
}
protected void ContactsListView_Sorting(Object sender, ListViewSortEventArgs e)
{
BindContacts(e.SortExpression + " " + ListViewSortDirection.ToString());
// Check the sort direction to set the image URL accordingly.
string imgUrl;
if (ListViewSortDirection == SortDirection.Ascending)
ListViewSortDirection = SortDirection.Descending;
else
ListViewSortDirection = SortDirection.Ascending;
}
private void BindContacts(string sortExpression)
{
sortExpression = sortExpression.Replace("Ascending", "ASC");
sortExpression = sortExpression.Replace("Descending", "DESC");
using (SqlConnection conn = new SqlConnection(_connStr))
{
conn.Open();
using (SqlDataAdapter dAd = new SqlDataAdapter("SELECT * FROM Customer", conn))
{
DataTable dTable = new DataTable();
dAd.Fill(dTable);
// Sort now
dTable.DefaultView.Sort = sortExpression;
// Bind data now
ContactsListView.DataSource = dTable;
ContactsListView.DataBind();
}
conn.Close();
}
}
I guess you could try to add a handler for ListView's Sorting event. There you are given the sorting column and the sort order in the event arguments. This could be easily usable to build a specific query and bind it to the list.
Because depending on the sort expression (which you defined in your markup) SqlDataSource will very likely do something like this (I'm not sure this is exactly what it does) for you behind the scenes:
Expression<Func<DataRow,object>> myExpression = row => row["SortExpressionYouDefinedForTheColumn"];
IEnumerable<DataRow> ex = ds.Tables[0].AsEnumerable().OrderBy(myExpression.Compile());
Or SqlDataSource may be using a DataView to sort the DataTable.
SqlDataSource can do this because by default it uses a DataSet to store the result set (or so says the documentation):
The data retrieval mode identifies how a SqlDataSource control retrieves data from the underlying database.
When the DataSourceMode property is set to the DataSet value, data is
loaded into a DataSet object and stored in memory on the server. This
enables scenarios where user interface controls, such as GridView,
offer sorting, filtering, and paging capabilities..
Since you chose to bind manually to a DataSet, you need to do the "magic" yourself; in other words, you handle the OnSort command, get the sort expression, get your data source again (however you do it, from Session or by calling the database again) and do your sort similarly to the lines shown above and rebind to your Gridview.
Related
I am trying to design a notification section for my ASP.NET website.
My requirement is similar to this image layout.
I was able to implement this with the asp repeater control and binding it with database.
I have table in my DB that has image, name and comments as its columns.
So according to the no. of rows in the DB table, the repeater control will get me the contents in the defined layout.
But i also wanted to implement the close button ("X" - top right corner). this close button will just remove the particular comment (but it will not delete data in DB).
Could you please help me in achieving this ??
Also thanks to suggest any other possible option to acheive this.
PFB the code i have done so far
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<table>
<tr>
<td style="height:64px;width:64px">
<asp:Image ID="imgEmployee" CssClass="imgClass" ImageUrl='<%# Eval("ImagePath")%>'runat="server" />
</td>
<td>
<asp:Label runat="server" Text='<%# Eval("Name")%>'></asp:Label>
<p> <%# Eval("Comments")%></p>
</td>
</tr>
</table>
</ItemTemplate>
</asp:Repeater>
and code behind
protected void Page_Load(object sender, EventArgs e)
{
DataSet ds = GetData();
Repeater1.DataSource = ds;
Repeater1.DataBind();
}
private DataSet GetData()
{
string CS = ConfigurationManager.ConnectionStrings["con"].ConnectionString;
using (SqlConnection con = new SqlConnection(CS))
{
SqlDataAdapter da = new SqlDataAdapter("Select * from tblNotification", con);
DataSet ds = new DataSet();
da.Fill(ds);
return ds;
}
}
And my table (tblNotification) has 3 columns - "ImagePath", "Name" and "Comments"
I m using asp 2.0, I need an requirement for sorting the repeater control. I search over the Internet but i could nt find the correct solution. If anyone knows the answer please help to solve my problem.
You need to sort the collection before binding to the repeater.
If you want to dynamically sort on post backs, sort in the event handler before re-binding to the repeater.
This article explains how to add sort functionality to a Repeater or a DataList control. It might help to your purpose or at least as a guide.
Finally i got the Sorting output in Repeater Control.
1.Maintaining the Static Variable;
static int count = 0;
2.In LinkButton click Event
protected void lnkreq_name_click(object sender, EventArgs e)
{
count=Convert.ToInt32(ViewState["count"].ToString());
ViewState["count"] = count;
loadRepeater("REQUEST_NAME",count);
}
3.call the Function
protected void loadRepeater(string reqname,int count)
{
//write the code to bind into Dataset
DataView dv = ds.Tables[0].DefaultView;
if (count == 0)
{
dv.Sort = reqname + " asc";
ViewState["count"] = 1;
}
else if (count == 1)
{
dv.Sort = reqname + " desc";
ViewState["count"] = 0;
}
//then bind into repeater
}
4.In Repeater
<asp:Repeater runat="server" ID="RepeaterEntry" >
<HeaderTemplate >
<table class="list_table" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<th><asp:LinkButton ID="lnkreq_name" runat="server" ForeColor="white" OnClick="lnkreq_name_click" >Request Name</asp:LinkButton></th>
</tr>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("REQUEST_NAME")%></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</table>
</FooterTemplate>
</asp:Repeater>
By pressing button in GridView i need to find control in Listview.
<ItemTemplate>
<td>
<asp:Label ID="lblMarketLVBalanceHeader" runat="server" Text="Balance: "></asp:Label>
</td>
<td>
<asp:Label ID="lblMarketLVBalanceValue" runat="server" Text='<%# Bind("Money", "{0:####}$") %>'></asp:Label>
</td>
</ItemTemplate>
Code Behind:
protected void GVMarketItems_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName.Equals("Buy"))
{ GridViewRow row = (GridViewRow)(((Button)e.CommandSource).NamingContainer);
/* string itemID = row.Cells[0].Text;
string itemName = row.Cells[1].Text;
string itemCostStr = row.Cells[3].Text; */
string curBalanceStr = ((Label)LVMarketBalanceShow.FindControl("lblMarketLVBalanceValue")).Text;
}
Code seems find, but i have "Object reference not set to an instance of an object" when i'm trying to Find control.
string curBalanceStr = ((Label)LVMarketBalanceShow.FindControl("lblMarketLVBalanceValue")).Text;
Did the same to DetailsView and it was Ok.. What's wrong with ListView?
UPD: Trying to get first fow of listview
ListViewDataItem curBalanceLV = LVMarketBalanceShow.Items[0];
string curBalanceStr =((Label)curBalanceLV.FindControl("lblMarketLVBalanceValue")).Text;
But getting an error "Index was out of range."
I think you want to find the control within the specific row.
string curBalanceStr = ((Label)row.FindControl("lblMarketLVBalanceValue")).Text
Did the same to DetailsView and it was Ok.. What's wrong with
ListView?
A DetailsView is used to tackle with one row at a time so you can directly call FindControl() on the detailsview but gridview and listview are meant to display multiple records and your markup you define inside <ItemTemplate /> is just a template for each row. You'll be able to find the controls that you define in the template inside of each row.
After doing a lot of research online I'm still stumped by this problem. I have a page that loads the names and count of categories into a drop down list. I only do this if !(Page.IsPostBack). When AutoPostBack fires the SelectedIndex = 0. I've tried several different things. Here is my code:
PAGE
<form id="AddAssignmentForm" runat="server">
<asp:ScriptManager ID="ScriptManager1" EnablePartialRendering="true" runat="server" />
<asp:UpdatePanel ID="CommentUpdate" runat="server">
<ContentTemplate>
Add Comment
<asp:DropDownList ID="ddlCategory" runat="server" Width="206" OnSelectedIndexChanged="ddlCategory_SelectedIndexChanged" AutoPostBack="true" />
<asp:TextBox ID="txtName" runat="server" Width="200" />
<asp:TextBox ID="txtAbbrv" runat="server" Width="200" />
<asp:TextBox ID="txtDescription" runat="server" Width="200" Height="90" TextMode="MultiLine" />
</ContentTemplate>
</asp:UpdatePanel>
</form>
Here is the back end code.
private void Page_Load(object sender, System.EventArgs e)
{
if (!Page.IsPostBack)
{
GetCategories();
}
}
public void GetCategories()
{
String strSql = #"SELECT Name, Total
FROM MyTable";
if (con.State == ConnectionState.Closed)
con.Open();
OleDbCommand cmdsql = new OleDbCommand(strSql, con);
OleDbDataReader cmdReader = cmdsql.ExecuteReader();
if (cmdReader.HasRows)
{
while (cmdReader.Read())
{
ddlCategory.Items.Add(new ListItem(cmdReader["Category_Name"].ToString(), cmdReader["Total"].ToString()));
}
ddlCategory.SelectedIndex = -1;
}
cmdReader.Close();
con.Close();
}
public void FillForm(int index)
{
ListItem item = ddlCategory.Items[index];
txtName.Text = item.Text + " " + (Convert.ToInt32(item.Value) + 1).ToString();
txtAbbrv.Text = item.Text.Substring(0, 1) + (Convert.ToInt32(item.Value) + 1).ToString();
}
public void ddlCategory_SelectedIndexChanged(Object sender, EventArgs e)
{
//When I break here SelectedIndex always = 1.
FillForm(ddlCategory.SelectedIndex);
}
I just want to be able to populate the form based on the selected index, but I can't seem to get the correct answer. Any help is appreciated.
Add AppendDataBoundItems="true" for dropdown list
I discovered the problem. The values being populated from my SQL statement contained values that repeated. For some reason this was causing the entire thing to malfunction in weird ways which made it so that every time I selected a ListItem the whole list would reset. By making sure no values repeated, the code started working perfectly. Thanks for everyone's help.
Make sure that your value fields are unique for each dropdown list item. If each item has the same value, it will default on index 0.
Do you have Viewstate enabled or disabled? SelectedIndex is Zero based, so it were resetting I think it would be set to zero.
Also, what do the other properties of the drop down list get set to? Is the selected value correct?
Try a different browser. I had an issue with cascading drop downs where it wasn't firing/behaving correctly in Firefox.
This happened to me when attempting to use a combined column value for the DataValueField. For example:
The stored procedure was written like this:
SELECT
Description,
Value1 + ',' + Value2 AS Value
FROM
DropDownListTable
And the DataValueField used the Value field which was a combination of the Value1 and Value2 fields separated by a comma. (I also tried a pipe and no delimiter but had the same results)
With ddl
.DataTextField = "Description"
.DataValueField = "Value"
.DataSource = ds
.DataBind()
End With
As soon as I used Value1 or Value2 as the DataValueField, the problem went away.
I struggled with this too, I tried EnableViewState="true" ViewStateMode="Enabled" but it's not needed in fact, you just have to addd IsPostBack in Page_Load event.
Do not forget to add IsPostBack, that is it...
if (!IsPostBack)
{
LoadDropDown();
}
You have to load list to DropDownList if not IsPostBack
Example code:
if (!IsPostBack)
{
//fill here
}
I've been experiencing the same problem, my dropdownlist stateview jump to index 1 right after a postback event from another control. My suggestion simply make sure your dropdownlist values are not empty.
Hope its help someone .... :)
Sorry if this post appears to be long winded.
I have a parent repeater with nested repeaters.
My problem is I am trying to use 'Next' and 'Previous' buttons to rebind my repeater (rptTabContent) and thus the inner repeaters also, but when I try to manipulate the state of these links during the ItemCommand event they are overwritten by the bind.
So I cannot disable or enable the buttons if there is to be no data after a consquent click of either 'Next' or 'Previous' links which is what I am trying to do.
The nested repeaters show a weeks worth of data in 7 columns. The next button is meant to show the next weeks data and previous the previous weeks data. Initially the previous button is inactive.
The parent repeater is bound again because initially it loops out 4 divs each containing a table of data. If one next button is hit on one repeater all tables must show their next 7 days of data.
Each repeater has the exact same number of items.
Initially on page load all of the data bind correctly.
I know the date of the very first item and last item in my collection so I am able to calculate the date range of the objects I need to bind to my parent repeater.
The HTML is below
<asp:Repeater ID="rptTabContent" runat="server" OnItemCommand="rptTabContent_ItemCommand">
<ItemTemplate>
<div id="<%# Eval("SlotTypeUrl") %>" class="delivery-timetable-container">
<table cellpadding="0" cellspacing="0" border="0">
<asp:Repeater ID="rptDeliverySlots" runat="server">
<ItemTemplate>
<tr class="time-slots">
<th><asp:Label id="lblRowTime" runat="server"></asp:Label></th>
<asp:Repeater ID="rptDeliverySlot" runat="server">
<ItemTemplate>
<td id="tdDay" runat="server">
<cc1:RepeaterRadioButton id="rptrdoDeliverySlot" groupname="del-times" runat="server" />
</td>
</ItemTemplate>
</asp:Repeater>
</tr>
</ItemTemplate>
</asp:Repeater>
<tr>
<td colspan="8"> </td>
</tr>
<tr>
<td colspan="4">
<div class="eco-rollover">
<div>
<img src="icon.gif" />
</div>
</div>
</td>
<td colspan="4">
<asp:LinkButton id="lnkPreviousWeek" enabled="false" runat="server" commandargument="Previous" cssclass="inactive">< Previous week</asp:LinkButton>|
<asp:LinkButton id="lnkNextWeek" runat="server" commandargument="Next" cssclass="active" >Next week ></asp:LinkButton>
</td>
</tr>
</table>
</div>
</ItemTemplate>
</asp:Repeater>
My ItemCommand event is here:
Protected Sub rptTabContent_ItemCommand(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.RepeaterCommandEventArgs)
If (e.Item.ItemType = ListItemType.Item) Or (e.Item.ItemType = ListItemType.AlternatingItem)
Dim filteredProposedDeliveries As New List(Of ProposedDeliverySlotDTO)
Dim firstDate, lastDate As Date
Dim startDate, endDate As Date
If (Session("FirstDeliveryDate") IsNot Nothing) AndAlso (Session("LastDeliveryDate") IsNot Nothing) Then
firstDate = CType(Session("FirstDeliveryDate"), Date)
lastDate = CType(Session("LastDeliveryDate"), Date)
Select Case e.CommandArgument.ToString()
Case "Next"
'Get new startDate using firstDate and store to use next time
'disable next button if startDate > lastDate
Case "Previous"
'Get new startDate from current displayed date and overright stored startdate
'disable previous button if startDate < firstDate
End Select
endDate = startDate.AddDays(7)
End If
'filteredProposedDeliveries = Get object between startDate and EndDate
Dim slots As List(Of SlotType) = PrepareSlotsForBinding(filteredProposedDeliveries)
Dim rptTabContent As Repeater = CType(e.Item.BindingContainer, Repeater)
rptTabContent.DataSource = slots
rptTabContent.DataBind()
End If
End Sub
How can I manage my 'Next' and 'Previous' links under these conditons.
Many thanks
Are you sure you need next/previous buttons four times?
Anyway, do your button management on rptTabContent_ItemDataBound. On ItemCommand, you get new starting point, get data and do data bind. On ItemDataBound, you get the Next button and the Previous button and enable or disable them based on what current date is. Both buttons should be disabled in the markup as a default state (for when there is no data).
Here is a working sample that simulates a data source of four items.
Page
<asp:Repeater ID="Repeater1" runat="server" onitemcommand="Repeater1_ItemCommand" onitemdatabound="Repeater1_ItemDataBound">
<ItemTemplate>
<div>
Label: <asp:Label ID="itemLabel" runat="server"></asp:Label><br />
<asp:LinkButton id="lnkPreviousWeek" Enabled="false" runat="server" commandargument="Previous" cssclass="inactive">< Previous week</asp:LinkButton> |
<asp:LinkButton id="lnkNextWeek" Enabled="false" runat="server" commandargument="Next" cssclass="active" >Next week ></asp:LinkButton>
<hr />
</div>
</ItemTemplate>
</asp:Repeater>
Code
private int current = 1;
protected void Page_Load(object sender, EventArgs e)
{
if (ViewState["current"] == null)
ViewState["current"] = current;
else
current = (int)ViewState["current"];
if (!IsPostBack)
{
Repeater1.DataSource = GetData(current);
Repeater1.DataBind();
}
}
private List<int> GetData(int start)
{
//pseudo data; simulates a page of 2 records
List<int> ds = new List<int>();
ds.Add(start);
ds.Add(start + 1);
return ds;
}
protected void Repeater1_ItemCommand(object source, RepeaterCommandEventArgs e)
{
if ((string)e.CommandArgument == "Next")
current++;
else if ((string)e.CommandArgument == "Previous")
current--;
ViewState["current"] = current;
Repeater1.DataSource = GetData(current);
Repeater1.DataBind();
}
protected void Repeater1_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
LinkButton prev = (LinkButton)e.Item.FindControl("lnkPreviousWeek");
LinkButton next = (LinkButton)e.Item.FindControl("lnkNextWeek");
//here 1 and 3 are artificial boundaries for data for the sake of example
if (current > 1)
prev.Enabled = true;
if (current < 3)
next.Enabled = true;
Label label = (Label)e.Item.FindControl("itemLabel");
label.Text = e.Item.DataItem.ToString();
}