I was at work the other day, just about to put a Panel below my repeater and write more code to handle if the source of the repeater didn't have any items in it, when I thought why not extend the repeater control and add an Empty template. I don't know why I've never thought of doing this before and why I hadn't come across other people doing it but that's neither here nor there.
Anyway here's the code:
[ParseChildren(true)]
[ToolboxData("<{0}:RepeaterWithEmptyTemplate runat=\"server\"></{0}:RepeaterWithEmptyTemplate>")]
public class RepeaterWithEmptyTemplate : Repeater
{
private ITemplate _emptyTemplate;
[Browsable(false)]
[TemplateContainer(typeof(EmptyTemplateContainer))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public ITemplate EmptyTemplate
{
get
{
return _emptyTemplate;
}
set
{
_emptyTemplate = value;
}
}
public bool RemoveHeaderAndFooterIfEmpty
{
get
{
return Convert.ToBoolean(ViewState["RemoveHeaderAndFooterIfEmpty"] ?? "true");
}
set
{
ViewState["RemoveHeaderAndFooterIfEmpty"] = value;
}
}
protected override void CreateControlHierarchy(bool useDataSource)
{
base.CreateControlHierarchy(useDataSource);
CheckEmptySource();
}
protected void CheckEmptySource()
{
if (Items.Count <= 0 && EmptyTemplate != null)
{
Controls.Clear();
if (!RemoveHeaderAndFooterIfEmpty)
HeaderTemplate.InstantiateIn(this);
EmptyTemplate.InstantiateIn(this);
if (!RemoveHeaderAndFooterIfEmpty)
FooterTemplate.InstantiateIn(this);
}
}
}
public class EmptyTemplateContainer : Control, INamingContainer
{
}
There isn't that much to talk about really, all I've done is create a basic EmptyTemplateContainer and used that as the container for the new EmptyTemplate ITemplate. I thought for a while about what was the best to do, remove the Header and Footer templates if there weren't any records or leave them there? Well I just decided to add an extra property "RemoveHeaderAndFooterIfEmpty" which is true by default. Then you can decide what you want to do.
I've changed all of my strongly typed controls to inherit from this one so that I have the bonuses of the strongly typed repeater with the additional bonus of a built in EmptyTemplate now.
Enjoy.
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
Using NHibernate I get this now and again and I always forget why. The reason is often because it doesn't know that an item you have added should be auto saved, make sure you double check the unsaved-value attribute and that when your class has the correct default value, basically the same as your unsaved-value attribute.
Simple really but hopefully it will save someone some heartache by seeing this here.
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
Posted by
John Harman on
4/15/2009 6:40 PM |
Comments (0)
I've shown you the strongly typed repeater which I use to help make my code less error prone and it also speeds my programming because I can stay in the presentation layer for longer because of the intellisense given to me and removes the need to keep adding logic into the code behind for a page / control. Now by itself it doesn't really give you a huge amount of extra functionality compared to a normal UserControl however the big reason why I use it will be seen in the next post but you will have to wait for that one.
So let's quickly go over the code for the control: (This code will likely go into a control library of yours for reuse)
[ToolboxData("<{0}:TypedUserControl runat=server></{0}:TypedUserControl>")]
public class TypedUserControl : UserControl
{
public string ControlUrl { get; set; }
protected ITypedUserControl TypedControl { get; set; }
protected override void OnInit(EventArgs e)
{
EnsureChildControls();
base.OnInit(e);
}
protected override void EnsureChildControls()
{
base.EnsureChildControls();
if (TypedControl == null)
{
Control ctrl = LoadControl(ControlUrl);
this.Controls.Add(ctrl);
TypedControl = (ITypedUserControl)ctrl;
}
}
public void DataSource(object model)
{
EnsureChildControls();
TypedControl.DataSource(model);
}
}
Basically the control loads the control's layout from a usercontrol which is defined by ControlUrl. One thing to note is that any UserControl that you load must inherit from ITypedUserControl so that the datasource details can be set against the UserControl. EnsureChildControls simply loads the ITypedUserControl from ControlUrl and updates the TypedControl property with a reference to the control.
You're currently missing the ITypedUserControl interface so lets look at that: (This will go in the class/ website that the initial TypedUserControl went)
public interface ITypedUserControl
{
void DataSource(object model);
}
So all you have to do now is create a new UserControl and inherit from ITypedUserControl and do whatever you want with it. To save myself some time I created a solid class based on the Interface above so that I have minimal amounts to do when creating a new UserControl: (This code will also likely go into your control library)
public class TypedUserControl<T> : UserControl, ITypedUserControl
{
public virtual void DataSource(object model)
{
Model = (T)model;
}
public T Model { get; set; }
}
Then when I create a new UserControl all I have to do is change it to inherit from TypedUserControl with the type that I want it to set the model as: (You should replace ProductLayout with the name of your usercontrol and Product with the type you wish to strongly type)
public partial class ProductLayout : TypedUserControl<Product>
{
}
Ok so once all of the above has been added to your control libary and website you can add something similar to the page. Then simply set the Datasource for the TypedUserControl and Databind it as usual then hey presto your control has been rendered. Note: Before you do try this make sure to add a reference to your control library in the Web.Config > System.Web > Controls section which is mentioned here, so please read that for more details
<CustomControl:TypedUserControl ControlUrl="~/UserControls/SearchLayout/Product.ascx" ID="tucProduct" runat="server" />
Are you wondering what you can put inside a control like the above Product.ascx? Well you can put anything in there, just remember that in the class TypedUserControl<T> we created a property called Model of the type T (seen above as Product) so to bind details of that model onto the page you would use this syntax.
<h2><%# Model.Title %</h2>
I'm loving the databinding side of things because it means I have fewer actual controls on the page, i.e. I don't have to place a literal on the page whenever I want to add something, and it just seems neater to me. Plus adding logic in the presentation layer for presentation itself just makes sense and so much more readable. In the below example it will only show the pnlTitle control if the title length is longer than 0, normally I would have to do something like that in the code behind but now it is in the presentation layer I can keep everything together a lot better.
<asp:Panel ID="pnlTitle" runat="server" Visible="<%# Model.Title.Length > 0%>">
<h2><%# Model.Title %</h2
</asp:Panel>
I'm going to post again probably after my long weekend away with details on the DynamicStronglyTyped control I've created which uses this control heavily, it helps add great flexability with minimal effort. You know what they say though, with great flexability comes great power... ha ok hit me next time you see me for that one ;)
Hope you enjoyed the above and found it useful.
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
Why? for one you get intellisense within your ItemTemplate and AlternatingItemTemplate, I can quickly bring up a list of the properties on the business object I'm binding (saves me trying to remember it or look up the class to double check not to mention the run time errors that are caught a lot earlier because of it). That also brings with it the ability to rename a property on the business object and have VS rename all of the other references within each repeater, none of this going around to find the repeaters that bind the object and modifying the strings. Same applies if you remove a property, VS will complain that the property no longer exists which means you can update all of your repeaters very quickly.
I'll throw some code at you now so you can see what I mean, then i'll chat a bit more. Firstly we have the control itself but that really doesn't have much to it, just rename the control and container to something sensible that relates to the business object you are binding.
[ParseChildren(true)]
[ToolboxData("<{0}:RepeaterProduct runat=\"server\"></{0}:RepeaterProduct>")]
public class RepeaterProduct : Repeater
{
[Browsable(false)]
[TemplateContainer(typeof(ProductContainer))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public override ITemplate ItemTemplate
{
get
{
return base.ItemTemplate;
}
set
{
base.ItemTemplate = value;
}
}
[Browsable(false)]
[TemplateContainer(typeof(ProductContainer))]
[PersistenceMode(PersistenceMode.InnerDefaultProperty)]
public override ITemplate AlternatingItemTemplate
{
get
{
return base.ItemTemplate;
}
set
{
base.ItemTemplate = value;
}
}
}
public class ProductContainer : TypedRepeaterItem<Product>
{
public ProductContainer(RepeaterItem ri)
: base(ri)
{ }
public static explicit operator ProductContainer(RepeaterItem ri)
{
return new ProductContainer(ri);
}
}
Now you will be having a few errors by now. The first will probably be because a few references will be missing, so just use the CTRL+. trick on the items with a red underscore on them and bring the using statements into your code automatically. The second one should be that TypedRepeaterItem doesn't exist, so here it is:
Note: I have this in a utility library so I can use it all over the place but you can do whatever you want with it as long as you can reference it in the code above.
public class TypedRepeaterItem<T> : INamingContainer where T : class
{
protected RepeaterItem m_repeaterItem = null;
public TypedRepeaterItem(RepeaterItem ri)
{
m_repeaterItem = ri;
}
public T DataItem
{
get
{
return (T)m_repeaterItem.DataItem;
}
set
{
m_repeaterItem.DataItem = value;
}
}
public int ItemIndex
{
get
{
return m_repeaterItem.ItemIndex;
}
}
public ListItemType ItemType
{
get
{
return m_repeaterItem.ItemType;
}
}
}
This class is the basis for quickly creating more and more strongly typed repeaters. I don't know if you guys know how INamingContainers work and how ITemplates work but basically in our initial control we are just overriding the ItemTemplate and AlternatingItemTemplate containers so that they are strongly typed which gives us intelisense in the repeater control. I'll be going over ITemplate and INamingContainers in another post coming soon so I won't go into that now.
Now you need to do one last thing before you can actually access these controls in all your pages and that is to add the namespace to the web.config. So if you go into your web.config and find the <controls> section which can be found in system.web > pages > controls you need to add a line similar to this:
<add tagPrefix="myprefix" namespace="The.Namespace.Of.My.Controls"/>
So just change "myprefix" to whatever you want, i use something like "jhl" (John Harman Ltd) but you could simply uses "controls". If you didn't have namespaces around the repeater class that that you created above then add them now and place that namespace in... well the namespace attribute. All you need to do then is compile to make sure everything is building OK, then go to a page where you need the repeater and start typing <myprefix and your control(s) should come up. Now you can use this syntax within the ItemTemplate and do this:
<%# Container.DataItem.PropertyName %>
Give the above a go and let me know if I have missed something because I'm just too damned lazy to double check it myself, I've literally copied the various bits out of my projects :)
Oh and I almost forgot to mention the guy that actually came up with this, so thanks go to
Mathias Körner for his implementation of a strongly typed repeater which I just converted over to C#
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5