Saturday, April 14, 2018

SXA Add New Language

In this blog, I will show you how to add new language to SXA site.

The first step, as usual, go to the Sitecore setting and add the needed new language,




After that go back to your site and add the language selector component, which is available in the SXA toolbox.



The last step we need to add the language which I added before in the settings to SXA site, then the language will be shown in this component.

To do that:

  1. Select your site under the tenant in the content editor.
  2. Right click on the site.
  3. Choose script menu
  4. Click on add site language,



In the popup dialog choose from the dropdown list "New Language" the second language,


Go back to your language selector and you can see the second language comes and you can select it.

Friday, March 23, 2018

SXA Search tricks - Sitecore

In this post, I will show you one of the tricks of SXA search feature and its Date-Filter component.
SXA search comes with many options and components and one of them is Date-Filter,

To use the search functionality in SXA you have to enable the search config first, SXA search by default is disabled.

Go to path website/app_data/include/z.foundation.overrides and enable the preferred search engine you need, Lucene, Solr or Azure.
After enabling the file make sure to rebuild your indexes.

Date-Filter component provides two filter fields (From Date) and (To Date).



To make this component works there are some steps you need to perform after adding the component:

  1. Set the settings for the Date-Filter: 

These are normal settings of the component, but the important setting is the Facet you want the Date-Filter to filter on.

      2. As you can see I use event date facet which I created this facet in settings folder of the site:



After that, the facet will be available in the dropdown list of the Date-Filter settings.

 3. Select Facet:

so here we connect the search result to filter on the date field:

now add search result component to display the results:



to make the results more specific for the needed items we have to create search scope and if you want sort order, after creating these two items select them in the search result setting boxes:


Go to site settings and create the needed scop:



Sort order is available in different location is available in Data folder  -> Search -> Sort Results:


in each sort order you need to select the facet and the direction of the order, and here we need to select the same facet we created before to sort on it.

this is normal Date-Filter with search results, and this Date-Filter will filter all the events that start between the (From Date) and (To Date), but what if you need all the events start after (From Date) and ends before (To Date),
To make the things clear let me show you an example:
event 1 - start date is 2-3-2018 and end date 6-3-2018.
event 2 - start date is 2-3-2018 and end date 8-3-2018.
if you are using normal Date-Filter settings and you filtering based on start date field between (2-3-2018) - (6-3-2018) the event 1 and event 2 will be showing in the search result. but if you are using two Date-Filters one of them filter on the start date and the other on end date and doing filter between 2-3-2018 and 6-3-2018 only event 2 will showing up in the result.
in this scenario the Date-Filter as is not helping you, the solution here as below:
         1.Add one more Date-Filter for (To Date) field.
         2.Hide the to date text box from the first Date-Filter (From Date).
         3.Hide the from date text box from the new Date-Filter (To Date).
         4.Add new facet (To Date) and assign it to the (To Date) Date-Filter.


First Date-Filter:


Second Date-Filter:



Both Date-Filters:



Create new (To Date) facet contains the (To Date) filed:




Now assign the (To Date) facet to the second Date-Filter component:



In this way, your filter will filter all the events start after (From Date) and ends before (To Date) :)

PS: Don't forget to use same search signature in all search components which belongs to the same search.


Tuesday, March 6, 2018

SXA - Sitecore - adding custom css calss to the container component

In this post, i will show you how to add a custom CSS class to the container component which is available in the SXA toolbox,
simply open your site and open settings section
find styles and click on it
find container and click on it,
add your style and name it and make sure to select the container render in the allowed renders.

when you going back to the page and adding container component you can find your class in the available styles, select your style and save the changes.

Monday, February 26, 2018

Sitecore Data Exchange Framework-Dealing with Complex fields

Data Exchange Framework is very helpful, powerful and flexible module.


In this blog post, I will show you how to import data from JSON file to complex Sitecore field using the Sitecore provider which is available in the official Sitecore download page Data Exchange Framework.

I will use the simplest and fastest way to import Complex Sitecore Fields. like date field, image field, drop link field ...etc, Which is "transform values to its Sitecore row values"

First, you need is to install data exchange framework on your Sitecore instance.

Then install Sitecore provider which is available on the same download page of data exchange framework.

Now we are ready to implement the custom JSON provider, as shown briefly below:

SC-Endpoint (JSON provider Endpoint):



It contains three fields :
  1. Path - the Physical file path.
  2. ItemNameSourceProperty - I use this field to name Sitecore item, which is taken from the JSON object.
  3. CastTypeFullName - To convert JSON to c# Type.

VS-Plugin:

We need to read these values and store them to use them later, to store these values you have to create Plugin as below:
public class JsonFileSettings : IPlugin
    {
        public JsonFileSettings()
        {
        }
        public string Path { get; set; }
        public string ItemNameSourceProperty { get; set; }
        public string CastTypeFullName { get; set; }
    }

After that we need to implement the converter to read the values from Sitecore as below:

VS-Endpoint Converter:
public class JsonFileEndpointConverter : BaseEndpointConverter
    {
        public JsonFileEndpointConverter(IItemModelRepository repository) : base(repository)
        {
            this.SupportedTemplateIds.Add(Templates.JsonFileEndpoint.ID);
        }
        protected override void AddPlugins(ItemModel source, Endpoint endpoint)
        {
            var settings = new JsonFileSettings();
            //the file physical path
            settings.Path = base.GetStringValue(source, Templates.JsonFileEndpoint.Fields.Path);
            //This is the property will used from sitecore item naming
            settings.ItemNameSourceProperty = base.GetStringValue(source, Templates.JsonFileEndpoint.Fields.ItemNameSourceProperty);
            //The model type to parse the Json to this type
            settings.CastTypeFullName = base.GetStringValue(source, Templates.JsonFileEndpoint.Fields.CastTypeFullName);

            endpoint.Plugins.Add(settings);
        }
    }

Now, to read the values form the JSON file we need to implement the JsonValueAccessor

SC-Value Accessor:

Make the accessor inherit from the Property Value Accessor because the JSON will be converted to C# object and we will access the values by property name.

VS-Value Accessor Converter:
public class JsonValueAccessorConverter : PropertyValueAccessorConverter
    {
        public JsonValueAccessorConverter(IItemModelRepository repository) : base(repository)
        {
            this.SupportedTemplateIds.Add(Templates.JsonValueAccessor.ID);
        }
        public override IValueAccessor Convert(ItemModel source)
        {
            var accessor = base.Convert(source);
            if (accessor == null)
            {
                return null;
            }
            var propertyName = base.GetStringValue(source, Templates.JsonValueAccessor.Fields.PropertyName);
            if (string.IsNullOrWhiteSpace(propertyName))
            {
                return null;
            }

            if (accessor.ValueReader == null)
            {
                accessor.ValueReader = new PropertyValueReader(propertyName);
            }
            if (accessor.ValueWriter == null)
            {
                accessor.ValueWriter = new PropertyValueWriter(propertyName);
            }
            return accessor;
        }
    }

SC-Pipline Step:

VS-Pipline Step Converter:
public class ReadJsonFileStepConverter : BasePipelineStepConverter
    {
        public ReadJsonFileStepConverter(IItemModelRepository repository) : base(repository)
        {
            this.SupportedTemplateIds.Add(Templates.ReadJsonFilePiplineStep.ID);
        }
        protected override void AddPlugins(ItemModel source, PipelineStep pipelineStep)
        {
            AddEndpointSettings(source, pipelineStep);
        }
        private void AddEndpointSettings(ItemModel source, PipelineStep pipelineStep)
        {
            var settings = new EndpointSettings();
            var endpointFrom = base.ConvertReferenceToModel(source, Templates.ReadJsonFilePiplineStep.Fields.EndpointFrom);
            if (endpointFrom != null)
            {
                settings.EndpointFrom = endpointFrom;
            }
            pipelineStep.Plugins.Add(settings);
        }
    }
VS-Pipline Step Processor:
[RequiredEndpointPlugins(typeof(JsonFileSettings))]
    public class ReadJsonFileStepProcessor : BaseReadDataStepProcessor
    {
        public override void Process(PipelineStep pipelineStep, PipelineContext pipelineContext)
        {
            base.Process(pipelineStep, pipelineContext);
        }

        public override bool CanProcess(PipelineStep pipelineStep, PipelineContext pipelineContext)
        {
            return base.CanProcess(pipelineStep, pipelineContext);
        }

        protected override void ReadData(Endpoint endpoint, PipelineStep pipelineStep, PipelineContext pipelineContext)
        {
            if (endpoint == null)
            {
                throw new ArgumentNullException(nameof(endpoint));
            }
            if (pipelineStep == null)
            {
                throw new ArgumentNullException(nameof(pipelineStep));
            }
            if (pipelineContext == null)
            {
                throw new ArgumentNullException(nameof(pipelineContext));
            }
            var logger = pipelineContext.PipelineBatchContext.Logger;

            var settings = endpoint.GetJsonFileSettings();
            if (settings == null)
            {
                logger.Error("No text file settings are specified on the endpoint. (pipeline step: {0}, endpoint: {1})", pipelineStep.Name, endpoint.Name);
                return;
            }
            if (string.IsNullOrWhiteSpace(settings.Path))
            {
                logger.Error("No path is specified on the endpoint. (pipeline step: {0}, endpoint: {1})", pipelineStep.Name, endpoint.Name);
                return;
            }

            var path = settings.Path;
            if (!Path.IsPathRooted(path))
            {
                path = string.Format("{0}{1}", AppDomain.CurrentDomain.BaseDirectory, path);
            }

            if (!File.Exists(path))
            {
                logger.Error("The path specified on the endpoint does not exist. (pipeline step: {0}, endpoint: {1}, path: {2})", pipelineStep.Name, endpoint.Name, path);
                return;
            }

            Type objectType = Type.GetType(settings.CastTypeFullName, false, true);

            Type genericListType = typeof(List<>).MakeGenericType(objectType);
            var items = (IList)Activator.CreateInstance(genericListType);

            using (StreamReader r = new StreamReader(settings.Path))
            {
                string json = r.ReadToEnd();

                JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings
                {
                    Converters = new List { new BadDateFixingConverter("yyyy-MM-dd") },
                    DateParseHandling = DateParseHandling.None
                };

                items = (IList)JsonConvert.DeserializeObject(json, genericListType, jsonSerializerSettings);

                foreach (var item in items)
                {
                    string itemNameSourcePropertyValue = item.GetPropertyValue(settings.ItemNameSourceProperty).ToString();

                    item.SetPropertyValue("ItemName", Helper.NormalizeItemName(itemNameSourcePropertyValue));
                }
            }

            var dataSettings = new IterableDataSettings(items);
            logger.Info("{0} objects were read from the file. (pipeline step: {1}, endpoint: {2})", items.Count, pipelineStep.Name, endpoint.Name);

            pipelineContext.Plugins.Add(dataSettings);
        }
    }

So this is a normal provider implementation, that use property value to read and write the values, but in this way, only the string value (regular) values will be imported correctly.
We still need to handle the other Sitecore field types.

for example, to import a Sitecore image from URL string:
  1. Read the URL and download the image.
  2. Upload the image to the Sitecore media library.
  3. Assign the uploaded image to the target Sitecore item.
I will use the value transformer to do this as below:

SC-Image Value reader:



let the image value mapping use this reader in "Apply Mapping Pipeline Step", as below :



VS-Image Value Converter:
public class ImageValueAccessorConverter : BaseItemModelConverter
    {
        public ImageValueAccessorConverter(IItemModelRepository repository)
        : base(repository)
        {
            this.SupportedTemplateIds.Add(Templates.ImageValueReader.ID);
        }

        public override IValueReader Convert(ItemModel source)
        {
            if (source == null)
            {
                return null;
            }
            if (!this.CanConvert(source))
            {
                return null;
            }

            var uploadPath = base.GetStringValue(source, Templates.ImageValueReader.Fields.UploadPath);
            if (string.IsNullOrWhiteSpace(uploadPath))
            {
                return null;
            }
            return new ImageValueReader(uploadPath);
        }
    }

VS-Image Value Reader:
 public class ImageValueReader : IValueReader
    {
        string mediaUploadPath = string.Empty;

        public ImageValueReader(string _mediaUploadPath)
        {
            mediaUploadPath = _mediaUploadPath;
        }
        public CanReadResult CanRead(object source, DataAccessContext context)
        {
            bool canReadValue = true;
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            canReadValue = !string.IsNullOrWhiteSpace(mediaUploadPath);

            if (canReadValue)
            {
                string imageUrl = (string)source;

                string fileName = imageUrl.Split(new string[] { "/" }, StringSplitOptions.None).LastOrDefault();

                canReadValue = !string.IsNullOrWhiteSpace(fileName);
            }

            return new CanReadResult()
            {
                CanReadValue = canReadValue
            };
        }

        public ReadResult Read(object source, DataAccessContext context)
        {
            string imageUrl = (string)source;
            ImageItemRepository imageItemRepository = new ImageItemRepository();

            string fileName = imageUrl.Split(new string[] { "/" }, StringSplitOptions.None).LastOrDefault();

            ID itemId = imageItemRepository.Create(fileName, mediaUploadPath, imageUrl);

            return new ReadResult(DateTime.UtcNow) { ReadValue = imageItemRepository.GetImageRawValue(itemId), WasValueRead = true };
        }
    }

VS-Image Item Repository:
  public class ImageItemRepository
    {
        public ID Create(string fileName, string uploadPath, string srcUrl)
        {
            try
            {
                if (string.IsNullOrWhiteSpace(uploadPath))
                {
                    return ID.Null;
                }

                string sitecoreItemName = Helper.NormalizeItemName(fileName.Split('.')[0]);
                Item mediaItem;
                var webRequest = WebRequest.Create(srcUrl.FixUrl());
                using (var webResponse = webRequest.GetResponse())
                {
                    using (var stream = webResponse.GetResponseStream())
                    {
                        if (stream != null)
                        {
                            using (var memoryStream = new MemoryStream())
                            {
                                stream.CopyTo(memoryStream);

                                var mediaCreator = new MediaCreator();
                                var options = new MediaCreatorOptions();
                                options.FileBased = false;
                                options.IncludeExtensionInItemName = false;
                                options.OverwriteExisting = true;
                                options.Versioned = false;
                                options.Destination = uploadPath + "/" + sitecoreItemName;
                                options.Database = Configuration.Factory.GetDatabase("master");

                                using (new SecurityDisabler())
                                {
                                    mediaItem = mediaCreator.CreateFromStream(memoryStream, fileName, options);

                                    return mediaItem.ID;
                                }
                            }
                        }
                    }
                }

                return ID.Null;
            }
            catch (Exception)
            {
                return ID.Null;
            }
        }

      public string GetImageRawValue(ID itemId)
        {
            return "<image mediaid=\"" + itemId.ToString() + "\" />";
        }
    }