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

Friday, July 15, 2016

Sitecore WFFM: act on success

The question I had to tackle was to change the redirect after a successful form submit based on a form value. By doing so I learned we can do a lot on success (or on error). Using Sitecore 8.1...

Success (and error) WFFM pipelines

All comes down to finding the correct pipelines - as usual in Sitecore. In our case we found a pipeline in case of success and one in case of error. But be aware: there is a big difference between the webforms and mvc solution.

Webforms

The pipelines in case of webforms are:
  • <successAction>
  • <errorSubmit>
These can be found in Sitecore.Forms.config.

Mvc

The pipelines in case of mvc are:
  • <wffm.success>
  • <wffm.error>
These can be found in Sitecore.MvcForms.config.


Custom redirect after success submit

In case of webforms, we could write a processor that redirects to the location we want and place it in the successAction pipeline as first processor (just before the original 'SuccessRedirect').

In case of MVC, it's a different story.

MVC solution 

The wffm.success is by default empty. The actual redirect happens in another pipeline later on. So redirecting here is not the right thing to do. I started writing the processor and noticed that I had a lot of information in a Sitecore.Forms.Mvc.Models.FormModel.

You have access to all the fields, and to the SuccessRedirectUrl property. At this point this is filled with the url as set in Sitecore (which is very useful as a base) and you can alter it as you please. The actual redirect will happen later but it will use the value you have set here.

Code example

public class SuccessRedirect : FormProcessorBase<IFormModel>
{
    public override void Process(FormProcessorArgs<IFormModel> args)
    {
        Assert.ArgumentNotNull((object)args, "args");
        var model = args.Model as FormModel;
        if (model == null)
            return;
         model.SuccessRedirectUrl = model.SuccessRedirectUrl + "?val=" + model.Results[0].Value;
    }
 }

This example shows you to inherit from Sitecore.Forms.Mvc.Pipelines.FormProcessorBase and implement the Process method. Cast the arguments Model property to a FormModel and start using it. I add a querystring to the redirect url with the value of the first field - sounds useless but it's just an example.

Conclusion

As often in WFFM there is a big difference between Webforms and Mvc. I tried the mvc way, and found that you can actually do a lot before Sitecore handles the model. You can change the redirect url, but there are probably also other useful possibilities here for scenarios I can't think of now...

Sunday, May 29, 2016

Sitecore WFFM MVC FileUploadField restrictions

Sitecore WFFM FileUploadField

There are already some blogpost around about custom FileUploadFields, but as things have changed a bit in the latest Web Forms for Marketers releases and certainly with the introduction of MVC in WFFM I thought it could be useful to share the custom field we created on a Sitecore 8.1 update-1 environment with MVC forms.

Let's start by creating some validation attributes...

Validation attributes


Validation for file size


We inherit from DynamicValidationBase and override ValidateFieldValue.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class LimitFileSizeAttribute : DynamicValidationBase
{
    private const string FileSizeLimitKey = "filesizelimit";
    private const string ErrorMessageKey = "filesizelimiterror";

    protected override ValidationResult ValidateFieldValue(IViewModel model, object value, ValidationContext validationContext)
    {
        // Required attribute should handle null value
        if (value == null)
        {
            return ValidationResult.Success;
        }

        var fileBase = value as HttpPostedFileBase;
        if (fileBase == null)
        {
            return new ValidationResult(ErrorMessage);
        }

        var fileUploadField = validationContext.ObjectInstance as FileUploadField;
        var limit = GetLimit(fileUploadField);

        if (limit == -1) // Limit not set
        {
            return ValidationResult.Success;
        }

        if (fileBase.ContentLength > limit)
        {
             return new ValidationResult(GetErrorMessage(fileUploadField));
        }

        return ValidationResult.Success;
    }
 
    private static int GetLimit(IViewModel field)
    {
        if (field == null || !field.Parameters.ContainsKey(FileSizeLimitKey))
        {
            return -1;
        }

        var parameter = field.Parameters[FileSizeLimitKey];
        int intValue;
        if (int.TryParse(parameter, out intValue))
        {
            return intValue;
        }

        return -1;
    }

    private string GetErrorMessage(IViewModel field)
    {
        if (field != null && field.Parameters.ContainsKey(ErrorMessageKey))
        {
            return field.Parameters[ErrorMessageKey];
        }

        return ErrorMessage;
    }
}

The size limit and the error message are retrieved from the parameters on the field.

Validation for allowed extensions

We can create a similar class to validate the extension by using the Path.GetExtension(fileBase.FileName). Of course, other validations are also possible.


MVC Restricted FileUploadField

We inherit from the base FileUploadField in Sitecore.Forms.Mvc.ViewModels.Fields and override the "Value" property, just to add the custom created attributes.

public class RestrictedUploadField : Sitecore.Forms.Mvc.ViewModels.Fields.FileUploadField
{
    [LimitAllowedExtensions]
    [LimitFileSize]
    public override HttpPostedFileBase Value { get; set; }
}



Register in Sitecore

The new custom field needs to be registered in Sitecore:



Usage
Now we can start using our field in a mvc form. Do not forget to fill in the necessary parameters and/or Localized parameters - in case of our example code with the file size limit these are:
  • Parameters : <FileSizeLimit>10485760</FileSizeLimit>
  • Localized parameters : <FileSizeLimitError>File size must be under 10 mb</FileSizeLimitError>

I did not present you a complete FileUpload module here, but the given examples should provide enough information to get you started in your quest to create a customized file upload field in Sitecore WFFM with MVC.

Friday, May 20, 2016

WFFM SendMail message routing

Sitecore WFFM SendMail SaveAction

People who have been working with Sitecore WebForms for Marketers probably all know the SendMail save action to send an email after submitting the form. The mail can contain data entered in the mail and can be send to a fixed address or an address provided in the form. But.. sometimes you do want/need your form to send this email to a different email address based on the value chosen in a dropdown list (e.g. the department the visitor wants to contact).

Custom routing

The code I will share here will demonstrate this routing scenario, where the administrators could define in the list of departments the corresponding email address(es). It can be used as a base for similar situations as well. [We are using Sitecore 8.1 update 1]

The implementation

Our implementation works on a value entered in the "To" field of the mail template. If that value contains a fixed string and a guid pointing towards the dropdown field to identify the recipient we check if we can find the value of that field in a defined folder. The found item will give us the recipients' email-address(es) in one of the fields.

The code

Lets have a look at the code (stripped a bit, I removed the logging to just keep the main points here):

public void Process(ProcessMessageArgs args)
{
 if (args == null || args.To == null) { return; }

 var toEmail = args.To.ToString();
 if (!toEmail.Contains(EmailKey)) { return; }

 var fieldIdValue = toEmail.Replace(EmailKey, string.Empty);
 Guid fieldId;
 if (!Guid.TryParse(fieldIdValue, out fieldId)) { return; }

 var field = args.Fields.GetEntryByID(new ID(fieldId));
 if (field == null) { return; }

 var formDataItem = settingsRepository.GetSettingItem("Forms.FormDataRoot");
 if (formDataItem == null) { return; }

 var result = Sitecore.Context.Database.SelectSingleItem(string.Format(CultureInfo.InvariantCulture, "fast:{0}//*[@@name='{1}']", formDataItem.Paths.FullPath, field.Value));
 if (result == null) { return; }

 var toAddress = result["To"];
 if (string.IsNullOrEmpty(toAddress)) { return; }

 args.To.Clear();
 args.To.Append(toAddress);
}

The steps we perform in this function are:

  1. Check the arguments, especially the "To" value as that is our target
  2. We check if the "To" value contains a constant value EmailKey (if not, we skip)
  3. We check if the remainder of the "To" field is a Guid (if not, we skip)
  4. We try to find a field in the form with this Guid as ID
  5. We (try to) get the root of the values (using our SettingsRepository which is a custom piece of code - it just comes down to fetching an item from Sitecore)
  6. We do a fast query in that root to get the item that corresponds with the selected item (value) in the defined field (if not found, we skip)
  7. We get the value of the "To" field in the found item and use this as the new recipient for the mail by clearing and assigning the "To" field of the arguments. 

The config


<pipelines>
  <processMessage>
    <processor patch:before="processor[@method='SendEmail']" type="xx.Wffm.Pipelines.RouteMessage, xx.Wffm" />
  </processMessage>
</pipelines>

We add our class to the processMessage pipeline just before the mail is send.
And that's it. Nothing more to it.

Wednesday, February 24, 2016

Translating WFFM 8.1 MVC forms error messages part II

Multilingual WFFM

The first post in this duo-series of posts handled the client-side validation of Sitecore's WebForms for Marketers. In this post I want to focus on the server-side error messages: changing them, translating them and the pitfalls you might encounter doing so.

Server-side validation and error messages

Server side validation is a must in forms - no need to tell you that. When using MVC forms you can alter the default messages here:
/sitecore/system/Modules/Web Forms for Marketers/Settings/Meta data/Mvc Validation Error Messages



Translating

As the "value" field of these items is not shared, one would assume that translating the error messages is nothing more than creating versions in other languages and filling in your translated message.
And yes, at first sight you might think that works as you will get to see error messages in a language that could be something else then English and could be even the current language. But it all depends on.. whether you were the first one to request the error messages. As far as we could detect, WFFM caches the error messages but forgot to take the language into account when caching...

So, how do we fix this? Luckily there is a way out. In the documentation of wffm 2.x the "Custom Errors" are mentioned and they are still present in 8.1...

Custom errors

Custom errors are items that can be used as error messages. A few ones exist and you can create more if needed. We used the available custom required and created a new custom email to test.
The items have a key: just look at the regular error message that you would like to use and copy the key (e.g. DynamicRequiredAttribute or DynamicEmailAttribute). In the "value" field you enter your custom message. 
Now create versions for all languages and translate the message as needed.

Using the custom errors

After creating custom error messages, we need to tell our forms to use them. Unfortunately this needs to be done for every field...


In the content editor go to your (required) field. Find the "Dictionary" section and select the desired entries from the list of all custom errors. In many cases that will just be the Custom required, but on an email field you can select for example Custom required and Custom email. Once you've done this for all your fields you're good to go.

Just don't forget to publish all your changes... 

Translating WFFM 8.1 MVC forms error messages part I

Multilingual WFFM

WebForms for Marketers has quite some issues challenges with multilingualism in version 8.1 but in this post I want to focus on the error messages. Error messages come in 2 flavors: client-side and server-side. This first post will handle the client-side validation - server side is for part II.

Client-side validation and error messages

For client-side validation we encountered several issues. First of all, we could not get the messages multi-lingual. Secondly, they didn't work for some fields (e.g. the checkbox list). So the idea we came up with is simple: we remove the client-side validation. 

Removing the client-side validation

First thing to do is disable "Is Ajax MVC Form". Normally this should be sufficient. And it will.. on the first request. But once your visitor gets a validation error (from the server-side validation) the form is reloaded and the client-side validation is back. 

So, let's go for some more serious enhancements then. We'll get rid of the javascript performing the validation!

In "sitecore modules\Web\Web Forms for Marketers\mvc" you will find (amongst others) 2 javascript files: main.js and wffm.js. 

Main.js

  • Remove "jquery_validate: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/jquery/jquery.validate.min",
  • Remove "jquery_validate_unobtrusive: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/jquery/jquery.validate.unobtrusive.min",
  • Edit the "shim" part by removing all references to jquery_validate and jquery_validate_unobtrusive
This should result in:

...
require.config(
{
  baseUrl: generateBaseUrl(),
  paths: {
   jquery: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/jquery/jquery-2.1.3.min",
   jquery_ui: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/jquery/jquery-ui-1.11.3.min",
   bootstrap: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/libs/bootstrap/bootstrap.min",
   wffm: "sitecore%20modules/Web/Web%20Forms%20for%20Marketers/mvc/wffm.min"
  },
  waitSeconds: 200,
  shim: {
    "bootstrap": {
      deps: ["jquery"]
    },
    "jquery_ui": {
      deps: ["jquery"]
    },
    "wffm": {
      deps: ["jquery", "jquery_ui", "bootstrap"]
    }
  }
});
...

Wffm.js

In wffm.js there are several lines referring to the validation which will show errors in your browsers console when you do not remove them. So we started cleaning up.. posting the whole resulting javascript file would be a bit too long but if you take these steps you will get there:

  • remove the "if (ajaxForm)" part
  • remove the $scw.validator.setDefaults part
  • search for validator and remove the references
  • remove any now unused functions 

And last but not least: as you could see in main.js, the minified version of wff.js is used so you will need to minify your changed version and overwrite wffm.min.js.


Wednesday, December 16, 2015

Sitecore WFFM 8.1 and multilingual save actions

WFFM Save actions

Every Sitecore developer that had the 'pleasure' of working with WFFM knows about save actions, and probably also knows that save actions by default are shared. And so: not multilingual. In some cases this is no issue, but if you want to send an email to your visitor you might want to do that in his own language.

KB : Solution 2

A solution for this issue is given in https://kb.sitecore.net/articles/040124. Most people use "Solution 2":

Apply the following customization:
  • Navigate to /sitecore/templates/Web Forms for Marketers/Form
  • Uncheck "Shared" checkbox for the Save Action field.
After this change, you must add Save Actions to each language version of the form item. This means that each language of the form item will keep its own list of Save Actions.

The error

This works up until Sitecore 8.0, but when we tried this in Sitecore 8.1 with WFFM 8.1 we got this:

When we go to a form and switch to another language this nice error appears. Apparently the reason is simple: by making the save action field un-shared we caused empty values in some languages for that field. Sounds very logical indeed.

But Sitecore does not expect an empty value in that field, it expects some xml.

The fix (workaround)

  1. Go to an affected form (in a working language)
  2. Switch to "Raw values"
  3. Open a needed language version of a form item.
  4. Insert the following value into the Save Actions field:
<?xml version="1.0" encoding="utf-16"?>
 <li xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <g id="{E5EABB1F-40BC-45BB-8D87-3B6C239B521B}" displayName="Actions" 
     onclick="javascript:return scForm.postEvent(this,event,'forms:addaction')" />
 </li>
Switch back to normal view (and save). This xml will insert an empty save actions block and you are good to go.