0

I have a template configured for two devices. Each device has its own type of rendering, but they share the same data source item. Each instance of the page template will have the following structure in the content tree:

- Page (with 2 devices)

- - Data source item (device renderings point at this)

These items will by added to the tree via a branch, grouping them.

Against the template's standard values, I'm trying to set the Data source property of the renderings relative to the Page, i.e. ./<Data source item name> but this doesn't work.

Is there a query syntax I can use to get the relative path?

  • 1
    Looks a lot like this question. – Dennis de Laat Feb 6 '17 at 14:47
  • It does, but I'm not sure that the data source of a rendering behaves in the same way as the data source of a field of an item, in terms of relativity? – David Masters Feb 6 '17 at 14:50
  • How are you setting the datasource on the std values? Are you creating items under the __Standard Values item to use to point the datasource at? – Richard Seal Feb 6 '17 at 14:51
  • @RichardSeal not entirely sure I understand what you mean, but against the standard values of the template, i'm going to presentation details> rendering > properties > data source = ./Data source item name – David Masters Feb 6 '17 at 14:53
  • Ok - that will not work because there is no item at that location for the presentation to store in the renderings field. You may be better off using branches. – Richard Seal Feb 6 '17 at 14:54
6

I'm going to try and make this not a link only answer.

What you are trying will not work because the renderings fields (shared and final) want to store the ID of the data source item. If you just type in a path to an item that does not exist, it will not be able to work out an ID.

If you want to make sure that Datasources are set when the item is created, a better way would be to use a Branch Template. This way you can have your datasource items created at the same time as you create your page item.

Use a Branch Template

So create your structure in the branch template area, and create all your datasource items, pointing the rendering datasources at the branch template/page/. You would get something like this:

Branch Templates
    - $name
        - Page Item
            - Data Source Content 1
            - Data Source Content 2
            - etc...

Good so far...

Fix Up Sitecore

Now we need to fix Sitecore - because when Sitecore creates the items from the branch templates, it will keep the original ID's of the items from the branch template location.

Fortunately there is already a fix for that. @Kamsar wrote up a nice fix here for Branch Datasource Presets. This modifies the addFromTemplate pipeline from the itemProvider group. I will include the code here so this doesn't become a link only answer. All credit for code goes to @Kamsar.

Configuration changes:
<!-- Add this to App_Config/Include to enable branch presets -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
    <sitecore>
        <pipelines>
            <group name="itemProvider" groupName="itemProvider">
                <pipelines>
                    <addFromTemplate>
                        <processor type="BranchPresets.AddFromBranchPreset, BranchPresets" />
                    </addFromTemplate>
                </pipelines>
            </group>
        </pipelines>
    </sitecore>
</configuration>

And this is the code that gets run to fix the datasource locations:

public class AddFromBranch : Sitecore.Pipelines.ItemProvider.AddFromTemplate.AddFromTemplateProcessor
{
    public override void Process(AddFromTemplateArgs args)
    {
        Assert.ArgumentNotNull(args, nameof(args));

        if (args.Destination.Database.Name != "master") return;

        var templateItem = args.Destination.Database.GetItem(args.TemplateId);

        Assert.IsNotNull(templateItem, "Template did not exist!");

        // if this isn't a branch template, we can use the stock behavior
        if (templateItem.TemplateID != TemplateIDs.BranchTemplate) return;

        Assert.HasAccess((args.Destination.Access.CanCreate() ? 1 : 0) != 0, "AddFromTemplate - Add access required (destination: {0}, template: {1})", args.Destination.ID, args.TemplateId);

        // Create the branch template instance
        Item newItem = args.Destination.Database.Engines.DataEngine.AddFromTemplate(args.ItemName, args.TemplateId, args.Destination, args.NewId);

        // find all rendering data sources on the branch root item that point to an item under the branch template,
        // and repoint them to the equivalent subitem under the branch instance
        RewriteBranchRenderingDataSources(newItem, templateItem);

        // now go through all descendants to translate their data sources
        var newItemDescendants = newItem.Axes.GetDescendants();
        for (int i = 0; i < newItemDescendants.Length; i++)
        {
            RewriteBranchRenderingDataSources(newItemDescendants[i], templateItem);
        }

        args.Result = newItem;
    }

    protected virtual void RewriteBranchRenderingDataSources(Item item, BranchItem branchTemplateItem)
    {
        string branchBasePath = branchTemplateItem.InnerItem.Paths.FullPath;

        LayoutHelper.ApplyActionToAllRenderings(item, rendering =>
        {
            if (string.IsNullOrWhiteSpace(rendering.Datasource))
                return RenderingActionResult.None;

                // note: queries and multiple item datasources are not supported
                var renderingTargetItem = item.Database.GetItem(rendering.Datasource);

            if (renderingTargetItem == null)
                Log.Warn("Error while expanding branch template rendering datasources: data source {0} was not resolvable.".FormatWith(rendering.Datasource), this);

                // if there was no valid target item OR the target item is not a child of the branch template we skip out
                if (renderingTargetItem == null || !renderingTargetItem.Paths.FullPath.StartsWith(branchBasePath, StringComparison.OrdinalIgnoreCase))
                return RenderingActionResult.None;

            var relativeRenderingPath = renderingTargetItem.Paths.FullPath.Substring(branchBasePath.Length).TrimStart('/');
            relativeRenderingPath = relativeRenderingPath.Substring(relativeRenderingPath.IndexOf('/')); // we need to skip the "/$name" at the root of the branch children

                var newTargetPath = item.Paths.FullPath.Replace("Home", "").Replace("Global", "").TrimEnd('/') + relativeRenderingPath;

            var newTargetItem = item.Database.GetItem(newTargetPath);

                // if the target item was a valid under branch item, but the same relative path does not exist under the branch instance
                // we set the datasource to something invalid to avoid any potential unintentional edits of a shared data source item
                if (newTargetItem == null)
            {
                rendering.Datasource = "INVALID_BRANCH_SUBITEM_ID";
                return RenderingActionResult.None;
            }

            rendering.Datasource = newTargetItem.ID.ToString();
            return RenderingActionResult.None;
        });
    }

There is a bit more to the code, so please get it from @Kamsar's blog post and github repo.

This should get you where you want to be with presetting the datasource items relative to the page item.

0

You can only use a relative Data Source on items in the Content tree, not on the template definition itself. A reason for this is that you'll never know where that particular item is placed.

Edit: And with 'items' I mean renderings on items.

  • At this point - they are not "relative" datasources, they are linked by the datasource item ID – Richard Seal Feb 6 '17 at 15:12

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.