I think I may have used a repeater when I should have used something else, so I'm ready to chalk this up to design but I wanted to check with the development community before changing this.
I should also say upfront that I'm using this repeater control within a custom user-control for an aspx page.
My situation is that I have to dynamically display a list of additional parts when a user selects an item. This is similar to an "you might also be interested in" list that you sometimes see during an online checkout.
So the user selects an item to order from a dropdown and up to 4 additional parts can be optionally added.
Currently I'm bringing back that optional part list in a generic list of data-objects and binding it to a repeater control and its textboxes. The textboxes basically list a part description in one box and an option for the user to type in a quantity of how many they want of that item in another textbox.
That all works great.
So to be clear, after the repeater control loads everything and the form is rendered, the users can then type in values in the quantity textboxes.
Since this is all in a user-control, I'm writing a method to gather all this information up, populate a business object and return it to whatever calls it.
I'm having trouble finding the auto-generated textboxes so I can retrieve their values.
I have this sneaking suspicion that I'm doing something obviously wrong in my design. So I wanted to run this by the hive-mind to see what others think :)
Here is my markup generated by the repeater control.
<div class="base-container-controls-75pct">
<div class="base-container-controls-98pct">
<div class="base-container-controls-75pct">
<input name="DownLoadItem1$UxAdditionalParts$ctl01$UxItemNumber" type="text" id="DownLoadItem1_UxAdditionalParts_ctl01_UxItemNumber" class="textbox-readonly-xl" />
</div>
<div class="base-container-controls-10pct">
<input name="DownLoadItem1$UxAdditionalParts$ctl01$UxQuantity" type="text" value="3" id="DownLoadItem1_UxAdditionalParts_ctl01_UxQuantity" class="textbox-md" />
</div>
</div>
... more repeating code here, basically the 98pct div above is repeated for each "row" ...
</div>
I basically figured this out one after piecing some things together, reading various other posts.
What I was missing was that I need to add [EnableViewState="True"] to the markup for my repeater. So my markup is below:
<asp:Repeater ID="UxAdditionalParts" runat="server" OnItemDataBound="UxAdditionalPartsItemDataBound" EnableViewState="True">
<HeaderTemplate>
</HeaderTemplate>
<ItemTemplate>
<div class="base-container-controls-98pct">
<div class="base-container-controls-75pct">
<asp:TextBox ID="UxItemNumber" runat="server" CssClass="textbox-readonly-xl"></asp:TextBox>
</div>
<div class="base-container-controls-10pct">
<asp:TextBox ID="UxQuantity" runat="server" CssClass="textbox-md"></asp:TextBox>
</div>
</div>
</ItemTemplate>
<FooterTemplate>
</FooterTemplate>
</asp:Repeater>
Once this was done, I could simply loop through the controls as I expected and grab their values, building my list of objects from them. Here is how that turned out:
// Find all textboxes, looping through them to build an object list. Generally there is a maximum of 4 parts
// associated here but there could be more in the future so this should expand too.
for (int j = 0; j <= UxAdditionalParts.Items.Count - 1; j++)
{
if (UxAdditionalParts.Items[j].ItemType == ListItemType.Item || UxAdditionalParts.Items[j].ItemType == ListItemType.AlternatingItem)
{
TextBox txtItm = (TextBox)UxAdditionalParts.Items[j].FindControl("UxItemNumber");
TextBox txtQty = (TextBox)UxAdditionalParts.Items[j].FindControl("UxQuantity");
if (txtItm != null & txtQty != null)
{
// Create a new part and add it to our list.
AdditionalPart objAdditionalPart = new AdditionalPart();
objAdditionalPart.ItemNumber = txtItm.Text;
objAdditionalPart.Quantity = Convert.ToInt32(txtQty.Text) ;
loAdditionalParts.Add(objAdditionalPart);
}
}
}
Mystery solved!
Related
I know its maybe unusual, but i want add htmlGenericControl to a div (it's outside a repeater) in ItemDataBound from CodeBehind..
HtmlGenericControl slider = (HtmlGenericControl)e.Item.FindControl("slider");
htmlGenericControl input = new HtmlGenericControl("input");
input.Attributes.Add("type", "radio");
input.Attributes.Add("name", "slide_switch");
input.Attributes.Add("id", string.Format("projectImage-{0}", item.ProjectImageId));
slider.Controls.Add(input);
but its return null everytime. this is the aspx code:
<div class="slider">
<asp:Repeater ID="rptProjectImages" runat="server" OnItemDataBound="rptProjectImages_ItemDataBound">
<ItemTemplate>
</ItemTemplate>
</asp:Repeater>
</div>
Didnt work with asp.net too long, but probably this should help
<div id="myDiv" runat="server">...</div>
and in codebehind myDiv should be accessible.
Several issues with this code.
First, your div is client-side only, from server-side point of view it's just a string. Turn it into server-side control with:
<div class="slider" runat="server" ID="slider">
Second, FindControl looks for immediate children only, in your case children of the repeater item. slider is not one of those. Moreover, it is not a part of the repeater item template and should be accessible as in in code behind, so just
slider.Controls.Add(...
That is unless slider and the repeater you showed are a part of some other template of some "outer" control. In which case make sure to use that "outer" control to call FindControl on.
Finally, don't mess with id. I bet this is either going to be overridden by ASP.NET, or will cause issues on the page. Instead set client ID mode to static and assign ID property:
input.ClientIDMode = ClientIDMode.Static;
input.ID = string.Format("projectImage_{0}", item.ProjectImageId);
This is eventually output the same value for id you needed, but in more ASP.NET compliant way. One note though is that I replaced "-" with "_" - server side controls cannot have hyphens in ID
FindControl finds a control within another, but does so looking for the control's id. Your "slider" control has no id, it uses a class named "slider" but has no id.
You will need to define the control as
<div runat="server" id="Slider" class="slider">
<asp:Repeater ID="rptProjectImages" runat="server" OnItemDataBound="rptProjectImages_ItemDataBound">
<ItemTemplate>
</ItemTemplate>
</asp:Repeater>
</div>
The runat="server" tells the framework to instantiate that control in your code behind. The id will be the name of the object that is that control. Then in your code, you can do
htmlGenericControl input = new HtmlGenericControl("input");
input.Attributes.Add("type", "radio");
input.Attributes.Add("name", "slide_switch");
input.ID = string.Format("projectImage-{0}", item.ProjectImageId);
Slider.Controls.Add(input);
In asp.net, you can use asp:Repeater for dynamically generating controls.
for example:
<asp:Repeater runat="server" id="rpt">
<ItemTemplate>
<asp:Textbox runat="server"/>
</ItemTemplate>
</asp:Repeater>
Then you can get loop through the items on the server side:
foreach(var items in rpt.Items)
{
}
And in MVC, you can do something like this:
#foreach(var item in model)
{
#Html.TextBoxFor(a=>item.searchCriteria);
}
But there is a problem:
I would like to generate textboxes when ever user clicks add more textbox button:
If I use foreach in MVC, then I won't be able to fetch the user input on the controller, also, I don't have a model to loop through.
And the final desired output will be:
After the user clicks the "Add more textbox" button, a new textbox will be append to the bottom, and after the user clicks the search button, the text inside the textbox will be generated into a query string, something like this:
View:
<input name="searchCriteria" type="text" class="form-control" />
<input name="searchCriteria" type="text" class="form-control" />
<input name="searchCriteria" type="text" class="form-control" />
<input name="searchCriteria" type="text" class="form-control" />
Controller:
public ActionResult Search()
{
string queryString = Request.RawUrl;
//parse the querystring here
return View(NumberOfVisitorsReport);
}
And the query string generated will be:
www.website.com/?searchCriteria=a&searchCriteria=b&searchCriteria=c
So are there any alternative way to achieve this in MVC?
Edit:
I've took a look at some post on "How to generate controls dynamically in MVC"
For example
Control creation based on model value in MVC4
He is generating controls based on value in database, if I implement this to my website, the controls will lose value on each post back.
You probably want to move away from "postback" and look to adding text boxes dynamically in the browser using jQuery or a similar framework. If you either name the new text boxes carefully or do the submit as an AJAX post of a JSON object, you can harvest all the values from all the added controls.
You must use jquery.
This article can help for solve to problem.
Dynamic Form in Asp.net MVC & jquery
You are going to have to use For loop insead of ForEach. Then use the index to Create new Textbox controls, this way when you submit the form, index of newly created textbox will be submited, you can even BIND it to a model if the data type of your model is LIST.
Example:
#for (int i = 0; i < Model.Contributor.Count(); i++)
{
#Html.HiddenFor(model => Model.Contributor[i].Id)
}
now you know the value/index of "i", use that to populate new controls.
At the moment, I have a GridView that pulls from a SQL data source. Every row has a summary in it, and when a user clicks expand on the row, they get a DetailsView with the details of that particular item in it.
I'm looking for a way to make it so DetailsView only binds to the DataSource when the row is expanded. At the moment it happens on RowDataBound, which results in heaps of queries going back and forth between the SQL server and the ASP NET server.
I'd also like to use Bootstrap to expand and collapse these rows, so it looks good when it expands or collapses.
Any help would be very much appreciated :) I've really tried to find some resources that would assist me in the above, but I've come up bumpkis. The closest I've gotten is an AJAX CollapsiblePanel, but it loads everything at Page_Load anyway.
You are battling two different areas here, and this comment is the one that makes it more complicated:
I'd also like to use Bootstrap to expand and collapse these rows, so it looks good when it expands or collapses.<
What I did was use BS panels and a repeater and with this approach, you can still databind details on a click event but you need some hidden controls inside your item templates for your DetailsView to bind the correct data. When you "loop" through your summary data, you'll need to provide an accurate link to BS expanding child - presumably an identifier and then another hidden control with the same. Run the link on the server so you can have a click event and assign the datasource and databind there.
Depending on how long it take your data to load, you might consider avoiding the databind on every click. If you are using BS to make it elegant, it seems counterproductive to add a postback - at least on the user experience/elegance side. I understand the want to avoid so many data calls though.
Hopefully this will get you started:
<asp:Repeater ID="RepeatCont" runat="server" ClientIDMode="Static">
<HeaderTemplate>
<div class="panel-inner">
<div class="panel-group" id="accCont"> // parent
</HeaderTemplate>
<ItemTemplate>
<div class="panel panel-dark-gray">
<div class="panel-heading">
<h5 class="panel-title">
<a class="panel-toggle" data-toggle="collapse" data-parent="#accCont" href='<%# "#" + Eval("MATCHID1") %>'> // add a postback here with linkbutton or javascript postback force - easier just do bind your child data because you'll have two different trying to happen - postback and data-toggle
<div>
//stuff on the panel
</div>
<div class="clearfix">
<br />
</div>
</a>
</h5>
</div>
<div id='<%# Eval("MATCHID1") %>' class="panel-body collapse">
//Hidden label with your MATCH ID 1 again - this is where you will tell your DetailsView to look for parameter
//put your details view here
</div>
</div>
</ItemTemplate>
<FooterTemplate>
</div>
</div>
</FooterTemplate>
I'm using Linq in server I fill my list like this
selected MenuId=3;
ul_HeaderMenu.DataSource = data.TABLE_MENUS.ToList();
ul_HeaderMenu.DataBind();
this client code
<asp:ListView ID="ul_HeaderMenu" runat="server" ClientIDMode="Static">
<ItemTemplate >
<li class="li-HeaderMenu" runat="server" ><%# Eval("Name") %>
<div class="TopMenuActive"></div>
</li>
<ItemTemplate>
</asp:ListView>
I need to add class to the li the I building in server some think like this
selected MeduId=3;
ul_HeaderMenu.DataSource = data.TABLE_MENUS.ToList();
ul_HeaderMenu.DataBind();
ul_HeaderMenu.ElementAt[3]AddClass('test');
I just find the way to add class to li.
You could use the itemdatabound event on your list
<asp:ListView OnItemDataBound="YourListView_ItemDataBound" ID="ul_HeaderMenu" runat="server" ClientIDMode="Static">
<ItemTemplate >
<li ID="listItem" class="li-HeaderMenu" runat="server" ><%# Eval("Name") %>
<div class="TopMenuActive"></div>
</li>
<ItemTemplate>
</asp:ListView>
then in code behind, something like this;
protected void YourListView_ItemDataBound(object sender, ListViewItemEventArgs e)
{
HtmlGenericControl myLi = (HtmlGenericControl)e.Item.FindControl("listItem");
myLi.Attributes.Add("class", myLi.Attributes["class"].ToString() + " yournewclass");
}
The ListView is comprised of the items which are defined by your ItemTemplate section, and not of the li elements. Therefore, when you get an element from the ListView by index, you would first need to find the li before you can add a class to it.
Therefore, you need to use FindControl method on the item to get the li.
Now, there are a few things wrong here.
ListView doesn't have an ElementAt method, it's Items collection has it, but still, I would use ul_HeaderMenu.Items[3] to get an element at an index.
If you want to use FindControl method, you need to add an id to your li inside your ItemTemplate which you will pass to FindControl to get the li.
AddControl method does not exist, you have to use Attributes collection on the li to change the class attribute. Here you have to be careful though since you want to keep the classes already there, so you'll have to concatenate the strings, but you don't want to add a same class multiple times, therefore it's best if you created a helper method AddClass that takes the string and returns it with the class added.
You can do all this at any point, but usually you'll want to use the ItemDataBound event, as described by Lars Anundskas in the meantime.
And lastly, while you're free to use any convention you like, personally I find your casing convention confusing - you can read a bit on Microsoft's suggestions here.
I am trying here to set correctly a Datasource of a nested control list.
Basic idea :
I have an asp:repeater item. Inside there is a DropDownList and a BulletedList plus a Button.
I want to add the selected Item in the DropDownList from the selected row in the Repeater to be added into the selected row's BulletedList.
So far I managed to do that with OnItemDataBound and OnClick wich is quite good. The problem is the event OnClick is fired after OnItemDataBdound. To view then the Item I added in the BulletedList's datasource I have to refresh the page.
I tried this :
((BulletedList)src.FindControl("sharedPlanDomains")).Items.Clear();
and then
((BulletedList)src.FindControl("sharedPlanDomains")).Datasource = myobject;
((BulletedList)src.FindControl("sharedPlanDomains")).DataBind();
But it seems that it is not working.
((BulletedList)src.FindControl("sharedPlanDomains")).DomainsAssociated.ForEach(f => list.Items.Add(new ListItem { Text = f.Name }));
Doesn't work either.
Any suggestions available ?
[Edit: Clarifications]
Say I have this structure:
Hosting Plan : i-Perso
Domains : [The dropdownlist] [Button: Associate]
Associated Domains :
google.com
google2.com
Hosting Plan : i-Mense
Domains : [The dropdownlist] [Button: Associate]
Associated Domains :
google3.com
so let's take example that I click on the 1st dropdownlist and select the domain google4.com, then google4.com is going to be added in the datasource of the bulletedlist of the row i-Perso.
What I want to do here is to associate domains and plans. I would have done it better if it would be only a relationship of 1 - 1 but it's a relationshop of 1 - Multiple.
You can bind the datasource of your inner list view to a property of the items bound to your outer listview. However, to do that the inner listview as to be in the item template of the outer listview.
See the code below and note the DataSource='<%# Eval("Labels_color") %>' attribute for the inner list view.
<asp:ListView ID="ListView_Orp_Results" runat="server" ItemPlaceholderID="itemPlaceholder">
<LayoutTemplate>
<div id="outer_result_container">
<div id="itemPlaceholder" />
</div>
</LayoutTemplate>
<ItemTemplate>
<div id="result_photo">...</div>
<div id="result_category">...</div>
<div id="result_detector">...</div>
<div id="inner_result_container" runat="server">
<asp:ListView ID="ListView_inner_results" runat="server" ItemPlaceholderID="itemPlaceholder" DataSource='<%# Eval("Labels_color") %>'>
<LayoutTemplate>
<div id="outer_result_container" runat="server" >
<div id="itemPlaceholder" runat="server"> </div>
</div>
</LayoutTemplate>
<ItemTemplate>
<div id="inner_result_photo">...
</div>
<div id="inner_result_category">...
</div>
<div id="inner_result_categoryID">...
</div>
</ItemTemplate>
</asp:ListView>
</div>
</div>
</ItemTemplate>
</asp:ListView>
This way you don't need to bond the nested listview from the code behind. Just bind the main listview, and all inner listview will be automatically bound to the Labels_texture property of the bound objects
About the runat="server" it is a required attribute for asp.net controls. Basically, this attribute means that asp.net will parse the tag and create a corresponding object.
Most of the time you don't need it on html elements (div, p, ...) but under some circumstance you might want it to manipulate the corresponding object in your code behind.