Multi content picker - from a widget

Posted by Community Admin on 04-Aug-2018 13:52

Multi content picker - from a widget

All Replies

Posted by Community Admin on 16-Apr-2014 00:00

Hi,

I've been trying to get it so that from a widget i can pick multiple content items. These picked items will then have their data pulled through to the widget. This will allow for reuse of content around the site and not trapped within a widget on a single page. So say for example i have an 'employee profile' content item and a widget that the CMS editor will be able to pick multiple profiles to show somewhere. This is a requirement from the client and other places we are doing it automatically using tags and the like. The issue i'm having is how to handle the GUID's in the widget designer's .js file.

So what I've done is this (warning: this is hacky):
- Created an MVC widget (without a designer to begin with) using thunder.
- In the controller I've added a property as a Guid type.
- Using thunder i then create a designer for my widget. For the Guid property i choose the DynamicContent and choose my 'employee profile' content item. (I do this purely so thunder generates all the code i need).
- This as it is will allow me to pick single content items. We want to pick multiple. So having been directed to this url - www.sitefinity.com/.../selecting_sitefinity_4_content_inside_widget_designers
- I now modify the designer .ascx and add an attribute to <FlatSelector/> of 'AllowMultipleSelection="true"'.
- I then switch the controllers property to be a string (this is all i can save as there is no Guid[] in a widget).

- I then modify the designer's .js file's methods applyChanges() and _SomethingDoneSelecting(). These will handle the selected content and output the names in list. And also save the selected id's as a comma separated string of Guid's (as suggested in the above linked article).

This is where my issues arise. With the above i can select multiple items and save them ok. Once they are saved however an i go to edit the values selected all it will show is the comma separate string of Guid's (as this is what is saved). I have no way of getting the names of the Guid's that i am storing in my refreshUI() method.

So essentially when the CMS editor drags a widget to a page and then selects multiple content items. That is OK. On the select methods of the designer's js i can get the guid's page names and output them nicely formed in a list. But when they return and want to edit all they can see is a string of Guid's, which is obviously useless. From the applyChanges() method all i can access is _propertyEditor which is some kind of Telerik usercontrol and doesn't give me any chance to extend (or does it?) to get say a list of page names that I've selected.

So can this be done? Can a widget pick multiple content items and display them to a CMS editor in a fashion that is readable and usable. Or is there something i'm missing/another better way to do this??

Any help would be much appreciated,
Jack

Posted by Community Admin on 21-Apr-2014 00:00

Hi Jack,

I have prepared a sample widget with a designer where you can select and save many dynamic content items. I use a string property, where I actually save the items Guid as json array, but you can use an Array property, as well and use the JavaScriptSerializer to serialize and deserialize the object and use it in the designer .js file for client side operations.
I have attached the sample widget with the designer. You have to change the itemsType and serviceUrl in the designer.js file to the corresponding ones of your project or pass them as properties from the widget designer class. You can also make the widget more generic and passing the item type dynamically.
The items container, which holds the items from load to save is a kendo observable object. To populate the observable we call a service and get the items, which have the id's we have saved.
The refreshUi method:

refreshUI: function ()
        var controlData = this._propertyEditor.get_control(); /* JavaScript clone of your control - all the control properties will be properties of the controlData too */
 
        /* RefreshUI Message */
        jQuery(this.get_message()).val(controlData.Message);
 
        /* RefreshUI Test */
 
        var value = controlData.Test;
        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 + 'OriginalContentId == ' + dataItems[i].toString();
            
            var data =
                "itemType": this._itemsType,
                "filter": filterExpression,
                "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._selectedItems.items.splice(0, self._selectedItems.items.length);
                    for (var i = 0; i < result.Items.length; i++)
                        self._selectedItems.items.push(result.Items[i]);
                    
                
            );
        
    ,

On applyChanges we get the items from the kendo observable and save them in a json array of their id's (Guids).

applyChanges: function ()
        var controlData = this._propertyEditor.get_control();
        /* ApplyChanges Message */
        controlData.Message = jQuery(this.get_message()).val();
 
        var data = new Array();
        var items = this._selectedItems.toJSON();
        /* ApplyChanges Test */
        for (var i = 0; i < items.items.length; i++)
            data.push(items.items[i].OriginalContentId);
        
 
        controlData.Test = JSON.stringify(data);
 
    ,

We also attach to the binding of the flat selector and remove the items which we have already selected and saved:

_binderDataBindingHandler: function (sender, args)
        var selectedItems = this._selectedItems.items.toJSON();
        if (selectedItems)
            var items = args.get_dataItem().Items;
            for (var i = 0; i < selectedItems.length; i++)
                var selectedItem = selectedItems[i];
                for (var j = 0; j < items.length; j++)
                    if (selectedItem.Id == items[j].Id)
                        items.splice(j, 1);
                    
                
            
        
    ,

If you need to display or alter the selected items you can get their Id's in the InitializeControls method in the widget class the following way:
List<Guid> list = new JavaScriptSerializer().Deserialize<List<Guid>>(this.Test);
You can then get each item using the API manager.

I have recorded a video of the sample usage.

Regards,
Nikola Zagorchev
Telerik
 
Do you want to have your say in the Sitefinity development roadmap? Do you want to know when a feature you requested is added or when a bug fixed? Explore the Telerik Sitefinity CMS Ideas&Feedback Portal and vote to affect the priority of the items
 

Posted by Community Admin on 29-Apr-2014 00:00

Thanks Nikola, that looks like a nice way of doing it!
I've got it working now but did it a slightly different way. I'll put it here so others will be able to see in future.

In the designer's .js i find the _[NameOfDesigner]DoneSelecting() function and alter the code as follows. This will get the selected items and display the titles, while hiding the Guids as data attributes. 

_DocumentsDoneSelecting: function (sender, args)
         
        var selectedItems = this.get_DocumentsSelectedItems();
        var guids = [];
        var titles = [];
 
        if (selectedItems)
 
            for (var i = 0; i < selectedItems.length; i++)
                titles.push(selectedItems[i].Title.Value);
                guids.push(selectedItems[i].OriginalContentId);
            
 
            var $selectedVideosContainer = $(this.get_selectedDocuments());
 
            //store guids
            $selectedVideosContainer.data('guids', guids.join(','));
 
            //set names
            $.each(titles, function (index, value)
                $selectedVideosContainer.append('<li>' + value + '</li>');
            );
 
            this.get_selectButtonDocuments().innerHTML = '<span class=\"sfLinkBtnIn\">Change</span>';
            $(this.get_deselectButtonDocuments()).show();
        
         
        this._selectDocumentsDialog.dialog("close");
        jQuery("#designerLayoutRoot").show();
        dialogBase.resizeToContent();
    

Then when saving i just grab the Guid string and save those

applyChanges: function ()
        var controlData = this._propertyEditor.get_control().Settings;
 
        /* ApplyChanges Documents */
        var $element = $(this.get_selectedDocuments());
        var guids = $element.data('guids');
        controlData.Documents = guids;
    

To get the titles to show when i return i do the following. It gets the guids and calls some content service to get the titles.

refreshUI: function ()
        var controlData = this._propertyEditor.get_control().Settings;
 
        /* RefreshUI Documents */
        var selector = this._DocumentsItemSelector;
        var guidString = controlData.Documents;
 
        if (guidString === null)
            return;
 
        var serviceUrl = selector._originalServiceBaseUrl;
 
        if (serviceUrl.endsWith("/live"))
            serviceUrl = serviceUrl.substring(0, serviceUrl.length - 5);
 
        var $element = $(this.get_selectedDocuments());
        var guids = guidString.split(',');
 
        $element.html('');
 
        for (var i = 0; i < guids.length; i++)
            var fullUrl = serviceUrl + "/" + guids[i] + "/?allproviders=true&itemType=" + selector.get_itemType();
            $.get(fullUrl, function (data)
                $element.append('<li>' + data.Item.Title.Value + '</li>');
            );
        
 
        dialogBase.resizeToContent();
    

Posted by Community Admin on 29-Apr-2014 00:00

Hi Jack,

I am glad that you have configured the widget.
Thanks for sharing your approach for setting this up.
The solution I have provided works fine, as well, you can just plug in your logic in different methods, which are called in different stages of the designer binding.

Regards,
Nikola Zagorchev
Telerik

 
Do you want to have your say in the Sitefinity development roadmap? Do you want to know when a feature you requested is added or when a bug fixed? Explore the Telerik Sitefinity CMS Ideas&Feedback Portal and vote to affect the priority of the items
 

This thread is closed