Library Selector for Sitefinity back-end

June 23, 2014 Digital Experience, Sitefinity

In Sitefinity 7.0 we can select media content through the new Related Media field and choose the content we want to relate to the item. However, there are use-case scenarios in which we want to select all media items in a certain folder or album without going through each of the items. We would also like to select directly an entire album and its child folders, from which to retrieve all images, for instance.

Consider the following – We want to implement an Image Carousel. The carousel will use a set of images. We want to use all images located in a specific album, as in the default Image Gallery widget. This way when a new image is uploaded to the album, or a current one is updated, it will be automatically used in our carousel.

In this blog post will be demonstrated how a library selector field control could be implemented and used in Sitefinity back-end, as well as, a simple field control showing how the already saved data from the Library selector could be used to retrieve all images in this album. We will be using the Library selector for Image Albums, however, it could be easily set to bind with Document and Files folders or Video Albums.

These are the main elements of the Library Selector field control template:

<asp:Label ID="titleLabel" runat="server" CssClass="sfTxtLbl" />
<span id="labelContainer" runat="server" class="sfSelectedItem sfMRight5" style="display: none">
    <asp:Label ID="fieldBox" runat="server" CssClass="sfTxt" />
    <a class="remove sfRemoveBtn">Remove</a>
</span>
 
<div id="selector" style="display: none" runat="server">
    <sitefinity:FolderSelector ID="folderSelector" runat="server"
        WebServiceUrl="~/Sitefinity/Services/Content/AlbumService.svc/folders"
        ShowIncludeChildLibraryItems="true">
    </sitefinity:FolderSelector>
</div>
 
<a href="javascript:void(0)" id="selectLibraryButton" class="sfLinkBtn sfChange" runat="server">
    <strong class="sfLinkBtnIn">
        <asp:Literal ID="lit" runat="server" Text="<%$Resources:LibrariesResources, Choose %>"></asp:Literal></strong>
</a>

 

The FolderSelector is the main control, which is responsible for binding and handling the data, also for filtering. Here we have to set the WebServiceUrl which will be used to bind the folders tree. It is set to the Images Albums service, since we want to select from them. For Document and Files folder the WebServiceUrl should be set to ‘Sitefinity/Services/Content/DocumentLibraryService.svc/folders’ and for Videos folders to ‘/Sitefinity/Services/Content/VideoLibraryService.svc/folders’.

There is a simple button for opening the selector dialog. Another component is the LabelContainer which shows which folder is selected – its full path, as well as, a remove button – to clear the selection.

We have registered all controls we have to use in the client-side of the control in our LibrarySelector class. There is not anything special in its implementation. The class is documented and could be found in the LibrarySelector archive attached to the blog post, so we will not give it such attention here.

In the JavaScript of the Control is where the events are handled and the custom logic is injected. We have to attach to the main events the folderSelector will fire and implement the logic of handling the returned data. We attach to the dialog open, done selecting – done or cancel events of the dialog, and the remove item click event:

this._selectLinkClickDelegate = Function.createDelegate(this, this._selectLibraryOpenClicked);
        if (this._selectLibraryButton) {
            $addHandler(this._selectLibraryButton, "click", this._selectLinkClickDelegate);
        }
         
        this._doneSelectingLibraryDelegate = Function.createDelegate(this, this._selectLibrary);
        this._folderSelector.add_doneClientSelection(this._doneSelectingLibraryDelegate);
 
        this._removeSelectedItemDelegate = Function.createDelegate(this, this._removeSelectedItem);
        if (this.get_labelContainer()) {
            jQuery(this.get_labelContainer()).delegate('.remove', 'click', this._removeSelectedItemDelegate);
        }

We initialize our dialog as well:

this._dialog = jQuery(this._selector).dialog({
            autoOpen: false,
            modal: true,
            width: 355,
            dialogClass: "",
            closeOnEscape: true,
            resizable: false,
            draggable: false,
            zIndex: 5000
        });

In _selectLibraryOpenClicked the selector is bound and opened, if we have already selected a value it is set to the selector:

if (this._selectedLibraryId != Telerik.Sitefinity.getEmptyGuid()) {
            this.get_folderSelector().get_foldersGenericSelector().set_selectedItemIds([this._selectedLibraryId]);
        }
        this.get_folderSelector().set_includesChildLibraryItems(this._includesChildLibraryItems);
 
        this.get_folderSelector().dataBind();

When the dialog is closed, the doneClientSelection event is fired and the selection arguments are passed, which holds whether or not a library is selected. If it is, the selected item is retrieved and persisted:

var selectedItems = this.get_folderSelector().get_foldersGenericSelector().get_selectedItems();
        if (selectedItems != null) {
            if (selectedItems.length > 0) {
                var selectedTitle = selectedItems[0].Path != null ? selectedItems[0].Path : selectedItems[0].Title;
                if (selectedTitle.hasOwnProperty('Value')) {
                    this.get_folderPathLabel().innerHTML = selectedTitle.Value;
                    this._folderPath = selectedTitle.Value;
                } else {
                    this.get_folderPathLabel().innerHTML = selectedTitle;
                    this._folderPath = selectedTitle;
                }
 
                // show the selected library label container
                this.get_labelContainer().style.display = "inline";
                // set properties
                this._selectedLibraryId = selectedItems[0].Id;
                this._includesChildLibraryItems = this.get_folderSelector().get_includesChildLibraryItems();
            }
        }

The most important part is how the value will be persisted. It will be saved as a short string holding a JSON object with the library data. We construct it using the properties we have set in the _selectLibrary function:

var result =
            {
                Id: this._selectedLibraryId,
                IncludeChildLibraries: this._includesChildLibraryItems,
                FolderPath: this._folderPath
            };
         
        var val = JSON.stringify(result);
        return val;

Note that the properties will be null if no folder is selected.

When the value needs to be set, we use the stringified JSON to set the value:

set_value: function (value) {
        if (value !== undefined && value != null && value != "") {       
            var result = JSON.parse(value);
            if (result.Id) {
                this._selectedLibraryId = result.Id;
 
                this._includesChildLibraryItems = result.IncludeChildLibraries;
                this._folderPath = result.FolderPath;
 
                this.get_folderPathLabel().innerHTML = result.FolderPath;
                this.get_labelContainer().style.display = "inline";
            }
        }
    }

We have managed to select the library we want and persist the selection – so far, so good. Now the saved value should be used to retrieve all data from the library we have selected. We will show this for images, since we use the LibrarySelector for Image Albums.

This is the model that will be used to serialize our data, so we could get the id of the selected folder:

public class LibraryViewModel
    {
        public Guid? Id { get; set; }
 
        public bool? IncludeChildLibraries { get; set; }
    }

Note that the properties should be nullable, since the values will be null if no library is selected.

Using the Id we retrieve all images and bind them to a repeater, the following way:

public void BindData(string value)
        {
            LibraryViewModel result =
  new JavaScriptSerializer().Deserialize<LibraryViewModel>(value);
 
            if (result.Id != null)
            {
                LibrariesManager manager = new LibrariesManager();
 
                // Get parent folder
                var parent = manager.GetFolder((Guid)result.Id);
                List<ImageModel> images = new List<ImageModel>();
 
                var folders = new List<IFolder>();
                folders.Add(parent);
 
                // If the folder is not a parent Album on first level (Default library) but has child folders,
                // GetAllFolders will return the current parent folder, as well, so we need to remove it
                if (result.IncludeChildLibraries != null && (bool)result.IncludeChildLibraries)
                {
                    var children = manager.GetAllFolders(parent);
 
                    if (children.Contains(parent))
                    {
                        folders.Remove(parent);
                    }
 
                    folders.AddRange(children);
                }
 
                foreach (var item in folders)
                {
                    // For folders
                    IQueryable<Telerik.Sitefinity.Libraries.Model.Image> imagesFolder = manager.GetImages()
                        .Where(img => img.FolderId == item.Id && img.Status == ContentLifecycleStatus.Live);
 
                    // If folder is actually an Album
                    IQueryable<Telerik.Sitefinity.Libraries.Model.Image> imagesAlbum = manager.GetImages()
                        .Where(img => img.Album != null && img.Album.Id == item.Id && img.FolderId == null && img.Status == ContentLifecycleStatus.Live);
 
                    IQueryable<Telerik.Sitefinity.Libraries.Model.Image> allImages = imagesFolder.Concat(imagesAlbum);
 
                    // Convert to model and retrieve in memory only the property needed
                    foreach (var img in allImages)
                    {
                        var imgvm = new ImageModel();
                        imgvm.Url = img.ResolveThumbnailUrl();
 
                        images.Add(imgvm);
                    }
                }
 
                // Bind data
                this.ImageRepeater.DataSource = images;
                this.ImageRepeater.DataBind();
 
            }
        }

The full source of the field controls could be found here:

LibrarySelector

ImagesLibraryField

This part of the page will be loaded later.

Nikola Zagorchev

Nikola Zagorchev is a Tech Support Engineer at Telerik. He joined the Sitefinity Support team in March 2014.