0

I have a Query which returns a list of all "blog posts" sorted by date. I have a "Page List" variant-definition to display each blog post in the list, but how do I make the first returned blog post use a different variant-definition?

I have found another question which is maybe the same: This answer for example
That is, do this by using CSS.

But what if I need to display extra data in the first result - for example I want to display the image for the first blog result, but not for the others? Do I "display" the image in the variant definition for all results, and use CSS to only show the image in the first result?

2
  • Which SXA version are you using?
    – Gatogordo
    Apr 15, 2021 at 10:56
  • The version at "/sitecore/system/Settings/Foundation/Experience Accelerator/Upgrade/Current" is 11200
    – SXAGOR
    Apr 15, 2021 at 11:07

4 Answers 4

1

If it's purely presentational Dawid's answer in your link works perfect.

However, if you want to display other fields for example you might be able to work with the Rules engine: enter image description here

Each individual field, section, Scriban etc. under the Variant Definition item has that field and can be utilized to make sure that does/doesn't render out depending on whether the rule evaluates to true or false. A step-by-step example of how you can use it can be found here.

I do believe you need to be using a reasonably new version of Sitecore/SXA (9.3 from the top of my head but I can be wrong)

edit

A very simple example of a custom rule to check whether the item is the first:

public class IsFirstItem<T> : WhenCondition<T> where T : RuleContext
{
    protected override bool Execute(T ruleContext)
    {
        return ruleContext.Item.Parent.Children.First().ID == ruleContext.Item.ID;
    }
}

You can then add a Condition item, for example here: /sitecore/system/Settings/Rules/Definitions/Elements/Item Hierarchy/your custom rule item

Set the Text field to something like 'where the item is the first child' and the Type to Namespace.Class.IsFirstItem,YourAssembly

7
  • And which rule would you use to identify the first element in a list?
    – Gatogordo
    Apr 15, 2021 at 11:41
  • Added a simple example of a custom condition
    – Trayek
    Apr 15, 2021 at 12:36
  • Thanks - a "rule" is maybe the way to go, but your example will not work as-is in my case. The "blog posts" on my site do not all have the same parent, but exist in an item hierarchy. Thanks for the input - I'll see if I can come any further.
    – SXAGOR
    Apr 15, 2021 at 15:14
  • Sure, the same principle applies - add a custom condition based on whatever attribute you need to sort on.
    – Trayek
    Apr 15, 2021 at 17:02
  • Hi - I have managed to get something working with a custom rule. I have no idea if this is the "correct" way to do it, but for a page-list it appears the "model" is a VariantListsRenderingModel, which is passed in the "ruleContext" to the custom rule. It is passed in ruleContext.Parameters["Model"]. I can use this to check the list of items (to get the first item) and the current item.
    – SXAGOR
    Apr 16, 2021 at 14:55
0

Using the CSS solution is an option but would add too many images to your result (even if they are hidden) so I wouldn't do that.

The rules engine as mentioned in another answer is usually a way to handle differences but I don't know of a rule that would be able to identify the first element of a custom query.

Another option is to use Scriban. Your version supports it. You would also need to use (a copy of) another component - e.g. Promo - though in order to have a variant that gets the complete datasource at once. This way you can loop through the list with Scriban and add some logic for the first item. You can choose to have everything in Scriban or use sc_execute and child items.

0

Here is an example how we accomplished this. Involves the use of more components.

enter image description here

First you will need a VariantPlaceholder in the promo's rendering variant.

enter image description here

Then add two page lists into the placeholder for the promo:

  • The first one has a page size of 1
  • The second has a page size of 4
  • Each page list can have it's own rendering variants

Considerations:

  • Using the rules on the rendering variant fields could allow you to use a single Page List. If you however wanted to make the list of items optional (sometimes show one item, other times show one + four) or to have different queries performed then this approach would suffice.
2
  • Thanks, that's a cunning idea. Would this execute 2 searches per page (one for each page-list)?
    – SXAGOR
    Apr 16, 2021 at 14:53
  • Yes, each component would run independently. In this case two page list queries. Apr 16, 2021 at 17:32
0

This is the example I have been working on to display a list of "news" items, with the possibility of differentiating the displayed rendering-variant according to whether or not the news item was the first in the results.

I have used a Sitecore custom "rule" for determining whether or not the item being displayed is the first item (as recommended by another poster).

Note that this example is part of a proof-of-concept (and SXA learning exercise) I have been working on, so it is definitely not representative of "best practice", and some classes and logic are really superfluous (as they were really only made to help in my learning). So this is presented "as is".

I also took a lot of inspiration from "https://alan-null.github.io/2017/12/sxa-blog#search-results-page". So if any of the below description does not make sense, then it's probably because I made a mistake, and you should check the this url.

The most relevant stuff below is probably in steps 5, 7 and 16.

(A) SITECORE Items:

1. Clone Page List.
Clone SXA's "Page List" rendering (from "/sitecore/layout/Renderings/Feature/Experience Accelerator/Page Content/Page List").
Call the new rendering "News List", set the "Parameters" to "make a copy"; and the "Datasource" to "make a copy". Set the View to a new view called "News List.cshtml".

In the newly cloned controller-rendering, set or check the fields:
Name="Controller"; Value="Quotation.Controller.NewsListController,Quotation.Controller"
Name="Controller Action"; Value="Index"
Name="Parameters Template"; Value= points to a cloned parameters template
Name="Rendering css class"; Value="news-list"
Name="Rendering view path"; Value="~/Views/News/News List.cshtml" (or as specified when the rendering was cloned)
Name="Other Properties"; Value=IsPaginationEnabledRendering=true, IsRenderingsWithDynamicPlaceholders=true

2. Rendering Parameters
Find the rendering parameters item (as created in step #1).
rendering parameters

Set/check the fields:
"Base template" field. Set the value to:
{8CA06D6A-B353-44E8-BC31-B528C7306971}|{A9F8A74E-5F7D-4506-8661-B08E7B9A7B91}|{D959F476-2A2C-40C6-81F5-FB75342BBFB9}|{FF75632A-03ED-4D31-B42A-1EEE45E5147F}|{5E680478-0705-4452-A14B-42B945EB1AB6}|{5BDF5EFE-B334-407B-BD42-ABFF8595B691}|{5C74E985-E055-43FF-B28C-DB6C6A6450A2}|{3DB3EB10-F8D0-4CC9-BE26-18CE7B139EC8}|{6DA8A00F-473E-487D-BEFE-6834350D5B67}

"Droplink" field called "SourceType". Set the "Source" to:
query:/sitecore/system/Settings/Foundation/Experience Accelerator/Search/Item Queries//[@@templatename='Query']|$site/[@@name='Settings']/Item Queries//[@@templatename='Query']|$sharedSites/[@@name='Settings']/Item Queries//*[@@templatename='Query']

3. Page Templates
Under "Templates/Project/..." (as appropriate for your site), create a page-template for the news-list page ("News List Page Template") and the news-detail page ("News Page Template").
Page templates

In the "News Page Template" create fields:
Name="Date"; Type=Date
Name="Teaser Image"; Type=Image; Source="query:$siteMedia"

4. Pages
Create some pages based on the templates created in step #3. For example, under your site's content, create a "News List Page Template" item.
Under this item, create several "News Page Template" items.
Content pages

5. Custom Rule
Create a "Condition" item called "News Item Is First" under "/sitecore/system/Settings/Rules/Definitions/Elements/Indexing and Search"
Condition

Set the "Text" field value to "where the News item is first result".
Set the "Type" to "Quotation.Controller.IsFirstResultRule,Quotation.Controller"
(Note the namespace would be something more relevant in reality - this is simply placed where I had some existing code).

6. Available Renderings
Under your site's "Presentation/Available Renderings" item, create an "Available Renderings" item called "News List".
Edit the "Renderings" field, and select the News List controller-rendering created in step #1.
Available renderings

7. News List Variant Definition
Under your site's "Presentation/Rendering Variants" item, create a "Variants" item called "News List".
Variant

Under this, create a "Variant Definition" item called "Default News List".
Under this, create 2 "VariantSection" items called "News List Section First" and "News List Section Normal". It is these sections we "switch" on and off depending on whether or not the news item being displayed is the first.

In the "News List Section First" item, edit the rule field - select the rule created in step #5.
Select rule

In the "News List Section Normal" item, edit the rule field - select the rule created in step #5 (make it "except where").
Select rule

Create the other display-field items as appropriate under the 2 variant-section items.

8. Page Content Variant Definition
Under your site's "Presentation/Rendering Variants/Page Content" item, create a "Variant Definition" item called "News Entry".
(I think "best practice" is to clone the "Page Content" rendering under "/sitecore/layout/Renderings/Feature/Experience Accelerator/Page Content", but I did not). In any case, create sub-items to "News Entry" called
"Title"; type=Field
"Date"; type=Date
"Content"; type=Field

9. Query
Create the query for the page-list. Under your site, under the "Settings" folder, create a "Query" item called "News Item Query".
Query

Set the Query field value to "+location:{EEF4E1F4-5B7B-48DF-BE28-84F40C462CB0};+template:{9BDD1D3D-855C-4A22-83A0-5BC67E505537};sort:date[desc]".
Note of course that the Sitecore IDs here relate to my specific site.
The location points to the News page for your site (step #4). The template is the "News Page Template" create in step #3.
(This can apparently be created using the query-builder, but I couldn't get it to work, so I typed it in manually).

10. Partial Designs.
Under your site's "Presentation/Partial Designs" folder, create partial designs for the news-list and news-details page.
Partial Designs

On the "News List" partial design, place a "News List" rendering.
I did this using the experience editor for the "News List" partial design. You should see the "News List" rendering in the toolbox which you can drag-and-drop to the page.
Edit the rendering parameters - in the "Source Type" field, select the query from step #9.

On the "News" partial design, drag-and-drop a "Page Content" rendering. Set the Variant to "News Entry" (created in step #8).

11. Page Designs
Under your site's "Presentation/Page Designs" item, create 2 Page Design items, called "News List Page" and "News Page".
Set up the Partial Designs on each Page Design. For example:

News List page design

News Page page design

12. Template/Design mappings
On your site's "Presentation/Page Designs" item, map the templates to the page-designs. For example:

Mappings

(B) CODE

13. Model.
As stated, this is really only created as part of my proof-of-concept, and is not strictly required.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Sitecore.XA.Foundation.Variants.Abstractions.Models;
using Sitecore.Data.Items;
using Sitecore.XA.Foundation.RenderingVariants.Models;
using Sitecore.XA.Foundation.Mvc.Repositories.Base;

namespace Quotation.Controller
{
    public class NewsListModel : VariantListsRenderingModel, IRenderingModelBase
    {
        public NewsListModel(VariantListsRenderingModel model)
        {
            if (model != null)
            {
                this.Attributes = model.Attributes;
                this.CssClasses = model.CssClasses;
                this.DataSourceItem = model.DataSourceItem;
                this.HeadingTag = model.HeadingTag;
                this.IsControlEditable = model.IsControlEditable;
                this.IsEdit = model.IsEdit;
                this.Item = model.Item;
                this.Items = model.Items;
                this.MessageEditHere = model.MessageEditHere;
                this.MessageIsEmpty = model.MessageIsEmpty;
                this.PageItem = model.PageItem;
                this.Rendering = model.Rendering;
                this.VariantFields = model.VariantFields;

                if (this.Items != null && this.Items.Any())
                {
                    FirstResultItem = this.Items.FirstOrDefault();
                }
            }
        }

        public Item FirstResultItem { get; private set; }
    }
}

14. Controller.
This controller is simply a copy of SXA's PageListController, and as stated not strictly required.

using Sitecore.XA.Feature.PageContent.Repositories;
using Sitecore.XA.Foundation.RenderingVariants.Controllers.Base;
using Sitecore.XA.Foundation.RenderingVariants.Lists.Pagination;
using Sitecore.Data.Items;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sitecore.XA.Foundation.Mvc.Repositories.Base;
using Sitecore.XA.Foundation.RenderingVariants.Models;

namespace Quotation.Controller
{
    /// <summary>
    /// Copy of Sitecore.XA.Feature.PageContent.Controllers.PageListController.
    /// </summary>
    public class NewsListController : PaginableController
    {
        protected IPageListRepository PageListRepository { get; }

        public NewsListController(IPageListRepository pageListRepository)
        {
            this.PageListRepository = pageListRepository;
        }

        protected override IListPagination PaginationConfiguration
        {
            get
            {
                var renderingParameters = this.Rendering.Parameters;
                var keyFunc = (Func<KeyValuePair<string, string>, string>)(p => p.Key);
                var valueFunc = (Func<KeyValuePair<string, string>, string>)(p => p.Value);
                var dict = (IDictionary<string, string>)renderingParameters.ToDictionary<KeyValuePair<string, string>, string, string>(keyFunc, valueFunc);
                var pg = (IListPagination)new ListPagination(dict, this.PageListRepository.Items.Count<Item>());
                return pg;
            }
        }

        protected override object GetModel()
        {
            IListPagination paginationConfiguration = this.PaginationConfiguration;
            IRenderingModelBase renderingModelBase = this.PageListRepository.GetModel(paginationConfiguration);
            NewsListModel res = new NewsListModel(renderingModelBase as VariantListsRenderingModel);
            return res;
        }
    }
}

15. Service Registration

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Extensions.DependencyInjection;
using Sitecore.DependencyInjection;
using Sitecore.XA.Foundation.RenderingVariants.Repositories;

namespace Quotation.Controller
{
    public class RegisterNewsServices : IServicesConfigurator
    {
        public void Configure(IServiceCollection serviceCollection)
        {
            serviceCollection.AddTransient<NewsListController>();
        }
    }
}

16. Custom Rule
Note that if you haven't made a "custom controller" and model, then the model supplied to this rule will be "VariantListsRenderingModel".

using Sitecore.Rules;
using Sitecore.Rules.Conditions;
using Sitecore.XA.Foundation.RenderingVariants.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Quotation.Controller
{
    public class IsFirstResultRule<T> : WhenCondition<T> where T : RuleContext
    {
        protected override bool Execute(T ruleContext)
        {
            var model = ruleContext.Parameters["Model"];
            NewsListModel newsListModel = model as NewsListModel;

            if (newsListModel != null)
            {
                var contextItem = ruleContext.Item;
                if (contextItem != null)
                {
                    var firstSearchResult = newsListModel.FirstResultItem;
                    if (firstSearchResult != null)
                    {
                        var isFirst = contextItem.ID == firstSearchResult.ID;
                        return isFirst;
                    }
                }
            }

            return true;
        }
    }
}

17. Configuration.
Put in a patch file in "App_Config/Include".

<?xml version="1.0" encoding="utf-8"?>

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
    <sitecore>
        <services>
            <configurator type="Quotation.Controller.RegisterNewsServices, Quotation.Controller" />
        </services>
    </sitecore>
</configuration>

18. View.
Create a cshtml called "News List.cshtml". I copied SXA's "Page List.cshtml".

@using Sitecore.Data.Items
@using Sitecore.XA.Foundation.MarkupDecorator.Extensions
@using Sitecore.XA.Foundation.RenderingVariants.Extensions
@using Sitecore.XA.Foundation.RenderingVariants.Fields
@using Sitecore.XA.Foundation.SitecoreExtensions.Extensions
@using Sitecore.XA.Foundation.Variants.Abstractions.Fields

@model Quotation.Controller.NewsListModel

@{
    var newsListModel = Model;
}

<!-- News List -->
<div @Html.Sxa().Component(newsListModel.Rendering.RenderingCssClass ?? "page-list", newsListModel.Attributes)>
    <div class="component-content">
        @if (newsListModel.Items.Any() && newsListModel.VariantFields != null)
        {
            <ul class="items">
                @foreach (Item item in newsListModel.Items)
                {
                    <li class="item">
                        <!-- @item.ID -->
                        @if (item.ID == newsListModel.FirstResultItem.ID)
                        {
                            <!-- FIRST RESULT! -->
                        }

                        @foreach (BaseVariantField variantField in newsListModel.VariantFields)
                        {
                            @Html.RenderingVariants().RenderVariant(variantField, item, newsListModel.RenderingWebEditingParams, newsListModel)
                        }
                    </li>
                }
            </ul>
        }
        else
        {
            @newsListModel.MessageIsEmpty
        }
    </div>
</div>
<!-- /News List -->

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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