6465773696

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="/schemas.sitecore.net/Visual-Studio-Intellisense"  xmlns:content="/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

6039339435

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="/www.sitecore.net/xmlconfig/" xmlns:set="/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 😉

oculinoid

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 😀

 

Page editor and structuring related items

Since Sitecore 6.4.1 I’ve been working alot with the Page Editor in Sitecore and how to make the editor experience as good as possible. I’ve found that it’s hard to keep the editor mainly editing in the Page Editor due to various limitations. One of those are structure.

My latest Sitecore solution, using Sitecore 7.1, utilizes the designer in the Page Editor to it’s full potential where I allow the editor to completely reorganize the content the way they see fit using rows, columns and blocks. As you can imagine this puts a higher demand on structure as one page can consist of several sub layouts which each have it’s own item and then add up that you have loads of pages constructed this way.

Idealy the editor could manage the structure at the same time as he/she is adding the content to the page in the page editor. However this is not possible out of the box in Sitecore.

Many of you might at this time say Item Buckets. However this requires the editor to search for the content and my experience with our customers is that many of them aren’t ready for this because they like to manage their own structure.

Well the solution to my problem ended up being quit simple. All I wanted was to have the context menu you have in the content editor. I also remebered that this context menu was available in the media browser when selecting media, so I started out looking there.

I found this attribute on the TreeviewEx in the media browser xml file (\sitecore\shell\Applications\Media\MediaBrowser\MediaBrowser.xml):

ContextMenu='Treeview.GetContextMenu("contextmenu")'

I made a copy of the SelectRenderingDatasource.xml (\sitecore\shell\Applications\Dialogs\SelectRenderingDatasource\SelectRenderingDatasource.xml) file to put in the override folder and added that attribute to the TreeviewEx in that file and the Context Menu was there. However it ended up opening a little off as you can see in this video /screencast.com/t/FkwhxHcM. So I had to make some adjustments to the code opening the context menu.

I ended up altering the attribute to this:

ContextMenu='Treeview.GetContextMenu("dialoguemenu")'

And make the following changes to the \sitecore\shell\Controls\Browser.js, I replaced this part:

if (dimensions.width > 0) {
  switch (data.where) {
    case "contextmenu":
      x = evt.pageX || evt.x;
      y = evt.pageY || evt.y;
    break;

With this:

if (dimensions.width > 0) {
  switch (data.where) {
    case "contextmenu":
    case "dialoguemenu":
      x = evt.pageX || evt.x;
      y = evt.pageY || evt.y;
    break;

And wrapped this part:

var vp = ctl.viewportOffset();
x += vp.left;
y += vp.top;

With this if case:

if (data.where != "dialoguemenu") {
  var vp = ctl.viewportOffset();
  x += vp.left;
  y += vp.top;
}

This made the context menu pop up where the mouse was when right clicking.

I know this might not be the optimal solution. I would prefer that you could configure the rendering with a folder template like you do with a datasource template which you could have a dedicated button for like the “Create new content” button and not relying on a context menu. This would increase the experience with the Page Editor it think. Unfortunatly I didn’t have more time to look into this, maybe next time 🙂