Custom modules: place a drop down list on edit form
Hello,
I need to manage specific content and, to do this, I develop a sitefinity custom module.
I'd like to know how I can display on my Edit form a drop down list with some data coming from another data table ?
Here are my needs :
This specific content is IT Interventions. An intervention is defined by a name and a date. An intervention has a state (scheduled, started, finished).
To apply a state to an intervention, in my module I have an InterventionItem class and a StateItem class.
In the InterventionItem class, I declare the relation between Intervention and States like this:
public
StateItem
get
return
this
._stateItem;
set
this
._stateItem = value,
private
StateItem _stateItem;
public
IList<Interventions>
get
return
this
._interventions ;
set
this
._interventions = value,
private
List<Interventions>;
Is that the right way to declare the 1 to many relation between interventions and state?
I cannot use Custom fields because my state has others properties (not only a title).
So, on the edit form, I want to display these states in a list. In standard asp.net, I will use a DropDownList.
I see in the developer's guide, there is a "FlasTaxonFieldDefinitionElement" class in the "Telerik.Web.UI.Fields.Config". Can I use this? Can you provide me a sample to declare it on the form?
Best regards,
Hello jocelyn,
There are 2 way you can currently implement this if you build your backend UI with the config element defintions, as the other sitefinity modules aer built including the Products SDK sample module. Check the ProuductDefinitions.cs
1)ChoiceField control
- You have to hard code the possible choices in the definition.
var selectSizeElement =
new
ChoiceFieldElement(defaultImgSizeSection.Fields)
ID =
"SelectStateFieldControl"
,
DataFieldName =
"State"
,
DisplayMode = FieldDisplayMode.Write,
MutuallyExclusive =
false
,
RenderChoiceAs = RenderChoicesAs.DropDown,
FieldType =
typeof
(ChoiceField)
;
selectSizeElement.ChoicesConfig.Add(
new
ChoiceElement(selectSizeElement.ChoicesConfig)
Text =
"Scheduled"
,
Value =
"0"
);
selectSizeElement.ChoicesConfig.Add(
new
ChoiceElement(selectSizeElement.ChoicesConfig)
Text =
"Started"
,
Value =
"1"
);
selectSizeElement.ChoicesConfig.Add(
new
ChoiceElement(selectSizeElement.ChoicesConfig)
Text =
"Finished"
,
Value =
"2"
new
HierarchicalTaxonFieldDefinitionElement(section)
ID =
"categoriesFieldControl"
, dataFieldName =
"Category"
, DisplayMode = FieldDisplayMode.Write, ResourceClassId =
typeof
(TaxonomyResources).Name, TaxonomyId = TaxonomyManager.CategoriesTaxonomyId, WebServiceUrl =
"~/Sitefinity/Services/Taxonomies/HierarchicalTaxon.svc"
, AllowMultipleSelection =
true
,WrapperTag = HtmlTextWriterTag.Li, Title =
"Categories"
, ExpandableDefinitionConfig =Expanded =
false
,ExpandText =
"ClickToAddCategories"
,ResourceClassId =
typeof
(TaxonomyResources).Name
===add the
new
taxonomy
var flatTaxonomy =
this
.GetOrCreateTaxonomy<FlatTaxonomy>(initializer,
"States"
, MyStatesTaxonomyID, Res.Get<ContentResources>().Tag);
flatTaxonomy
==add code to add the required taxons
for
the states
=== add the taxonomy field to your type
var metaMan = initializer.Context.MetadataManager;
var type = metaMan.CreateMetaType(yourType);
field = metaMan.CreateMetafield(
"StatesField"
);
field.TaxonomyProvider = taxMan.Provider.Name;
field.TaxonomyId = MyStatesTaxonomyID;
field.IsSingleTaxon = trie;
type.Fields.Add(field);
Hello Nikolay,
Thanks for your answer.
Regarding the ChoiceField control, there is no way to bind it to another datatable doing some thing like this:
var selectSizeElement =
new
ChoiceFieldElement(defaultImgSizeSection.Fields)
ID =
"SelectStateFieldControl"
,
DataFieldName =
"State"
,
DisplayMode = FieldDisplayMode.Write,
MutuallyExclusive =
false
,
RenderChoiceAs = RenderChoicesAs.DropDown,
FieldType =
typeof
(ChoiceField),
ServiceBaseUrl =
"Sitefinity/Services/Content/States.svc"
;
==add code to add the required taxons for the states
=== add the taxonomy field to your type
var metaMan = initializer.Context.MetadataManager;
var type = metaMan.CreateMetaType(yourType);
field = metaMan.CreateMetafield(
"StatesField"
);
field.TaxonomyProvider = taxMan.Provider.Name;
field.TaxonomyId = MyStatesTaxonomyID;
field.IsSingleTaxon = trie;
type.Fields.Add(field);
Hello jocelyn payneau,
I will try to answer to your questions here one buy one:
What is the type of the variable yourType?
It is the type which will be classified with the taxonomy,e.g. if an intervention is classified with scheduled/started/finished. Than we need to add a taxonomy field to the Intervention class. So yourType would be typeof(Intervention).
Can this new type have more than one field?
This is not a new type, it is the type that you want to classify. You can have your fields declared in the type in your code. You can also add as many dynamic fields to it, and hence you can add more than one taxonomy to it, by adding several taxonomy fields.
Which types or fields are available? Can a field be an image?
Currently we don't natively support adding an image field.
A state is defined by a name (United states, France...) and a flag (an image). Can this model be implement using taxonomies?
For example you can make use of some kind of convention. Let's say you want to have the flag of United States -> You can have an Image Library - called StateFlags and add images with the same Title as the Country title. Later you can find the image that you want to show on the front-end by the name of the State that is persisted with your item.
From the edit form of our product catalog, we must be able to set the availability of a product in the world.
Or should we develop our own field element?
Just to say, that developing a backend UI for your module has not to be done necessarily with our field controls(elements) if your backend scenarios require too much customizations. You can make your own entirely Custom or User Control and add to the Controls collection of the Backend Page you install with the Module.
On the other hand doing a new field element is also a viable option - you can probably inherit the ChoiceField - and pre-populate it on the server with the items from the taxonomy. It needs though to have a TaxonomyId property so you can specify from which taxonomy it is going to populate. Btw - every field element- actually has a property FieldType - where you can specify your own custom FieldType.
So for examle you can use the TaxonFieldDefinitionElement, but put in FieldType -> your own custom control type that inherits ChoiceField and completely overrides the following method.
public override void Configure(IFieldDefinition definition)
In this method the control, reads the element defintion and gets populated from the chocies, in your case it can get populated from the taxonomy, - which is perfectly ok. You just have to cast the defintion to ITaxonFieldDefinition, so you can get the TaxnomyId and use Taxonomy API to get the taxons data. (TaxonomyManager)
Using this approach you could also actually pre-populate your ChoiceField not from a taxonomy but from the records of a custom type States, etc.
Nikolay Datchev
the Telerik team
Hello Nikolay,
I create a new class DropDownListField inherited from ChoiceFIeld.
I override the method Configure like this:
public
override
void
Configure(IFieldDefinition definition)
var tManager = TaxonomyManager.GetManager();
var taxonomy = tManager.GetTaxonomies<FlatTaxonomy>().Where(t => t.Name ==
"Countries"
).Single();
var countries = taxonomy.Taxa.OrderBy(c => c.Title.ToString());
this
.RenderChoicesAs = Telerik.Sitefinity.Web.UI.Fields.Enums.RenderChoicesAs.DropDown;
var ddlField =
new
TaxonFieldDefinitionElement(mainSection.Fields)
ID =
"ddlFIeldControl"
,
AllowMultipleSelection =
false
,
DataFieldName =
"country_id"
,
FieldType =
typeof
(DropDownListField),
WrapperTag = HtmlTextWriterTag.Li
;
mainSection.Fields.Add(ddlField);
Invalid resource name "Telerik.Sitefinity.Resources.Templates.Fields.ChoiceField.ascx" for assembly "Contacts, Version=1.0.4049.23058, Culture=neutral, PublicKeyToken=null" or empty template.
Hello jocelyn payneau,
This is actually a problem with how our embedded ascx control templates are resolved. Since you inherit this control in a new assembly, the template resolver cannot guess where to get the template from(using the LayoutTemplateName). So you need to explicitly override the method ResourcesAssemblyInfo of the ChoiceField and return Config.Get<ControlsConfig>().ResourcesAssemblyInfo. Which is basically saying to sitefinity that it needs to lookup the tempalte in our main resources assembly, not in yours. This problem is scheduled to be fixed soon, so the LayoutTemplateName location is not ambiguous. Sorry for the inconvenience.
About the configure method - you still need to fill the Choices collection manually from the taxonomy that you have read, somehing like...
public override void Configure(IFieldDefinition definition)
====first===
put your taxonomy retrieval code here.It is better to get the taxonomyid from the defintion - rather than hard-coding "Countries" -since you can reuse your control for other taxonomies too.
say: var taxonDefintion = defintion as ITaxonFieldDefinition;
var taxonomyId = defintion.TaxonomyId
you can have some kind of well known constant Guid for your taxonomy id, and set this when you create the definition element
====next==
this.Choices.Clear();
foreach (var taxon in countries)
var choice = new ChoiceItem();
choice.Value = taxon.Id.ToString();
choice.Text = taxon.Title;
this.Choices.Add(choice);
Regards,
Hello,
I hard coding only to make some tests. It will be dynamic once it works.
Overriding the ResourcesAssemblyInfo method solve the asp.net error.
But when I create a new item, the insert form is broken and I have a JavaScript error "Contact is undefined":
Sys.Application.add_init(
function
()
$create(Contacts.Web.UI.DropDownListField,
"_mutuallyExclusive"
:
false
,
"_renderChoicesAs"
:0,
"_selectedChoicesIndex"
:
null
,
"choices"
:[
"Text"
:
"France"
,
"Value"
:
"c21c18ad-08b7-460f-ba82-0fd84df8acc6"
,
"Description"
:
null
,
"Enabled"
:
false
,
"Selected"
:
false
,
"Text"
:
"United States"
,
"Value"
:
"7f5adb3b-d3b1-430b-9a3d-ac46c3829a45"
,
"Description"
:
null
,
"Enabled"
:
false
,
"Selected"
:
false
],
"controlErrorCssClass"
:
null
,
"dataFieldName"
:
null
,
"dataFormatString"
:
null
,
"description"
:
null
,
"descriptionElement"
:$get(
"ctl04_ctl00_contentView_ctl00_ctl00_sections_section_0_ctl00_0_fields_0_ctl00_2_ctl00_2_descriptionLabel_2"
),
"displayMode"
:0,
"example"
:
null
,
"exampleElement"
:$get(
"ctl04_ctl00_contentView_ctl00_ctl00_sections_section_0_ctl00_0_fields_0_ctl00_2_ctl00_2_exampleLabel_2"
),
"readModeLabel"
:$get(
"ctl04_ctl00_contentView_ctl00_ctl00_sections_section_0_ctl00_0_fields_0_ctl00_2_ctl00_2_read_2"
),
"title"
:
null
,
"titleElement"
:$get(
"ctl04_ctl00_contentView_ctl00_ctl00_sections_section_0_ctl00_0_fields_0_ctl00_2_ctl00_2_titleLabel_2"
),
"validatorDefinition"
:
"\"AlphaNumericViolationMessage\":\"Non alphanumeric characters are not allowed.\",\"ComparingValidatorDefinitions\":[],\"CurrencyViolationMessage\":\"You have entered an invalid currency.\",\"EmailAddressViolationMessage\":\"You have entered an invalid email address.\",\"ExpectedFormat\":0,\"IntegerViolationMessage\":\"You have entered an invalid integer.\",\"InternetUrlViolationMessage\":\"You have entered an invalid URL.\",\"MaxLength\":0,\"MaxLengthViolationMessage\":\"Too long\",\"MaxValue\":null,\"MaxValueViolationMessage\":\"Too big\",\"MessageCssClass\":null,\"MessageTagName\":\"div\",\"MinLength\":0,\"MinLengthViolationMessage\":\"Too short.\",\"MinValue\":null,\"MinValueViolationMessage\":\"Too small.\",\"NonAlphaNumericViolationMessage\":\"Alphanumeric characters are not allowed.\",\"NumericViolationMessage\":\"You have entered an invalid number.\",\"PercentageViolationMessage\":\"You have entered an invalid percentage.\",\"RegularExpression\":null,\"RegularExpressionViolationMessage\":\"Ivalid format.\",\"Required\":null,\"RequiredViolationMessage\":\"Required field.\",\"USSocialSecurityNumberViolationMessage\":\"You have entered an invalid US social security number.\",\"USZipCodeViolationMessage\":\"You have entered an invalid US ZIP code.\",\"ValidateIfInvisible\":true"
,
"value"
:
null
,
null
,
null
, $get(
"ctl04_ctl00_contentView_ctl00_ctl00_sections_section_0_ctl00_0_fields_0_ctl00_2"
));
);
Hello jocelyn,
Ok, seems like the next thing i missed to mention. I really hope this to be the last blocker, and i guess we have to publish your case as a good sample for the other customers, how to inherit our field controls.
The problem here seems to be related with the fact that we automatically assume that there is a client side javascript component with the name as your field control class. But since you don't want to change anything in the client side behavioral of choiceField, you can still use the base js component. In order to achieve this you should override the ScriptDescriptorTypeName method and return typeof(ChoiceField).FullName. I hope this helps.
Greetings,
Nikolay Datchev
the Telerik team
Hello Nikolai,
It does not solve the problem. I still have the javascript error.
Here is my Dropdownlistfield class code:
namespace
Contacts.Web.UI
public
class
DropDownListField : ChoiceField
protected
override
Type ResourcesAssemblyInfo
get
return
Config.Get<ControlsConfig>().ResourcesAssemblyInfo;
protected
override
string
ScriptDescriptorTypeName
get
return
typeof
(ChoiceField).FullName;
public
override
void
Configure(IFieldDefinition definition)
var tManager = TaxonomyManager.GetManager();
var taxonomy = tManager.GetTaxonomies<FlatTaxonomy>().Where(t => t.Name ==
"Countries"
).Single();
var countries = taxonomy.Taxa.OrderBy(c => c.Title.ToString());
this
.Choices.Clear();
foreach
(var taxon
in
countries)
var choice =
new
ChoiceItem();
choice.Value = taxon.Id.ToString();
choice.Text = taxon.Title;
this
.Choices.Add(choice);
var statesManager = StatesManager.GetManager();
var states = statesManager.GetStates();
this
.Choices.Clear();
foreach
(var state
in
states)
var choice =
new
ChoiceItem();
choice.Value = state.Id.ToString();
choice.Text = state.Title;
this
.Choices.Add(choice);
Hi jocelyn payneau,
I attached a control that will do the trick. It seems that ScriptDescriptorTypeName is not considered , which is a bug which we are going to fix, but i am sending you a workaround here. You should use the following definition:
var dropDownTaxonomyField = new TaxonFieldDefinitionElement(mainSection.Fields)
ID = "testTaxonomyField",
Title = Res.Get<ProductsResources>().lTitle,
DataFieldName = "Tags",
TaxonomyId = TaxonomyManager.TagsTaxonomyId,
FieldType = typeof(TaxonomyDropDownField),
ResourceClassId = typeof(ProductsResources).Name,
DisplayMode = displayMode,
;
mainSection.Fields.Add(dropDownTaxonomyField);
In general this control will work fine for the drop-down scenario with single item taxonomy. The multiple checkbox scenario though has a problem that if only one item is checked - it cannot save the value correctly since the choicefield control doesn't return an array of choices in the javascript component, so we will need to also inherit the javascript component of the ChoiceField class . I will provide this inherited js additionally. I will try to do this tomorrow. I assume that you will also need the code to install your custom taxonomy. I had some problems reproducing this today, which can be related to some bug that we have, so I will keep investigating this tomorrow. If this is a bug most probably i will provide a fix in the Friday's build.
Greetings,
Hi Nikolay,
It works!
I managed to display the countries list.
I also managed to display some contents from a custom module I develop (based on the product catalog sample). This module allows to manage some parameters of a product (i.e Diameters). A diameter is defined by a title and an image. I managed to display diameters title in a drop down on my edit form. But the association between a product and a diameter is not saved.
Here is the piece of code which declare the model:
[DataMember]
public
DiameterItem Diameter
get
return
this
.diameter;
set
this
.diameter= value;
private
DiameterItem diameter;
Hi jocelyn ,
Currently i am afraid we can not manage such associations through the Edit Form. We do this only for taxonomies. What i would suggest as a workaround is to have only the DiameterId field , so you field control is bound to the "DiameterId" (Guid or int - whatever is your key). The association can be implemented additionally in your Data Provider. For example you can have GetProductDiameterByID method in your manager/provider.
Greetings,
Nikolay Datchev
the Telerik team
Hello Nikolai,
Thanks for your reply.
I add in my model class (ProductItem.cs) this code:
[DataMember]
public
Guid DiameterId
get
return
this
.diameterId;
set
this
.diameterId= value;
private
Guid diameterId;
public
ContactItemViewModel(ContactItem contentItem, ContentDataProviderBase provider)
:
base
(contentItem, provider)
... // my other fields
this
.LocationId = contentItem.LocationId;
Hello jocelyn,
I think this is related with the db upgrade algorithm of Sitefinity. To initiate db upgrade It checks for a part of the build number of the custom module assembly, which is actually the same during all builds in the same day. So i would suggest to go with a blank installation of sitefinity, with a new database, or manually change this number to a lower number in the builds tracking table - sf_schema_versions - column version_number. Just type a lower number than the current. you see. Then restart Sitefinity by making a dummy change in the web.config, it will read the schema information - see that your assembly build has changed and force an upgrade. I really hope this will fix your problem.
Kind regards,
Nikolay Datchev
the Telerik team
Hi Nikolay,
I tried the code you posted for a subclass of ChoiceField using it as a control on a form (registered it in the FormControls Toolbox and added it to the form). But there seems nothing to happen: the code in the Configure() method doesn't fire. Is the behaviour of controls different when added to a form? What control can I use to create dynamically added choices to a form?
Thanks for a reply (I know the original post is more than a year old),
alf borrmann
Hello,
The Configure(IFieldDefinition definition) method is called when the field control is created from definition, which is not the case if the control is added to a form.
It will be better if you use InitializeControls(GenericContainer container) to add dynamically choices.
Regards,
Vlad
the Telerik team