Custom Field Definition Element and Control
I am trying to implement a custom field control that is similar to the FieldImage control. However, I would like to bind to multiple images. I decided to create a custom control wrapping a FieldImage in a RadListView. I have already added a OneToMany relationship using the MetadataManager on my custom content to store the images. I am having trouble getting a handle to the DataItem that should be bound to my custom field control. Any help would be greatly appreciated. I have posted my code below:
[FieldDefinitionElement(typeof(ImageListFieldDefinition)), RequiresDataItem] public class ImageListField : FieldControl, IRequiresDataItem protected override System.Web.UI.WebControls.WebControl DescriptionControl get return this.Container.GetControl<Label>("description", false); protected override System.Web.UI.WebControls.WebControl ExampleControl get return this.Container.GetControl<Label>("example", false); protected override System.Web.UI.WebControls.WebControl TitleControl get return this.Container.GetControl<Label>("title", false); void ListView_ItemDataBound(object sender, RadListViewItemEventArgs e) RadListViewItem radDataItem = e.Item; Telerik.Sitefinity.Web.UI.Fields.ImageField image = radDataItem.FindControl("imageField") as Telerik.Sitefinity.Web.UI.Fields.ImageField; if (image != null) Definition.Title = ""; image.Configure(Definition); protected override void InitializeControls(Telerik.Sitefinity.Web.UI.GenericContainer container) DataItem = container.GetDataItemContainer().DataItem as IDataItem; if (DataItem != null) App.Prepare().SetContentProvider(Definition.ImageProviderName); Telerik.Sitefinity.GenericContent.Model.Content data = App.WorkWith().AnyContentItem(Definition.DataFieldType, DataItem.Id).Get(); if (data != null) Label label = TitleControl as Label; label.Text = Title; IQueryable<ContentLink> imageLinks = data.GetValue(DataFieldName) as IQueryable<ContentLink>; if (imageLinks != null) ListView.DataSource = imageLinks.Take(2).ToList(); ListView.ItemDataBound += new EventHandler<RadListViewItemEventArgs>(ListView_ItemDataBound); void ImageListField_DataBinding(object sender, EventArgs e) Console.WriteLine(Value.ToString()); public override void Configure(Telerik.Sitefinity.Web.UI.Fields.Contracts.IFieldDefinition definition) base.Configure(definition); ImageListFieldDefinition definition2 = definition as ImageListFieldDefinition; Definition = definition2; DataFieldName = definition2.DataFieldName; Title = definition2.Title; WrapperTag = definition2.WrapperTag; CssClass = definition2.CssClass; ResourceClassId = definition2.ResourceClassId; private ImageListFieldDefinition Definition get; set; private int MaxNumberImages get; set; protected override string LayoutTemplateName get return layoutTemplateName; private const string layoutTemplateName = "Pintek.Modules.Businesses.Web.Controls.ImageListField.ascx"; private RadListView ListView get return this.Container.GetControl<RadListView>("listView", true); private IDataItem dataItem; public IDataItem DataItem get return dataItem; set dataItem = value; I thought having the RequiresDataItem attribute and Implementing IRequireDataItem would give my class access to the DataItem being bound to my control. However, this does not seem to be the case. Can you help me figure out what I am doing wrong?
Thanks,
Bobby
Hi Bobby,
You could use some logic like this:
protected override void OnDataBinding(EventArgs e) base.OnDataBinding(e); var container = this.GetDataItemContainer(); object dataItem = null; if (container != null) dataItem = container.DataItem; if (dataItem != null) var propDescr = TypeDescriptor.GetProperties(dataItem)[this.DataFieldName]; // Catch the exception in the front end, propagate the exception in the backend if (this.IsDesignMode()) this.Value = propDescr.GetValue(dataItem); else try this.Value = propDescr.GetValue(dataItem); catch this.DataItem = dataItem as IDataItem; this.BoundOnServer = true; Hi,
I have tried your suggestion and I have not been able to get it to work. So, I am going to take a step back and explain my goal and ask you the best way to accomplish my goal. I want to associate one or more images with an item. For example, for a User object can have several Images associated with it. I have setup my relationship using OpenAccess to mimic the following sudo code.
public Class User
public List<Image> Images;
I want to display this images for a user and allows them to add, edit or delete their associated images. I see that you guys have a MultiImageField control but as I understand it, that control is only valid for Products for ecommerce. I want to mimic this behavior for any object that wants to have associated images. With everything that I have tried, I am unable to get the DataItem for the field control. The DataItem in this example would be the User object. As shown in my example below, I have marked by field control to require the DataItem and it also implements the IRequiredDataItem interface. So, there must be something that I don't understand and I was hoping you could help me find a solution. Am I going about this the right way and if so, could you help me understand where I am going wrong?
Thanks,
Bobby
Hi Bobby,
Here is a sample implementation of how to get a reference to the DataItem during the OnDataBinding event -
protected override void OnDataBinding(EventArgs e) base.OnDataBinding(e); var container = this.GetDataItemContainer(); object dataItem = null; if (container != null) dataItem = container.DataItem; if (dataItem != null) var propDescr = TypeDescriptor.GetProperties(dataItem)[this.DataFieldName]; // Catch the exception in the front end, propagate the exception in the backend if (this.IsDesignMode()) this.Value = propDescr.GetValue(dataItem); else try this.Value = propDescr.GetValue(dataItem); catch this.DataItem = dataItem as IDataItem; this.BoundOnServer = true; Hi again,
I am using the code but I am not having any luck getting the DataItem. The OnDataBinding function never gets called. I am sure there is something that I am missing on how to get the DataItem. So, I guess my question is, when and how does the DataBinding function get called. Since, this is an override, I would expect it be be called automatically. Is there something that I need to do when creating the FieldControlDefinitionElement that alerts my control of the DataItem? Below is my latest code. I have attached my field control code below along with your suggestion, unless I am missing something, this code does not give me the DataItem. Can you please help. Does my custom code below work for you? Any help would be appreciated.
Thanks,
Bobby
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Telerik.Sitefinity.Web.UI.Fields;using Telerik.Sitefinity.Web.UI.Fields.Enums;using System.Web.UI.WebControls;using Telerik.Sitefinity.Data;using Telerik.Sitefinity.Model;using Telerik.Web.UI;using Telerik.Sitefinity;using Telerik.Sitefinity.Model.ContentLinks;using System.Web.UI;using Telerik.Sitefinity.Web.UI.Fields.Definitions;using Telerik.Sitefinity.Web.UI.Fields.Contracts;using Telerik.Sitefinity.Web;using Telerik.Sitefinity.Web.UI.Fields.Config;using System.ComponentModel;using Telerik.Sitefinity.Web.UI;using Telerik.Sitefinity.Utilities.TypeConverters;using Telerik.Sitefinity.Web.Configuration;using Telerik.Sitefinity.Configuration;using Telerik.Sitefinity.Localization;using Telerik.Sitefinity.Modules.Libraries;using Telerik.Sitefinity.Web.Services;using Telerik.Sitefinity.Modules.Libraries.Images;using System.Web.UI.HtmlControls;using System.Web;namespace Pintek.Modules.Businesses.Web.Controls [FieldDefinitionElement(typeof(ImageListFieldElement)), RequiresDataItem] public class ImageListField : FieldControl, IRequiresDataItem private const string ajaxUploadScript = "Telerik.Sitefinity.Resources.Scripts.ajaxupload.js"; private bool? boundOnServer; private Type dataFieldType; private IDataItem dataItem; private bool displayEmptyImage; private const string fieldDisplayModeScript = "Telerik.Sitefinity.Web.UI.Fields.Scripts.FieldDisplayMode.js"; private const string imageFieldModeScript = "Telerik.Sitefinity.Web.UI.Fields.Scripts.ImageFieldUploadMode.js"; private const string imageFieldScript = "Telerik.Sitefinity.Web.UI.Fields.Scripts.ImageField.js"; private const string jqueryUIScript = "Telerik.Sitefinity.Resources.Scripts.jquery-ui-1.8.8.custom.min.js"; private const string reqDataItemScriptFileName = "Telerik.Sitefinity.Web.UI.Fields.Scripts.IRequiresDataItem.js"; private ImageFieldUploadMode uploadMode; private const string uploadServiceUrl = "~/Telerik.Sitefinity.AsyncImageUploadHandler.ashx"; private static readonly string layoutTemplateName; private const string imageListFieldScript = "Pintek.Modules.Businesses.Web.Controls.ImageListField.js"; static ImageListField() layoutTemplateName = "Pintek.Modules.Businesses.Web.Controls.ImageListField.ascx"; public ImageListField() this.displayEmptyImage = true; protected override void OnDataBinding(EventArgs e) base.OnDataBinding(e); var container = this.GetDataItemContainer(); object dataItem = null; if (container != null) dataItem = container.DataItem; if (dataItem != null) var propDescr = TypeDescriptor.GetProperties(dataItem)[this.DataFieldName]; // Catch the exception in the front end, propagate the exception in the backend if (this.IsDesignMode()) this.Value = propDescr.GetValue(dataItem); else try this.Value = propDescr.GetValue(dataItem); catch this.DataItem = dataItem as IDataItem; this.boundOnServer = true; public override void Configure(IFieldDefinition definition) base.Configure(definition); IMultiImageFieldDefinition definition2 = definition as IMultiImageFieldDefinition; if (definition2 != null) this.ImageProviderName = definition2.ImageProviderName; this.ProviderNameForDefaultImage = definition2.ProviderNameForDefaultImage; this.DefaultImageId = definition2.DefaultImageId; this.MaxWidth = definition2.MaxWidth; this.MaxHeight = definition2.MaxHeight; this.DataFieldType = definition2.DataFieldType; this.DefaultSrc = definition2.DefaultSrc; this.DisplayEmptyImage = definition2.DisplayEmptyImage; public override IEnumerable<ScriptDescriptor> GetScriptDescriptors() ScriptControlDescriptor descriptor = base.GetScriptDescriptors().Last<ScriptDescriptor>() as ScriptControlDescriptor; return new ScriptControlDescriptor[] descriptor ; public override IEnumerable<ScriptReference> GetScriptReferences() string fullName = typeof(ImageListField).Assembly.FullName; string name = Config.Get<ControlsConfig>().ResourcesAssemblyInfo.Assembly.GetName().Name; List<ScriptReference> list = new List<ScriptReference>(base.GetScriptReferences()) new ScriptReference("Telerik.Sitefinity.Web.UI.Fields.Scripts.IRequiresDataItem.js", name) ; ScriptReference item = new ScriptReference("Telerik.Sitefinity.Resources.Scripts.jquery-ui-1.8.8.custom.min.js", name); list.Add(item); ScriptReference reference2 = new ScriptReference("Telerik.Sitefinity.Resources.Scripts.ajaxupload.js", name); list.Add(reference2); ScriptReference reference3 = new ScriptReference(imageListFieldScript, fullName); list.Add(reference3); ScriptReference reference4 = new ScriptReference("Telerik.Sitefinity.Web.UI.Fields.Scripts.FieldDisplayMode.js", fullName); list.Add(reference4); return list; protected override string LayoutTemplateName get return layoutTemplateName; [TypeConverter(typeof(StringTypeConverter))] public Type DataFieldType get return this.dataFieldType; set this.dataFieldType = value; public IDataItem DataItem get return this.dataItem; set this.dataItem = value; public string DefaultAlt get; set; public Guid DefaultImageId get; set; public string DefaultSrc get; set; protected override WebControl DescriptionControl get return null; public bool DisplayEmptyImage get return this.displayEmptyImage; set this.displayEmptyImage = value; protected override WebControl ExampleControl get return null; public string ImageProviderName get; set; public int? MaxHeight get; set; public int? MaxWidth get; set; public string ProviderNameForDefaultImage get; set; protected override void InitializeControls(GenericContainer container) Here's what I have gathered from trying to do something similar...
The databinding doesn't happen on the server side in the backend tools. That is taken care of on the client side by the prototype scripts. As far as I can see, the only time the OnDatabinding event will fire is when the control is in read mode on a read only page. I am still working on a solution to this problem. I'm still a little frustrated that something that would've been 6 lines of code on any other site is at 250+ lines right now... and I still haven't gotten to the final solution.
Hello Bobby,
Have you tried overriding OnPreRender or InitializeControls and there use this code
var container = this.GetDataItemContainer();object dataItem = null;if (container != null)dataItem = container.DataItem;