<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><language>en</language><title>Blog posts by pjangid</title> <link>https://world.optimizely.com/blogs/pjangid/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>Boosting Indexing Efficiency: Reindex Pages Directly from Optimizely’s Navigation Pane</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2025/6/adding-html-templates-in-rte-field-tinymce/</link>            <description>&lt;p&gt;There can be various reasons why you might want to trigger indexing or reindexing of a page/node directly from the navigation pane. In my case, we were working with a large volume of content and needed to test newly introduced custom indexing fields. Waiting for the entire site to be reindexed through the standard indexing job wasn&amp;rsquo;t feasible.&lt;/p&gt;
&lt;p&gt;While there are certainly other ways to handle this, adding a manual reindex trigger proved especially helpful in our upper environments (such as Integration and Preproduction), where faster feedback loops are critical.&lt;/p&gt;
&lt;p&gt;Here&#39;s a quick look at how it appears in the UI:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/731db5e34b81489d9e5a17fa840ae8aa.aspx&quot; width=&quot;461&quot; height=&quot;476&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;To implement this, I used&amp;nbsp;&lt;strong&gt;Dojo&lt;/strong&gt; to create new navigation items and added them to Optimizely&amp;rsquo;s plug-in area within the navigation tree. Here&#39;s the setup from&amp;nbsp;&lt;code&gt;Initializer.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code&gt;define([
    &quot;dojo&quot;,
    &#39;dojo/_base/declare&#39;,
    &#39;epi-cms/plugin-area/navigation-tree&#39;,
    &#39;epi/_Module&#39;,
    &#39;optimizelyModules/ReIndexTree/ReIndexTree&#39;,
    &#39;optimizelyModules/ReIndexTree/ReIndexChildren&#39;
], function (
    dojo,
    declare,
    navigationTreePluginArea,
    _Module,
    ReIndexTree,
    ReIndexChildren
) {

    return declare([_Module], {
        initialize: function () {
            this.inherited(arguments);
            navigationTreePluginArea.add(ReIndexTree);
            navigationTreePluginArea.add(ReIndexChildren);
        }
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Both &lt;code&gt;ReIndexChildren.js&lt;/code&gt; and &lt;code&gt;ReIndexTree.js&lt;/code&gt; follow a similar pattern, differing mainly in naming and intent. For reference, here&amp;rsquo;s the code for &lt;code&gt;ReIndexChildren.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code&gt;define([
    &quot;dojo/topic&quot;,
    &quot;dojo/_base/declare&quot;,
    &quot;epi/dependency&quot;,
    &quot;epi/shell/XhrWrapper&quot;,
    &quot;epi/shell/command/_Command&quot;
], function (topic, declare, dependency, XhrWrapper, _Command) {

    return declare([_Command], {

        label: &quot;Reindex Children&quot;,
        iconClass: &quot;epi-iconReload epi-icon--success&quot;,

        constructor: function () {
            var registry = dependency.resolve(&quot;epi.storeregistry&quot;);
            this.store = registry.get(&quot;epi.cms.contentdata&quot;);
        },

        _execute: function () {
            var contentLinkId = this.model.contentLink;

            // Create an XhrWrapper for making the API call
            var xhr = new XhrWrapper();
            xhr.xhrGet({
                url: &quot;/api/indexing/reindex-tree&quot;,
                handleAs: &quot;json&quot;,

                content: {
                    contentLinkId: contentLinkId,
                    childrenOnly: true
                },

                failOk: true,

                load: function (response) {
                    // Handle the success response (e.g., show a message)
                    console.log(&quot;Reindexing successful:&quot;, response);
                },

                error: function (response) {
                    // Handle the error response (e.g., show an error message)
                    console.error(&quot;Error reindexing:&quot;, error);
                }
            });
        },

        _onModelChange: function () {
            this.set(&quot;canExecute&quot;, true);
        }
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In the above implementation, I&amp;rsquo;m calling a custom controller endpoint at&amp;nbsp;&lt;code&gt;/api/indexing/reindex-tree&lt;/code&gt;. This endpoint accepts two parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;childrenOnly&lt;/code&gt;: A flag indicating whether to reindex only the direct children or the entire content tree under the selected node.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;contentLinkId&lt;/code&gt;: The ID of the selected node, which is used to identify the starting point for reindexing.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach gives editors or developers an easy way to trigger indexing selectively, without needing to reindex the entire site.&lt;/p&gt;
&lt;p&gt;You can find the full code in the GitHub repository (branch) here: &lt;a href=&quot;https://github.com/pjangid/OptimizelyReindexModule/tree/feature/opti-module-reindex-tree&quot;&gt;https://github.com/pjangid/OptimizelyReindexModule/tree/feature/opti-module-reindex-tree&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;As always, there&amp;rsquo;s room for improvement. I&amp;rsquo;d love to hear your thoughts&amp;mdash;please feel free to leave suggestions or feedback in the comments below.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Thanks &amp;amp; Regard,&lt;/p&gt;
&lt;p&gt;Praful&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2025/6/adding-html-templates-in-rte-field-tinymce/</guid>            <pubDate>Wed, 11 Jun 2025 04:48:36 GMT</pubDate>           <category>Blog post</category></item><item> <title>Optimizely SaaS vs PaaS: A Comparison from Client and Developer Perspectives</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2025/2/optimizely-saas-vs-paas-a-comparison-from-client-and-developer-perspectives/</link>            <description>&lt;p&gt;Optimizely, one of the leading digital experience platform. Offering both &lt;strong&gt;Software-as-a-Service (SaaS)&lt;/strong&gt; and &lt;strong&gt;Platform-as-a-Service (PaaS)&lt;/strong&gt; solutions. Organizations choosing between these options must consider factors such as scalability, customization, maintenance, and development flexibility. In this article we are going to talk about the key differences between Optimizely SaaS and PaaS from both &lt;strong&gt;client&lt;/strong&gt; and &lt;strong&gt;developer&lt;/strong&gt; perspectives.&lt;/p&gt;
&lt;h2&gt;Overview of Optimizely SaaS and PaaS&lt;/h2&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Optimizely SaaS&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Fully managed cloud-based service.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automatic updates and patches.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Limited customization but quick deployment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Best suited for businesses looking for a low-maintenance, high-availability solution.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Optimizely PaaS&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Hosted in a managed cloud environment with more control.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Requires ongoing maintenance, including updates and security.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Offers greater flexibility for customization and integration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Best suited for businesses needing tailored solutions with specific functionalities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Comparison from a Client&amp;rsquo;s Perspective&lt;/h2&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Cost &amp;amp; Pricing&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Subscription-based pricing with predictable costs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Higher upfront costs with variable hosting expenses.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Ease of Use &amp;amp; Maintenance&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Managed by Optimizely, reducing IT overhead and maintenance efforts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Requires internal IT resources for deployment, security, and scaling.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Scalability &amp;amp; Performance&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Automatically scales with demand, ensuring uptime and performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Scaling requires configuration and monitoring from the client&amp;rsquo;s IT team.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Customization &amp;amp; Flexibility&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Limited customization options; best for businesses with standard needs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: High degree of customization, allowing unique integrations and tailored workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Security &amp;amp; Compliance&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Security and compliance are managed by Optimizely, reducing client concerns.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Requires internal governance and security management, adding complexity.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Comparison from a Developer&amp;rsquo;s Perspective&lt;/h2&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Development Flexibility&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Limited ability to customize backend functionalities; mostly front-end modifications.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Full control over development, with access to APIs, extensions, and third-party integrations.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;DevOps &amp;amp; CI/CD&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Minimal need for DevOps management, as infrastructure is handled by Optimizely.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Requires setup and maintenance of CI/CD pipelines, infrastructure, and monitoring.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Integration Capabilities&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Predefined integrations and API limitations may restrict some use cases.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Allows deep integrations with custom systems, databases, and third-party services.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;Deployment &amp;amp; Version Control&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SaaS&lt;/strong&gt;: Automatic updates without client intervention; risk of breaking changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;PaaS&lt;/strong&gt;: Controlled deployment cycles, allowing developers to test and release updates at their own pace.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Which One Should You Choose?&lt;/h2&gt;
&lt;table style=&quot;margin-left: 40px;&quot;&gt;
&lt;tbody style=&quot;padding-left: 40px;&quot;&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;th style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Factor&lt;/th&gt;
&lt;th style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Optimizely SaaS&lt;/th&gt;
&lt;th style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Optimizely PaaS&lt;/th&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Cost &amp;amp; Pricing&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Predictable, subscription-based&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Higher upfront, variable costs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Maintenance&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Fully managed by Optimizely&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Requires internal IT management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Customization&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Limited&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Highly customizable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Scalability&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Automatic&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Requires configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Security &amp;amp; Compliance&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Managed by Optimizely&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Client-managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Developer Flexibility&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Limited backend access&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Full control over development&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;padding-left: 40px;&quot;&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Deployment Control&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Automatic updates&lt;/td&gt;
&lt;td style=&quot;padding-left: 40px; text-align: left;&quot;&gt;Controlled release cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;When to Choose Optimizely SaaS&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you need a &lt;strong&gt;low-maintenance&lt;/strong&gt; solution with &lt;strong&gt;automatic updates&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If your business processes fit within standard Optimizely capabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If cost predictability and ease of use are top priorities.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;padding-left: 40px;&quot;&gt;&lt;strong&gt;When to Choose Optimizely PaaS&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If your business requires &lt;strong&gt;custom integrations and development flexibility&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you have an in-house IT team to manage updates, security, and scaling.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you need greater control over &lt;strong&gt;deployment cycles and infrastructure&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Short &amp;amp; Sweet Conclusion&lt;/h2&gt;
&lt;p&gt;Both &lt;strong&gt;Optimizely SaaS and PaaS&lt;/strong&gt; offer robust solutions, but the right choice depends on your organization&amp;rsquo;s needs. &lt;strong&gt;Clients&lt;/strong&gt; should weigh cost, maintenance, and customization, while &lt;strong&gt;developers&lt;/strong&gt; should consider flexibility and control over the infrastructure. By carefully assessing these factors, businesses can ensure they select the best Optimizely platform for their digital experience strategy. You can read for more information on the &lt;a href=&quot;https://docs.developers.optimizely.com/&quot;&gt;Optimizely&#39;s documentations site&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;/link/6d3bb925352f40a183d98c7fe6aae451.aspx&quot;&gt;https://world.optimizely.com/products/cms/saas/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2025/2/optimizely-saas-vs-paas-a-comparison-from-client-and-developer-perspectives/</guid>            <pubDate>Mon, 03 Feb 2025 13:09:40 GMT</pubDate>           <category>Blog post</category></item><item> <title>Bulk publishing of unpublished content items/nodes</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2021/2/bulk-publishing-of-unpublished-content-itemsnodes/</link>            <description>&lt;p&gt;In Episerver, we don&#39;t have any built in feature that allows us to publish full site (bulk items) in one go. We recently faced one challenge to publish all category nodes.&lt;/p&gt;
&lt;p&gt;In CMS, we have an option of Project to perform bulk item update (change of state or publish). But in commerce we don&#39;t. And, in our case, we did import of content using custom code and a huge number of items were not published (even we used the SaveAction.Publish).&lt;/p&gt;
&lt;p&gt;Then, we decided to write custom code to do that (via scheduled job). And, after doing some RnD, I found that the following Method returns only published items.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;ContentRepository.GetChildren&amp;lt;T&amp;gt;(ContentReference contentLink)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, the question is how to retrieve the unpublished node/items?&lt;/p&gt;
&lt;p&gt;Here is the answer, use the Language option with GetChildren method.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;ContentRepository.GetChildren&amp;lt;NodeContent&amp;gt;(parentNode.ContentLink, LanguageSelector.AutoDetect(true))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is just a trick to get the unpublished items/nodes. And, for bulk publish, you can use the&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;ContentRepository.Publish(listOfPages);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That&#39;s it. You are done.&lt;/p&gt;

&lt;p&gt;Thanks&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2021/2/bulk-publishing-of-unpublished-content-itemsnodes/</guid>            <pubDate>Sat, 06 Feb 2021 07:26:50 GMT</pubDate>           <category>Blog post</category></item><item> <title>Skip jobs to execute on specific day(s) of the week</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2019/12/skip-jobs-to-execute-on-specific-days-of-the-week/</link>            <description>&lt;p&gt;Hi All,&lt;/p&gt;
&lt;p&gt;Recently came across a requirement of skipping jobs execution on weekends. So, I thought why not allow editors to control all/any jobs to stop execute on specific days of week.&lt;/p&gt;
&lt;p&gt;So, I took inspiration from &lt;strong&gt;@Paul&lt;/strong&gt;&#39;s reply on &lt;a href=&quot;/link/e348c78e8746471f931272aa98573e0b.aspx&quot;&gt;this thread&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First, I created SelectionFactory for all existing jobs on StartPage (Home page), you can create this property on your setting item (as per your wish). Along with another property to select days of week to disable the jobs.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[Display(
    Name = &quot;Scheduled jobs&quot;,
    Description = &quot;Select jobs to stop execution on specific day&quot;,
    GroupName = Global.GroupNames.SiteSettings,
    Order = 1000)]
[SelectMany(SelectionFactoryType = typeof(ScheduledJobsSelectionFactory))]
public virtual string ScheduledJobs { get; set; }

[Display(
    Name = &quot;Skip job execution on day(s)&quot;,
    Description = &quot;Select days on which you don&#39;t want to execute jobs&quot;,
    GroupName = Global.GroupNames.SiteSettings,
    Order = 1010)]
[SelectMany(SelectionFactoryType = typeof(WeekDaysSelectionFactory))]
public virtual string SkipJobExecutionOnDays { get; set; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which something will look like this in our CMS (in below image).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/a8d8ed65d9564528b5868f946fcedfdf.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;[I selected some jobs and days to disable].&lt;/p&gt;
&lt;p&gt;&lt;em&gt;*I will add code of selection factories at the end&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Next, our scheduled job that will scan all our jobs and check if their next execution is fallin on the skip day, then just updated next execution field and saved back. Here this code contains function to get HomePage.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[ScheduledPlugIn(DisplayName = &quot;Skip JobExecution Scheduled Job&quot;)]
public class SkipJobExecutionScheduledJob : ScheduledJobBase
{
    private bool _stopSignaled;
    private readonly IContentLoader _contentLoader;
    private readonly IScheduledJobRepository _scheduledJobRepository;

    public SkipJobExecutionScheduledJob(
        IContentLoader contentLoader,
        IScheduledJobRepository scheduledJobRepository)
    {
        _contentLoader = contentLoader ?? throw new ArgumentNullException(nameof(contentLoader));
        _scheduledJobRepository = scheduledJobRepository ?? throw new ArgumentNullException(nameof(scheduledJobRepository));
        IsStoppable = true;
    }

    /// &amp;lt;summary&amp;gt;
    /// Called when a user clicks on Stop for a manually started job, or when ASP.NET shuts down.
    /// &amp;lt;/summary&amp;gt;
    public override void Stop()
    {
        _stopSignaled = true;
    }

    /// &amp;lt;summary&amp;gt;
    /// Called when a scheduled job executes
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;returns&amp;gt;A status message to be stored in the database log and visible from admin mode&amp;lt;/returns&amp;gt;
    public override string Execute()
    {
        //Call OnStatusChanged to periodically notify progress of job for manually started jobs
        OnStatusChanged($&quot;Starting execution of {this.GetType()}&quot;);

        var sb = new StringBuilder();
        var homePage = this.GetHomePage();
        if (homePage == null) return $&quot;Could not retrieve site settings&quot;;

        var jobs = homePage.ScheduledJobs?.Split(&#39;,&#39;).ToList();
        var jobToDisable = jobs?.Select(x =&amp;gt; this._scheduledJobRepository.Get(Guid.Parse(x))).ToList()
               ?? new List&amp;lt;ScheduledJob&amp;gt;();
        var skipJobExecutionOnDays = homePage.SkipJobExecutionOnDays?.Split(&#39;,&#39;).ToList();
        if (jobToDisable.Count &amp;gt; 0 &amp;amp;&amp;amp; skipJobExecutionOnDays != null &amp;amp;&amp;amp; skipJobExecutionOnDays.Count &amp;gt; 0)
        {
            foreach (var job in jobToDisable)
            {
                if (skipJobExecutionOnDays.Contains(job.NextExecution.DayOfWeek.ToString()))
                {
                    job.NextExecution = job.NextExecution.AddDays(1);
                    this._scheduledJobRepository.Save(job);
                    OnStatusChanged($&quot;Updated \&quot;{job.Name}\&quot; to run on \&quot;{job.NextExecution}\&quot;&quot;);
                    sb.AppendLine($&quot;Updated \&quot;{job.Name}\&quot; to run on \&quot;{job.NextExecution}\&quot;&quot;);
                }

                //For long running jobs periodically check if stop is signaled and if so stop execution
                if (_stopSignaled)
                {
                    return &quot;Stop of job was called&quot;;
                }
            }
        }

        //For long running jobs periodically check if stop is signaled and if so stop execution
        if (_stopSignaled)
        {
            return &quot;Stop of job was called&quot;;
        }
        var rtn = sb.ToString();
        if (rtn == string.Empty)
        {
            rtn = &quot;Job completed successfully. No updates required.&quot;;
        }
        return rtn;
    }

    private StartPage GetHomePage()
    {
        var pageLink = SiteDefinition.Current.StartPage;
        if (pageLink == null || pageLink == ContentReference.EmptyReference)
            return null;

        this._contentLoader.TryGet(pageLink, out PageData page);

        if (page == null)
            return null;

        return this.GetHomePage(page);
    }

    private StartPage GetHomePage(PageData page)
    {
        if (page == null)
            return null;

        if (page is StartPage homePage)
            return homePage;

        homePage = this._contentLoader
                .GetAncestors(page.ContentLink)
                .FirstOrDefault(x =&amp;gt; x is StartPage)
            as StartPage;

        return homePage;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition, I am adding selection factory code here.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class ScheduledJobsSelectionFactory: ISelectionFactory
{
    public Injected&amp;lt;IScheduledJobRepository&amp;gt; ScheduledJobRepository { get; set; }

    public IEnumerable&amp;lt;ISelectItem&amp;gt; GetSelections(ExtendedMetadata metadata)
    {
        var scheduledJobs = this.ScheduledJobRepository.Service.List();

        var selections =
            scheduledJobs
                .Select(x =&amp;gt; new SelectItem
                {
                    Text = x.Name,
                    Value = x.ID.ToString()
                })
                .ToList();

        return selections;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class WeekDaysSelectionFactory : ISelectionFactory
{
    public IEnumerable&amp;lt;ISelectItem&amp;gt; GetSelections(ExtendedMetadata metadata)
    {
        return Enum
            .GetNames(typeof(DayOfWeek))
            .Select(x =&amp;gt; new SelectItem { Text = x, Value = x })
            .ToList();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On a special note, you can add fields for time range to skip in that duration (Stop Execution Time and Start Execution Time).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Special Note: &lt;/strong&gt;&lt;span class=&quot;ILfuVd&quot;&gt;&lt;span class=&quot;e24Kjd&quot;&gt;This implementation is more focused with single site implementation&lt;/span&gt;&lt;/span&gt;. If you have jobs that are site specific you can follow comment by &lt;strong&gt;@Antti &lt;/strong&gt;(below in comments).&lt;/p&gt;
&lt;p&gt;And to restrict the access you can follow &lt;a href=&quot;/link/6c91863219a14ab988fe035a60573808.aspx&quot;&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Reach-out to me if you have any question or concern :)&lt;/p&gt;
&lt;p&gt;Thanks &amp;amp; Regards&lt;/p&gt;
&lt;p&gt;Praful Jangid&lt;/p&gt;
&lt;p&gt;Happy Coding!&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2019/12/skip-jobs-to-execute-on-specific-days-of-the-week/</guid>            <pubDate>Sat, 07 Dec 2019 09:09:18 GMT</pubDate>           <category>Blog post</category></item><item> <title>Disabling Cache for Test Driven (page with active A/B test) content</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2019/11/disabling-cache-for-test-driven-content/</link>            <description>&lt;p&gt;Caching is a great feature to improve the site performance. But recently we faced issue with test driven content being cached. The user was seeing the same test content that get loaded first because it get cached. And, there were no good solution to vary content based on test driven content. So, we came up with a solution to disable cache for test driven content (page with active A/B testing).&lt;/p&gt;
&lt;p&gt;In order to do that you need to find if there is any active test for context item. I created an attribute by inheriting from default &lt;strong&gt;ContentOutputCacheAttribute&lt;/strong&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class CustomContentOutputCacheAttribute : ContentOutputCacheAttribute
{
     private static readonly Injected&amp;lt;IMarketingTestingWebRepository&amp;gt; _marketingTestingWebRepository;

    public CustomContentOutputCacheAttribute()
    {
        this.UseOutputCacheValidator = UseOutputCache;
    }

    private static bool UseOutputCache(IPrincipal principal, HttpContextBase context, TimeSpan duration)
    {
        var url = context.Request.Url?.ToString();
        var content = UrlResolver.Current.Route(new UrlBuilder(url));
        if (content == null) return false;
        return !_marketingTestingWebRepository.Service.GetActiveTestsByOriginalItemId(content.ContentGuid).Any();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, next step is apply this attribute to your Page controller Index method. You can see the test content is not loading from cached.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class StoryPageController : PageController&amp;lt;StoryPage&amp;gt;
{
    [CustomContentOutputCache(Duration = 7200)]
    public ActionResult Index(StoryPage currentPage)
    {
        return this.View(currentPage);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, open your browser and check in network tab if the current page &lt;strong&gt;Response Headers &amp;gt; Cache-Control &lt;/strong&gt;value. If you have any active test running for current page then the value should be &lt;strong&gt;private &lt;/strong&gt;otherwise public with max-age=7200 [value set by you for how long you want to cache].&lt;/p&gt;
&lt;p&gt;Thanks and regards&lt;/p&gt;
&lt;p&gt;Happy Coding&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2019/11/disabling-cache-for-test-driven-content/</guid>            <pubDate>Wed, 13 Nov 2019 09:16:43 GMT</pubDate>           <category>Blog post</category></item><item> <title>Make property help text permanently visible</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2019/10/make-property-help-text-permanently-visible/</link>            <description>&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Supported upto CMS version 12.9&lt;/p&gt;
&lt;p&gt;In continuous efforts to make editors life easy, it will be helpful to make the property help text (shown in below image) permanently visible.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/9cbcc2248052421e9e01d795e519be3d.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;In reference to this &lt;a href=&quot;https://talk.alfnilsson.se/2014/12/18/display-help-text-in-on-page-editing/&quot;&gt;blog post&lt;/a&gt;, I would like address one &lt;a href=&quot;/link/405ece92732f4a2a974c9fa6aaef9c92.aspx&quot;&gt;issue&lt;/a&gt; that, when we try to create block directly from content area those help text are not visible.&lt;/p&gt;
&lt;p&gt;So, Let&#39;s start...&lt;/p&gt;
&lt;p&gt;We will add CSS properties, that will add title text into html element&#39;s :after selector (for elements that contains help text in title attribute).&lt;/p&gt;
&lt;p&gt;In Episerver, to add a css you will need &lt;strong&gt;module.config&lt;/strong&gt; file, to be added in main project. Create a module.config file and paste the following lines (don&#39;t forget to update the path to your css file)&lt;/p&gt;
&lt;pre class=&quot;language-markup&quot;&gt;&lt;code&gt;&amp;lt;module xmlns:xdt=&quot;http://schemas.microsoft.com/XML-Document-Transform&quot;&amp;gt;
  &amp;lt;clientResources&amp;gt;
    &amp;lt;!-- Add css file path in path attribute--&amp;gt;
    &amp;lt;add name=&quot;epi-cms.widgets.base&quot; path=&quot;/ClientResources/epi-cms.css&quot; resourceType=&quot;Style&quot; /&amp;gt;
  &amp;lt;/clientResources&amp;gt;
&amp;lt;/module&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;epi-cms.css&lt;/code&gt; is our css file, in which we are going to add our own css classes and properties. Copy paste below css into your css file.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code&gt;/*  Set property descriptions to be visible */
.Sleek .dijitTabPaneWrapper .epi-form-container__section__row label[title]:after,
.Sleek .epi-createContent .epi-form-container__section__row &amp;gt; label[title]:after {
    content: attr(title);
    display: block;
    font-size: 0.8em;
    font-style: normal;
    margin: 0.3em 0 0 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This css class is for the normal edit window and when we create a block in assets pan. The second class is for, when you create a block from content area (by clicking on &lt;strong&gt;Create a new block&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;And here we got our help text visible&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/10c2f82a4dc24d698f0726d15a0a7919.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Let me know if you have any question or concern.&lt;/p&gt;
&lt;p&gt;Thanks &amp;amp; Regards&lt;/p&gt;
&lt;p&gt;Praful Jangid&lt;/p&gt;
&lt;p&gt;Happy coding :)&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2019/10/make-property-help-text-permanently-visible/</guid>            <pubDate>Sat, 19 Oct 2019 10:01:59 GMT</pubDate>           <category>Blog post</category></item><item> <title>Adding icons/thumbnails to content tree items in Episerver CMS</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2019/7/adding-iconsthumbnails-to-content-tree-items-in-episerver-cms/</link>            <description>&lt;p&gt;&lt;strong&gt;Updates [2021/01/04]:&lt;/strong&gt; Geta has updated the module to allow you to configure the tree icons. See below link&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Geta/Epi.FontThumbnail#tree-icon-feature&quot;&gt;https://github.com/Geta/Epi.FontThumbnail#tree-icon-feature&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You might not need this customization anymore.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;

&lt;p&gt;In continuous efforts to make the content editor&amp;rsquo;s life easy, we will learn to add icons/thumbnails, to the items in our content tree. So that we can easily identify the type of an item.&lt;/p&gt;
&lt;p&gt;In EpiServer, we have an attribute to add preview images to our custom types (Page/Block types). But that will only show while adding new item (as shown in below image). So, this is the default scenario. When we are using the Episerver&amp;rsquo;s &lt;code&gt;&lt;strong&gt;ImageUrl&lt;/strong&gt;&lt;/code&gt; attribute.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/a95e610fb93248a4ba3ede424f7670ff.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;You can see the icons on the content types (on right side), but those are missing on the items in tree (in left panel).&lt;/p&gt;
&lt;p&gt;So, I would recommend Geta module for thumbnails (&lt;a href=&quot;https://github.com/Geta/Epi.FontThumbnail&quot;&gt;Geta.Epi.FontThumbnail&lt;/a&gt;). That is very handy and easy to use. And, this (below image) is when using the Geta module.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/4f677fdbe47d4f6c9c8c141954f7c7ea.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The same situation we face here, icons are missing on content tree items.&lt;/p&gt;
&lt;p&gt;Here, I will add some code along with Geta module to add icons to our items in tree.&lt;/p&gt;
&lt;p&gt;Here we go.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start by adding an Initialization Module, named let&#39;s say &lt;code&gt;TreeIconsInitialization&lt;/code&gt; inherited from &lt;code&gt;IInitializableModule&lt;/code&gt; and dependencies as show below.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(InitializableModule))]
[ModuleDependency(typeof(FontThumbnailInitialization))]
public class TreeIconsInitialization : IInitializableModule
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following code inside the Initialize function&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public void Initialize(InitializationEngine context)
{
    if (context == null)
        return;

    var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
    assemblies = assemblies.Where(x =&amp;gt; x.GetName().Name.StartsWith(&quot;AlloyDemo&quot;)).ToList();

    var types = assemblies.SelectMany(x =&amp;gt; x.GetTypes()).ToList();
    var typesWithIcon = types
        .Where(type =&amp;gt; type.IsDefined(typeof(ThumbnailIcon), false))
        .ToList();

    var iconClasses = typesWithIcon
        .Select(type =&amp;gt; new
        {
            Type = type,
            IconClass = this.ParseIconClass(type)
        })
        .ToDictionary(key =&amp;gt; key.Type, value =&amp;gt; value.IconClass);

    foreach (var uiDescriptor in context
        .Locate
        .Advanced
        .GetInstance&amp;lt;UIDescriptorRegistry&amp;gt;().UIDescriptors)
    {
        if (iconClasses.ContainsKey(uiDescriptor.ForType))
        {
            uiDescriptor.IconClass += iconClasses[uiDescriptor.ForType];
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In above code, we are scanning the project assemblies to retrieve the types that are defined with the &lt;code&gt;ThumbnailIcon&lt;/code&gt; attribute. Here, function &lt;code&gt;ParseIconClass()&lt;/code&gt; is responsible for parsing the icon class (font awesome icon class).&lt;/p&gt;
&lt;p&gt;Here is the code for parsing the icon class:&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private string ParseIconClass(Type type)
{
    if (type == null)
        return null;

    var attribute = Attribute.GetCustomAttribute(type, typeof(ThumbnailIcon)) as ThumbnailIcon;

    string path = attribute?.Path;

    if (string.IsNullOrWhiteSpace(path))
        return null;

    Uri.TryCreate($&quot;http://localhost{path}&quot;, UriKind.RelativeOrAbsolute, out Uri uri);
    var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
    string characterCodeString = query[&quot;Character&quot;];

    if (!int.TryParse(characterCodeString, out var characterCode))
        return null;

    FontAwesome fa = (FontAwesome)characterCode;
    string name = System.Enum.GetName(typeof(FontAwesome), fa);
    string kebab = FromPascalCaseToKebabCase(name);
    string iconClass = $&quot;fa fa-{kebab}&quot;;

    return iconClass;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this function, we are getting the path value of the &lt;code&gt;ThumbnailIcon&lt;/code&gt; attribute, as this path contains the icon code in querystring param &amp;ldquo;&lt;strong&gt;Character&lt;/strong&gt;&amp;rdquo;. Then converting that code into the font awesome icon class.&lt;/p&gt;
&lt;p&gt;The method&amp;nbsp;&lt;code&gt;FromPascalCaseToKebabCase()&lt;/code&gt; is for converting the icon name into actual font-awesome icon class.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private static string FromPascalCaseToKebabCase(string input)
{
    if (string.IsNullOrWhiteSpace(input))
        return null;

    StringBuilder retVal = new StringBuilder(32);

    retVal.Append(char.ToLowerInvariant(input[0]));
    for (int i = 1; i &amp;lt; input.Length; i++)
    {
        if (char.IsLower(input[i]))
        {
            retVal.Append(input[i]);
        }
        else
        {
            retVal.Append(&quot;-&quot;);
            retVal.Append(char.ToLower(input[i], CultureInfo.InvariantCulture));
        }
    }

    return retVal.ToString();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After all these, of course we will need to add reference to font awesome css file. To do so, add (if, not already added) &lt;code&gt;module.config&lt;/code&gt; and following code.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;module&amp;gt;
  &amp;lt;clientResources&amp;gt;
    &amp;lt;add name=&quot;epi-cms.widgets.base&quot; path=&quot;/Static/css/font-awesome.min.css&quot; resourceType=&quot;Style&quot; /&amp;gt;
  &amp;lt;/clientResources&amp;gt;
&amp;lt;/module&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, here we have our icons/thumnails to content tree items&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/680b512f433d4a73bb504522564f7984.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/link/860a80d6a0184296874811735a1008f9.aspx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;RnD credit goes to our EMVP &lt;a href=&quot;/link/5341f632537c4b0ab6b8fb651bd310f8.aspx?userid=ee58d302-1eab-e411-9afb-0050568d2da8&quot;&gt;Drew Null&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If anyone need a hand on any issue or understanding any code line, do let me know.&lt;/p&gt;
&lt;p&gt;Happy Coding, thanks and regards,&lt;/p&gt;
&lt;p&gt;Praful Jangid&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2019/7/adding-iconsthumbnails-to-content-tree-items-in-episerver-cms/</guid>            <pubDate>Mon, 15 Jul 2019 21:18:55 GMT</pubDate>           <category>Blog post</category></item><item> <title>Indexing Block&#39;s Content to make it searchable</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2019/4/indexing-blocks-content-to-make-it-searchable/</link>            <description>&lt;p&gt;By default, the content of a block (that is added to ContentArea on a page) is not indexed and therefore you can&amp;rsquo;t search for the content of that block instance in your site.&lt;/p&gt;
&lt;p&gt;So, the content of a page (including block&#39;s content) is indexed as normal text under &quot;SearchText$$string&quot;. To check if the content from Block is being indexed or not. Go to Episerver CMS, &lt;strong&gt;Find -&amp;gt; Overview -&amp;gt; Explore&lt;/strong&gt;, look for the page you want to confirm. Expand to see all its indexed properties. You can see here that, by default the content of the block is not being indexed.&lt;/p&gt;
&lt;p&gt;Let&#39;s talk about how to enable it. Here we go....&lt;/p&gt;
&lt;p&gt;There are several ways to do that, use any of them as per your requirement.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;To allow content of all the instances of a block type to be indexed. Just add an attribute &lt;code&gt;[IndexInContentArea]&lt;/code&gt; on your block.&lt;code&gt;&lt;/code&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[IndexInContentAreas]
public class CopyBlock : SectionBlock&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; If you still not see the content is being indexed. Perform some changes to your page and you will see it worked. It&#39;s only to reindex the page.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To exclude content of certain instances of the block, add a bool type property with exact name &lt;strong&gt;IndexInContentAreas&lt;/strong&gt; on your block type.&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public virtual bool IndexInContentAreas { get; set; }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And set its value to &lt;strong&gt;True&lt;/strong&gt;, so that content of all instances will be indexed by default. To exclude content of any instance, uncheck the checkbox for property &lt;strong&gt;IndexInContentAreas &lt;/strong&gt;(for that instance only).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using search convention, changing the default behaviour of &lt;span&gt;&lt;strong&gt;IContentIndexerConventions.ShouldIndexInContentAreaConvention&lt;/strong&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span&gt;To index a particular block type, create a class and inherit it with interface &lt;/span&gt;&lt;strong&gt;IShouldIndexInContentAreaConvention&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class ShouldIndexInContentAreaConvention : IShouldIndexInContentAreaConvention
{
       public bool? ShouldIndexInContentArea(IContent content)
       {
       	return content is CopyBlock;
       }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;Now create an Initializable module&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(EPiServer.Find.Cms.Module.IndexingModule))]
public class SearchConventionInitializationModule : IInitializableModule
{
        public void Initialize(InitializationEngine context)
        {
            ContentIndexer.Instance.Conventions.ShouldIndexInContentAreaConvention = new ShouldIndexInContentAreaConvention();
        }

        public void Uninitialize(InitializationEngine context)
        {
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In addition, to control the depth of ContentArea to be indexed, can be controlled by &lt;strong&gt;&lt;span class=&quot;classLib&quot;&gt;MaxDepthContentAreaConverter&lt;/span&gt;&amp;nbsp;&lt;/strong&gt;(by default all nested ContentArea are indexed)&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class SearchConventionInitializationModule : IInitializableModule
{
        private const int MaxDepth = 4;

        public void Initialize(InitializationEngine context)
        {
            ContentIndexer.Instance.Conventions.ShouldIndexInContentAreaConvention = new ShouldIndexInContentAreaConvention();
            SearchClient.Instance.Conventions.ForInstancesOf&amp;lt;ContentArea&amp;gt;().ModifyContract(x =&amp;gt; x.Converter = new MaxDepthContentAreaConverter(MaxDepth));
        }

        public void Uninitialize(InitializationEngine context)
        {
        }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Document reference: &lt;a href=&quot;/link/117e06a6d16f44178bb023043938a37b.aspx&quot;&gt;https://world.episerver.com/documentation/developer-guides/find/Integration/episerver-cms-7-5-with-updates/Indexing-content-in-a-content-area/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Happy Coding :)&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2019/4/indexing-blocks-content-to-make-it-searchable/</guid>            <pubDate>Fri, 12 Apr 2019 21:44:27 GMT</pubDate>           <category>Blog post</category></item><item> <title>Sorting dictionaries / translations with Episerver events</title>            <link>https://world.optimizely.com/blogs/pjangid/dates/2019/2/sorting-dictionaries--translations-with-episerver-events/</link>            <description>&lt;p&gt;What we are trying to achieve here is, whenever I create a new entry in Translations Block (to create dictionary item), the latest entry goes at the end of the list. Which is not sorted in alphabetic order. Therefore, it is difficult to find any entry as the list size increases.&lt;/p&gt;
&lt;p&gt;So, we have a &lt;strong&gt;TranslationsBlock&lt;/strong&gt;, that contains &lt;strong&gt;Translations&lt;/strong&gt; property list type. This Translations contains three properties (&lt;strong&gt;Key&lt;/strong&gt;, &lt;strong&gt;Phrase&lt;/strong&gt; and &lt;strong&gt;Description&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;We will apply sorting in alphabetic ascending (a-&amp;gt;z) order. First on Key, then Phrase and then Description.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Here we go,&lt;/p&gt;
&lt;p&gt;Create an &lt;strong&gt;Initialization&lt;/strong&gt; module named EventInitialization (whatever name you prefer) and decorate it with attributes&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))] 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also, don&amp;rsquo;t forget to inherit class with interface IInitializableModule.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class EventInitialization : IInitializableModule &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Implement the methods of this interface.&lt;/p&gt;
&lt;p&gt;Now, inside the &lt;strong&gt;Initialize&lt;/strong&gt; (implemented from interface) method bind your event.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;var events = ServiceLocator.Current.GetInstance&amp;lt;IContentEvents&amp;gt;();
events.PublishedContent += this.OnSavedContent;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I attached it to PublishContent event, that will be raised as the content is being published.&lt;/p&gt;
&lt;p&gt;Now, the finishing work goes inside the &lt;strong&gt;OnSavedContent&lt;/strong&gt; method.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;private void OnSavedContent(object sender, ContentEventArgs contentEventArgs)
{
    if (contentEventArgs.Content is TranslationsBlock translationsBlock)
    {
        var repository = ServiceLocator.Current.GetInstance&amp;lt;IContentRepository&amp;gt;();
        var block = (TranslationsBlock)translationsBlock.CreateWritableClone();
        block.Translations = block.Translations?
            .OrderBy(x =&amp;gt; x.Key)
            .ThenBy(x =&amp;gt; x.Phrase)
            .ThenBy(x =&amp;gt; x.Description)
            .ToList();
        repository.Save((IContent)block, SaveAction.Publish);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, first I checked for the content type is our required type TranslationsBlock or not. Then creating a writable clone to update it. Now apply your operations and save it with the help of Save method of ContentRepository.&lt;/p&gt;
&lt;p&gt;Happy Coding :)&lt;/p&gt;
&lt;p&gt;Thanks,&lt;/p&gt;
&lt;p&gt;Praful Jangid&lt;/p&gt;</description>            <guid>https://world.optimizely.com/blogs/pjangid/dates/2019/2/sorting-dictionaries--translations-with-episerver-events/</guid>            <pubDate>Wed, 27 Feb 2019 03:47:22 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>