Nytt kontor

Kompilera har tagit ett stort steg vidare i sin utveckling och har under veckan flyttat från kontorshotell på Berga i utkanten av Helsingborg till helt egen kontorslokal på Norra Kyrkogatan 15 mitt i centrala Helsingborg. Är du i behov av extra kompetens inom .NET/.NET Core, React och Angular i ert utvecklingsteam, eller kanske bara se våra nya lokaler, tveka inte att kontakta oss.

Identifying visitor in Web forms for marketers without login in

As you might know, out of the box, it is not possible to just identify a contact with Web forms for marketers (WFFM) module in Sitecore without either creating or login into an account. This makes it troublesome if you for example just want to have something simple like a newsletter subscription form with just an email field. Why create an account for that, right?

So what to do then? We create our own Save Action to do this! And this is what I’ll present in this post.

Save action editing

So where do we start? Well first of, what do I want? I want this Save Action to be dynamic, so nothing hardcoded. I need to be able to select what field in the form that I want to use a an identifier when identifying the current visitor. So I started looking at the existing Save Action and found that the Register Campaign hade an editor with a tree to select a campaign. Nice, this I can use. So I copied it’s editor xml control \sitecore\shell\Applications\Modules\Web Forms for Marketers\Dialogs\Action Editor\CampaignEditor.xml and named it IdentifyContact.xml and created a new codebesid file to be able to set the correct root item and also to pass the selected field back to the save action with the key FieldId.

<?xml version="1.0" encoding="utf-8" ?>
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense"  xmlns:content="http://www.sitecore.net/content">
  <IdentifyContact.Editor>

    <FormDialog ID="Dialog" Icon="Business/32x32/money2.png" >

      <Stylesheet Src="FormBuilder.css" DeviceDependant="true"/>

      <CodeBeside Type="Namespace.IdentifyContactEditor,Assembly"/>

      <DataContext ID="ItemDataContext" DataViewName="Master" Database="master" ShowRoot="true" />

      <GridPanel Columns="1" CellPadding="4" Width="100%" Height="100%" Style="table-layout:fixed">
        <Border Width="100%" Height="100%">
          <Literal ID="SelectCampaignEventLiteral" />
          <Scrollbox Width="100%" Height="460px" Background="white" Border="1px inset" Margin="0px 0px 10px 0px">
            <DataTreeview ID="ItemLister" MultiSelect="false" DataContext="ItemDataContext" AllowDragging="false"  />
          </Scrollbox>
        </Border>
      </GridPanel>

    </FormDialog>
  </IdentifyContact.Editor>
</control>
public class IdentifyContactEditor : DialogForm
{
    public static readonly string FieldIdKey = "FieldId";
    private readonly IResourceManager resourceManager;
    protected DataTreeview ItemLister;
    protected DataContext ItemDataContext;
    protected Literal SelectCampaignEventLiteral;
    protected XmlControl Dialog;
    private NameValueCollection nvParams;
    public Database CurrentDatabase
    {
        get
        {
            return Factory.GetDatabase(Sitecore.Web.WebUtil.GetQueryString("db"));
        }
    }

    public string CurrentID
    {
        get
        {
            return Sitecore.Web.WebUtil.GetQueryString("id");
        }
    }
    public FormItem FormItem
    {
        get
        {
            return new FormItem(this.CurrentDatabase.GetItem(this.CurrentID, this.CurrentLanguage));
        }
    }

    public Language CurrentLanguage
    {
        get
        {
            return Language.Parse(Sitecore.Web.WebUtil.GetQueryString("la"));
        }
    }
    public string Params
    {
        get
        {
            return HttpContext.Current.Session[Sitecore.Web.WebUtil.GetQueryString("params")] as string;
        }
    }
    public string FieldIdValue
    {
        get
        {
            return this.GetValueByKey(IdentifyContactEditor.FieldIdKey);
        }
        set
        {
            this.SetValue(IdentifyContactEditor.FieldIdKey, value);
        }
    }

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        if (Sitecore.Context.ClientPage.IsEvent) return;

        this.Localize();
        this.ItemDataContext.Root = FormItem.ID.ToString();

        if (!string.IsNullOrEmpty(FieldIdValue))
            this.ItemDataContext.DefaultItem = FieldIdValue;            
    }

    protected virtual void Localize()
    {
        this.Dialog["Header"] = (object)"Identify contact";
        this.Dialog["Text"] = (object)"Configure what field data should be used when identifying the current visitor session";
        this.SelectCampaignEventLiteral.Text = "Select forms field to used when identifying:";
    }


    protected override void OnOK(object sender, EventArgs args)
    {
        var selectedItem = this.ItemLister.GetSelectionItem();
        if(selectedItem != null)
            this.SetValue(IdentifyContactEditor.FieldIdKey, selectedItem.ID.ToString());

        string str1 = ParametersUtil.NameValueCollectionToXml(this.nvParams ?? new NameValueCollection());
        if (str1.Length == 0)
            str1 = "-";
        SheerResponse.SetDialogValue(str1);
        base.OnOK(sender, args);
    }
        
    private void SetValue(string key, string value)
    {
        if (this.nvParams == null)
            this.nvParams = StringUtil.GetNameValues(this.Params, '=', '&');
        this.nvParams[key] = value;
    }

    private string GetValueByKey(string key)
    {
        if (this.nvParams == null)
            this.nvParams = ParametersUtil.XmlToNameValueCollection(this.Params);
        return this.nvParams[key] ?? string.Empty;
    }
}

After that I created a Save Action item at the WFFM Save Action folder in Sitecore and pointed out what Editor I wanted by entering control:IdentifyContact.Editor in the Editor field.

Great now I have a way to add the Save action and select what field I want to use to identify the visitor with like this:

Save action execution

Next thing is to have this Save Action do something when a form is submitted. This is kind of straight forward. I created a class IdentifyContact which inherits Sitecore.WFFM.Actions.Base.WffmSaveAction that looks like this

public class IdentifyContact : Sitecore.WFFM.Actions.Base.WffmSaveAction
{
    public string FieldId { get; set; }
        

    public override void Execute(ID formId, AdaptedResultList adaptedFields, ActionCallContext actionCallContext = null, params object[] data)
    {
        if (!Tracker.IsActive)
            Tracker.StartTracking();

        if (string.IsNullOrEmpty(FieldId)) return;
        if (!ID.IsID(FieldId)) return;
            
        var field = adaptedFields.FirstOrDefault(x => x.FieldID.Equals(FieldId, StringComparison.CurrentCultureIgnoreCase));
        Assert.ArgumentNotNull(field, "field");

        var identifier = field.Value;
        if(IsEmail(identifier))
        {
            var userName = Membership.GetUserNameByEmail(identifier);
            identifier = string.IsNullOrEmpty(userName) ? identifier : userName;
        }

        Tracker.Current.Session.Identify(identifier);
    }

    private bool IsEmail(string value)
    {
        return Regex.IsMatch(value, @"\A(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)\Z", RegexOptions.IgnoreCase);
    }
}

Here the selected field id is loaded automatically into the property FieldId (done by WFFM module, it’s like magic) which I used to find the field with entered value in the adaptedFields list. For good measure if the selected field value is an email I check if there is a user with that email and if so we used the username to identify and if not we used the value entered in the field to identify the current visitor.

Next step is to add this class to the Save Action item by entering the Namespace and Assembly to the Assembly and Class fields. Also do not forget to check the checkbox Client Action since we want this action to be performed on the Content Delivery server and not the Content Management server.

This was all developed in Sitecore XP 8.2 Update-1 using Web forms for marketers 8.2 Update-1

Sitecore Page Editor Tips and tricks – component datasource versions part 2

In my previous post I built a rule that would hide a component based on some criterias regarding the datasource item. In the Page Editor these hidden component would show as grey blocks which is nice since you get a clear indication that there’s a component and that it isn’t translated. But now what? A natural next step would be to be able to add a version of that component to be able to translate it.

My main idea was to add a button to the component in the page editor, visible when the component was greyed out. When clicked would add a version in the current language of the related item.

As I didn’t realy know if things would work or not, my approach was kind of touch and go. My first goal was to add a button when the component was hidden (greyed out). On a rendering you can add additional buttons visible for that component when in the Page Editor using the Page Editor Buttons. However once a rendering is hidden it will be switched in the Page Editor to another rendering and all the configured Page Editor Buttons would disappear. You could probably add buttons to the Hidden Rendering which will replace the original rendering, but I didn’t have that in mind at the time 🙂

I went with adding a Default Rendering Button, which is located here in the Core database /sitecore/content/Applications/WebEdit/Default Rendering Buttons/. This is where all default buttons for a component is located such as the Edit Related item, Change Position and so on. So I created a button.

 

Then I created a class inheriting the WebEditCommand. I didn’t know what to expect when the button executed the command. If I would get the datasouce item out of the box or if I would have to manually find it in some way. But it turned out to be quit easy. If the rendering had no datasource configured the CommandContext object passed to the command would give the current item and if the datasource was set it would pass that item instead. Since this was a default rendering button, it would be visible for all renderings all the time. So I had to override the QueryState and write my own to hide the button if the item had a version. Below is the resulting command:

public class AddDatasourceVersion : WebEditCommand
    {
        public override void Execute(CommandContext context)
        {
            Assert.ArgumentNotNull((object)context, "context");
            Item obj = context.Items[0];
            if (obj.Versions.Count > 0) return;
            using (new SecurityDisabler())
            {
                obj.Versions.AddVersion();
            }
        }
        public override CommandState QueryState(CommandContext context)
        {
            Assert.ArgumentNotNull((object)context, "context");
            Item obj = context.Items[0];
            return obj.Versions.Count > 0 ? CommandState.Hidden : CommandState.Enabled;

        }
    }

A command was defined and configured for the button, I went with:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:set="http://www.sitecore.net/xmlconfig/set/">
  <sitecore>
    <commands>      
      <command name="custom:adddatasourceversion" type="Lab.Sample.Commands.WebEdit.AddDatasourceVersion,Lab.Sample"/>
    </commands>
  </sitecore>
</configuration>

A recommendation though! For best user experience, use Standard Values with prefilled dummy data, because it can be hard to find the component once a version has been added if there is no content or any obvious graphical elements 😉

Sitecore Page Editor Tips and tricks – component datasource versions

Out of the box, Sitecore does nothing if a components datasouce doesn’t exist or if there aren’t any versions in the current language. This means that you will have to handle this your self.
Well I thought that it would be nice to let the rules engine in Sitecore handle this.

My plan was to create a global conditional rendering rule that would check the following criterias:

  • Is the Rendering Item of the current rendering configured with a Datasource Template?
  • If not, ignore the following statements and display the rendering!
  • Does the rendering have any datasource set?
  • Does the datasource exist?
  • If the datasource item is a media item, the next statement should be ignored and the rendering displayed.
  • Does it have any versions in the current language?

If it would fail on any of these statement, the rendering should be hidden. Since there weren’t any existing condition which did this, I had to build it my self. Things said and done and the result was this:

public class HasItemVersionForDatasource : OperatorCondition
      where T : ConditionalRenderingsRuleContext
    {
        protected override bool Execute(T ruleContext)
        {
            var renderingItem = ruleContext.Item.Database.GetItem(ruleContext.Reference.RenderingID);
            var dsLocation = renderingItem.Fields["Datasource Location"].Value;
            var dsTemplate = renderingItem.Fields["Datasource Template"].Value;
            if ((dsLocation.Length > 0 || dsTemplate.Length > 0) && string.IsNullOrEmpty(ruleContext.Reference.Settings.DataSource)) return false;
            else if (string.IsNullOrEmpty(ruleContext.Reference.Settings.DataSource)) return true;

            var datasourceitem = ruleContext.Item.Database.GetItem(ruleContext.Reference.Settings.DataSource);
            return datasourceitem.Paths.IsMediaItem || datasourceitem.Versions.Count > 0;
        }
    }

This condition was added to Sitecore and enabled for the Conditional Rendering Rules. Since the condition doesn’t take any parameters you can write anything you like, I went with “where the renderings datasource has item version”.
A new rule at /sitecore/system/Settings/Rules/Conditional Renderings/Global Rules was created using the new condition looking like this:

 

With this now set up, all renderings with a datasource which doesn’t have any version in the current language will be hidden. And in the Page Editor they will be visible as grey blocks like this:

 

That’s it, pretty usefull right? 😀 Now you’ll be relieved from performing null-checks and so on in each component. And also you will get a clear indication in the Page Editor that there are components that aren’t translated.

Note that this only handles guid and path based datasources and not query based datasources. However it could surely be extended.

I will follow up with some additional post regarding Page Editor tips and tricks 😀