Multiple Media selector for Sitefinity Widget designers

Multiple Media selector for Sitefinity Widget designers

Posted on November 17, 2014 0 Comments

The content you're reading is getting on in years
This post is on the older side and its content may be out of date.
Be sure to visit our blogs homepage for our latest news, updates and information.

In this blog post I will show how you can implement a multiple media selector, using which, you will be able to select multiple images, documents and files or videos at once.

The content selectors in Sitefinity use the services provided, passing different parameters and filtering using them. Most of the non hierarchical selectors are based on the FlatSelector, which inherits from the ItemSelector.They provide out of the box binding, item selection, paging and searching. We have only to ensure the binder of the selector is bound to the service we want and it sets the parameters for item type, sort and filter in the right order the service expects. 

You could be familiar with the above mentioned selectors, since they are used in the automatically generated field controls and designers for selecting of dynamic content. I will show you how you can modify these selectors and build on top of them. This way you can easily bind to any service in Sitefinity and modify the parameters order and values. In this specific post I am going to provide an implementation of an images selector. It can be bound to documents and files or videos service, as well. It provides also a library filtering. This way I can show how you can take advantage of other controls coming from Sitefinity or custom built. In this case, I will be using an abstraction based on top of the FolderSelector customized especially for the scenario I want - selection of libraries and passing only the library id.

 

Configuring the selector 

The selector template is a usual setup of the FlatSelector, we set the service Url to bind, the template of the bound items, as well as, the kendo template of the selected items. The kendo template is used for the observable object - the list of items it is bound to.

<li class="sfFormCtrl">
            <label class="sfTxtLbl" for="selectedTestLabel">ImagesSelector</label>
            <div id="ImagesSelector">
                <div class="left">
                    <sf:FlatSelector ID="ItemSelector" runat="server" ItemType="Telerik.Sitefinity.Libraries.Model.Image"
                        DataKeyNames="Id" ShowSelectedFilter="true" AllowPaging="true" PageSize="3" AllowMultipleSelection="true"
                        AllowSearching="true" ShowProvidersList="false" InclueAllProvidersOption="false" EnablePersistedSelection="true"
                        SearchBoxTitleText="Filter by Title" ShowHeader="true" ServiceUrl="/Sitefinity/Services/Content/ImageService.svc/">
                        <DataMembers>
                            <sf:DataMemberInfo runat="server" Name="Title" IsExtendedSearchField="true" HeaderText='Title'>
                                <div><img alt="Alternate Text" class="tmb" /><span>{{ThumbnailUrl}}</span></div>
                <strong>{{Title}}</strong>
                            </sf:DataMemberInfo>
 
                            <sf:DataMemberInfo runat="server" Name="PublicationDate" HeaderText='Date'>
                <span>{{PublicationDate ? PublicationDate.sitefinityLocaleFormat('dd MMM, yyyy') : ""}}</span>
                            </sf:DataMemberInfo>
                        </DataMembers>
                    </sf:FlatSelector>
                </div>
                <div class="right">                  
                    <sf:MyLibrarySelector ID="est" runat="server" DisplayMode="Write"></sf:MyLibrarySelector>
                </div>
                <asp:Panel runat="server" ID="buttonAreaPanel" class="sfButtonArea sfSelectorBtns">
                    <asp:LinkButton ID="lnkDone" runat="server" OnClientClick="return false;" CssClass="sfLinkBtn sfSave">
                <strong class="sfLinkBtnIn">
                    <asp:Literal runat="server" Text="<%$Resources:Labels, Done %>" />
                </strong>
                    </asp:LinkButton>
                    <asp:Literal runat="server" Text="<%$Resources:Labels, or%>" />
                    <asp:LinkButton ID="lnkCancel" runat="server" CssClass="sfCancel" OnClientClick="return false;">
                <asp:Literal runat="server" Text="<%$Resources:Labels, Cancel %>" />
                    </asp:LinkButton>
                </asp:Panel>
            </div>
            <ul id="selectedItemsList" runat="server" data-template="ul-template-ImagesSelector" data-bind="source: items" class="sfCategoriesList"></ul>
            <script id="ul-template-ImagesSelector" type="text/x-kendo-template">
    <li>
    <div data-id="#: Id#"><img src="#: ThumbnailUrl#" alt="#: Title#" class="tmb" /></div>
        <span data-bind="text: Title, attr: {data-id: Id}"> </span>
  
        <a class="remove sfRemoveBtn">Remove</a>
    </li>
            </script>
            <asp:HyperLink ID="selectButton" runat="server" NavigateUrl="javascript:void(0);" CssClass="sfLinkBtn sfChange">
    <strong class="sfLinkBtnIn">Add items...</strong>
            </asp:HyperLink>
        </li>

The server side code finds the controls we will need to work with client-side and pass them to the ScriptDescriptor:

/// <summary>
        /// Gets a collection of script descriptors that represent ECMAScript (JavaScript) client components.
        /// </summary>
        public override System.Collections.Generic.IEnumerable<System.Web.UI.ScriptDescriptor> GetScriptDescriptors()
        {
            var scriptDescriptors = new List<ScriptDescriptor>(base.GetScriptDescriptors());
            var descriptor = (ScriptControlDescriptor)scriptDescriptors.Last();
 
            descriptor.AddElementProperty("message", this.Message.ClientID);
            descriptor.AddElementProperty("selectButton", this.SelectButton.ClientID);
            descriptor.AddComponentProperty("ItemSelector", this.ItemSelector.ClientID);
            descriptor.AddElementProperty("lnkDone", this.DoneButton.ClientID);
            descriptor.AddElementProperty("lnkCancel", this.CancelButton.ClientID);
            descriptor.AddElementProperty("selectedItemsList", this.SelectedItemsList.ClientID);
 
            descriptor.AddComponentProperty("myLibrarySelector", this.LibrarySelector.ClientID);
 
            return scriptDescriptors;
        }

Now we can operate with all the controls we need in the JavaScript of the Designer.

In order to configure the selector to bind correctly to all content services, we have to configure the order of the parameters. In order to do this, we have to replace the default binding of the selector, since there is the Url params setting:

_bindSelector: function () {
        var urlParams = this._binder.get_urlParams();
        urlParams['itemType'] = this._itemType;
        if (this._itemSurrogateType != null)
            urlParams['itemSurrogateType'] = this._itemSurrogateType;
        urlParams['allProviders'] = (this._providerName == "" || this._providerName == null);
        if (this.get_combinedFilter())
            urlParams['filter'] = this.get_combinedFilter();
        urlParams.filter = "";
        this._binder.set_provider(this._providerName);
        this._binder.DataBind();
    },

Set the filter parameter as last. Then, after the binding has begun, we need to select the already chosen items:

this._binderDataBindingDelegate = Function.createDelegate(this, this._binderDataBindingHandler);
        this._ItemSelector.add_binderDataBinding(this._binderDataBindingDelegate);
 
_binderDataBindingHandler: function (sender, args) {
        var selectedItems = this._selectedItems.items.toJSON();
        var itemSelector = this.get_ItemSelector();
        if (selectedItems) {
            var items = args.get_dataItem().Items;
            for (var i = 0; i < selectedItems.length; i++) {
                var selectedItem = selectedItems[i];
                itemSelector.selectItem(selectedItem.Id, selectedItem);
            }
        }
    },

We need to attach to the library selector events used, as well, so when we want to filter the items by the library, we just change the base service and add the parent parameter and the album Id:

        this.get_myLibrarySelector()._dataBind('albums');
        this.get_myLibrarySelector()._addSelectionChanged(this._librarySelectionChanged);
        this.get_myLibrarySelector()._addAllLibrariesSelected(this._resetSelectors);
 
_librarySelectionChanged: function (sender, args) {
        if (args && args[0] && args[0].Id) {
            _selfDesigner.get_ItemSelector()._binder._serviceBaseUrl = _selfDesigner._serviceUrl + "parent/" + args[0].Id + "/";
            _selfDesigner.get_ItemSelector().dataBind();
        }
        else {
            _selfDesigner._resetSelectors();
        }
    },
 
    _resetSelectors: function () {
        _selfDesigner.get_ItemSelector().get_binder()._serviceBaseUrl = _selfDesigner._serviceUrl;
        _selfDesigner.get_myLibrarySelector()._clearSelection();
        _selfDesigner.get_ItemSelector().dataBind();
    },

We need to rebind when chaning the parent folder, so the selector could re-build its paging. Note that this way, the selected items will be shown only from the current library.

Saving and Showing the selected items

We save all the items initially selected and newly added in a kendo observable. This way the selected items in the designer are automatically refreshed.

The items are loaded using a call to the service and passing a filter using the items Ids:

var value = controlData.SelectedImagesIds;
       if (value != null && value != "") {
           var dataItems = JSON.parse(value);
           var filterExpression = "";
           for (var i = 0; i < dataItems.length; i++) {
               if (i > 0) {
                   filterExpression = filterExpression + ' OR ';
               }
               filterExpression = filterExpression + 'Id == ' + dataItems[i].toString();
           }
           var data = {
               "filter": filterExpression,
 
               "itemType": this._itemsType,
 
               "provider": this._providerName,
           };
 
           var self = this;
           $.ajax({
               url: this._serviceUrl,
               type: "GET",
               dataType: "json",
               data: data,
               headers: { "SF_UI_CULTURE": "en" },
               contentType: "application/json; charset=utf-8",
               /*on success add them to the kendo observable array*/
               success: function (result) {
                   self._resizeControlDesigner();
                   self._selectedItems.items.splice(0, self._selectedItems.items.length);
                   for (var i = 0; i < result.Items.length; i++) {
                       self._selectedItems.items.push(result.Items[i]);
                   }
               }
           });
       }

The selected items are updated upon selection done in the selector:

if (this._lnkDone) {
            this._DoneSelectingDelegate = Function.createDelegate(this, this._DoneSelecting);
            $addHandler(this._lnkDone, "click", this._DoneSelectingDelegate);
        }
 
_DoneSelecting: function (sender, args) {
        this._selectedItems.items.splice(0, this._selectedItems.items.length);
 
        var selectedItems = this.get_SelectedItems();
        if (selectedItems != null && selectedItems.length > 0) {
            var data = selectedItems;
            for (var i = 0; i < data.length; i++) {
                this._selectedItems.items.push(data[i]);
            }
        }
        this._selectDialog.dialog("close");
        jQuery("#designerLayoutRoot").show();
        dialogBase.resizeToContent();
    },

You can download the full source from GitHub.

Here is a short video of the selector and designer:

Unable to display content. Adobe Flash is required.

Nikola Zagorchev

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

Comments

Comments are disabled in preview mode.
Topics

Sitefinity Training and Certification Now Available.

Let our experts teach you how to use Sitefinity's best-in-class features to deliver compelling digital experiences.

Learn More
Latest Stories
in Your Inbox

Subscribe to get all the news, info and tutorials you need to build better business apps and sites

Loading animation