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;