Filtering dynamic content in MVC custom widget designer

Posted by Community Admin on 04-Aug-2018 05:45

Filtering dynamic content in MVC custom widget designer

All Replies

Posted by Community Admin on 08-Jan-2016 00:00

Hi there,

I have a custom MVC widget, with a custom MVC designer that allows the user to select one or more records from a dynamic module.

The dynamic module has a "page type" string property.  Is it possible to limit the items in my custom MVC widget designers selector to only those where "page type" = [some value]?

The selector I'm using in the designer is as follows:

<div class="form-group">
    <label class="control-label">Links</label>
    <sf-list-selector sf-dynamic-items-selector
                      sf-multiselect="true"
                      sf-item-type="properties.ModuleType.PropertyValue"
                      sf-selected-ids="itemSelector.selectedItemsIds"
                      sf-master="true" />
</div>

Cheers

Posted by Community Admin on 12-Jan-2016 00:00

Hi Michael,

You can filter the Items you get by using filter expression. You can check the following sample:
http://docs.sitefinity.com/example-filter-dynamic-content-items-by-dynamic-field

More information about the filter expressions is available here:
http://docs.sitefinity.com/filter-expressions-for-content-items

I hope this helps.

Regards,
Svetoslav Manchev
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 14-Jan-2016 00:00

Hi Svetoslav,

I checked out the links you provided, however it's showing how to filter in code-behind, and not in the MVC widget designer.  

Is there a way to add a filter to the designer definition? e.g. some kind of filter property in here (as far as I understand the custom MVC widget designer works as an angular app with no code behind that I can access):

<div class="form-group">
    <label class="control-label">Links</label>
    <sf-list-selector sf-dynamic-items-selector
                      sf-multiselect="true"
                      sf-item-type="properties.ModuleType.PropertyValue"
                      sf-selected-ids="itemSelector.selectedItemsIds"
                      sf-master="true" />
</div>

Thanks,
Michael

 

Posted by Community Admin on 18-Jan-2016 00:00

Hello Michael,

Unfortunately it is not possible to achieve your desired behavior with the out-of-the-box functionality of the dynamic content selector.

Fortunately, however, Feather is an open source project available on GitHub and it is easy to look at its source code and plug in in the right places to achieve the desired behavior.

Like most of the things in Feather it is quite easy to override the default scripts for the selectors and services.

Firstly override the sf-dynamic-items-selector directive and find out whether it is currently working with the desired dynamic module. After that pass on that information to the data service. To override the sf-dynamic-items-selector create the following folder structure on the root of the project:

client-components
--selectors
----dynamic-modules

In the dynamic-modules folder create a sf-dynamic-items-selector.js file and make the necessary changes to the source code like so:

(function ($)
    angular.module('sfSelectors')
        .directive('sfDynamicItemsSelector', ['sfDataService', function (dataService)
            return
                require: '^sfListSelector',
                restrict: 'A',
                link:
                    pre: function (scope, element, attrs, ctrl)
                        var master = attrs.sfMaster === 'true' || attrs.sfMaster === 'True';
 
                        ctrl.getItems = function (skip, take, search)
                            var provider = ctrl.$scope.sfProvider;
 
                            // check if correct dynamic module and send the information along to the data service
                            var isMyModule = ctrl.$scope.sfItemType == "Telerik.Sitefinity.DynamicTypes.Model.Pressreleases.PressRelease";
 
                            if (master)
                                return dataService.getItems(ctrl.$scope.sfItemType, provider, skip, take, search, ctrl.identifierField, isMyModule);
                            else
                                return dataService.getLiveItems(ctrl.$scope.sfItemType, provider, skip, take, search, ctrl.identifierField, isMyModule);
                        ;
 
                        ctrl.getSpecificItems = function (ids)
                            var provider = ctrl.$scope.sfProvider;
                            if (master)
                                return dataService.getSpecificItems(ids, ctrl.$scope.sfItemType, provider);
                            else
                                return dataService.getSpecificLiveItems(ids, ctrl.$scope.sfItemType, provider);
                        ;
 
                        ctrl.selectorType = 'DynamicItemsSelector';
 
                        ctrl.dialogTemplateUrl = 'client-components/selectors/dynamic-modules/sf-dynamic-items-selector.sf-cshtml';
                        ctrl.$scope.dialogTemplateId = 'sf-dynamic-items-selector-template';
 
                        var closedDialogTemplate = attrs.sfMultiselect ?
                            'client-components/selectors/common/sf-list-group-selection.sf-cshtml' :
                            'client-components/selectors/common/sf-bubbles-selection.sf-cshtml';
 
                        ctrl.closedDialogTemplateUrl = closedDialogTemplate;
                    
                
            ;
        ]);
)(jQuery);

The next step is to override the sf-data-service, accept the information about the module and pass it along to the search filter builder. To do that create a sf-data-service.js file in the dynamic-modules folder and make the necessary changes to the source code like so:

(function ()
    angular.module('sfServices')
        .factory('sfDataService', ['serviceHelper', 'serverContext', function (serviceHelper, serverContext)
            /* Private methods and variables */
            var serviceUrl = serverContext.getRootedUrl('Sitefinity/Services/DynamicModules/Data.svc/'),
                dataItemPromise;
 
            var getResource = function (itemId)
                var url = serviceUrl;
                if (itemId && itemId !== serviceHelper.emptyGuid())
                    url = url + itemId + '/';
                
 
                return serviceHelper.getResource(url);
            ;
 
            var getLiveItems = function (itemType, provider, skip, take, search, searchField)
                var filter = serviceHelper.filterBuilder()
                    .searchFilter(search, null, searchField)
                    .and()
                    .cultureFilter()
                    .getFilter();
 
                dataItemPromise = getResource('live').get(
                    
                        itemType: itemType,
                        itemSurrogateType: itemType,
                        provider: provider,
                        skip: skip,
                        take: take,
                        filter: filter
                    )
                    .$promise;
 
                return dataItemPromise;
            ;
 
            var getItems = function (itemType, provider, skip, take, search, searchField, isMyModule)
                var filter = serviceHelper.filterBuilder()
                    // pass on the information to the filter
                    .searchFilter(search, null, searchField, isMyModule)
                    .and()
                    .cultureFilter()
                    .getFilter();
 
                dataItemPromise = getResource().get(
                    
                        itemType: itemType,
                        itemSurrogateType: itemType,
                        provider: provider,
                        skip: skip,
                        take: take,
                        filter: filter
                    )
                    .$promise;
 
                return dataItemPromise;
            ;
 
            var getSpecificLiveItems = function (ids, itemType, provider)
                var filter = serviceHelper.filterBuilder()
                    .specificItemsFilter(ids)
                    .and()
                    .cultureFilter()
                    .getFilter();
 
                dataItemPromise = getResource('live').get(
                    
                        itemType: itemType,
                        itemSurrogateType: itemType,
                        provider: provider,
                        skip: 0,
                        take: 100,
                        filter: filter
                    )
                    .$promise;
 
                return dataItemPromise;
            ;
 
            var getSpecificItems = function (ids, itemType, provider)
                var filter = serviceHelper.filterBuilder()
                    .specificItemsFilter(ids)
                    .and()
                    .cultureFilter()
                    .getFilter();
 
                dataItemPromise = getResource().get(
                    
                        itemType: itemType,
                        itemSurrogateType: itemType,
                        provider: provider,
                        skip: 0,
                        take: 100,
                        filter: filter
                    )
                    .$promise;
 
                return dataItemPromise;
            ;
 
            var getItem = function (itemId, itemType, provider)
                dataItemPromise = getResource(itemId).get(
                    
                        itemType: itemType,
                        provider: provider
                    )
                    .$promise;
 
                return dataItemPromise;
            ;
 
            return
                /* Returns the data items. */
                getLiveItems: getLiveItems,
                getSpecificLiveItems: getSpecificLiveItems,
                getItems: getItems,
                getSpecificItems: getSpecificItems,
                getItem: getItem
            ;
        ]);
)();

The last step is to override the sf-services factory, where the FilterBuilder is implemented, and add the filter by custom field.

To do that create a common folder under the selectors folder and create a sf-services.js file in it, where the changes will be applied. The new folder structure should look as follows:

client-components
--selectors
----common
------sf-services.js
----dynamic-modules
------sf-data-service.js
------sf-dynamic-items-selector.js

Inside the searchFilter function of the FilterBuilder accepts the module information and add the custom filter when necessary like so:

(function ()
    var module = angular.module('sfServices', ['ngResource', 'serverDataModule']);
 
    module.config(['$httpProvider', function ($httpProvider)
        if (!$httpProvider.defaults.headers.get)
            $httpProvider.defaults.headers.get = ;
        
 
        var getHeaders = $httpProvider.defaults.headers.get;
 
        //disable IE ajax request caching
        //NOTE: This breaks angular logic for loading templates through XHR request leading to 400 - bad request.
        //Only specific format is accepted for this header by the server. 
        //getHeaders['If-Modified-Since'] = 'Thu, 01 Feb 1900 00:00:00';
        getHeaders['Cache-Control'] = 'no-cache';
        getHeaders.Pragma = 'no-cache';
    ]);
 
    module.factory('serviceHelper', ['$resource', 'serverContext', function ($resource, serverContext)
        /* Private methods and variables */
        var emptyGuid = '00000000-0000-0000-0000-000000000000';
 
        function endsWith(str, suffix)
            return str.indexOf(suffix, str.length - suffix.length) >= 0;
        
 
        function trimRight(str, suffix)
            return str.substr(0, str.length - suffix.length);
        
 
        var getResource = function (url, options, headers, isArray)
            var headerData = headers || ;
 
            var resourceOption = options || stripTrailingSlashes: false ;
 
            var culture = serverContext.getUICulture();
 
            if (culture && !headerData.SF_UI_CULTURE)
                headerData.SF_UI_CULTURE = culture;
            
 
            //headerData['Cache-Control'] = 'no-cache';
            //headerData.Pragma = 'no-cache';
 
            return $resource(url, ,
                get:
                    method: 'GET',
                    isArray: isArray,
                    headers: headerData
                ,
                put:
                    method: 'PUT',
                    isArray: isArray,
                    headers: headerData
                
            , resourceOption);
        ;
 
        function FilterBuilder(baseFilter)
            this.filter = baseFilter || '';
            this.liveItemsFilter = 'Visible==true AND Status==live';
            this.andOperator = ' AND ';
        
        FilterBuilder.prototype =
            constructor: FilterBuilder,
            lifecycleFilter: function ()
                this.filter += this.liveItemsFilter;
                return this;
            ,
            cultureFilter: function ()
                var culture = serverContext.getUICulture();
                if (culture)
                    this.filter += 'Culture==' + culture;
                    return this;
                
                else
                    return this.trimOperator();
                
            ,
            searchFilter: function (search, frontendLanguages, searchField, isMyModule)
                var customFieldName = "Test",
                    searchString = "var";
 
                if (!search)
                    if (!isMyModule)
                        return this.trimOperator();
                    
 
                    this.filter += '(' + customFieldName + '.ToUpper().Contains("' + searchString + '".ToUpper()))';
 
                    return this;
                
 
                var field = searchField || 'Title';
 
                var searchFilter;
 
                if (isMyModule)
                    searchFilter = '(' + field + '.ToUpper().Contains("' + search + '".ToUpper()) AND ' + customFieldName + '.ToUpper().Contains("' + searchString + '".ToUpper()))';
                 else
                    searchFilter = '(' + field + '.ToUpper().Contains("' + search + '".ToUpper()))';
                
 
                if (frontendLanguages && frontendLanguages.length > 1)
                    for (var i = 0; i < frontendLanguages.length; i++)
                        var localizedField = String.format("0[\"1\"]", field, frontendLanguages[i]);
                        searchFilter += String.format("OR 0.ToUpper().Contains(\"1\".ToUpper())", localizedField, search);
                    
                    searchFilter = '(' + searchFilter + ')';
                
 
                this.filter += searchFilter;
 
                return this;
            ,
            differFilter: function (items, identifier)
                var itemsFilterArray = [];
 
                if (!items || items.length === 0) return this.trimOperator();
 
                itemsFilterArray.push(identifier + '!="' + items[0] + '"');
 
                if (items.length > 1)
                    for (var i = 1; i < items.length; i++)
                        itemsFilterArray.push(' AND ' + identifier + '!="' + items[i] + '"');
                    
                
 
                this.filter += '(' + itemsFilterArray.join('') + ')';
 
                return this;
            ,
            specificItemsFilter: function (ids)
                var itemsFilterArray = [];
 
                if (ids.length === 0) return this.trimOperator();
 
                itemsFilterArray.push('Id=' + ids[0]);
 
                if (ids.length > 1)
                    for (var i = 1; i < ids.length; i++)
                        if (ids[i] === emptyGuid) continue;
                        itemsFilterArray.push(' OR Id=' + ids[i]);
                    
                
 
                this.filter += '(' + itemsFilterArray.join('') + ')';
 
                return this;
            ,
            append: function (filter)
                if (filter)
                    this.filter += '(' + filter + ')';
                
 
                return this;
            ,
            and: function ()
                if (this.filter)
                    this.filter += this.andOperator;
                
 
                return this;
            ,
            trimOperator: function ()
                if (endsWith(this.filter, this.andOperator))
                    this.filter = trimRight(this.filter, this.andOperator);
                
 
                return this;
            ,
            getFilter: function ()
                return this.filter;
            
        ;
 
        /* Public interface */
        return
            filterBuilder: function (baseFilter)
                return new FilterBuilder(baseFilter);
            ,
            emptyGuid: function ()
                return emptyGuid;
            ,
            getResource: getResource
        ;
    ]);
 
    module.provider('serverContext', function ServerContextProvider()
        var customContext = customContext || ;
 
        var constructContext = function ($injector)
            return
                getRootedUrl: customContext.getRootedUrl || sitefinity.getRootedUrl,
                getEmbeddedResourceUrl: customContext.getEmbeddedResourceUrl || sitefinity.getEmbeddedResourceUrl,
                getFrontendLanguages: customContext.getFrontendLanguages || sitefinity.getFrontendLanguages,
                getCurrentFrontendRootNodeId: customContext.getCurrentFrontendRootNodeId || sitefinity.getCurrentFrontendRootNodeId,
                setCurrentFrontendRootNodeId: customContext.setCurrentFrontendRootNodeId || sitefinity.setCurrentFrontendRootNodeId,
                getCurrentUserId: customContext.getCurrentUserId || sitefinity.getCurrentUserId,
                getUICulture: function ()
                    if ($injector.has('widgetContext'))
                        return $injector.get('widgetContext').culture;
                    
 
                    return customContext && customContext.uiCulture;
                ,
                isMultisiteEnabled: customContext.isMultisiteEnabled || sitefinity.isMultisiteEnabled
            ;
        ;
 
        /* The context should be object containing properties: 'appPath' and optionally 'currentPackage' and 'uiCulture'. */
        this.setServerContext = function (context)
            customContext = context;
        ;
 
        this.$get = ['$injector', function ($injector)
            return constructContext($injector);
        ];
    );
)();

A complete sample of a widget achieving this behavior is attached.

Note: With this implementation using the search functionality of the selector will only search within the already filtered items.

Regards,
Velizar Bishurov
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 18-Jan-2016 00:00

Thanks very much for the detailed reply Velizar, much appreciated

 

Cheers,

Michael

This thread is closed