+1-888-365-2779
Try Now
More in this section

Forums / Developing with Sitefinity / Filtering dynamic content in MVC custom widget designer

Filtering dynamic content in MVC custom widget designer

5 posts, 0 answered
  1. michael
    michael avatar
    17 posts
    Registered:
    15 Nov 2015
    08 Jan
    Link to this post

    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

  2. Svetoslav Manchev
    Svetoslav Manchev avatar
    735 posts
    Registered:
    29 Nov 2016
    12 Jan
    Link to this post
    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
     
  3. michael
    michael avatar
    17 posts
    Registered:
    15 Nov 2015
    14 Jan
    Link to this post

    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

     

  4. Velizar Bishurov
    Velizar Bishurov avatar
    143 posts
    Registered:
    01 Dec 2016
    18 Jan
    Link to this post
    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
     
  5. michael
    michael avatar
    17 posts
    Registered:
    15 Nov 2015
    18 Jan
    Link to this post

    Thanks very much for the detailed reply Velizar, much appreciated

     

    Cheers,

    Michael

5 posts, 0 answered