Tuesday, July 5, 2022

Extending the Edge endpoint in Sitecore JSS

Sitecore JSS & the Edge endpoint

I was working on a project with Sitecore JSS, NextJS and the Edge endpoint - which is the default when you use the starter template from Sitecore and apparently the way to go now. If you read some of my previous you might know we had some issues getting it all to work but in the end it all worked like a charm. 

But we are used to tweak/improve Sitecore to our specific needs...  so I wanted to add a custom field (the tokened treelist as described before). If I now query items with such a field I get those values as strings - the field is not handled like a normal TreeList. I read in the documentation that extending the schema is not supported as it would break compatibility with Experience Edge - so that would mean we cannot use this custom field. So I was curious and wanted to try anyway.

I found 2 options to get this done. Well, to be honest.. one that actually worked and one that should work but I didn't have enough time to investigate fully. 

Let's start with that last one.

SchemaExtender

A SchemaExtender should be able to add or modify types to the completed schema. There is some documentation on the Sitecore docs but it's rather limited. I managed to get some code working to extend the options for an image field. However, in the (limited) time I had I couldn't get the extra custom TreeList to work. So if anyone knows how to do this, I would really appreciate a blogpost. Maybe I'll add this question to Sitecore StackExchange as well.

I can share the code to extend the images though:

public class ImageSchemaExtender : Sitecore.Services.GraphQL.Schemas.SchemaExtender
{
  public ImageSchemaExtender()
  {
    ExtendType<Sitecore.Services.GraphQL.EdgeSchema.GraphTypes.FieldTypes.ImageFieldGraphType>("ImageField", type =>
    {
      ExtendField(type, "src", field =>
      {
        field.Arguments.Add(new QueryArgument<BooleanGraphType>
        {
          Name = "ratio",
          Description = "Ignore ratio"
        });
        
        field.Resolver = new FuncFieldResolver<Field, object>(context => GetUrl(context.Source, context.Arguments));
      });
    });
  }

  protected string GetUrl(ImageField field, Dictionary<string, object> arguments)
  {
    var mediaItem = field.MediaItem;
    if (mediaItem == null)
      return (string)null;
    var options = new MediaUrlBuilderOptions();
    if (arguments.TryGetValue("maxWidth", out var obj1) && int.TryParse(obj1?.ToString(), out var result1))
      options.MaxWidth = result1;
    if (arguments.TryGetValue("maxHeight", out var obj2) && int.TryParse(obj2?.ToString(), out var result2))
      options.MaxHeight = result2;
    if (arguments.TryGetValue("ratio", out var obj3) && bool.TryParse(obj3?.ToString(), out var result3))
      options.IgnoreAspectRatio = result3;
    return MediaManager.GetMediaUrl(mediaItem, options);
  }
}
<sitecore>
  <api>
    <GraphQL>
      <endpoints>
        <edge>
          <extenders hint="list:AddExtender">
            <imageExtender type="Project.Platform.Foundation.Images.ImageSchemaExtender, Project.Platform" />
          </extenders>
        </edge>
      </endpoints>
    </GraphQL>
  </api>
</sitecore>
You can see here that we can extend the ImageFieldGraphType. As mentioned - if anyone reading this knows how to create such an extender that would extend the schema in a way that a custom TreeList field works just like the ootb one I would love to read that solution 😉


FieldTypeFactoryStore

Other endpoints seem to have options to plug your code in through config so I tried to find out where the Edge endpoint defines how a field should be mapped to a piece of code. And I found that part in a class called FieldTypeFactoryStore. 

Problem with this class is that it is clearly not meant to be extended. As I'm not sure if adding custom fields is actually supported this might make sense. But just for being curious I tried and it can work. Create your own version of this class and register it.. that can do it. Although I believe it is not a good idea.

using System.Collections.Generic;
using Sitecore.Services.GraphQL.Content.TemplateGeneration.FieldMapping;
using Sitecore.Services.GraphQL.EdgeSchema.GraphTypes.FieldTypes;
using Sitecore.Services.GraphQL.EdgeSchema.TemplateGeneration.FieldMapping;

namespace Project.Platform.Foundation.Images
{
  public class FieldTypeFactoryStore : IFieldTypeFactoryStore
  {
    private readonly Dictionary<string, IFieldTypeFactory> fieldTypes;

    public FieldTypeFactoryStore()
    {
      fieldTypes = new Dictionary<string, IFieldTypeFactory>();
      fieldTypes.Add("Checkbox", new GenericFieldTypeFactory<CheckboxFieldGraphType>());
      fieldTypes.Add("Date", new GenericFieldTypeFactory<DateFieldGraphType>());
      fieldTypes.Add("Datetime", new GenericFieldTypeFactory<DateFieldGraphType>());
      fieldTypes.Add("Image", (IFieldTypeFactory)new GenericFieldTypeFactory<ImageFieldGraphType>());
      fieldTypes.Add("Integer", new GenericFieldTypeFactory<IntegerFieldGraphType>());
      fieldTypes.Add("Number", new GenericFieldTypeFactory<NumberFieldGraphType>());
      fieldTypes.Add("Checklist", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("Multilist", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("Multilist with Search", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("Name Value List", new GenericFieldTypeFactory<NameValueListFieldGraphType>());
      fieldTypes.Add("Name Lookup Value List", new GenericFieldTypeFactory<NameValueListFieldGraphType>());
      fieldTypes.Add("Treelist", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("TreelistEx", new GenericFieldTypeFactory<MultilistFieldGraphType>());
      fieldTypes.Add("Droplink", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("Droptree", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("General Link", new GenericFieldTypeFactory<LinkFieldGraphType>());
      fieldTypes.Add("General Link with Search", new GenericFieldTypeFactory<LinkFieldGraphType>());
      fieldTypes.Add("Rich Text", new GenericFieldTypeFactory<RichTextFieldGraphType>());
      fieldTypes.Add("lookup", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("reference", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("tree", new GenericFieldTypeFactory<LookupFieldGraphType>());
      fieldTypes.Add("default", new GenericFieldTypeFactory<ItemFieldGraphType>());
      ....
    }

    public IFieldTypeFactory GetFieldFactory(string fieldType)
    {
      fieldTypes.TryGetValue(fieldType, out var fieldTypeFactory);
      return fieldTypeFactory;
    }

    public IFieldTypeFactory Default => fieldTypes["default"];
  }
}
<sitecore>
  <services>
    <register serviceType="Sitecore.Services.GraphQL.EdgeSchema.TemplateGeneration.FieldMapping.IFieldTypeFactoryStore, Sitecore.Services.GraphQL.EdgeSchema" implementationType="Sitecore.Services.GraphQL.EdgeSchema.TemplateGeneration.FieldMapping.DefaultFieldTypeFactoryStore, Sitecore.Services.GraphQL.EdgeSchema">
	  <patch:attribute name="implementationType">Project.Platform.Foundation.Images.FieldTypeFactoryStore,Project.Platform</patch:attribute>
    </register>
  </services>
</sitecore>

So..  I'm afraid I didn't really get it working - yet.  But sharing it anyway as we/you can also learn from attempts that were not completely successful. If anything changes I'll keep you posted. I anyone who reads this has more information, please keep me posted as well...

Future - the cloud

What this means in the future with XM Cloud..  I don't know. I was told it should be possible so maybe it will. Or maybe it won't. It makes sense that you can't change how the Edge works - but as I don't know the details about it, it's not (yet) clear what will be possible and what not. Room for more blog posts 🙂


No comments:

Post a Comment