Multi content picker - from a widget
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
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]);
);
,
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);
,
_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);
,
List<Guid> list =
new
JavaScriptSerializer().Deserialize<List<Guid>>(
this
.Test);
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();
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