8

In several of the accelerators available: Score, SXA, etc... when creating a new item for a rendering datasource, you have a folder available to put the new item right under the current item or in a tenant folder. Is there a package or github repo out there that helps get this set up in a custom solution?

10

You don't actually need a package - Sitecore gives you this ability out of the box.

What I normally do is; I have a "global" area, and a "page local" area for datasource content.

The way you set it up, is to go to your "Datasource Location" field on your rendering definition, and pipe | the different data sources - one of them bing relative to your context item.

enter image description here

In this example, the content is not a direct child of the content item - but resides in a "Local Data" folder beneath it.

When doing it like this; Sitecore will allow you to switch between different root folders when selecting to "use existing content" or "create new content".

| improve this answer | |
5

As already mentioned in the answer from Mark Cassidy, this is available "out of the box" with Sitecore. You need to watch out for the fact that the folders need to exist though.

One way to ensure this is to use Branch Templates, but this can become quite become quite difficult to manage if you have a lot of different templates.

An alternative is to essentially create the folders on demand. You can achieve this by creating a custom getRenderingDatasource processor, something very similar to this, which will create the datasource folder if it does not exist. You can change the ID of the folder template to something else custom if you so wish.

public class CreateRelativeDataSourceFolder
{
    private static ID DataSourceLocationField = new ID("{B5B27AF1-25EF-405C-87CE-369B3A004016}");
    private static ID FolderTemplateID = new ID("{A87A00B1-E6DB-45AB-8B54-636FEC3B5523}");
    private static TemplateID FolderTemplate = new TemplateID(FolderTemplateID);
    private static string RelativePath = "./";

    public void Process(GetRenderingDatasourceArgs args)
    {
        string dataSourceLocation = args.RenderingItem.Fields[DataSourceLocationField].Value;

        if (string.IsNullOrWhiteSpace(dataSourceLocation))
        {
            return;
        }

        if (!dataSourceLocation.StartsWith(RelativePath))
        {
            return;
        }

        if (string.IsNullOrWhiteSpace(args.ContextItemPath))
        {
            return;
        }

        string subFolderPath = args.ContextItemPath + dataSourceLocation.Substring(1);

        if (args.ContentDatabase.GetItem(subFolderPath) != null)
        {
            return;
        }

        Item currentItem = args.ContentDatabase.GetItem(args.ContextItemPath);

        if (currentItem == null)
        {
            return;
        }

        string newItemName = dataSourceLocation.Substring(2);

        using (new SecurityDisabler())
        {
            currentItem.Add(newItemName, FolderTemplate);
        }
    }
}

And then patch it in config:

  <getRenderingDatasource>
        <processor patch:before="processor[@type='Sitecore.Pipelines.GetRenderingDatasource.GetDatasourceLocation, Sitecore.Kernel']" type="Testing.CreateRelativeDataSourceFolder, Testing"/>
  </getRenderingDatasource>

You can find more information about this technique in this blog post by Jeremy Davis, and this blog post by Reinoud van Dalen has created a Nuget package which yo can directly use.

One tip if to set the folder template ID to a custom one of your choice, and to style the Standard Values of the template item using the "Tree node style" option in the ribbon. This will then mean your datasource folders will be visually different in the content tree from the rest of your content.

| improve this answer | |
3

If you want to avoid having to create the folder ahead of time (either in a tenant folder or a relative path), you can tap into the getRenderingDatasource pipeline. The solution I've used for some time now checks if the datasource location is a relative path, and then checks if that path exists under the context item before creating it. A parameter on the rendering is used to configure the template of the folder.

public class CreateContentFolder
{
    protected const string CONTENT_FOLDER_TEMPLATE_PARAM = "contentFolderTemplate";

    public virtual void Process(GetRenderingDatasourceArgs args)
    {
        Assert.IsNotNull(args, "args");
        Sitecore.Data.Items.RenderingItem rendering = new Sitecore.Data.Items.RenderingItem(args.RenderingItem);
        UrlString urlString = new UrlString(rendering.Parameters);
        var contentFolder = urlString.Parameters[CONTENT_FOLDER_TEMPLATE_PARAM];
        if (string.IsNullOrEmpty(contentFolder))
        {
            return;
        }
        if (!ID.IsID(contentFolder))
        {
            Log.Warn(string.Format("{0} for Rendering {1} contains improperly formatted ID: {2}", CONTENT_FOLDER_TEMPLATE_PARAM, args.RenderingItem.Name, contentFolder), this);
            return;
        }

        string text = args.RenderingItem["Datasource Location"];
        if (!string.IsNullOrEmpty(text))
        {
            if (text.StartsWith("./") && !string.IsNullOrEmpty(args.ContextItemPath))
            {
                var itemPath = args.ContextItemPath + text.Remove(0, 1);
                var item = args.ContentDatabase.GetItem(itemPath);
                var contextItem = args.ContentDatabase.GetItem(args.ContextItemPath);
                if (item == null && contextItem != null)
                {
                    string itemName = text.Remove(0, 2);
                    //if we create an item in the current site context, the WebEditRibbonForm will see an ItemSaved event and think it needs to reload the page
                    using (new SiteContextSwitcher(SiteContextFactory.GetSiteContext("system")))
                    {
                        contextItem.Add(itemName, new TemplateID(ID.Parse(contentFolder)));
                    }
                }
            }
        }
    }
}

The site context switcher is important (or at least used to be?) to avoid strange errors / messaging in the Experience Editor.

<getRenderingDatasource>
    <processor type="My.Pipelines.GetRenderingDatasource.CreateContentFolder, ActiveCommerce.Kernel" patch:before="processor[@type='Sitecore.Pipelines.GetRenderingDatasource.GetDatasourceLocation, Sitecore.Kernel']"/>
</getRenderingDatasource>

Then on the rendering: Rendering contentFolderTemplate config

By tapping into the same pipeline, you could add tokens to your Datasource Location, which you could parse and resolve dynamically. This is how SXA accomplishes tenant folders.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.