Showing posts with label WFFM. Show all posts
Showing posts with label WFFM. Show all posts

Thursday, February 5, 2015

Error when rendering WFFM form

I came across an interesting WFFM exception on a production CM environment today. It turned out to be a configuration error, so I decided to share
[InvalidOperationException: folder]
   Sitecore.Form.Core.Configuration.ThemesManager.GetThemeName(Item form, ID fieldID) +434
   Sitecore.Form.Core.Configuration.ThemesManager.GetThemeUrl(Item form, Boolean deviceDependant) +270
   Sitecore.Form.Core.Configuration.ThemesManager.ScriptsTags(Item form, Item contextItem) +49
   Sitecore.Form.Core.Configuration.ThemesManager.RegisterCssScript(Page page, Item form, Item contextItem) +184
   Sitecore.Form.Web.UI.Controls.SitecoreSimpleFormAscx.OnInit(EventArgs e) +233
   System.Web.UI.Control.InitRecursive(Control namingContainer) +186
   System.Web.UI.Control.AddedControl(Control control, Int32 index) +189
   Sitecore.Form.Core.Renderings.FormRender.OnInit(EventArgs e) +846
   System.Web.UI.Control.InitRecursive(Control namingContainer) +186
   System.Web.UI.Control.InitRecursive(Control namingContainer) +291
   System.Web.UI.Control.InitRecursive(Control namingContainer) +291
   System.Web.UI.Control.InitRecursive(Control namingContainer) +291
   System.Web.UI.Control.InitRecursive(Control namingContainer) +291
   System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2098

The method - Sitecore.Form.Core.Configuration.ThemesManager.GetThemeName(Item form, ID fieldID) - looks at the Form ID that's configured as the Forms root ID in the site definition.
string formsRootForSite = SiteUtils.GetFormsRootForSite(Context.Site);
Item item = form;
if (form.TemplateID != IDs.FormFolderTemplateID)
{
    item = form.Database.GetItem(formsRootForSite);
}
Assert.IsNotNull(item, "folder");
In my case, the configured ID did not match the actual forms folder item ID in Sitecore.

Tuesday, September 25, 2012

Custom field type for the Sitecore Web Forms for Marketers Module

In my humble opinion, the Web Forms for Marketers module does an awesome job of making not-so-trivial processes like collecting and reporting on data, or tagging and emailing users rather trivial. If you've ever had to make a form with 100+ input fields (and their respective labels and validation expressions), I'm sure you would appreciate it, too.

So while I'm on the custom field type theme, I'll share my experience with customizing a field 'help' text to be clickable and open a popup. This was inspired by the "What's this?" links next to CVV input fields in payment forms where a tidy popup shows you a picture of a credit card with a circled verification code.

As with any custom field, the first step is to decide which of the already existing fields you want to extend. For this example, I took the SingleLineText field and created a SingleLinePopupField. You would need to add references to Sitecore.Forms.Core.dll and Sitecore.Forms.Custom.dll to the project.

[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design", typeof(IDesigner))]
    public class SingleLinePopupField : SingleLineText
    {
        public SingleLinePopupField()
            : this(HtmlTextWriterTag.Div)
        {
        }
        public SingleLinePopupField(HtmlTextWriterTag tag)
            : base(tag)
        {
        }
        protected override void DoRender(HtmlTextWriter writer)
        {
            base.DoRender(writer);
        }
    }

Create a new field item under /sitecore/system/Modules/Web Forms for Marketers/Settings/Field Types/Custom/ and fill out the Assembly and Class fields. Once this is set up, you should be able to easily create a new form and add a field of your custom type.


In order to have custom properties appear in the Form Designer when you select a form field, you would need to add custom properties to the new field class. For my specific requirements, I added a link type (media library image, video, or external link that will open in a new window), link text, and the actual link.

        [VisualCategory("Custom Properties")]
        [VisualFieldType(typeof(LabelTypeField)), VisualProperty("Popup Link Type", 99), DefaultValue("{9975237B-B750-4A17-86C7-48D5E6D58587}")]
        public string LabelLinkType { get; set; }

        [VisualCategory("Custom Properties")]
        [VisualFieldType(typeof(TextAreaField)), VisualProperty("Popup Link Text", 99), DefaultValue("What's this?")]
        public string LabelLinkText { get; set; }

        [VisualCategory("Custom Properties")]
        [VisualFieldType(typeof(TextAreaField)), VisualProperty("Popup Link", 99), DefaultValue("")]
        public string LabelLink { get; set; }

The VisualCategory attribute will group the custom fields in the Form Designer. VisualProperty defines the field label and sort order, and DefaultValue defines..how many cucumbers are sold obviously.

I used a custom VisualFieldType to create a drop down of link types. I added the custom link types in a new enumeration under /sitecore/system/Modules/Web Forms for Marketers/Settings/Meta data similar to other WFFM enumerations. The custom field type should inherit from WebControl and implement IVisualFieldType


 public class LabelTypeField : WebControl, IVisualFieldType
    {
        public LabelTypeField(): base(HtmlTextWriterTag.Select.ToString()) { }

        public string DefaultValue { get; set; }

        public string EmptyValue { get; set; }

        public bool IsCacheable
        {
            get { return true; }
        }

        public bool Localize { get; set; }

        public ValidationType Validation { get; set; }

        protected virtual void OnPreRender(object sender, EventArgs ev)
        {
            this.Controls.Clear();
            base.OnPreRender(ev);
            //Configuration.LabelLinkTypesRoot is the ID of the enumeration folder item
            foreach (Item type in StaticSettings.ContextDatabase.GetItem(Configuration.LabelLinkTypesRoot).Children)
            {
                string str = type.ID.ToShortID().ToString();
                Literal literal2 = new Literal();
                literal2.Text = string.Format("<option {0} regex='{4}' value='{1}' title='{2}'>{3}</option>", new object[] { (DefaultValue == type.ID.ToString()) ? "selected='selected'" : string.Empty, str, type.DisplayName, type.DisplayName, HttpContext.Current.Server.UrlEncode(type.Fields[FieldIDs.MetaDataListItemValue].Value) });
                Literal child = literal2;
                this.Controls.Add(child);
            }
            base.Attributes["onblur"] = string.Format("Sitecore.PropertiesBuilder.onSavePredefinedValidatorValue('{0}', '{1}')", StaticSettings.prefixId + (Localize ? StaticSettings.prefixLocalizeId : string.Empty), this.ID);
            base.Attributes["onchange"] = base.Attributes["onblur"];
            base.Attributes["onkeyup"] = base.Attributes["onblur"];
            base.Attributes["onpaste"] = base.Attributes["onblur"];
            base.Attributes["oncut"] = base.Attributes["onblur"];
            base.Attributes["class"] = "scFbPeValueProperty";
            base.Attributes["value"] = DefaultValue;
        }

        public string Render()
        {
            this.OnPreRender(this, null);
            StringWriter writer = new StringWriter();
            HtmlTextWriter writer2 = new HtmlTextWriter(writer);
            this.RenderControl(writer2);
            return writer2.InnerWriter.ToString();
        }
    }

Now that all properties are set up, the Form Designer should look something like this:


All that's left is to render the proper html for the custom field to achieve the popup effect. You could actually use any modal popup or video player you wish. I find the ColorBox plugin very lightweight and easy to implement, so it's one of my favorites. The markup it requires is minimal as long as it's integrated and initialized properly. For my example, I included the necessary resource files onto the same layout where the WFFM form placeholder sits, and I defined css classes to initialize the popup for the different types of links. Thus, in the custom field type I would need to override the DoRender() method to render a link with the respective class and attach it to the base 'help' field value:

protected override void DoRender(HtmlTextWriter writer)
{
   if (!string.IsNullOrEmpty(this.LabelLinkText))
   {
       string help = string.Empty;

       if (this.LabelType == LabelLinkTypes.ImagePopup)
       {
           help = string.Format("{0} <a class=\"popup\" href=\"{1}\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLinkUrl, this.LabelLinkText);
       }
       else if (this.LabelType == LabelLinkTypes.VideoPopup)
       {
           help = string.Format("{0} <a class=\"iframe\" href=\"{1}\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLinkUrl, this.LabelLinkText);
       }
       else if (this.LabelType == LabelLinkTypes.NewPage)
       { 
           help = string.Format("{0} <a href=\"{1}\" target=\"_blank\"><span style=\"font-size:11px; color:gray;\" >{2}</span></a>", this.Information, this.LabelLink, this.LabelLinkText);
       }
       this.Information = help;
   }

base.DoRender(writer);
}

//Sitecore.Data.ID of the chosen drop down type item
public ID LabelType
{
   get
   {
      return new ID(LabelLinkType);
   }
}

//It might be easier for an editor to input the path to a media item instead of its url, so we'll try for that
public string LabelLinkUrl
{
   get
   {
       string url = string.Empty;

       if (this.LabelType == LabelLinkTypes.ImagePopup)
       {
          //media item
          Item item = StaticSettings.ContextDatabase.GetItem(LabelLink);
          if (item != null)
          {
             MediaItem mi = (MediaItem)item;
             url = MediaManager.GetMediaUrl(mi);
          }
       }
       else
       {
           url = LabelLink;
       }
       return url;
   }
}

And that was it! The links will now be appearing next to the 'help' message below the single-line text field.



Monday, July 23, 2012

Extracting data from the Sitecore Web Forms for Marketers Module

The Web Forms for Marketers module for Sitecore comes with an extensive reporting tool inside the Sitecore Desktop. Information about submissions and activity on forms can be viewed in the reports already provided by the module. Recently, I had to create a custom report on WFFM forms that would live outside of the Desktop, so I decided to put together the basics on accessing form items and entries.
You would need a reference to the Sitecore.Forms.Core.dll

private void Example()
{
     string formID = "{FCD67950-6473-4962-B090-B4821BDB2C80}";
     ItemUri uri = new ItemUri(Sitecore.Data.ID.Parse(formID), Sitecore.Context.Database);

     //1. Get Form
     FormItem form = new FormItem(Database.GetItem(uri));
     string name = form.FormName;

     //2. Data Filters
     List<GridFilter> filters = new List<GridFilter>();
     // 2.a Form filter
     filters.Add(new GridFilter(Sitecore.Form.Core.Configuration.Constants.DataKey, formID, GridFilter.FilterOperator.Contains));
     // 2.b Get archived items
     filters.Add(new GridFilter(Sitecore.Form.Core.Configuration.Constants.StorageName, Sitecore.Form.Core.Configuration.Constants.Archive, GridFilter.FilterOperator.Contains));
            
     //3. Get all entries
     IEnumerable<IForm> entries = Sitecore.Forms.Data.DataManager.GetForms().GetPage(new PageCriteria(0, 0x7ffffffe), null, filters);

     // 3.a Apply custom filtering on the entries
     entries = entries.Where(a => a.Timestamp.Date.CompareTo(startDate) >= 0 && a.Timestamp.Date.CompareTo(endDate) <= 0);
            
     //4. Create a form packet
     FormPacket packet = new FormPacket(entries);

     CustomProcessor export = new CustomProcessor();
     string result = export.Process(form, packet);
}

1. Get the form
Use the FormItem constructor, passing in the inner data item, which, like any other item, can be grabbed based on its ID from the Context.Database. The FormItem class will give you access to various form properties such as the form.Introduction, form.Footer, and form.FormName as well as all the form fields and save actions.


2. Data filters
There are two filters that are obligatory in order to use the Sitecore.Forms.Data.DataProvider. To grab entries for a specific form, you will need to apply a GridFilter on the DataKey ("dataKey") field where the criteria would be the ID of the form. And the second filter specifies whether you are getting entries out of the archive or not. 

3. Get the entries
Having defined the grid filters, you can now grab all entries using the DataManager.

4. Create a form packet
The FormPacket makes it easy to ship a packet of entries that you've already filtered out off to a processor. For example, this could be a processor for a specific file type.