I'm setting up a User Control driven by a XML configuration. It is easier to explain by example. Take a look at the following configuration snippet:
<node>
<text lbl="Text:"/>
<checkbox lbl="Check me:" checked="true"/>
</node>
What I'm trying to achieve to translate that snippet into a single text box and a checkbox control. Of course, had the snippet contained more nodes more controls would be generated automatically.
Give the iterative nature of the task, I have chosen to use Repeater. Within it I have placed two (well more, see bellow) Controls, one CheckBox and one Editbox. In order to choose which control get activate, I used an inline switch command, checking the name of the current configuration node.
Sadly, that doesn't work. The problem lies in the fact that the switch is being run during rendering time, long after data binding had happened. That alone would not be a problem, was not for the fact that a configuration node might offer the needed info to data bind. Consider what would happen if the check box control will try to bind to the text node in the snippet above, desperately looking for it's "checked" attribute.
Any ideas how to make this possible?
Thanks,
Boaz
Here is my current code:
Here is my code (which runs on a more complex syntax than the one above):
<asp:Repeater ID="settingRepeater" runat="server">
<ItemTemplate>
<%
switch (((XmlNode)Page.GetDataItem()).LocalName)
{
case "text":
%>
<asp:Label ID="settingsLabel" CssClass="editlabel" Text='<%# XPath("#lbl") %>' runat="server" />
<asp:TextBox ID="settingsLabelText" Text='<%# SettingsNode.SelectSingleNode(XPath("#xpath").ToString()).InnerText %>'
runat="server" AutoPostBack="true" Columns='<%# XmlUtils.OptReadInt((XmlNode)Page.GetDataItem(),"#width",20) %>'
/>
<% break;
case "checkbox":
%>
<asp:CheckBox ID="settingsCheckBox" Text='<%# XPath("#lbl") %>' runat="server"
Checked='<%# ((XmlElement)SettingsNode.SelectSingleNode(XPath("#xpath").ToString())).HasAttribute(XPath("#att").ToString()) %>'
/>
<% break;
} %>
</ItemTemplate>
</asp:Repeater>
One weekend later, here is what I came with as a solution. My main goal was to find something that will both work and allow you to keep specifying the exact content of the Item Template in markup. Doing things from code would work but can still be cumbersome.
The code should be straight forward to follow, but the gist of the matter lies in two parts.
The first is the usage of the Repeater item created event to filter out unwanted parts of the template.
The second is to store decisions made in ViewState in order to recreate the page during post back. The later is crucial as you'll notice that I used the Item.DataItem . During post backs, control recreation happens much earlier in the page life cycle. When the ItemCreate fires, the DataItem is null.
Here is my solution:
Control markup
<asp:Repeater ID="settingRepeater" runat="server"
onitemcreated="settingRepeater_ItemCreated"
>
<ItemTemplate>
<asp:PlaceHolder ID="text" runat="server">
<asp:Label ID="settingsLabel" CssClass="editlabel" Text='<%# XPath("#lbl") %>' runat="server" />
<asp:TextBox ID="settingsLabelText" runat="server"
Text='<%# SettingsNode.SelectSingleNode(XPath("#xpath").ToString()).InnerText %>'
Columns='<%# XmlUtils.OptReadInt((XmlNode)Page.GetDataItem(),"#width",20) %>'
/>
</asp:PlaceHolder>
<asp:PlaceHolder ID="att_adder" runat="server">
<asp:CheckBox ID="settingsAttAdder" Text='<%# XPath("#lbl") %>' runat="server"
Checked='<%# ((XmlElement)SettingsNode.SelectSingleNode(XPath("#xpath").ToString())).HasAttribute(XPath("#att").ToString()) %>'
/>
</asp:PlaceHolder>
</ItemTemplate>
</asp:Repeater>
Note: for extra ease I added the PlaceHolder control to group things and make the decision of which controls to remove easier.
Code behind
The code bellow is built on the notion that every repeater item is of a type. The type is extracted from the configuration xml. In my specific scenario, I could make that type to a single control by means of ID. This could be easily modified if needed.
protected List<string> repeaterItemTypes
{
get
{
List<string> ret = (List<string>)ViewState["repeaterItemTypes"];
if (ret == null)
{
ret = new List<string>();
ViewState["repeaterItemTypes"] = ret;
}
return ret;
}
}
protected void settingRepeater_ItemCreated(object sender, RepeaterItemEventArgs e)
{
string type;
if (e.Item.DataItem != null)
{
// data binding mode..
type = ((XmlNode)e.Item.DataItem).LocalName;
int i = e.Item.ItemIndex;
if (i == repeaterItemTypes.Count)
repeaterItemTypes.Add(type);
else
repeaterItemTypes.Insert(e.Item.ItemIndex, type);
}
else
{
// restoring from ViewState
type = repeaterItemTypes[e.Item.ItemIndex];
}
for (int i = e.Item.Controls.Count - 1; i >= 0; i--)
{
if (e.Item.Controls[i].ID != type) e.Item.Controls.RemoveAt(i);
}
}
You need something that looks more like this:
<ItemTemplate>
<%# GetContent(Page.GetDataItem()) %>
</ItemTemplate>
And then have all your controls generated in the code-behind.
Related
Sounds a bit cluttered, but basically I have a databound repeater. On the ASP side, I have this:
<asp:Label ID="Label2" runat="server" Text='<%#Eval("uMessage") %>'></asp:Label>
I'm using the same template for 4 different datasets, and for 2 of them this should be a hyperlink and for the other 2 it shouldn't. So, I'm guessing you have to add a hyperlink programmatically in the code-behind? Has anyone ever done something like this?
Easiest way without all kinds of code-behind and therefor less code fragmentation, I would say you need a property that is set based on your condition prior to data binding.
protected bool LinkVisible { get; set; }
Then you just do this:
<asp:Label ID="Label2" runat="server" Text='<%#Eval("uMessage") %>' Visible="<%# !LinkVisible %>"></asp:Label>
<asp:HyperLink ID="Link" runat="server" Visible="<%# LinkVisible %>" ><%#Eval("uMessage") %></asp:HyperLink>
This sets the Visible for either the Label or the HyperLink. Visible false means it won't even get rendered. In your markup you can see that there will be a label or a hyperlink and no special things popup from the code behind.
You don't need to add the property LinkVisible, but can do the condition there too.
yes it is possible in code behind on DataItem bound
if (e.Row.RowType == DataControlRowType.DataRow)
{
Label lbl = (Label)e.Row.FindControl("Label2");
if (lbl.Text == "your condition")
{
HyperLink yourLink = (HyperLink)e.Row.FindControl("yourID");
yourLink.enabled = false;
}
}
I am trying to find out if it's possible to have a control inside another control in ASP, like this:
<asp:FormView ID="FormView1" runat="server" Width="630px" Height="496px">
<ItemTemplate>
<asp:Literal ID="ID" runat="server">Idnumber: </asp:Literal><%#Eval("ID") %>
<asp:DataList ID="DataList1" runat="server">
<ItemTemplate>
<asp:HyperLink ID="ID" runat="server"> <%# Eval("FILE") %> </asp:HyperLink>
</ItemTemplate>
</asp:DataList>
</ItemTemplate>
</asp:FormView>
Can I access DataList1 control? I have been trying, but I can't figure it out, I should be able to access nested controls, but I cant get it to do it.
Controls inside Template tags cannot be directly accessed in code behind. Instead you should use FindControl method:
var dataList1 = (DataList)FormView1.FindControl("DataList1");
Note that this might not work in early stages of page life cycle (not until Page_Load I believe).
I'm currently working on a little dummy application to get my head around the new (awesome) model binding in WebForms. I've succesfully listed data both in a ListView and a FormView, however I'm stuck on updating the data.
I have a very simple FormView control:
<asp:FormView runat="server" ID="formViewOrderDetail"
ItemType="ModelBindingDummy.Models.Order" DataKeyNames="Id"
SelectMethod="GetOrder" UpdateMethod="UpdateOrder">
<ItemTemplate>
<p>Supplier Order Number: <%#: Item.SupplierOrderNumber %></p>
<asp:LinkButton Text="Edit" runat="server" ID="linkEdit"
CommandName="Edit" />
</ItemTemplate>
<EditItemTemplate>
<p>Supplier Order Number:
<asp:TextBox runat="server" ID="SupplierOrderNumber"
Text='<%#: Item.SupplierOrderNumber %>' />
</p>
<asp:LinkButton Text="Cancel" runat="server" CommandName="Cancel" />
<asp:LinkButton Text="Save" runat="server" CommandName="Update" />
</EditItemTemplate>
</asp:FormView>
My UpdateOrder method looks like this:
public void UpdateOrder(int id)
{
ModelBindingDummy.Models.Order item = GetOrder(id);
if (item == null)
{
ModelState.AddModelError("", String.Format("Item with id {0} was not found", id));
return;
}
TryUpdateModel(item);
//All data in the app is in-memory, so no need to actually update
//the item as it is only a reference
}
Now my problem is that the TryUpdateModel doesn't work. It simply ignores the value in the TextBox SupplierOrderNumber
I've tried adding a parameter to the update method, [Control] string supplierOrderNumber, and while that works, I'd really like to see a way to automatically map that value.
I've also seen solutions using the <asp:DynamicEntity /> control, but that seems somewhat overkill.
So my question is: Is it possible to get the TryUpdateModel method to map the TextBox value, or do I need to go all the way with the DynamicEntities control, or are there any other fancy solutions?
Thanks!
You need to use BindItem.SupplierOrderNumber instead of Item.SupplierOrderNumber.
You need to use DataItem.SupplierOrderNumber instead of Item.SupplierOrderNumber
There is another question like this out there with a better explanation but i can't find it.
So I've been looking around for a good answer to this question, but haven't really found anything useful. Hopefully someone can shed some light on this for me.
Basically, I have a repeater that is backed by a database table. Inside the ItemTemplate for this repeater, I have some HTML elements that are populated with properties from each item in the list. Pretty standard stuff. However, there is a possibility that one of the items could be null. In that case, it would make sense for me to put some sort of if (blah != null) logic around the offending code. The only problem is, when I've tried to do so, ASP throws up on me, telling me that I can't use an if statement inside of <%# %>.
My question to the masses is this: if you can't use an if statement inside of <%# %>, then how are you supposed to do conditional logic based on the values of each item?
I know that you can call your own methods inside the repeater, but that won't work for what I'm trying to do.
Below is what I'm trying to accomplish, to better illustrate my point.
<asp:Repeater runat="server" ID="repeater">
<ItemTemplate>
<div class="item-wrap">
<% if(Eval("imageUrl") != null) { %>
<div class="plan-img">
<asp:Image runat="server" ImageUrl='<%# Eval("imageUrl") %>'/>
</div>
<% } %>
</div>
</ItemTemplate>
</asp:Repeater>
inside your ItemTemplate write the markup like this:
<asp:Panel runat="server" Visible='<%# Eval("imageUrl") != null %>'>
<asp:Image runat="server" ImageUrl='<%# Eval("imageUrl") %>'/>
</asp:Panel>
Basically you can't mix code <% with databinding constructs <%#.
My advice would be to add the following property in your CodeBehind:
protected YourClass DataItem
{
get
{
return (YourClass)this.Page.GetDataItem();
}
}
and then write the markup without Eval():
<asp:Image runat="server" ImageUrl='<%# DataItem.imageUrl %>'/>
You're supposed to generate the same content for every item in the template. If you don't need to use it for a particular item just set it's visibility to false in the binding events.
I have a gridview. its data source is a datatable that is loaded from the database.
In this gridview, i have a template column. The content of this column is not directly pulled from the database but instead, i use the id of the current item to make a name and look for that name in a directory of images. The template field is:
<asp:TemplateField>
<itemtemplate>
<img src='../user/images/<% =itemType %><%# DataBinder.Eval(Container.DataItem, "id") %>.jpg?'
alt='<%# DataBinder.Eval(Container.DataItem, "Title") %>' />
</itemtemplate>
</asp:TemplateField>
Not all the items have images so I would like to check if this file exists. If it does, I'd like to use the above code to place it, if it doesn't i'd like to leave the field empty.
In the .cs file this is a matter of an if statement with the condition set to File.Exist(). But I could not find the syntax to do it in the .aspx file.
Is this possible and if so how?
Thanks.
you could code this behavior on the RowDataBound event. somethink like the below
Remember to make the image runat="server"
protected void GridViewProducts_RowDataBound(object sender, GridViewRowEventArgs e)
{
if(e.Row.RowType = DataControlRowType.DataRow){
if(!File.Exist(yourFileName){
//hide the image
var img=e.Row.FindControl("theImageId");
img.visible=false;
}
}
}
I think you should go for the above solution.
anyway Just add the below
<asp:TemplateField>
<itemtemplate>
<img src='../user/images/<% =itemType %><%# DataBinder.Eval(Container.DataItem, "id") %>.jpg?'
alt='<%# DataBinder.Eval(Container.DataItem, "Title") %>'
<%= File.Exists("yourFileName")? string.Empty : "style='display: none'" %> />
</itemtemplate>
</asp:TemplateField>
img tag has an event called onerror. So, if your requirement is to do something if the image is not found, do something like this.
onerror="this.src='Images/default.png'"