We’ve seen many requests for the option to associate a Sitefinity news item with a collection of images. A long time ago I blogged on how you could associate a single news item with a single image. Extending that sample to enable multiple image selection is not very hard, and this blog post is going to show you how to do it.
If you just want the source code for the sample, you can download it. Follow the installation instructions at the end of the blog post.
A selector for a single image
The sample you can read about in the previous blog post implements a single image selector. This is a summary of what has been implemented so far:
- A selector control, which displays a list of images and returns the ID of the one clicked by the user
- A dialog which wraps the selector control and returns the ID of the image when closed
- A field control with two modes:
- Write mode (used in the backend to select the image) – opens the dialog and saves the returned image ID.
- Read mode (used to display the selected image in the news widget template) – reads the image ID, and outputs an <img> tag with correct URL.
NOTE: This blog post is an extension to the previous one. It will only go over the new functionality and changes, so if you haven’t gone through the initial implementation, I recommend you do that now.
What needs to be changed
To support multiple images, we need to change the following from our previous sample:
- Modify the selector to allow multiple selection
- Modify the field control to display all images in Read Mode.
We will go over each of the above items and see how it was implemented. The way the dialog returns the selected images, and how the field control persists them in the custom field stays the same. Instead of saving the ID, we will save a comma-separated list of IDs.
Modify the selector to allow multiple selection
The image selector should be able to highlight multiple selected images. In the previous sample, any time you click an image, your previous selection was lost (because you could only select one). This is all done in the Javascript _binderCommand method of the SimpleImageSelector. Here is the changed code that handles multiple image selection:
if
(args.get_commandName() ==
"selectImage"
) {
var
imageUrl = args.get_dataItem().ThumbnailUrl;
var
imageId = args.get_dataItem().Id;
var
jItemElement = jQuery(args.get_itemElement())
var
isItemAlreadySelected = jItemElement.attr(
'class'
).indexOf(
'sf_selectedImage'
) != -1;
if
(isItemAlreadySelected) {
var
itemIndex =
this
.arrayContains(
this
._selectedImageIds, imageId);
if
(itemIndex !== -1) {
this
._selectedImageIds.splice(itemIndex, 1);
}
var
itemIndexUrl =
this
.arrayContains(
this
._selectedImageUrls, imageUrl);
if
(itemIndex !== -1) {
this
._selectedImageUrls.splice(itemIndex, 1);
}
jItemElement.removeClass(
"sf_selectedImage"
);
}
else
{
if
(
this
.arrayContains(
this
._selectedImageIds, imageId) === -1) {
this
._selectedImageIds.push(imageId);
}
if
(
this
.arrayContains(
this
._selectedImageUrls, imageUrl) === -1) {
this
._selectedImageUrls.push(imageUrl);
}
jItemElement.addClass(
"sf_selectedImage"
);
}
}
The ID of the image that was clicked is passed as an argument. We keep the IDs and URLs of the already selected images in two arrays. These arrays were not present in the previous sample, where we only kept a single ID and URL. We check if the item is already added, and add or remove it accordingly. Each new item the user clicks will be added to the array, or removed on second click. The code for changing the style of the selected image and returning the selected value does not change.
Modify the field control to display all images in Read Mode
Once a collection of images is selected, the value is persisted as a comma-separated string of their IDs. We need to parse that string and get the images themselves using the API. In the previous version, with only one image to select, the Value property of the field control was returning the ID directly from the UI. We now need to add a private field to hold the value. Here is the full implementation of the Value property.
public
override
object
Value
{
get
{
var val =
string
.Empty;
switch
(
this
.DisplayMode)
{
case
FieldDisplayMode.Read:
if
(
this
.value !=
null
)
val =
this
.value.ToString();
break
;
case
FieldDisplayMode.Write:
if
(
this
.value !=
null
)
val =
this
.value.ToString();
break
;
}
return
val;
}
set
{
if
(
this
.ChildControlsCreated)
{
switch
(
this
.DisplayMode)
{
case
FieldDisplayMode.Write:
this
.TextBoxControl.Text = value
as
string
;
break
;
case
FieldDisplayMode.Read:
var imageIds = value.ToString().Split(
';'
);
this
.value = imageIds;
break
;
}
this
.value =
null
;
}
else
{
this
.value = value;
}
}
}
Apart from doing the selection itself, our field control also has the job of presenting the selected images when used in Read mode (i.e. the widget template of a widget). For the purposes of this sample, we will just bind a Repeater control to the list of images. This is done in three of the methods – OnLoad, InitializeControls, and OnPreRender.
protected
override
void
OnLoad(EventArgs e)
{
base
.OnLoad(e);
if
(
this
.DisplayMode == FieldDisplayMode.Read)
this
.ImageGalleryControl.ItemDataBound +=
new
RepeaterItemEventHandler(ImageGalleryControl_ItemDataBound);
}
protected
override
void
InitializeControls(GenericContainer container)
{
this
.ConstructControl();
if
(
this
.DisplayMode == FieldDisplayMode.Read)
{
this
.LabelControl.Visible =
false
;
}
}
protected
override
void
OnPreRender(EventArgs e)
{
base
.OnPreRender(e);
if
(
this
.DisplayMode == FieldDisplayMode.Read)
{
var filterBuilder =
new
StringBuilder();
var imageIds =
this
.Value.ToString().Split(
','
);
for
(var i = 0; i < imageIds.Length; i++)
{
if
(i > 0)
{
filterBuilder.Append(
" OR "
);
}
filterBuilder.Append(String.Format(
"Id == {0}"
, imageIds[i]));
}
var filter = filterBuilder.ToString();
var images = App.WorkWith().Images().Where(i => i.Status == ContentLifecycleStatus.Master).Where(filter).Get();
this
.ImageGalleryControl.DataSource = images;
this
.ImageGalleryControl.DataBind();
}
}
void
ImageGalleryControl_ItemDataBound(
object
sender, RepeaterItemEventArgs e)
{
if
(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
var imageControl = e.Item.FindControl(
"image"
)
as
System.Web.UI.WebControls.Image;
if
(imageControl !=
null
)
{
imageControl.ImageUrl = (e.Item.DataItem
as
Telerik.Sitefinity.Libraries.Model.Image).ThumbnailUrl;
}
}
}
By using the Split() method, we convert the comma-separated string of IDs into an array. From those IDs, we build a filter, which can be passed to the API to get a collection of the selected images. The we just set the Repeater’s data source and call its DataBind() method. On the ItemDataBound event of the Repeater, we set the URL of each <img> tag to the selected image’s thumbnail.
Including the field control in a widget template
Once we’ve implemented multiple selection in our field control, we can include it in the widget template for each news item. This is done with the following markup:
<%@ Register Assembly="ThumbnailSelectorField" Namespace="Telerik.Sitefinity.Samples" TagPrefix="samples" %>
...
<
samples:SimpleImageField
ID
=
"imgGalleryField"
runat
=
"server"
Value='<%# Eval("Thumbnails") %>'></
samples:SimpleImageField
>
Note that in the Value property, we need to supply the name of the custom field which we use to persist the selected images. The next section lists all steps you need to follow in order to setup the multiple image selector for the News module.
Installation Instructions
- Create an empty Sitefinity project
- Download the source code for the sample
- Include the Global.asax and Global.asax.cs files in your web project
- Include the existing ThumbnailSelectorField project from the archive, and change its references to point to the BIN folder of your web project. Also include a project reference to it in the web project
- Build and run your project
- Create a custom field in News with name “Thumbnails” and select a Custom widget for entering data. Enter “Telerik.Sitefinity.Samples.SimpleImageSelector” for the widget type.
- Edit the widget template for the Full news item mode of news.
- Register the “samples” prefix with the following line:
<%@ Register Assembly="ThumbnailSelectorField" Namespace="Telerik.Sitefinity.Samples" TagPrefix="samples" %> - Include the field control in <ItemTemplate> section
<samples:SimpleImageField ID="imgGalleryField" runat="server" Value='<%# Eval("Thumbnails") %>'></samples:SimpleImageField>
If you have further questions about the sample, please ask them in the comments.