Repeater grouping items from single database - c#

I have a table with headers(example): Group, DisplayName, EditableData, description.
I have been trying for the last few days to learn repeaters to sort the information based on the Group so I can get something that looks like the following layout. This is a user control I am trying to put together.
Group1
------------------
Name1 EditableData Some Description
Name2 EditableData Some Description
Group2
------------------
Name3 EditableData Some Description
Name4 EditableData Some Description
I have looked a the following other examples online:
Nested repeaters - grouping data and
Nested Repeaters in ASP.NET
I believe I do not properly understand how repeaters work enough to deal with nesting or datasource.
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
Group: <%#Eval("Group")%><br />
<%--Nested Repeater would go here for all the children info for each "Group"--%>
</ItemTemplate>
</asp:Repeater>
Using DISTINCT in my SQL to just retrieve "Group" leaves me with the proper groups without repeats and I guess I could just instead set the groups in labels and then later make repeaters for each specific label... This seems terrible when I may later update editableData back to the database.
What I really want I guess is at least a link to a walkthrough that explains how repeaters work along with Eval() and datasources. I mean, code to do everything I need to complete this first step in my project would be perfect ;P But I also want to be able to understand these better as I am probably going to be using them often in the near future.

I once encountered the same issue where I was to sort the data by group and I had to display the common items in a grouped segment.
There are of-course multiple ways of how you retrieve data, for example, you can get Distinct Group Names and bind it to the repeater and then on ItemDataBound event you can execute and get other elements like this:
<asp:Repeater runat="server" ID="rptrGroups" OnItemDataBound="rptrGroups_ItemDataBound">
<ItemTemplate>
<asp:Label runat="server" ID="lblGroupName" Text='<%# Eval("GroupName") %>' />
<asp:GridView runat="server" ID="gv">
</asp:GridView>
</ItemTemplate>
</asp:Repeater>
protected void rptrGroups_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item)
{
var lblGroupName = (Label)e.Item.FindControl("lblGroupName");
GridView gv = (GridView)e.Item.FindControl("table");
var dataTable = FetchDataWithGroupName(lblGroupName.Text); // Method that fetches data with groupname.
gv.DataSource = dataTable;
gv.DataBind();
}
}
This is not a recommended way because it goes to database, runs query, and then fetches data for each item (if you are fetching this data from db). If you have thousands of Groups then it will make db calls for thousands of times which is a bad thing.
The second solution is, you design a model and feed a custom model that will do the job. Let me explain it by a sample model:
public class GroupedModel
{
public string GroupName {get; set;}
public List<NestedData> TableData {get; set;}
}
public class NestedData
{
public string Id {get; set;}
// Your columns here...
}
Then query and initialize list of GroupedModel class then feed it to the repeater. Let me do it with some dummy data.
var tableData = new List<NestedData>();
var nestedData1 = new NestedData { Id = "1" };
var nestedData2 = new NestedData { Id = "2" };
tableData.Add(nestedData1);
tableData.Add(nestedData2);
var groupedModel = new GroupedModel
{
GroupName = "Group1",
TableData = tableData
};
var listGroupedModel = new List<GroupedModel>();
listGroupedModel.Add(groupedModel);
rptrGroups.DataSource = listGroupedModel;
Then modify the markup like this:
<asp:Repeater runat="server" ID="rptrGroups">
<ItemTemplate>
<asp:Label runat="server" ID="lblGroupName" Text='<%# Eval("GroupName") %>' />
<asp:GridView runat="server" ID="gv" DataSource='<%# ((GroupedModel)Container.DataItem).TableData %>'>
</asp:GridView>
</ItemTemplate>
</asp:Repeater>

I find that just using a gridview or list view works rather nice. However, DO NOT attempt to use the grouping feature - as it is for placing items across the page, not down.
Lets make this really simple!
Ok, so I have a list of Hotels, but I want to group by city.
So, you build a query like this:
Dim strSQL As String =
"SELECT ID, FirstName, LastName, HotelName, City FROM tblHotels ORDER BY City, HotelName"
GridView1.DataSource = Myrst(strSQL)
GridView1.DataBind()
Ok, so that fills out our grid view. We get this:
So far, two lines of code!
But, we want to group by City.
So at the forms class level, add simple var:
Public Class HotelGroupGrid
Inherits System.Web.UI.Page
Dim LastCity As String <----- this one
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If IsPostBack = False Then
LastCity = ""
Call LoadGrid()
End If
End Sub
Ok, so now on the data item bind event, simply add a NEW row.
The code looks like this:
If e.Row.RowType = DataControlRowType.DataRow Then
' if grouping = 1 then create a new row!
Dim gvRow As DataRowView = DirectCast(e.Row.DataItem, DataRowView)
If gvRow("City") <> LastCity Then
LastCity = gvRow("City")
' insert a new row for grouping header
Dim MyRow As New GridViewRow(-1, -1, DataControlRowType.DataRow, DataControlRowState.Normal)
Dim MyCel As New TableCell()
'MyCel.Width = Unit.Percentage(100)
Dim MyTable As Table = e.Row.Parent
MyCel.ColumnSpan = MyTable.Rows(0).Controls.Count
Dim MyLable As New Label
MyLable.Text = "<h2>" & gvRow("City") & "</h2>"
MyCel.Controls.Add(MyLable)
MyRow.Cells.Add(MyCel)
MyTable.Rows.AddAt(MyTable.Rows.Count - 1, MyRow)
End If
End If
Now, above is a "bit" of a chunk to chew on - but still not a lot of code.
So, now when we run above, we get this:
Our grid view markup looks like this:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="ID">
<Columns>
<asp:BoundField DataField="City" HeaderText="City" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="ID" HeaderText="ID" InsertVisible="False" ReadOnly="True" SortExpression="ID" />
<asp:BoundField DataField="FirstName" HeaderText="FirstName" SortExpression="FirstName" />
<asp:BoundField DataField="LastName" HeaderText="LastName" SortExpression="LastName" />
<asp:BoundField DataField="HotelName" HeaderText="HotelName" SortExpression="HotelName" />
</Columns>
</asp:GridView>
So, not too bad.
If you decide to use a listview? Then the code becomes quite a bit less, but the markup for listview is quite a handfull.
All we do is create a row that is our heading, and now show, or hide that row based on the start of new grouping.
So, if one decides to use a list view? Then we get this:
(I assume you use the databind wizards - your not possibly typing in the markup by hand - right? - saving world poverty here)
So, for a list view (and I think the list view is BETTER, since the layout options for that heading row is wide open to any kind markup and extra controls you dream up.
So, the markup (generated - and then chopped out the fat) is this:
<asp:ListView ID="ListView1" runat="server" DataKeyNames="ID">
<EmptyDataTemplate>
<table runat="server" style="">
<tr><td>No data was returned.</td></tr>
</table>
</EmptyDataTemplate>
<ItemTemplate>
<tr id="GroupHeading" runat="server" style="display:none">
<td colspan="4">
<h2><asp:Label ID="City" runat="server" Text='<%# Eval("City") %>' /></h2>
</td>
</tr>
<tr>
<td><asp:Label ID="IDLabel" runat="server" Text='<%# Eval("ID") %>' /></td>
<td><asp:Label ID="FirstNameLabel" runat="server" Text='<%# Eval("FirstName") %>' /></td>
<td><asp:Label ID="LastNameLabel" runat="server" Text='<%# Eval("LastName") %>' /></td>
<td><asp:Label ID="HotelNameLabel" runat="server" Text='<%# Eval("HotelName") %>' /></td>
</tr>
</ItemTemplate>
<LayoutTemplate>
<table id="itemPlaceholderContainer" runat="server" border="0" style="">
<tr runat="server">
<th runat="server">ID</th>
<th runat="server">FirstName</th>
<th runat="server">LastName</th>
<th runat="server">HotelName</th>
</tr>
<tr id="itemPlaceholder" runat="server">
</tr>
</table>
</LayoutTemplate>
</asp:ListView>
Now no question that the listview spits out a lot more markup - but we now have a full row for the heading. So we get this:
But, now our code simply will hide or show that "extra" row we have in the marketup.
And it quite simple now:
If e.Item.GetType = GetType(ListViewDataItem) Then
Dim MyRow As HtmlTableRow = e.Item.FindControl("GroupHeading")
Dim lblCity As Label = MyRow.FindControl("City")
If lblCity.Text <> LastCity Then
LastCity = lblCity.Text
' Hide/show group heading
MyRow.Style("display") = "normal"
Else
MyRow.Style("display") = "none"
End If
End If
So the trick in most cases is to simply layout that extra row item, and then on the item data bound event you simply hide or show that heading part.

Related

Access HyperLink inside ItemTemplate inside TemplateField ASP.Net WebForms

I have an ASP TemplateField inside a data populated GridView as follows:
<asp:GridView ID="gvReport" runat="server" AutoGenerateColumns="False" ShowHeaderWhenEmpty="true" OnRowDataBound="gvReport_RowDataBound" CssClass="table table-bordered">
<Columns>
<asp:BoundField DataField="Delete" HeaderText="Delete" SortExpression="Delete" />
<asp:TemplateField HeaderText="Delete">
<ItemTemplate>
<asp:HyperLink runat="server" NavigateUrl='<%# "Edit?from=Delete&ID=" + Eval("ID") %>' ImageUrl="Assets/SmallDelete.png" SortExpression="PathToFile" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
In other words, at the end of each row, there is a delete 'button'. I can tell whether a particular row/record has been deleted by checking the true/false value of the BoundField Delete in my code behind as follows:
if (e.Row.RowType == DataControlRowType.DataRow)
{
TableCell statusCell = e.Row.Cells[0];
if (statusCell.Text == "True")
{
//change ImageUrl of Hyperlink for this row
}
}
Right now, my icon is red for delete, but in the case that a record has already been deleted,
I would like to change the image to a different icon (a blue one).
How can I change this ImageUrl if statusCell evaluates to true?
ok, so we will need two parts here:
We need (want) to persist the data table that drives the grid).
The REASON for this?
We kill about 3 birds with one stone.
We use the data table store/save/know the deleted state
We use that delete information to toggle our image
(and thus don't have to store the state in the grid view).
We ALSO then case use that table state to update the database in ONE shot, and thus don't have to code out a bunch of database operations (delete rows). We just send the table back to the database.
In fact in this example, I can add about 7 lines of code, and if you tab around, edit the data? It ALSO will be sent back to the database. And this means no messy edit templates and all that jazz (which is panful anyway).
Ok, so, here is our grid - just some hotels - and our delete button with image:
markup:
<div style="width:40%;margin-left:20px">
<asp:GridView ID="GridView1" runat="server"
AutoGenerateColumns="False"
DataKeyNames="ID" CssClass="table table-hover borderhide" >
<Columns>
<asp:TemplateField HeaderText="HotelName" >
<ItemTemplate><asp:TextBox id="HotelName" runat="server" Text='<%# Eval("HotelName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="FirstName" SortExpression="ORDER BY FirstName" >
<ItemTemplate><asp:TextBox id="FirstName" runat="server" Text='<%# Eval("FirstName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="LastName" >
<ItemTemplate><asp:TextBox id="LastName" runat="server" Text='<%# Eval("LastName") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="City" >
<ItemTemplate><asp:TextBox id="City" runat="server" Text='<%# Eval("City") %>' /></ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Delete">
<ItemTemplate>
<asp:ImageButton ID="cmdDelete" runat="server" Height="20px" Style="margin-left:10px"
ImageUrl="~/Content/cknosel.png"
Width="24px" OnClick="cmdDelete_Click"></asp:ImageButton>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<br />
<asp:Button ID="cmdDeleteAll" runat="server" Text="Delete selected" Width="122px"
OnClientClick="return confirm('Delete all selected?')" />
</div>
Ok, and the code to load up the grid - NOTE WE persist the data table!!!!!
Code:
DataTable rstTable = new DataTable();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadGrid();
else
rstTable = (DataTable)ViewState["rstTable"];
}
void LoadGrid()
{
// load up our grid
using (SqlCommand cmdSQL = new SqlCommand("SELECT * from tblHotels ORDER BY HotelName ",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
cmdSQL.Connection.Open();
rstTable.Clear();
rstTable.Load(cmdSQL.ExecuteReader());
GridView1.DataSource = rstTable;
GridView1.DataBind();
}
ViewState["rstTable"] = rstTable;
}
Ok, so far, real plain jane. The result is this:
Ok, so now all we need is to add the delete button click event.
That code looks like this:
protected void cmdDelete_Click(object sender, ImageClickEventArgs e)
{
ImageButton btn = (ImageButton)sender;
GridViewRow rRow = (GridViewRow)btn.Parent.Parent;
DataRow OneRow = rstTable.Rows[rRow.DataItemIndex];
// toggle data
if (OneRow.RowState == DataRowState.Deleted)
{
// undelete
OneRow.RejectChanges();
btn.ImageUrl = "/Content/cknosel.png";
}
else
{
// delete
OneRow.Delete();
btn.ImageUrl = "/Content/ckdelete.png";
}
}
Again, really simple. But NOTE how we use the data table row state (deleted or not).
And this ALSO toggles the image for the delete button.
So, we don't have to care, worry, or store/save the sate in the grid.
So, if I click on a few rows to delete, I now get this:
So far - very little code!!!
Ok, so now the last part. the overall delete selected button. Well, since that is dangerous - note how I tossed in a OnClientClick event to "confirm the delete. if you hit cancel, then of course the server side event does not run.
But, as noted since we persisted the table? Then we can send the table deletes right back to the server without a loop. The delete code thus looks like this:
protected void cmdDeleteAll_Click(object sender, EventArgs e)
{
// send updates (deletes) back to database.
using (SqlCommand cmdSQL = new SqlCommand("SELECT * from tblHotels WHERE ID = 0",
new SqlConnection(Properties.Settings.Default.TEST4)))
{
cmdSQL.Connection.Open();
SqlDataAdapter daUpdate = new SqlDataAdapter(cmdSQL);
SqlCommandBuilder daUpdateBuild = new SqlCommandBuilder(daUpdate);
daUpdate.Update(rstTable);
}
LoadGrid(); // refesh display
}
So, note how easy it is to update (delete) from the database. If you look close, I used text boxes for that grid - not labels. The reason of course is that if the user tabs around in the grid (and edits - very much like excel), then I can with a few extra lines of code send those changes back to the database. In fact the above delete button code will be 100% identical here (it will send table changes back with the above code. So, if we add rows, edit rows then the above is NOT really a delete command, but is in fact our save edits/deletes/adds command!

ASP.NET Web Forms: How to reference CheckBoxList items as 'selected' on a table on another page using only C# and ASP.NET web forms?

Assignment Instructions:
Develop a simple shopping cart application that uses session objects to store the information. You will have to create an ASP.NET project that contains two web forms. The first web form, ShoppingCart.aspx, allows the user to select items using a set of checkboxes. This page shows also the number of items in the cart.
The second web form, Display.aspx, displays the number of selected items in a table and allows the user to go back to the first web form and continue shopping.
I am using a CheckBoxList to list my products, and I built a table with IDs in the cells to receive the data the user checks off.
(I considered the idea of using CheckBox vs. CheckBoxList, but this would not be the appropriate solution and doesn't easily allow counting total items selected)
I have tried using cookies and session state variables, but I can't figure out how to associate them to the table.
I can transfer test data from labels and textboxes, anything with an ID, but not my CheckBoxList items.
How do I reference which checkboxes are selected on page 1, that will update the desired table cells on page 2, after clicking the 'checkout' button without unique IDs on the list items?
Example: If... 1lb Dark Roast is selected on page 1 ...
(identified as 'p001' for product 1 on next page)
<asp:ListItem Value="15.99">1lb Dark Roast</asp:ListItem>
On page 2 (Display.aspx) the Amount column should update to '1' beside that product
<asp:TableCell ID="p001_amt" runat="server"></asp:TableCell>
If an item is checked on page 1, this should be the result... (along with the other products).
Product
Price
Amount
1lb dark Roast
$15.99
1
1lb Med Roast
$15.99
0
1 = checked, 0 = unchecked (assignment desired outcome shows this table)
Any help would be appreciated. Thanks.
Here are snippets from my code, if more is needed I can add a link to the full pages.
Page 1 (ShoppingCart.aspx)
ShoppingCart.aspx
------------------
<asp:Label ID="TotalCart_lbl" runat="server" Text="Total Items in your Cart: "></asp:Label>
<br />
<asp:CheckBoxList ID="Shop_ckbx" runat="server">
<asp:ListItem Value="15.99">1lb Dark Roast</asp:ListItem>
<asp:ListItem Value="15.99">1lb Medium Roast</asp:ListItem>
<asp:ListItem Value="12.99">1lb Decaf Roast</asp:ListItem>
<asp:ListItem Value="16.99">1lb Cold Brew</asp:ListItem>
<asp:ListItem Value="10.99">1 box Tea</asp:ListItem>
<asp:ListItem Value="35.99">French Press</asp:ListItem>
<asp:ListItem Value="8.99">CCM Mug</asp:ListItem>
</asp:CheckBoxList>
<br />
<asp:Button ID="UpdateCart_btn" runat="server" Text="Update Your cart" OnClick="UpdateCart_btn_Click"/>
<br />
<asp:Label ID="Contents_lbl" runat="server" Text="Update To See Cart Contents"></asp:Label>
<br />
<asp:Label ID="Cost_lbl" runat="server" Text="Total: "></asp:Label>
<br />
<asp:Button ID="Checkout_btn" runat="server" PostBackUrl="~/Display.aspx" Text="Go to Checkout" />
ShoppingCart.aspx.cs
---------------------
protected void UpdateCart_btn_Click(object sender, EventArgs e)
{
int total = 0; //Total item count
Contents_lbl.Text = "Your Cart Contains: <br/>- - - - - - - - - - - - - - - -";
foreach (ListItem listItem in Shop_ckbx.Items)
{
if (listItem.Selected == true)
{
//Add Text to label
Contents_lbl.Text += "<br/>&#8226 " + listItem.Text;
//Count Items in Cart
total += 1;
}
}
Contents_lbl.Text += "<br/>";
for (int i = 0; i < Shop_ckbx.Items.Count; i++)
{
if (Shop_ckbx.Items[i].Selected)
{
cost += Convert.ToDouble(Shop_ckbx.Items[i].Value);
Cost_lbl.Text = "Total: $ " + cost.ToString();
}
}
//Update total items
TotalCart_lbl.Text = "Total Items in Your Cart: " + total;
}
protected void Checkout_btn_Click(object sender, EventArgs e)
{
How do I reference the items in the checklist on page 1
to update the table on page 2 without unique IDs on each list item?
}
Page 2 code (Display.aspx)
Display.aspx
------------------
<asp:Table ID="Display_tbl" runat="server">
<asp:TableRow runat="server" ID="Header_row" Font-Bold="True" Font-Size="Larger" BackColor="#FFCC99">
<asp:TableCell ID="Product_cell" runat="server" Width="250">Product</asp:TableCell>
<asp:TableCell ID="Price_cell" runat="server" Width="100">Price</asp:TableCell>
<asp:TableCell ID="Amount_cell" runat="server" Width="100">Amount</asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" ID="p001">
<asp:TableCell ID="p001_prod" runat="server">1lb Dark Roast</asp:TableCell>
<asp:TableCell ID="p001_price" runat="server">$ 15.99</asp:TableCell>
<asp:TableCell ID="p001_amt" runat="server"></asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" ID="p002" Width="500px">
<asp:TableCell ID="p002_prod" runat="server">1lb Med Roast</asp:TableCell>
<asp:TableCell ID="p002_price" runat="server">$ 15.99</asp:TableCell>
<asp:TableCell ID="p002_amt" runat="server"></asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" ID="p003">
<asp:TableCell ID="p003_prod" runat="server">1lb Decaf</asp:TableCell>
<asp:TableCell ID="p003_price" runat="server">$ 12.99</asp:TableCell>
<asp:TableCell ID="p003_amt" runat="server"></asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" ID="p004">
<asp:TableCell ID="p004_prod" runat="server">1lb Cold Brew</asp:TableCell>
<asp:TableCell ID="p004_price" runat="server">$ 16.99</asp:TableCell>
<asp:TableCell ID="p004_amt" runat="server"></asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" ID="p005">
<asp:TableCell ID="p005_prod" runat="server">1 box Tea (50 bags)</asp:TableCell>
<asp:TableCell ID="p005_price" runat="server">$ 10.99</asp:TableCell>
<asp:TableCell ID="p005_amt" runat="server"></asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" ID="p006">
<asp:TableCell ID="p006_prod" runat="server">French Press</asp:TableCell>
<asp:TableCell ID="p006_price" runat="server">$ 35.99</asp:TableCell>
<asp:TableCell ID="p006_amt" runat="server"></asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" ID="p007">
<asp:TableCell ID="p007_prod" runat="server">CCM Coffee Mug</asp:TableCell>
<asp:TableCell ID="p007_price" runat="server">$ 8.99</asp:TableCell>
<asp:TableCell ID="p007_amt" runat="server"></asp:TableCell>
</asp:TableRow>
<asp:TableRow runat="server" ID="Total_row">
<asp:TableCell ID="Total_prod" runat="server">Total</asp:TableCell>
<asp:TableCell ID="Total_price" runat="server"></asp:TableCell>
<asp:TableCell ID="Total_amt" runat="server"></asp:TableCell>
</asp:TableRow>
</asp:Table>
<asp:LinkButton ID="Back_linkbtn" runat="server" OnClick="Back_linkbtn_Click">Go Back to the Shop</asp:LinkButton>
Display.aspx.cs
-------------------
protected void Page_Load(object sender, EventArgs e)
{
ShoppingCart prevPage = PreviousPage as ShoppingCart;
if (prevPage != null)
{
NOT SURE HOW TO LINK TO MY CHECKLIST ITEMS
TO UPDATE 'AMOUNT' TABLE CELLS
Also,
CHECKBOXES ON PREVIOUS PAGE NEED TO STAY SELECTED
in order to CONTINUE SHOPPING.
}
}
protected void Back_linkbtn_Click(object sender, EventArgs e)
{
Response.Redirect("ShoppingCart.aspx");
}
Ok, so the problem we have here is of course matching up the “thing” you selected on one page, and then figuring this out on the next web page.
To be fair, the problem here is that no doubt you have to "make up" the data here. And that REALLY hurts this whole process, since you having to type in the data two times (once on this page, and then on the target page). Worse, you have to match up by "text". One little space, period etc. and your whole system will fall down.
I going to suggest we NOT worry and NOT have to match up the values. We can in one shot get rid of a HUGE problem, and ALSO reduce the markup, the code and all your life hassles (well, ok, just the coding part - can't fix everything!!!).
So, I going to suggest that in place of the check box "list", we create a table and use that for BOTH pages. This will then not only keep us out of rooms with padded walls, but we can deal with a "row" of something that you select, and just not give one hoot as to the spelling or even what we place in that row. And by doing this, you could with almost no code changes change the "list" from our table to a database - and the whole she-bang would work!!
So, lets use (create) a table, and not only is this easy, but I think far more clean to setup our data.
So, we will have this at the start of the page (a table and some total values for the WHOLE page class.
public class MyFirstOrderPage : System.Web.UI.Page
{
private DataTable MyTable = new DataTable();
private decimal MyTotal = 0;
private int MyTotalQty = 0;
Ok, now lets do the sub in this page to load up the above table.
public void CreateData()
{
MyTable.Columns.Add("Product", typeof(string));
MyTable.Columns.Add("Price", typeof(decimal));
MyTable.Columns.Add("Qty", typeof(int));
MyTable.Columns.Add("Amount", typeof(decimal));
MyTable.Columns.Add("Purchased", typeof(bool));
MyTable.Rows.Add({"11b Dark Roast",15.99,1,0,false});
MyTable.Rows.Add({"11b Medium Roast",15.99,1,0,false});
MyTable.Rows.Add({"1b Decaf Roast",12.99,1,0,false});
MyTable.Rows.Add({"11b Cold Brew",16.99,1,0,false});
MyTable.Rows.Add({"1 box Tea Brew",10.99,1,0,false});
MyTable.Rows.Add({"French Press",35.99,1,0,false});
MyTable.Rows.Add({"CCM Mug",8.99,1,0,false});
}
Now not only can you "easy" read the above, but you can change/view and see the columns and then the data follows - so you can see we decided to have a Qty and also a column for the purchased. this will save BUCKETS of code, and BUCKETS of markup. And we can use the above to BOTH pages - no having to have two copies of this list!!!!
Ok, so now we have our table. next up, lets display that table. And we can use VERY much the same grid in our order page as we can in the check out page (saves even more time, more code and even MORE markup!!!).
So, lets use a gridview. We could use a listview and I would consider that if you wanted a cute picture and some more product description. But, gridview is fine.
So, here is the markup for the gridview:
<form id="form1" runat="server">
<div>
<asp:GridView ID="GridView1" runat="server" ShowFooter="true"
Font-Bold="True" Font-Size="Larger" BackColor="#FFCC99" AutoGenerateColumns="False">
<Columns>
<asp:BoundField DataField="Product" HeaderText="Product" Headerstyle-width="250px" />
<asp:BoundField DataField="Price" DataFormatString="{0:C2}" HeaderText="Price" Headerstyle-width="80px" />
<asp:TemplateField HeaderText="Qty" >
<ItemTemplate>
<asp:TextBox ID="Qty" runat="server" Width="60px"
Text ='<%# Eval("Qty") %>'
MyRow = '<%# Container.DataItemIndex %>'
AutoPostBack="true" OnTextChanged="Qty_TextChanged"
/>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Amount">
<ItemTemplate>
<asp:TextBox ID="Amount" runat="server" width="80px"
Text ='<%# FormatCurrency(Eval("Qty") * Eval("Price")) %>' Enabled="false" DataFormatString="{0:C2}" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Purchase" ItemStyle-HorizontalAlign="Center">
<ItemTemplate >
<asp:CheckBox ID="CheckBox1" runat="server" width="110px"
Checked ='<%# Eval("Purchased") %>'
MyRow = '<%# Container.DataItemIndex %>'
AutoPostBack="true"
OnCheckedChanged="CheckBox1_CheckedChanged1"
/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
<FooterStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
</asp:GridView>
<asp:HiddenField ID="qtycheck" runat="server" ClientIDMode="Static" />
<br />
<asp:Button ID="Checkout_btn" runat="server" Text="Proceed to Checkout"
style="width:140px;height:60px;background-color:#FFCC99;border-radius:25px"
OnClientClick="hasorder();return false;"/>
<script>
function hasorder() {
var CheckQty = document.getElementById("qtycheck")
if (CheckQty.value > 0) {
window.location.href = "/CheckOut.aspx"
}
else {
alert("Please select at least one Purchase before proceding to check out");
return false;
}
}
</script>
Now that is a bit of markup, but it is STILL much less then say your 2nd page table, and better you see how our 2nd page now is VERY little markup!!
Ok, so we have the above. The only real issue is that when you check a box, we need to total things up. And when you edit or change the qty, then SAME thing - update the totals.
So, the code we have for each part is again rather limited. I'll post in all in one shot:
protected void Page_Load(object sender, System.EventArgs e)
{
if (System.Web.UI.Page.IsPostBack == false)
{
// ok first page load, now check if reutrn from checkout
if (System.Web.UI.Page.Session["MyTable"] == null)
{
CreateData(); // first time here - create the data
System.Web.UI.Page.Session["MyTable"] = MyTable; // save the data table
}
else
// we have data - (so this is user change of mind from checkout page)
MyTable = System.Web.UI.Page.Session["MyTable"];
// ok, now load our data into the grid
LoadData(); // load the data
}
else
// this is a post back due to general changes and selections for this
// page under normal and mutliple operations and choices by the user.
// we dont' need to re-display the grid - it persists automatic
// however the datatable variable does NOT persist - so get from session
MyTable = System.Web.UI.Page.Session["MyTable"];
}
public void LoadData()
{
MyTotal = 0;
MyTotalQty = 0;
qtycheck.Value = 0;
GridView1.DataSource = MyTable;
GridView1.DataBind();
}
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
TextBox MyAmount = e.Row.FindControl("Amount");
DataRow MyRow = MyTable.Rows(e.Row.RowIndex);
MyRow("Amount") = MyRow("Qty") * MyRow("Price");
if (MyRow("Purchased") == true)
{
// total up the qty purcchased and the amount
MyTotal += MyRow("Amount");
MyTotalQty += MyRow("Qty");
}
}
else if (e.Row.RowType == DataControlRowType.Footer)
{
// this is our bottom last footer.
e.Row.Cells(3).Text = Strings.FormatCurrency(MyTotal);
e.Row.Cells(2).Text = MyTotalQty;
e.Row.Cells(1).Text = "Totals:";
qtycheck.Value = MyTotalQty; // a hidden control - to check for qty > 0 on checkout
}
}
protected void CheckBox1_CheckedChanged1(object sender, EventArgs e)
{
// user click on purchase a row - udpate totals.
CheckBox cBox = sender;
int RowID = cBox.Attributes("MyRow");
MyTable.Rows(RowID).Item("Purchased") = cBox.Checked;
// data table has been changed - so we call the load data routine to udpate
// our grid
LoadData();
}
protected void Qty_TextChanged(object sender, EventArgs e)
{
// user changed the qry in a row
TextBox tBox = sender;
int RowID = tBox.Attributes("MyRow");
MyTable.Rows(RowID).Item("Qty") = tBox.Text;
// data table has been changed - so we call the load data routine to udpate
// our grid
LoadData();
}
}
Now that is a "bit" of code, but again, not really much.
So, you now get this after running:
So, notice what we do WHEN you check a box!
We simple do this:
protected void CheckBox1_CheckedChanged1(object sender, EventArgs e)
{
// user click on purchase a row - udpate totals.
CheckBox cBox = sender;
int RowID = cBox.Attributes("MyRow");
MyTable.Rows(RowID).Item("Purchased") = cBox.Checked;
// data table has been changed - so we call the load data routine to udpate
// our grid
LoadData();
}
So, now NO MATCHING problem really exists, does it? You click on a row, and we simply change the Purchased setting (a true/false flag). And that is it - done! no matching required. When you thus check a box, we change the table value, and then simply say to the code will you please re-display the data!!! So NOW we are SAVING all that matching work!!!
Now, the check out code becomes a walk in the park.
when you click on the button to check out, we jump to the checkout page.
The markup is now dead simple - since it much the same as the first page - but no ability to change things. Hence this:
<asp:GridView ID="GridView2" runat="server" ShowFooter="true"
Font-Bold="True" Font-Size="Larger" BackColor="#FFCC99"
AutoGenerateColumns="False"
>
<Columns>
<asp:BoundField DataField="Product" HeaderText="Product" Headerstyle-width="250px" />
<asp:BoundField DataField="Price" DataFormatString="{0:C2}" HeaderText="Price" Headerstyle-width="80px" />
<asp:BoundField DataField="Qty" HeaderText="Qty" Headerstyle-width="80px" />
<asp:BoundField DataField="Amount" HeaderText="Amount" Headerstyle-width="80px" />
</Columns>
<FooterStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
</asp:GridView>
<br />
<asp:Button ID="ChangeOrder_btn" runat="server" PostBackUrl="~/SimpleButton.aspx" Text="Change order"
style="width:140px;height:60px;background-color:#FFCC99;border-radius:25px"
/>
<br />
</div>
And the page looks like:
So, you NOW see why we using a table - since the SAME data now drives both pages, and our problem becomes smaller and smaller - not larger and larger!!
And our code for that checkout page, it is this:
class CheckOut : System.Web.UI.Page
{
private decimal MyTotal = 0;
private int MyTotalQty = 0;
protected void Page_Load(object sender, System.EventArgs e)
{
DataTable MyTable = System.Web.UI.Page.Session["MyTable"];
DataView dv = new DataView(MyTable); // we use data view in palce of data table - it can fitler!!
dv.RowFilter = "Purchased = True";
GridView2.DataSource = dv;
GridView2.DataBind();
}
protected void GridView2_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
MyTotal += e.Row.Cells(3).Text;
MyTotalQty += e.Row.Cells(2).Text;
}
else if (e.Row.RowType == DataControlRowType.Footer)
{
e.Row.Cells(3).Text = Strings.FormatCurrency(MyTotal);
e.Row.Cells(2).Text = MyTotalQty;
e.Row.Cells(1).Text = "Totals:";
}
}
}
So, by build a better design and mouse trap with the first page, and a better approach, then the next page becomes VERY simple, since we re-use the data, re-use the table and in fact darn near re-used the grid too!!!
And to display the order we ONLY have to get the table, and filter the results by purchased.
And the button to go back and change your mind? Well, it just jumps back to the that order page.
So while we did have to build up a bit more "markup" in the first page, we reduced by huge amounts the markup in the 2nd page.

How to display a Navigation Property of a table inside a Data control like a FormView?

Ok I have a many to many relationship like this:
Walk = {WalkID, title, ...., (Navigation Properties) Features}
Feature = {FeatureID, featureName, description, (Navigation Properties) DogWalks}
I do of course have a junction table, but EF assumes this thus it is not shown in my edmx diagram:
WalkFeatures = {WalkID, FeatureID} //junction, both FK
So using LINQ with EF, I am now trying to grab the features for the Walk at WalkID=xx.
This is my formview:
<asp:FormView ID="FormView1" runat="server" ItemType="Walks.DAL.Walk" SelectMethod="FormView1_GetItem">
<ItemTemplate>
<h1><%# Item.Title %></h1>
...
</ItemTemplate>
</asp:FormView
<asp:Label ID="lbFeatures" runat="server" Text="Label"></asp:Label>
And selectMethod:
public Walks.DAL.Walk FormView1_GetItem([QueryString("WalkID")] int? WalkID)
{
using (WalkContext db = new WalkContext())
{
var walk = (from n in db.Walks.Include("Features")
where n.WalkID == WalkID
select n).SingleOrDefault();
foreach(var f in walk.Features){
lbFeatures.Text += f.FeatureName + "<br/>";
}
return walk;
}
}
The code runs fine, but is there a way that I can display the Walk.Features directly inside the <ItemTemplate> of the formview rather than using a label and a loop? Can the attribute be directly binded like the other properties in the .aspx page?
I have also used this new feature not that extensively but just gave it a try for this particular scenario and this is what I have:-
Simply return walk from FormView1_GetItem method and don't manipulate your label control there. Now, you can use a Repeater control to display the lbFeatures control (since it is going to repeat dynamically) like this:-
<ItemTemplate>
<h1><%# Item.Title %></h1>
<asp:Repeater ID="lbFeatures" runat="server" DataSource='<%# Item.Features%>'>
<ItemTemplate>
<asp:Label ID="lblTest" runat="server"
Text='<%# Eval("FeatureName") %>'></asp:Label>
<br />
</ItemTemplate>
</asp:Repeater>
</ItemTemplate>
As you can see I am able to assign the datasouce of repater control as Item.Features, then use the conventional approach to bind the label. This looks clean and simple :)

What in the Repeater

An issue has manifested in a particular area of a page I'm working on that utilizes the infamous Repeater. The control is bound to a valid Data Source that does persist through the View State.
The Repeater code is as follows:
<asp:Repeater ID="creditRightItems" runat="server" DataSourceID="sdsOrder">
<HeaderTemplate>
<thead>
<td>Qty Returning:</td>
<td>Price:</td>
</thead>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><asp:TextBox ID="txtQuantity" runat="server" PlaceHolder="0" CssClass="txtQuantity Credit-Check" data-item='<%# Eval("ProductNum") %>' /><span class="creditX">X</span></td>
<td><span id="ProductPrice" class='Credit-Container price<%# Eval("ProductNum") %>'><%# ConvertToMoney(Eval("Price").ToString()) %></span>
<input type="hidden" id="hfPrice" value="<%# Eval("Price") %>" />
<input type="hidden" id="hfProdNum" value="<%# Eval("ProductNum") %>" />
<input type="hidden" id="hfSKU" value="<%# Eval("SKU") %>" />
</td>
</tr>
</ItemTemplate>
</asp:Repeater>
The issue occurs in code behind when I iterate through the Repeater. Essentially the loop only finds two controls, which may be apart of the issue. However, when I attempt to grab those values from code-behind they return null. If I add a runat="server" then it will actually error the Repeater.
foreach (RepeaterItem item in creditRightItems.Items)
{
TextBox inputQuantity = (TextBox)item.FindControl("txtQuantity");
string quantity = inputQuantity.Text;
TextBox inputProduct = (TextBox)item.FindControl("hfProdNum");
string product = inputProduct.Text;
HtmlInputHidden productPrice = (HtmlInputHidden)item.FindControl("hfPrice");
string price = productPrice.Value;
TextBox inputSKU = (TextBox)item.FindControl("hfSKU");
string sku = inputSKU.Text;
if (string.Compare(quantity, "0") != 0 && string.IsNullOrEmpty(quantity))
items.Add(new Items(product, quantity, price, sku));
}
The question is, how can I get a valid value for:
ProductPrice or hfPrice
hfProdNum
hfSku
For the life of me I can't get them to return a valid content. I've tried:
HiddenField productPrice = (HiddenField).item.FindControl("hfPrice");
string price = productPrice.Value;
HtmlInputHidden productPrice = (HtmlInputHidden).item.FindControl("hfPrice");
string price = productPrice.Value;
I know that FindControl requires the runat so I'm trying to either achieve a way to avoid the Repeater breaking when I add a runat or a way to grab the contents of those inputs.
Any thoughts and help would be terrific.
What you have in your source aren't server controls, so FindControl won't find them.
Why can't you just convert the hidden fields to asp:HiddenField tags?
<asp:HiddenField id='hfPrice' value='<%# Eval("Price") %>' runat='server' />
The runat probably isn't breaking your page; I think it's the single vs double quotes on your Eval call. If you alternate them like I have in this sample, it will work.
Well, without knowing where your code is at exactly, I guess that you should consider doing this manipulation on good event.
Try handling ItemDataBound event. And instead of iterating trought every Rows, do something like :
(VB.net code, sorry)
Private Sub myRepeater_ItemDataBound(sender as object, e as RepeaterItemEventArgs) andles myRepeater.ItemDataBound
If (e.Item IsNot Nothing AndAlso (e.Item.ItemType = ListItemType.Item OrElse e.Item.ItemType = ListItemType.AlternatingItem)) Then
' DO STUFF HERE
Dim productPrice As HtmlInputHidden = (HtmlInputHidden).item.FindControl("hfPrice")
Dim price As String = productPrice.Value
End If
End Sub
So the culprit is due to the quotation and single quote, the error exist when:
value="<%# Eval("Price") %>"
The error no longer occurs, if you do:
value='<%# Eval("Price") %>'
Which alleviates the error in the page breaking, which allows me to properly run FindControl. A careless error on my part, but hopefully this helps someone in the future.

Populating a listbox control in asp.net

I have a code block that returns a list of employee object.
The resultset contains more than one employee record. One of the elements, is EmployeeID.
I need to populate listview (lstDepartment) with only the EmployeeID. How can I do that?
lstDepartment.DataSource = oCorp.GetEmployeeList(emp);
lstDepartment.DataBind()
You have to also specify this:
lstDepartment.DataSource = oCorp.GetEmployeeList(emp);
lstDepartment.DataTextField = "EmployeeID";
lstDepartment.DataValueField = "EmployeeID";
lstDepartment.DataBind()
One way would be to use an anonymous type:
lstDepartment.DataSource = oCorp.GetEmployeeList(emp)
.Select(emp => new { emp.EmployeeID });
lstDepartment.DataBind();
Edit: But you also could select all columns but diplay only one. A ListView is not a ListBox or DropDownList. Only what you use will be displayed. So if you're ItemTemplate looks like:
<ItemTemplate>
<tr runat="server">
<td>
<asp:Label ID="LblEmployeeID" runat="server" Text='<%# Eval("EmployeeID") %>' />
</td>
</tr>
... only the EmployeeID is displayed, no matter whatelse is in your DataSource.
You may mention what to display in your ItemTemplate of ListView
<asp:ListView ID="lstDepartment" runat="server">
<ItemTemplate>
<p> <%#Eval("EmployeeID") %> </p>
</ItemTemplate>
</asp:ListView>

Categories