<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blog posts by Le Giang</title><link href="http://world.optimizely.com" /><updated>2018-04-05T17:40:03.0000000Z</updated><id>https://world.optimizely.com/blogs/le-giang/</id> <generator uri="http://world.optimizely.com" version="2.0">Optimizely World</generator> <entry><title>Upload media in the Select content dialog for TinyMCE 2.0.0</title><link href="https://world.optimizely.com/blogs/le-giang/dates/2018/4/upload-option-in-the-select-content-dialog-for-tinymce-2-0-0/" /><id>&lt;p&gt;Hi there!&lt;/p&gt;
&lt;p&gt;In the previous &lt;a href=&quot;/link/cff3877f76f645e28fef4d727ee08954.aspx&quot;&gt;post&lt;/a&gt;&lt;a href=&quot;/blogs/le-giang/dates/2018/3/upload-option-in-the-select-content-dialog/),&quot;&gt;,&lt;/a&gt;&amp;nbsp;I have implemented upload media function for Selection content dialog but it just works with CMS 10.x. I have updated the add-on to work with CMS 11 and TinyMCE 2.0.0.&lt;/p&gt;
&lt;p&gt;I also make the&amp;nbsp;&lt;span&gt;uploaded&amp;nbsp;media&amp;nbsp;selected automatically. You can find and download the nuget package by adding the nuget source:&amp;nbsp;https://www.myget.org/F/episerver-util/api/v3/index.json.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: After installing, you may face with the run time error &quot;module not found&quot;, please kindly refer to this &lt;a href=&quot;/link/37191124e57d46ca84c979429e37fac0.aspx&quot;&gt;post&lt;/a&gt; to fix it. Good luck!&lt;/p&gt;
&lt;p&gt;&lt;span&gt;&lt;video width=&quot;100%&quot; height=&quot;auto&quot; src=&quot;/link/6180de8d52ed455fa8160a9923e63c42.aspx&quot; autoplay=&quot;autoplay&quot; preload=&quot;none&quot; controls=&quot;controls&quot;&gt;&lt;object width=&quot;0&quot; height=&quot;240&quot; data=&quot;https://world.episerver.com/epiui/EPiServer.Cms.TinyMce/1.0.0/ClientResources/tinymce/plugins/media/moxieplayer.swf&quot; type=&quot;application/x-shockwave-flash&quot;&gt;&lt;param name=&quot;src&quot; value=&quot;https://world.episerver.com/epiui/EPiServer.Cms.TinyMce/1.0.0/ClientResources/tinymce/plugins/media/moxieplayer.swf&quot; /&gt;&lt;param name=&quot;flashvars&quot; value=&quot;url=/globalassets/tinycme_withupload.mp4&amp;amp;poster=/epiui/CMS/&quot; /&gt;&lt;param name=&quot;allowfullscreen&quot; value=&quot;true&quot; /&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;true&quot; /&gt;&lt;/object&gt;&lt;/video&gt;&lt;/span&gt;&lt;/p&gt;</id><updated>2018-04-05T17:40:03.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Implement search provider for pages, blocks and media</title><link href="https://world.optimizely.com/blogs/le-giang/dates/2018/4/search-for-pages-blocks-and-media-in-navigationtree/" /><id>&lt;p&gt;Currently when I type to search in navigationtree, surprisingly there is no result returned even for pages, block or media. It seems that someone forgot to implement searching feature or it is being underplayed&amp;nbsp;&lt;img alt=&quot;smile&quot; src=&quot;/Scripts/tinymce/plugins/emoticons/img/smiley-smile.gif&quot; /&gt;.&lt;/p&gt;
&lt;p&gt;In this post, I would like show you how to implement a search provider for pages, blocks and media and you can customize to fit your need.&lt;/p&gt;
&lt;p&gt;For pages:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[SearchProvider]
    public class DefaultPageSearchProvider : ContentSearchProviderBase&amp;lt;IContent, ContentType&amp;gt;
    {
        public DefaultPageSearchProvider(LocalizationService localizationService, ISiteDefinitionResolver siteDefinitionResolver, IContentTypeRepository&amp;lt;ContentType&amp;gt; contentTypeRepository, EditUrlResolver editUrlResolver, ServiceAccessor&amp;lt;SiteDefinition&amp;gt; currentSiteDefinition, LanguageResolver languageResolver, UrlResolver urlResolver, TemplateResolver templateResolver, UIDescriptorRegistry uiDescriptorRegistry) : base(localizationService, siteDefinitionResolver, contentTypeRepository, editUrlResolver, currentSiteDefinition, languageResolver, urlResolver, templateResolver, uiDescriptorRegistry)
        {

        }

        /// &amp;lt;summary&amp;gt;
        ///
        /// &amp;lt;/summary&amp;gt;
        public override string Area
        {
            get
            {
                return &quot;cms/pages&quot;;
            }
        }

        /// &amp;lt;summary&amp;gt;
        ///
        /// &amp;lt;/summary&amp;gt;
        public override string Category
        {
            get
            {
                return &quot;pages&quot;;
            }
        }

        protected override string IconCssClass
        {
            get
            {
                return &quot;epi-iconObjectPage&quot;;
            }
        }

        public override IEnumerable&amp;lt;SearchResult&amp;gt; Search(Query query)
        {
            var contentRepository = ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;();
            var pageQueryService = ServiceLocator.Current.GetInstance&amp;lt;IPageCriteriaQueryable&amp;gt;();
            PropertyCriteriaCollection crits = new PropertyCriteriaCollection();

            PropertyCriteria nameCriteria = new PropertyCriteria();
            nameCriteria.Name = MetaDataProperties.PageName;
            nameCriteria.Value = query.SearchQuery;
            nameCriteria.Type = PropertyDataType.String;
            nameCriteria.Required = true;
            nameCriteria.Condition = CompareCondition.Contained;

            crits.Add(nameCriteria);
            //Add criteria so search is performed against all providers
            crits.Add(new PropertyCriteria
            {
                Name = &quot;EPI:MultipleSearch&quot;,
                Value = &quot;*&quot;
            });

            PageDataCollection pages = null;
            try
            {
                pages = pageQueryService.FindAllPagesWithCriteria(ContentReference.RootPage, crits, null, LanguageSelector.MasterLanguage());
            }
            catch (NotImplementedException)
            {
                // If the provider hasn&#39;t implemented FindAllPagesWithCriteria, call old FindPagesWithCriteria instead.
                pages = pageQueryService.FindPagesWithCriteria(ContentReference.RootPage, crits, null, LanguageSelector.MasterLanguage());
            }
            return pages.Select(CreateSearchResult);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;For blocks&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt; [SearchProvider]
    public class DefaultBlockSearchProvider : ContentSearchProviderBase&amp;lt;IContent, ContentType&amp;gt;
    {
        public DefaultBlockSearchProvider(LocalizationService localizationService, ISiteDefinitionResolver siteDefinitionResolver, IContentTypeRepository&amp;lt;ContentType&amp;gt; contentTypeRepository, EditUrlResolver editUrlResolver, ServiceAccessor&amp;lt;SiteDefinition&amp;gt; currentSiteDefinition, LanguageResolver languageResolver, UrlResolver urlResolver, TemplateResolver templateResolver, UIDescriptorRegistry uiDescriptorRegistry) : base(localizationService, siteDefinitionResolver, contentTypeRepository, editUrlResolver, currentSiteDefinition, languageResolver, urlResolver, templateResolver, uiDescriptorRegistry)
        {

        }

        /// &amp;lt;summary&amp;gt;
        ///
        /// &amp;lt;/summary&amp;gt;
        public override string Area
        {
            get
            {
                return &quot;cms/blocks&quot;;
            }
        }

        /// &amp;lt;summary&amp;gt;
        ///
        /// &amp;lt;/summary&amp;gt;
        public override string Category
        {
            get
            {
                return &quot;blocks&quot;;
            }
        }

        protected override string IconCssClass
        {
            get
            {
                return &quot;epi-objectIcon&quot;;
            }
        }

        public override IEnumerable&amp;lt;SearchResult&amp;gt; Search(Query query)
        {
            ContentReference root = null;
            if (query.SearchRoots != null &amp;amp;&amp;amp; query.SearchRoots.Count() &amp;gt; 0)
            {
                root = new ContentReference(query.SearchRoots.First());
            }
            else
            {
                root = new ContentReference();
            }

            var contentRepository = ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;();
            var contentTypeRepository = ServiceLocator.Current.GetInstance&amp;lt;IContentTypeRepository&amp;gt;();
            var contentProviderManager = ServiceLocator.Current.GetInstance&amp;lt;IContentProviderManager&amp;gt;();
            var blockTypes = contentTypeRepository.List().Where(x =&amp;gt; typeof(BlockData).IsAssignableFrom(x.ModelType));
            var provider = contentProviderManager.GetProvider(root);

            if (provider != null &amp;amp;&amp;amp; provider.HasCapability(ContentProviderCapabilities.Search))
            {
                List&amp;lt;IContent&amp;gt; blocks = new List&amp;lt;IContent&amp;gt;();
                foreach(var blockType in blockTypes)
                {
                    blocks.AddRange(provider.ListContentOfContentType(blockType).Where(x =&amp;gt; x.Name.IndexOf(query.SearchQuery, StringComparison.InvariantCultureIgnoreCase) &amp;gt;= 0).Select(x =&amp;gt; contentRepository.Get&amp;lt;IContent&amp;gt;(x.ContentLink)));
                }

                return blocks.Select(CreateSearchResult);
            }

            return Enumerable.Empty&amp;lt;SearchResult&amp;gt;();
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For media:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[SearchProvider]
    public class DefaultMediaSearchProvider : ContentSearchProviderBase&amp;lt;IContent, ContentType&amp;gt;
    {
        public DefaultMediaSearchProvider(LocalizationService localizationService,
            ISiteDefinitionResolver siteDefinitionResolver,
            IContentTypeRepository&amp;lt;ContentType&amp;gt; contentTypeRepository,
            EditUrlResolver editUrlResolver,
            ServiceAccessor&amp;lt;SiteDefinition&amp;gt; currentSiteDefinition,
            LanguageResolver languageResolver,
            UrlResolver urlResolver,
            TemplateResolver templateResolver,
            UIDescriptorRegistry uiDescriptorRegistry) : base(localizationService,
                siteDefinitionResolver,
                contentTypeRepository,
                editUrlResolver,
                currentSiteDefinition,
                languageResolver,
                urlResolver,
                templateResolver,
                uiDescriptorRegistry)
        {

        }

        /// &amp;lt;summary&amp;gt;
        ///
        /// &amp;lt;/summary&amp;gt;
        public override string Area
        {
            get
            {
                return &quot;CMS/files&quot;;
            }
        }

        /// &amp;lt;summary&amp;gt;
        ///
        /// &amp;lt;/summary&amp;gt;
        public override string Category
        {
            get
            {
                return &quot;media&quot;;
            }
        }

        protected override string IconCssClass
        {
            get
            {
                return &quot;&quot;;
            }
        }

        public override IEnumerable&amp;lt;SearchResult&amp;gt; Search(Query query)
        {
            ContentReference root = null;
            if (query.SearchRoots != null &amp;amp;&amp;amp; query.SearchRoots.Count() &amp;gt; 0)
            {
                root = new ContentReference(query.SearchRoots.First());
            }
            else
            {
                root = new ContentReference();
            }

            var contentRepository = ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;();
            var contentTypeRepository = ServiceLocator.Current.GetInstance&amp;lt;IContentTypeRepository&amp;gt;();
            var contentProviderManager = ServiceLocator.Current.GetInstance&amp;lt;IContentProviderManager&amp;gt;();
            var mediaTypes = contentTypeRepository.List().Where(x =&amp;gt; typeof(MediaData).IsAssignableFrom(x.ModelType));
            var provider = contentProviderManager.GetProvider(root);

            if (provider != null &amp;amp;&amp;amp; provider.HasCapability(ContentProviderCapabilities.Search))
            {
                List&amp;lt;IContent&amp;gt; mediaList = new List&amp;lt;IContent&amp;gt;();
                foreach (var blockType in mediaTypes)
                {
                    mediaList.AddRange(provider.ListContentOfContentType(blockType).Where(x =&amp;gt; x.Name.IndexOf(query.SearchQuery, StringComparison.InvariantCultureIgnoreCase) &amp;gt;= 0).Select(x =&amp;gt; contentRepository.Get&amp;lt;IContent&amp;gt;(x.ContentLink)));
                }

                return mediaList.Select(CreateSearchResult);
            }

            return Enumerable.Empty&amp;lt;SearchResult&amp;gt;();
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After adding these search providers, now I can enjoy the filtering feature. You can customize the code to fit your need and improve the performance (for example: implement caching mechanism).&lt;/p&gt;
&lt;p&gt;&lt;video width=&quot;100%&quot; height=&quot;auto&quot; src=&quot;/link/d17abf0512a94dbdb17a1d2646efcdd3.aspx&quot; preload=&quot;none&quot; controls=&quot;controls&quot;&gt;&lt;object width=&quot;0&quot; height=&quot;240&quot; data=&quot;https://world.episerver.com/epiui/EPiServer.Cms.TinyMce/1.0.0/ClientResources/tinymce/plugins/media/moxieplayer.swf&quot; type=&quot;application/x-shockwave-flash&quot;&gt;&lt;param name=&quot;src&quot; value=&quot;https://world.episerver.com/epiui/EPiServer.Cms.TinyMce/1.0.0/ClientResources/tinymce/plugins/media/moxieplayer.swf&quot; /&gt;&lt;param name=&quot;flashvars&quot; value=&quot;url=/globalassets/cms-searching.mp4&amp;amp;poster=/epiui/CMS/&quot; /&gt;&lt;param name=&quot;allowfullscreen&quot; value=&quot;true&quot; /&gt;&lt;param name=&quot;allowscriptaccess&quot; value=&quot;true&quot; /&gt;&lt;/object&gt;&lt;/video&gt;&lt;/p&gt;</id><updated>2018-04-02T03:50:21.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Upload option in the Select content dialog</title><link href="https://world.optimizely.com/blogs/le-giang/dates/2018/3/upload-option-in-the-select-content-dialog/" /><id>&lt;p&gt;Hi again!&lt;/p&gt;
&lt;p&gt;When working with media contents (image, video...) in the CMS, you can only upload your files in the Media component.&amp;nbsp;In this post I would like to introduce an add-on which allows you to upload media file when you are in the Select content dialog. Let&#39;s see it in action.&lt;/p&gt;
&lt;p&gt;&lt;video width=&quot;100%&quot; height=&quot;auto&quot; controls=&quot;controls&quot; autoplay=&quot;autoplay&quot; preload=&quot;none&quot; src=&quot;/link/cbb2a92c69f449638606baffc890754d.aspx&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;I have also added this function to the TinyMCE editor, let&#39;s see it below.&lt;/p&gt;
&lt;p&gt;&lt;video width=&quot;100%&quot; height=&quot;auto&quot; controls=&quot;controls&quot; autoplay=&quot;autoplay&quot; preload=&quot;none&quot; src=&quot;/link/fed5966caac3433b9e2ce792e1c12f74.aspx&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Basically, it works as same as when you working in Media component but has some notable points.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Upload button only get enabled when you select folder node on the tree.&lt;/li&gt;
&lt;li&gt;OK button only get enabled when you select actual media content.&lt;/li&gt;
&lt;li&gt;There is a sync between Select content dialog and Media component, this means that you can see uploaded files immediately in Media component after the uploading process completes in the Select content dialog.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It took pretty much code so I have created a nuget package for this feature, but currently it only supports&amp;nbsp;Episerver CMS 10. I will make it work with Episerver CMS 11 soon.&lt;/p&gt;
&lt;p&gt;&lt;a title=&quot;EpiserverSite.TinyMCEFileBrowserPlugin.1.0.0-developerbuild.nupkg&quot; href=&quot;https://drive.google.com/open?id=1xUVtQAAWTtIOJu6wSPTZVPNtBhPkb7zs&quot;&gt;EpiserverSite.TinyMCEFileBrowserPlugin.1.0.0-developerbuild.nupkg&lt;/a&gt;&lt;/p&gt;</id><updated>2018-03-06T11:10:45.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Single copy and paste in PageNavigationTree</title><link href="https://world.optimizely.com/blogs/le-giang/dates/2018/3/single-copy-and-paste-in-pagenavigationtree/" /><id>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;By the default, when you copy and paste a page in&amp;nbsp;PageNavigationTree (PNT), all its children also come along. Recently a discussion on Yammer has raised question about how to just copy and paste parent page without its children. Currently edit mode UI&amp;nbsp;does not support this function, so in this post I&amp;nbsp;would like to describe the way to do this.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Firstly, we need a new command called &quot;Copy this page&quot; in the tree&#39;s context menu. It works same as the default Copy command except that it sets a flag &quot;copyMode&quot; to &quot;single&quot;. We also have to override the default Copy and Paste commands to set and send this flag to the server side for further processing.&amp;nbsp;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;//CopySingleContent
define([
    &quot;dojo/_base/declare&quot;,
    &quot;epi-cms/command/CopyContent&quot;
], function (declare, CopyContent) {

    return declare([CopyContent], {
        
        label: &quot;Copy this page&quot;,        
        iconClass: &quot;epi-iconCopy&quot;,
        _execute: function () {
            this.clipboard.set(&quot;copyMode&quot;, true);
            this.inherited(arguments);
        }
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;// PasteContent
define([
    &quot;dojo/_base/declare&quot;,
    &quot;epi-cms/command/PasteContent&quot;,
    &quot;epi-propertydemo/service/CustomContentHierarchyService&quot;
], function (declare, PasteContent, CustomContentHierarchyService) {

    return declare([PasteContent], {
        _execute: function () {
            // summary:
            //      Override to handle &quot;single copy&quot;
            // tags:
            //      protected

            if (this.clipboard.copyMode === &quot;single&quot;) {
                this.model.service = new CustomContentHierarchyService();
                this.model.service.singleCopy = true;
            }
            this.inherited(arguments);
        }
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;// CopyContent
define([
    &quot;dojo/_base/declare&quot;,
    &quot;epi-cms/command/CopyContent&quot;
], function (declare, CopyContent)
{

    return declare([CopyContent], {
        _execute: function() {
            // summary:
            //      Override to set copyMode to null
            // tags:
            //      protected

            this.clipboard.set(&quot;copyMode&quot;, null);
            this.inherited(arguments);
        }
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Now we need send the &quot;copyMode&quot; to the server. This is done in a custom&amp;nbsp;ContentHierarchyService&amp;nbsp;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;define([
    &quot;dojo/_base/declare&quot;,
    &quot;dojo/_base/lang&quot;,
    &quot;dojo/aspect&quot;,
    &quot;epi/dependency&quot;,
    &quot;epi-cms/contentediting/ContentHierarchyService&quot;
], function (declare,lang, aspect, dependency, ContentHierarchyService) {

    return declare([ContentHierarchyService], {

        postscript: function (params) {
            this.inherited(arguments);

            aspect.before(this.store.xhrHandler, &quot;xhr&quot;, lang.hitch(this, function (/*String*/method, /*dojo.__XhrArgs*/args, /*Boolean?*/hasBody) {
                if (this.singleCopy &amp;amp;&amp;amp; method == &quot;POST&quot;) {
                    args.headers = args.headers || {};
                    args.headers = lang.mixin({}, args.headers, { &quot;X-EpiCopyMode&quot;: &quot;single&quot; });
                }
                return [method, args];
            }));

            aspect.after(this.store.xhrHandler, &quot;xhr&quot;, lang.hitch(this, function (deferred) {
                this.singleCopy = null;
                return deferred;
            }));
        }        
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We attach&quot;X-EpiCopyMode&quot;&amp;nbsp;param to the request header, this will be consummed in the server-side to decide to perform &quot;single copy&quot;&amp;nbsp;or &quot;normal copy&quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;We have created custom commands but they haven&#39;t been used anywhere. We need attach them to page tree&#39;s context menu. Unfortunately, there is no an easy way to do this, but the hard way.&amp;nbsp;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;define([
  &quot;dojo/_base/declare&quot;,
  &quot;dojo/_base/array&quot;,
  &quot;dojo/aspect&quot;,

  &quot;epi/_Module&quot;,
  &quot;epi/dependency&quot;,

  &quot;epi-cms/plugin-area/navigation-tree&quot;,
  &quot;epi-cms/component/MainNavigationComponent&quot;,
  &quot;epi-cms/command/PasteContent&quot;,  
  &quot;epi-cms/command/CopyContent&quot;,

  &quot;epi-propertydemo/command/CopySingleContent&quot;,
  &quot;epi-propertydemo/command/PasteContent&quot;,
  &quot;epi-propertydemo/command/CopyContent&quot;
], function (
  declare,
  array,
  aspect,

  _Module,
  dependency,

  navigationTreePluginArea,
  MainNavigationComponent,
  PasteContent,
  CopyContent,

  CopySingleContent,
  CustomPasteContent,
  CustomCopyContent
) {
    return declare([_Module], {

      initialize: function () {

          this.inherited(arguments);

          var widgetFactory = dependency.resolve(&quot;epi.shell.widget.WidgetFactory&quot;);
          aspect.after(widgetFactory, &quot;onWidgetCreated&quot;, function (widget, componentDefinition) {
            if (widget.isInstanceOf(MainNavigationComponent)) {                
              aspect.after(widget, &quot;own&quot;, function () {
                var cmd = new CopySingleContent({
                    category: &quot;context&quot;,
                    clipboard: this.tree._clipboardManager,
                    selection: this.tree.selection,
                    model: this.tree.model
                });
                navigationTreePluginArea.add(cmd);

                var self = this;
                aspect.after(this.tree._contextMenuCommandProvider, &quot;get&quot;, function (commands) {
                  
                  for (var i = 0; i &amp;lt; commands.length; i++) {
                    var command = commands[i];
                    if (command.isInstanceOf(PasteContent)) {
                        commands[i] = new CustomPasteContent({
                            category: &quot;context&quot;,
                            clipboard: self.tree._clipboardManager,
                            selection: self.tree.selection,
                            model: self.tree.model
                        });
                    }
                    if (command.isInstanceOf(CopyContent) &amp;amp;&amp;amp; !command.isInstanceOf(CopySingleContent)) {
                        commands[i] = new CustomCopyContent({
                            category: &quot;context&quot;,
                            clipboard: self.tree._clipboardManager,
                            selection: self.tree.selection,
                            model: self.tree.model
                        });
                    }
                  }
                  
                  return commands;
                });                
              });              
            }              
          }, true);         
      }
    });
  });&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;We have completed client-side work, now is server-side.&amp;nbsp;To decide to perform &quot;single copy&quot; or &quot;normal copy&quot;, we need create an intercept for&amp;nbsp;IContentCopyHandler.&amp;nbsp;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt; public class ContentCopyHandlerIntercept : IContentCopyHandler
    {
        private readonly IContentCopyHandler _defaultHandler;
        public ContentCopyHandlerIntercept(IContentCopyHandler defaultHandler)
        {
            _defaultHandler = defaultHandler;
        }

        public ContentReference Copy(ContentReference contentLink, ContentReference destinationLink, AccessLevel requiredSourceAccess, bool publishOnDestination)
        {
            var singleCopy = HttpContext.Current.Request.Headers[&quot;X-EpiCopyMode&quot;] == &quot;single&quot;;
            if(!singleCopy)
            {
                return _defaultHandler.Copy(contentLink, destinationLink, requiredSourceAccess, publishOnDestination);
            }

            var customContentCopyHandler = ServiceLocator.Current.GetInstance&amp;lt;SingleContentCopyHandler&amp;gt;();
            return customContentCopyHandler.Copy(contentLink, destinationLink, requiredSourceAccess, publishOnDestination);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We need register this intercept at initialization time&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[ModuleDependency(typeof(Cms.Shell.InitializableModule))]
    public class InitializationModule : IConfigurableModule
    {
        public void ConfigureContainer(ServiceConfigurationContext context)
        {
            context.ConfigurationComplete += (o, e) =&amp;gt;
            {
                context.Services.Intercept&amp;lt;IContentCopyHandler&amp;gt;((locator, defaultImplement) =&amp;gt;
                {
                    return new ContentCopyHandlerIntercept(defaultImplement);
                });
            };            
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Finally, we have to implement the&amp;nbsp;SingleContentCopyHandler.&amp;nbsp;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[ServiceConfiguration(typeof(IContentCopyHandler))]
    public class SingleContentCopyHandler : IContentCopyHandler
    {
        private class CopyContext
        {
            public CopyContext()
            {
                ExportSources = new List&amp;lt;ExportSource&amp;gt;();
            }

            public IList&amp;lt;ExportSource&amp;gt; ExportSources { get; set; }
            public ContentReference DestinationRoot { get; set; }
            public ContentReference OriginalLink { get; set; }
            public ContentReference NewLink { get; set; }
            public bool PublishOnDestination { get; set; }
            public bool InBackground { get; set; }
            public int PageCount { get; set; }
            public String LanguageCode { get; set; }
            public AccessLevel RequiredSourceAccess { get; set; }
        }        

        public SingleContentCopyHandler(IContentRepository contentRepository,
            ServiceAccessor&amp;lt;IDataImporter&amp;gt; dataImporterAccessor, 
            ServiceAccessor&amp;lt;IDataExporter&amp;gt; dataExporterAccessor,
            ContentOptions contentOptions)
        {
            _contentRepository = contentRepository;
            _dataImporterAccessor = dataImporterAccessor;
            _dataExporterAccessor = dataExporterAccessor;
            _contentOptions = contentOptions;
        }

        private static readonly ILog _log = LogManager.GetLogger(typeof(ContentCopyHandler));
        private readonly IContentRepository _contentRepository;
        private readonly ServiceAccessor&amp;lt;IDataImporter&amp;gt; _dataImporterAccessor;
        private readonly ServiceAccessor&amp;lt;IDataExporter&amp;gt; _dataExporterAccessor;
        private readonly ContentOptions _contentOptions;


        #region IContentCopyHandler Members

        /// &amp;lt;summary&amp;gt;
        /// Copy pages to another container.
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;param name=&quot;pageLink&quot;&amp;gt;The link to the content data to copy.&amp;lt;/param&amp;gt;
        /// &amp;lt;param name=&quot;destinationLink&quot;&amp;gt;The container where the page will be copied&amp;lt;/param&amp;gt;
        /// &amp;lt;param name=&quot;requiredSourceAccess&quot;&amp;gt;The required source access to check access against&amp;lt;/param&amp;gt;
        /// &amp;lt;param name=&quot;publishOnDestination&quot;&amp;gt;If the new pages should be published on the destination&amp;lt;/param&amp;gt;
        /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
        public virtual ContentReference Copy(ContentReference pageLink, ContentReference destinationLink, AccessLevel requiredSourceAccess, bool publishOnDestination)
        {
            CopyContext arguments = new CopyContext()
            {
                NewLink = ContentReference.EmptyReference,
                OriginalLink = pageLink,
                DestinationRoot = destinationLink,
                PublishOnDestination = publishOnDestination,
                PageCount = 1,
                LanguageCode = Thread.CurrentThread.CurrentUICulture.Name,
                RequiredSourceAccess = requiredSourceAccess
            };

            arguments.ExportSources.Add(new ExportSource(arguments.OriginalLink, ExportSource.NonRecursive));
            ExecuteCopying(arguments);

            //Export:Review
            return arguments.NewLink;
        }

        #endregion
        
        private void ExecuteCopying(CopyContext context)
        {
            bool useFile = context.PageCount &amp;gt; _contentOptions.InMemoryCopyThreshold;
            string filePath = useFile ? Path.GetTempFileName() : null;
            try
            {
                using (Stream stream = CreateStream(useFile, filePath))
                {
                    using (var exporter = _dataExporterAccessor())
                    {

                        var options = new ExportOptions()
                        {
                            TransferType = TypeOfTransfer.Copying,
                            RequiredSourceAccess = context.RequiredSourceAccess,
                            AutoCloseStream = false,
                            ExcludeFiles = false
                        };

                        var exporterLog = exporter.Export(stream, context.ExportSources, options);
                        if (exporter.Status.Log.Errors.Count &amp;gt; 0)
                        {
                            Rollback(context, exporter.Status.Log);
                        }
                    }

                    stream.Seek(0, SeekOrigin.Begin);

                    var importer = _dataImporterAccessor();
                    var copyOptions = new ImportOptions()
                    {
                        TransferType = TypeOfTransfer.Copying,
                        EnsureContentNameUniqueness = true,
                        SaveAction = (context.PublishOnDestination ? SaveAction.Publish : SaveAction.CheckOut) | SaveAction.SkipValidation
                    };

                    var log = importer.Import(stream, context.DestinationRoot, copyOptions);
                    context.NewLink = importer.Status.ImportedRoot;
                    if (importer.Status.Log.Errors.Count &amp;gt; 0)
                    {
                        Rollback(context, importer.Status.Log);
                    }
                }
            }
            finally
            {
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                }
            }
        }

        private static Stream CreateStream(bool useFile, string filePath)
        {
            if (useFile)
            {
                return new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite);
            }
            else
            {
                return new MemoryStream();
            }
        }

        private void Rollback(CopyContext context, ITransferLogger transferLogger)
        {
            try
            {
                if (!ContentReference.IsNullOrEmpty(context.NewLink))
                {
                    _contentRepository.Delete(context.NewLink, true, EPiServer.Security.AccessLevel.NoAccess);
                }
            }
            catch (Exception ex)
            {
                transferLogger.Error(String.Format(&quot;Failed to rollback creation of &#39;{0}&#39;&quot;, context.NewLink.ToString()), ex);
            }

            if (_log.IsErrorEnabled)
            {
                _log.ErrorFormat(&quot;Failed to copy pages with root &#39;{0}&#39; to &#39;{1}&#39;&quot;,
                    context.OriginalLink.ToString(), context.DestinationRoot.ToString());
            }

            String translatedMessage = LocalizationService.Current.GetStringByCulture(&quot;/copy/backgroundreport/failed&quot;, &quot;Failed to copy content &#39;{0}&#39; to &#39;{1}&#39;&quot;, CultureInfo.GetCultureInfo(context.LanguageCode));
            String formattedMessage = String.Format(translatedMessage, _contentRepository.Get&amp;lt;IContent&amp;gt;(context.OriginalLink)?.Name, _contentRepository.Get&amp;lt;IContent&amp;gt;(context.DestinationRoot)?.Name);
            String errorMessage = String.Format(&quot;{0}:{1}&quot;, formattedMessage, String.Join(&quot;,&quot;, transferLogger.Errors.Cast&amp;lt;String&amp;gt;().ToArray&amp;lt;string&amp;gt;()));
            throw new EPiServerException(errorMessage);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The main point here is the line : arguments.ExportSources.Add(new ExportSource(arguments.OriginalLink, ExportSource.NonRecursive)); It allows to copy just content without its children.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;And here is the result for a very long code:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/link/0524ec6994904903a6f92f1c135f0984.aspx&quot; alt=&quot;Image TtDWKW6yLa.gif&quot; /&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</id><updated>2018-03-01T07:01:23.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Custom FieldSet emelent block for EPiServer.Form</title><link href="https://world.optimizely.com/blogs/le-giang/dates/2018/2/custom-fieldset-emelent-block-for-episerver-form/" /><id>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;Hi there.&lt;/p&gt;
&lt;p&gt;In this post, I will show you how to create a custom field set element block for FORM addon. This block aims to group some of form elements and add some pretty appearances for the form. And this is what we will achieve.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/9860ad0e575f41dbb6ed87553527e509.aspx&quot; alt=&quot;Image custom_form_1.png&quot; /&gt;&lt;img src=&quot;/link/fbd544900b7d4f9ca3ea9c3a16937fd4.aspx&quot; alt=&quot;Image custom_form_submissions_1.png&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new form element block called &quot;FieldsetBlock&quot; which cotains a element area allow user to drag and drop other form elements into. I also introduce a new interface called &quot;IFieldSet&quot;, we will use this for some additional logic later.&amp;nbsp;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[ContentType(GUID = &quot;2A4F56F9-2D3A-4C0A-AD78-1381D6A57808&quot;, GroupName = &quot;Layout&quot;, DisplayName = &quot;Field set&quot;)]
    public class FieldsetBlock: ElementBlockBase, IFieldSet, IExcludeInSubmission
    {
        [AllowedTypes(new[] { typeof(DataElementBlockBase) })]
        [Display(GroupName = &quot;Elements&quot;)]
        [UIHint(&quot;FormsContentAreaEditor&quot;)]
        public virtual ContentArea ElementsArea { get; set; }

        public virtual string Title { get; set; }
    }

    public interface IFieldSet
    {
        ContentArea ElementsArea { get; set; }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Create a view for the&amp;nbsp;&lt;span&gt;&lt;span&gt;FieldsetBlock&lt;/span&gt;&lt;/span&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code&gt;@model EpiserverSite2.Models.FormElement.FieldsetBlock

&amp;lt;div class=&quot;Form__Element fieldsetBlock&quot;&amp;gt;
    &amp;lt;fieldset&amp;gt;
        &amp;lt;legend class=&quot;fieldset_title&quot; @Html.EditAttributes(x =&amp;gt; x.Title)&amp;gt;@Model.Title&amp;lt;/legend&amp;gt;
        @Html.PropertyFor(x =&amp;gt; x.ElementsArea)
    &amp;lt;/fieldset&amp;gt;
&amp;lt;/div&amp;gt;  &lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;By the default, form elements have no on-page edit view, but the field set element need one. Add this code to restore the on-page edit view for the f&lt;span&gt;&lt;span&gt;ield set element:&lt;/span&gt;&lt;/span&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[UIDescriptorRegistration]
    public class FieldsetBlockUIDescriptor: UIDescriptor&amp;lt;FieldsetBlock&amp;gt;
    {
        public FieldsetBlockUIDescriptor()
        {
            DisabledViews = new List&amp;lt;string&amp;gt;();
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;We need to override the way the FORM retrieves form elements to include &quot;sub elements&quot; contained in the field set blocks. We have to because by the default, FORM only retrieves &quot;first-level&quot; elements.
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[ServiceConfiguration(ServiceType = typeof(DataSubmissionService), Lifecycle = ServiceInstanceScope.Singleton)]
    public class CustomDataSubmissionService : DataSubmissionService
    {
        protected override IFormStep GetCurrentStep(NameValueCollection rawSubmittedData, FormContainerBlock formContainer)
        {            
            var step = base.GetCurrentStep(rawSubmittedData, formContainer);
            var lang = (formContainer as ILocale).Language.Name;
            var elements = new List&amp;lt;IFormElement&amp;gt;(step.Elements);

            foreach (var el in step.Elements)
            {
                if (el.SourceContent is IFieldSet)
                {
                    var allElements = (el.SourceContent as IFieldSet).ElementsArea.Items;
                    foreach (var item in allElements)
                    {
                        var formElementBlock = item.ContentLink.GetContent(lang) as ElementBlockBase;
                        
                        if (formElementBlock != null &amp;amp;&amp;amp; !elements.Contains(formElementBlock.FormElement))
                        {
                            elements.Add(formElementBlock.FormElement);
                        }
                    }
                }
            }
            step.Elements = elements;
            return step;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;&amp;nbsp;[ServiceConfiguration(ServiceType = typeof(FormBusinessService), Lifecycle = ServiceInstanceScope.Singleton)]
    public class CustomFormBusinessService: FormBusinessService
    {
        public override IEnumerable&amp;lt;ElementBlockBase&amp;gt; GetDisplayableFormElementBlocks(FormContainerBlock formContainerBlock)
        {
            var allElements = GetFormElementBlocks(formContainerBlock, false);
            var elementList = new List&amp;lt;ElementBlockBase&amp;gt;(allElements);
            var lang = (formContainerBlock as ILocale).Language.Name;

            foreach (var el in allElements)
            {
                if (el is IFieldSet)
                {
                    var allSubElements = (el as IFieldSet).ElementsArea?.Items;
                    if (allSubElements != null &amp;amp;&amp;amp; allSubElements.Count &amp;gt; 0)
                    {
                        foreach (var item in allSubElements)
                        {
                            var formElementBlock = item.ContentLink.GetContent(lang) as ElementBlockBase;

                            if (formElementBlock != null &amp;amp;&amp;amp; !elementList.Contains(formElementBlock))
                            {
                                elementList.Add(formElementBlock);
                            }
                        }
                    }
                    
                }
            }

            return elementList;
        }

        /// &amp;lt;summary&amp;gt;
        /// Return all FormElementBlock of this FomrContainer, regardless the publish, visibility status or personalization content condition.
        /// &amp;lt;/summary&amp;gt;
        /// &amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;
        public override IEnumerable&amp;lt;ElementBlockBase&amp;gt; GetAllInnerFormElementBlocks(FormContainerBlock formContainerBlock)
        {
            var allElements = GetFormElementBlocks(formContainerBlock, true);
            var elementList = new List&amp;lt;ElementBlockBase&amp;gt;(allElements);
            var lang = (formContainerBlock as ILocale).Language.Name;

            foreach (var el in allElements)
            {
                if (el is IFieldSet)
                {
                    var allSubElements = (el as IFieldSet).ElementsArea?.Items;
                    if (allSubElements != null &amp;amp;&amp;amp; allSubElements.Count &amp;gt; 0)
                    {
                        foreach (var item in allSubElements)
                        {
                            var formElementBlock = item.ContentLink.GetContent(lang) as ElementBlockBase;

                            if (formElementBlock != null &amp;amp;&amp;amp; !elementList.Contains(formElementBlock))
                            {
                                elementList.Add(formElementBlock);
                            }
                        }
                    }
                }
            }

            return elementList;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;Make sure these custom services take place the original ones.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;context.Services.AddTransient&amp;lt;DataSubmissionService, DataSubmissionService&amp;gt;()
                    .AddTransient&amp;lt;DataSubmissionService, CustomDataSubmissionService&amp;gt;();

                context.Services.AddTransient&amp;lt;FormBusinessService, FormBusinessService&amp;gt;()
                    .AddTransient&amp;lt;FormBusinessService, CustomFormBusinessService&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Since we have changed the way FORM retrieves form elements,&amp;nbsp; so we&amp;nbsp;have to&amp;nbsp;remove redundant elements and empty field sets before rendering the form. To do this, we need a custom form container block controller.
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[TemplateDescriptor(AvailableWithoutTag = true, Default = true, ModelType = typeof(FormContainerBlock), TemplateTypeCategory = TemplateTypeCategories.MvcPartialController)]
    public class CustomFormContainerBlockController: FormContainerBlockController, IRenderTemplate&amp;lt;FormContainerBlock&amp;gt;
    {
        public override ActionResult Index(FormContainerBlock currentBlock) // Note: convention, the parameter must be &quot;currentBlock&quot;, not formBlock or another
        {
            var result = base.Index(currentBlock) as PartialViewResult;
            var formModel = result.Model as FormContainerBlock;

            if (formModel != null)
            {
                var steps = new List&amp;lt;IFormStep&amp;gt;(formModel.Form.Steps);
                var allSubItems = new List&amp;lt;ContentReference&amp;gt;();

                // find all sub items (child of FieldSetElement)
                foreach (var step in steps)
                {
                    foreach(var el in step.Elements)
                    {
                        if (el.SourceContent is IFieldSet)
                        {
                            var fieldSetElement = el.SourceContent as IFieldSet;
                            if (fieldSetElement.ElementsArea != null &amp;amp;&amp;amp; fieldSetElement.ElementsArea.Items.Count &amp;gt; 0)
                            {
                                allSubItems.AddRange(fieldSetElement.ElementsArea.Items.Select(i =&amp;gt; i.ContentLink));
                            }                            
                        }
                    }
                }

                // Remove redundant elements
                foreach (var step in steps)
                {
                    var elements = new List&amp;lt;IFormElement&amp;gt;(step.Elements);
                    foreach (var el in step.Elements)
                    {
                        if (el.SourceContent is IFieldSet)
                        {
                            var fieldSetElement = el.SourceContent as IFieldSet;
                            if (fieldSetElement.ElementsArea == null || fieldSetElement.ElementsArea.Items.Count == 0)
                            {
                                elements.Remove(el);
                            }
                        }

                        if (allSubItems.Contains(el.SourceContent.ContentLink))
                        {
                            elements.Remove(el);
                        }
                    }
                    step.Elements = elements;
                }

                formModel.Form.Steps = steps;
            }
            return result;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Finally, add some style for the field set element.
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;.fieldsetBlock fieldset {
    border: 1px groove red;
    padding: 1em;
}

.fieldsetBlock fieldset legend {
    width: auto;
    margin: 0 5px;
    padding: 0 5px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/body&gt;
&lt;/html&gt;</id><updated>2018-02-08T09:17:24.0000000Z</updated><summary type="html">Blog post</summary></entry> <entry><title>Content type filter when creating a new content</title><link href="https://world.optimizely.com/blogs/le-giang/dates/2018/1/create-content-type-filter-for-createcontent-view/" /><id>&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p&gt;&lt;span&gt;By the default, when you create a new content (page or block), the CreateView view will be displayed and allows you to choose content type for the content is being created. If you have a lot of content types, then the selecting action can get difficult a bit.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/fbf055a097104c988ed2942474c39dce.aspx&quot; alt=&quot;Image createview_nofilter.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;This blog will show you how to create &quot;filter function&quot; to quickly narrow content types in the CreateContent view when creating content.&lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;span&gt;Replace the default CreateContent view by our custom one:&lt;/span&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;define([
  &quot;dojo/_base/declare&quot;,
  &quot;dojo/aspect&quot;,

  &quot;epi/_Module&quot;,  
  &quot;epi/routes&quot;,
  &quot;epi/dependency&quot;
], function (
  declare,
  aspect,

  _Module,  
  routes,
  dependency
) {
  return declare([_Module], {          
    initialize: function () {
      this.inherited(arguments);

      this._replaceCreateCommand();
    },

    _replaceCreateCommand: function () {
      var widgetFactory = dependency.resolve(&quot;epi.shell.widget.WidgetFactory&quot;);
      aspect.after(widgetFactory, &quot;onWidgetCreated&quot;, function (widget, componentDefinition) {
        if (componentDefinition.widgetType === &quot;epi/shell/widget/WidgetSwitcher&quot;) {
          aspect.around(widget, &quot;viewComponentChangeRequested&quot;, function (originalMethod) {            
            return function () {              
              if (arguments[0] === &quot;epi-cms/contentediting/CreateContent&quot;) {
                arguments[0] = &quot;epi-contactmanager/contentediting/CreateContent&quot;;
              }
              originalMethod.apply(this, arguments);
            };
          });
        }
      }, true);
    }
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;In the custom CreateContent view, add a search box next to the Name text box, and handle its&amp;nbsp;searchBoxChange event.&lt;/span&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;define([
  &quot;dojo/_base/declare&quot;,
  &quot;dojo/_base/lang&quot;,
  &quot;dojo/_base/array&quot;,
  &quot;dojo/aspect&quot;,
  &quot;dojo/dom-style&quot;,
  &quot;dojo/dom-class&quot;,
  &quot;epi-cms/contentediting/CreateContent&quot;,
  &quot;epi/shell/widget/SearchBox&quot;
], function (
  declare,
  lang,
  array,
  aspect,
  domStyle,
 domClass,
  CreateContent,
  SearchBox
) {
  return declare([CreateContent], {
     
      postCreate: function () {
          this.inherited(arguments);
          // search box
          this.own(this._searchBox = new SearchBox({}));
          this._searchBox.placeAt(this.namePanel, &quot;last&quot;);
          domStyle.set(this._searchBox.domNode, &quot;width&quot;, &quot;auto&quot;);
          domClass.add(this.namePanel, &quot;epi-gadgetInnerToolbar&quot;);
          this.own(
            this._searchBox.on(&quot;searchBoxChange&quot;, lang.hitch(this, this._onSearchTextChanged)),
             
            aspect.before(this.contentTypeList, &quot;refresh&quot;, lang.hitch(this, function () {
                // reset the search box and _originalGroups
                 this._searchBox.clearValue();
                 this._originalGroups = null;
            })),
 
            aspect.after(this.contentTypeList, &quot;setVisibility&quot;, lang.hitch(this, function (display) {
                 if (!display) {
                     domStyle.set(this._searchBox.domNode, &quot;display&quot;, &quot;none&quot;);
                 }                 
             }), true)
          );
      },
      _onSearchTextChanged: function (queryText) {
          this._originalGroups = this._originalGroups || lang.clone(this.contentTypeList.get(&quot;groups&quot;));
          var groupKeys = Object.keys(this._originalGroups);
           
          array.forEach(groupKeys, function (key) {
              var contentTypes = this._originalGroups[key].get(&quot;contentTypes&quot;);
              contentTypes = array.filter(contentTypes, function (item) {
                  return item.name.toLowerCase().indexOf(queryText.toLowerCase()) != -1;
              });
              if (!contentTypes.length) {                 
                  domStyle.set(this.contentTypeList.groups[key].domNode, &quot;display&quot;, &quot;none&quot;);
              }
              else {
                  domStyle.set(this.contentTypeList.groups[key].domNode, &quot;display&quot;, &quot;&quot;);
                  this.contentTypeList.groups[key].set(&quot;contentTypes&quot;, contentTypes);
              }             
          }, this);
      }
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;Enjoy the filtering!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;/link/3417b18901f647d98d7099b095d65a11.aspx&quot; alt=&quot;Image createcontent_filtered.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;[Pasting files is not allowed]&lt;/span&gt;&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</id><updated>2018-01-25T02:56:38.0000000Z</updated><summary type="html">Blog post</summary></entry></feed>