šŸ‘‰ Important Update About Horizontal Forms īˆ°

  • 2 November 2017
  • 32 replies
  • 107 views

Userlevel 7
Badge +1
  • Former Community Manager @ Unbounce
  • 831 replies

Big news from Unbounce HQ! :spinbounce:

Weā€™re excited to announce that in the coming weeks, youā€™ll be able to set your landing pages and overlay forms horizontally natively within the Unbounce app! No more workarounds or custom scripts, just drag, drop and convert!

⚠️ However, thereā€™s some important information that goes along with this. Please read onā€¦

Although native horizontal forms will be starting to roll out November 14, and should be available to everyone by November 30th, we wanted to give you a heads-up as weā€™ve had to make a few changes to how our forms are built.

If youā€™ve added custom code that targets the forms on your pages or overlays, there is a possibility that your forms may be adversely affected by this release (i.e. your forms may no longer look or function as before).

PLEASE NOTE: Our new form functionality will only take effect, possibly compromising your custom code, if you re-publish your landing page or overlay. If your page or overlay is currently published and remains that way, it will continue to function as normal. If youā€™d like further clarification on whether your scripts will be effected, comment on this thread and weā€™ll sort you out.

If you havenā€™t created any custom code targeting forms, feel free to ignore this warning and just bask in the exciting glow of new features coming to our page builder! 🎉

For those of you who have created custom scripts, thereā€™s more information here into the changes weā€™ve made on the forms. We hope this will help you in making any applicable changes needed to your scripts.

If you have any questions, hit reply on this post and weā€™ll be happy to provide more information! 👇


32 replies

We added a custom date picker to this page. Do you think itā€™ll be affected?
https://www.canuckplace.org/team-canuck-place-birthday-page/

Userlevel 7
Badge +1

Hi there!

If youā€™re referring to the date picker found in our documentation here, your script wonā€™t be affected. All good! If itā€™s a different script, let me know and Iā€™ll make sure you have all the information you need!
(PS - Go Canucks go!)

Userlevel 6
Badge +3

This is SO exciting @Jess! I never got around to testing out the horizontal form script on my pages, but will definitely do this once it rolls out in the builder. 😍

I have a few scripts/css in place on most of my pages ā€“ will it affect these?

I had a scan through the Google doc, but not exactly clear ā€¦

  1. Form placeholders

  2. Form inline error messages/CSS

  3. Form CSS styling ā€“

    <style>
    
    #lp-pom-form-962 .lp-pom-form-field input[type=text] {
     color: #333;  
     }
    
    #lp-pom-form-962 .lp-pom-form-field textarea {
    color: #333;
    } 
    
    #lp-pom-form-962 
    .form_elem_email, 
    .form_elem_first_name, 
    .form_elem_last_name, 
    .form_elem_phone_number, 
    .form_elem_how_did_you_hear_about_the_program, 
    .form_elem_where_do_you_live {  
       font-family: 'Open Sans', sans-serif;
       font-weight: 300;
       color: #333;
    }
    </style>
Userlevel 3

Hey @Canuck_Place @Zoe_Tattersall

Took a quick peek of those scripts. From quick glance those should work exactly as they did before. We tried to make our form code as backwards compatible as possible. If itā€™s a popular script found on this community it will likely still work.

A possible caveat is if you do use the new horizontal form feature to layout your fields into multiple columns. We canā€™t guarantee some of these scripts will work with this new dimension.

That said, before you hit that ā€˜publishā€™ button be sure to check the results in ā€˜previewā€™. Preview is a great way to validate any potential conflicts before you take those changes live.

Johnny

Hi @Jess, thanks for the update!
We use a javascript to autofill the field names in our forms. Just wanted to see if you thought the new changes would impact our form fields. (script is below)

<script type="text/javascript">
  lp.jQuery(function($) {
  
    // Define your placeholder texts here, corresponding to Unbounce's field names
    var placeholders = {
      "FirstName": "First Name",
      "LastName": "Last Name",
      "Email": "Email",
      "Phone": "Phone"
    };
    // Sets the HTML5 placeholders
    for(var id in placeholders){$("#"+id).attr("placeholder",placeholders[id])}  
    // Polyfill to add support for browsers like IE<=9
    if(document.createElement("input").placeholder===undefined){$("html").attr("data-placeholder-focus","false");$.getScript("//jamesallardice.github.io/Placeholders.js/assets/js/placeholders.jquery.min.js",function(){$(function(){var e=window.module.lp.form.data.validationRules;var t=window.module.lp.form.data.validationMessages;lp.jQuery.validator.addMethod("notEqual",function(e,t,n){return this.optional(t)||$(t).attr("data-placeholder-active")!=="true"||e!==n},function(e,n){return t[$(n).attr("id")].required});for(var n in placeholders){if($("#"+n).length){if(typeof t[n].required!=="undefined"){e[n].notEqual=placeholders[n]}else{e[n]={}}}}})})}
  });
</script>
Userlevel 7
Badge +1

Hey @Samantha_Blaser!

Are you using this workaround for local storage to autofill function? If thatā€™s the case, youā€™re all good! 🙂

You know for us novices, a short video of what you are talking would really be nice.

We appreciate your business, if there is anything further we can do please contact us.

Warmest regards,

Tony Bonifacic

Userlevel 6
Badge +3

Awesome, Iā€™ll be sure to test it out first 🙂

Hi @Jess, thank you for the update.

We are using custom scripts heavily inside our landing pages, in order not to just collect emails, but to let people signup to our website right inside Unbounce (so server-side data validation is necessary).

Some examples are:
ā€¢ https://benvenuto.cortilia.it/spesa-online/ ā€”> cortilia_1_1.js
ā€¢ https://benvenuto.cortilia.it/spesa-online-classico/ ā€”> cortilia_1_0.js

The scripts are identical for the most part, but version _1_1 also inject a second button to the form to redirect visitors to a dedicated page in our website to let them signup with Facebook/Google.

Do you think our scripts will be affected by the update?

Thank you,
Umberto

This is awesome!!! Canā€™t wait to try it out, thanks guys!

We have a form label script to our pages, do you think itā€™ll be affected? From: Form titles in fields

Userlevel 7
Badge +1

Hi Tony! Thatā€™s really great feedback. Iā€™ll see what I can come up with to help make this a bit more clear 🙂

Hi @iumbs! Iā€™m one of the developers who worked on this feature. I looked into your scripts and I believe they should be okay, but I recommend duplicating the page to test it out when the feature rolls out. Specifically, I have concerns on the inline errors that are attached to the form, presumably used for the server-side validation you mentioned. Thereā€™s a possibility they may not appear where youā€™re expecting them to.

Horizontal forms changes our element wrappers (the divs that contain the label, input, and error field) to flex box elements to ensure the inputs span the entire width of the form. If your inline errors rely on absolute positioning they may not appear in the correct spot. If theyā€™re simply inserted into our element wrapper, as it looks like they are, I think you should be fine.

If youā€™d like more details or have any other questions please reach out!

Hi @mae!

The label placeholder script wonā€™t be affected. And weā€™ll have native placeholder text available after a bit more testing so you wonā€™t need to use the custom script anymore 🙂

Hi,

1/ I use mask_plugin.js and add_mask.js in order to force people to indicate their phone number in an international format.

2/ And I use a script called ā€œCTMā€ in order to pass data to CallTrackingMetricsā€™s FormReactor.

mask_plugin.js

<script>
var $j = jQuery.noConflict();

!function(factory) {
    "function" == typeof define && define.amd ? define([ "jquery" ], factory) : factory("object" == typeof exports ? require("jquery") : jQuery);
}(function($j) {
    var caretTimeoutId, ua = navigator.userAgent, iPhone = /iphone/i.test(ua), chrome = /chrome/i.test(ua), android = /android/i.test(ua);
    $j.mask = {
        definitions: {
            "1": "[1-9]",
            "9": "[0-9]",
            a: "[A-Za-z]",
            "*": "[A-Za-z0-9]"
        },
        autoclear: !0,
        dataName: "rawMaskFn",
        placeholder: "_"
    }, $j.fn.extend({
        caret: function(begin, end) {
            var range;
            if (0 !== this.length && !this.is(":hidden")) return "number" == typeof begin ? (end = "number" == typeof end ? end : begin, 
            this.each(function() {
                this.setSelectionRange ? this.setSelectionRange(begin, end) : this.createTextRange && (range = this.createTextRange(), 
                range.collapse(!0), range.moveEnd("character", end), range.moveStart("character", begin), 
                range.select());
            })) : (this[0].setSelectionRange ? (begin = this[0].selectionStart, end = this[0].selectionEnd) : document.selection && document.selection.createRange && (range = document.selection.createRange(), 
            begin = 0 - range.duplicate().moveStart("character", -1e5), end = begin + range.text.length), 
            {
                begin: begin,
                end: end
            });
        },
        unmask: function() {
            return this.trigger("unmask");
        },
        mask: function(mask, settings) {
            var input, defs, tests, partialPosition, firstNonMaskPos, lastRequiredNonMaskPos, len, oldVal;
            if (!mask && this.length > 0) {
                input = $j(this[0]);
                var fn = input.data($j.mask.dataName);
                return fn ? fn() : void 0;
            }
            return settings = $j.extend({
                autoclear: $j.mask.autoclear,
                placeholder: $j.mask.placeholder,
                completed: null
            }, settings), defs = $j.mask.definitions, tests = [], partialPosition = len = mask.length, 
            firstNonMaskPos = null, $j.each(mask.split(""), function(i, c) {
                "?" == c ? (len--, partialPosition = i) : defs[c] ? (tests.push(new RegExp(defs[c])), 
                null === firstNonMaskPos && (firstNonMaskPos = tests.length - 1), partialPosition > i && (lastRequiredNonMaskPos = tests.length - 1)) : tests.push(null);
            }), this.trigger("unmask").each(function() {
                function tryFireCompleted() {
                    if (settings.completed) {
                        for (var i = firstNonMaskPos; lastRequiredNonMaskPos >= i; i++) if (tests[i] && buffer[i] === getPlaceholder(i)) return;
                        settings.completed.call(input);
                    }
                }
                function getPlaceholder(i) {
                    return settings.placeholder.charAt(i < settings.placeholder.length ? i : 0);
                }
                function seekNext(pos) {
                    for (;++pos < len && !tests[pos]; ) ;
                    return pos;
                }
                function seekPrev(pos) {
                    for (;--pos >= 0 && !tests[pos]; ) ;
                    return pos;
                }
                function shiftL(begin, end) {
                    var i, j;
                    if (!(0 > begin)) {
                        for (i = begin, j = seekNext(end); len > i; i++) if (tests[i]) {
                            if (!(len > j && tests[i].test(buffer[j]))) break;
                            buffer[i] = buffer[j], buffer[j] = getPlaceholder(j), j = seekNext(j);
                        }
                        writeBuffer(), input.caret(Math.max(firstNonMaskPos, begin));
                    }
                }
                function shiftR(pos) {
                    var i, c, j, t;
                    for (i = pos, c = getPlaceholder(pos); len > i; i++) if (tests[i]) {
                        if (j = seekNext(i), t = buffer[i], buffer[i] = c, !(len > j && tests[j].test(t))) break;
                        c = t;
                    }
                }
                function androidInputEvent() {
                    var curVal = input.val(), pos = input.caret();
                    if (oldVal && oldVal.length && oldVal.length > curVal.length) {
                        for (checkVal(!0); pos.begin > 0 && !tests[pos.begin - 1]; ) pos.begin--;
                        if (0 === pos.begin) for (;pos.begin < firstNonMaskPos && !tests[pos.begin]; ) pos.begin++;
                        input.caret(pos.begin, pos.begin);
                    } else {
                        for (checkVal(!0); pos.begin < len && !tests[pos.begin]; ) pos.begin++;
                        input.caret(pos.begin, pos.begin);
                    }
                    tryFireCompleted();
                }
                function blurEvent() {
                    checkVal(), input.val() != focusText && input.change();
                }
                function keydownEvent(e) {
                    if (!input.prop("readonly")) {
                        var pos, begin, end, k = e.which || e.keyCode;
                        oldVal = input.val(), 8 === k || 46 === k || iPhone && 127 === k ? (pos = input.caret(), 
                        begin = pos.begin, end = pos.end, end - begin === 0 && (begin = 46 !== k ? seekPrev(begin) : end = seekNext(begin - 1), 
                        end = 46 === k ? seekNext(end) : end), clearBuffer(begin, end), shiftL(begin, end - 1), 
                        e.preventDefault()) : 13 === k ? blurEvent.call(this, e) : 27 === k && (input.val(focusText), 
                        input.caret(0, checkVal()), e.preventDefault());
                    }
                }
                function keypressEvent(e) {
                    if (!input.prop("readonly")) {
                        var p, c, next, k = e.which || e.keyCode, pos = input.caret();
                        if (!(e.ctrlKey || e.altKey || e.metaKey || 32 > k) && k && 13 !== k) {
                            if (pos.end - pos.begin !== 0 && (clearBuffer(pos.begin, pos.end), shiftL(pos.begin, pos.end - 1)), 
                            p = seekNext(pos.begin - 1), len > p && (c = String.fromCharCode(k), tests[p].test(c))) {
                                if (shiftR(p), buffer[p] = c, writeBuffer(), next = seekNext(p), android) {
                                    var proxy = function() {
                                        $j.proxy($j.fn.caret, input, next)();
                                    };
                                    setTimeout(proxy, 0);
                                } else input.caret(next);
                                pos.begin <= lastRequiredNonMaskPos && tryFireCompleted();
                            }
                            e.preventDefault();
                        }
                    }
                }
                function clearBuffer(start, end) {
                    var i;
                    for (i = start; end > i && len > i; i++) tests[i] && (buffer[i] = getPlaceholder(i));
                }
                function writeBuffer() {
                    input.val(buffer.join(""));
                }
                function checkVal(allow) {
                    var i, c, pos, test = input.val(), lastMatch = -1;
                    for (i = 0, pos = 0; len > i; i++) if (tests[i]) {
                        for (buffer[i] = getPlaceholder(i); pos++ < test.length; ) if (c = test.charAt(pos - 1), 
                        tests[i].test(c)) {
                            buffer[i] = c, lastMatch = i;
                            break;
                        }
                        if (pos > test.length) {
                            clearBuffer(i + 1, len);
                            break;
                        }
                    } else buffer[i] === test.charAt(pos) && pos++, partialPosition > i && (lastMatch = i);
                    return allow ? writeBuffer() : partialPosition > lastMatch + 1 ? settings.autoclear || buffer.join("") === defaultBuffer ? (input.val() && input.val(""), 
                    clearBuffer(0, len)) : writeBuffer() : (writeBuffer(), input.val(input.val().substring(0, lastMatch + 1))), 
                    partialPosition ? i : firstNonMaskPos;
                }
                var input = $j(this), buffer = $j.map(mask.split(""), function(c, i) {
                    return "?" != c ? defs[c] ? getPlaceholder(i) : c : void 0;
                }), defaultBuffer = buffer.join(""), focusText = input.val();
                input.data($j.mask.dataName, function() {
                    return $j.map(buffer, function(c, i) {
                        return tests[i] && c != getPlaceholder(i) ? c : null;
                    }).join("");
                }), input.one("unmask", function() {
                    input.off(".mask").removeData($j.mask.dataName);
                }).on("focus.mask", function() {
                    if (!input.prop("readonly")) {
                        clearTimeout(caretTimeoutId);
                        var pos;
                        focusText = input.val(), pos = checkVal(), caretTimeoutId = setTimeout(function() {
                            input.get(0) === document.activeElement && (writeBuffer(), pos == mask.replace("?", "").length ? input.caret(0, pos) : input.caret(pos));
                        }, 10);
                    }
                }).on("blur.mask", blurEvent).on("keydown.mask", keydownEvent).on("keypress.mask", keypressEvent).on("input.mask paste.mask", function() {
                    input.prop("readonly") || setTimeout(function() {
                        var pos = checkVal(!0);
                        input.caret(pos), tryFireCompleted();
                    }, 0);
                }), chrome && android && input.off("input.mask").on("input.mask", androidInputEvent), 
                checkVal();
            });
        }
    });
});
  
  window.jQuery = $ ;
/**
    * Do not remove this section; it allows our team to troubleshoot and track feature adoption. 
    * TS:0002-03-048
*/
</script>

add_mask.js

<script>
$j(function($j){
    //Below are few examples. Replace the IDs with your own form field IDs. 
   	$j("#phone").mask("+33 1 99 99 99 99");
  	$j("#phone_number_2_north_america").mask("1-999-999-9999");
  	$j("#postal_code").mask("a9a 9a9");
  	$j("#zipcode").mask("99999");
  });
</script>

CTM:

<script>
(function() {
  	function getFormValue(objArr, property) {
      	var obj = objArr.filter(function(item) { return item.name === property})
  		return obj.length > 0 ? obj[0].value : "";
    }
  
  	document.getElementsByTagName("form")[0].onsubmit = function(event) {
      	event.preventDefault()
      	var formData = $(this).serializeArray();
      	
      	// trigger this method once you have the form data captured within you web form.  This means you have to capture the form submission events and data.
		__ctm.form.track('app.calltrackingmetrics.com', // the capture host
                 '----', // this FormReactor
                 '----',
                 {
                    country_code: "33", // the expected country code e.g. +1, +44, +55, +61, etc... the plus is excluded
                    name: getFormValue(formData, "nom_de_famille"),
                    phone: getFormValue(formData, "phone").substr(3),
                    email: getFormValue(formData, "email"),
                    custom: {
                        civilite: getFormValue(formData, "civilite"),
                        message:  getFormValue(formData, "message")
                    }
                 });
      
      	return false;
    }
})()
</script>

Hi,

I used a script with the help of someone at Unbounce on this page https://go.adrianflux.co.uk/learner-driver-insurance-1/ will this be affected?

Userlevel 7
Badge +1

Hey Dez, can you tell me which script you used exactly? Thatā€™ll help us to give you all the info you need. 🙂

Hi @Etienne! It looks like both your scripts, Mask and CTM, will be fine. These scripts deal with the input values and the change to forms is strictly visual.

Youā€™re probably already aware, but if you add or remove form fields that are using Mask, you may need to reconnect them. For example, if youā€™re using Mask on a field named ā€œ#phone_aā€ like:

<script>
$j(function($j){
  $j("#phone_a").mask("+33 1 99 99 99 99");
});
</script>

and you replace that field with a new one named ā€œ#phone_bā€, youā€™ll need to update your function like:

<script>
$j(function($j){
  $j("#phone_b").mask("+33 1 99 99 99 99");
});
</script>

Feel free to reach out if you need a hand!

Tx a ton Brian.

You guys rock! Thanks @Brian_Holt

Could someone be so kind as to explain to me what a horizonal form is??

Form boxes are side by side in columns vs. stacked on top of one another 🙂

Exciting stuff!

Are there any changes at all to the JS code responsible for the form functionality, or are these just HTML/CSS changes?

I have a script that hooks into the submit event in order to do some client-side validation to one of my fields. It doesnā€™t use any of the classes that were outlined in the Google doc, but I just want to be extra sure!

<script>
lp.jQuery().ready(function() { 

  var id=window.module.lp.form.data.formButtonId; 

  // deletes unbounce-added submit function: 

  lp.jQuery("#"+id).unbind("click tap touchstart"); 

  lp.jQuery("#"+id).bind("click tap touchstart",function(e) { 
    e.preventDefault(); 
    e.stopPropagation(); 
    
    if (!validateEmail(lp.jQuery("#inf_field_Email").val())) { 
      lp.jQuery("#inf_field_Email").val('');
      lp.jQuery("form").submit(); 
    } else {
      lp.jQuery("form").submit();
    }
    
    function validateEmail(email) {
      var expr = /^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/;
      return expr.test(email);
    };
  }); 
});
</script>

Hi @Daniel_Gillen

When fields within a form (like inputs, checkboxes, etc) are displayed side by side, the community has called that ā€œHorizontal Formsā€. By default, Forms in Unbounce are ā€œverticalā€ as in each field takes its own row, stacking on top of each other.

The community has used JavaScript to get this effect, but weā€™re about to release as a standard option in the Unbounce Builder.

Iā€™ve included a little gif that toggles between a ā€œverticalā€ form and a ā€œhorizontalā€ to make things a little clearer. When there is 1 column, itā€™s vertical. When there are 2 (or more) columns, itā€™s horizontal.

horizontal-forms

Hope that makes sense, but let me know if youā€™d like more clarification!

Hi @leah.ann!

I donā€™t believe your script will be affected. We havenā€™t changed the submit button itself, but we have changed some of the structure and classes wrapping the fields.

You should be in the clear!

Hi,

Here is the script

<script>
/**
    * Do not remove this section; it allows our team to troubleshoot and track feature adoption. 
    * TS:0002-08-042
*/
  lp.jQuery(document).ready(function($) {
    
    //Change #lp-pom-box-01, etc. for the IDs of the boxes where you want the form fields to display.
   var boxes = [
    		   "#lp-pom-box-08",
               "#lp-pom-box-14",           
               "#lp-pom-box-09",
               "#lp-pom-box-10"
              ];
    
    $('.lp-pom-form .lp-pom-form-field')
      .each(function(i, field) {
        $(field)
          .offset($(boxes[i]).offset())
          $(this).children().width($(boxes[i]).width()-16)
      });    
  });
</script>

And here is the stylesheet

<style>
  #lp-pom-form-12 #container_hour {
    top: 150px !important;
  }
  #lp-pom-form-12 #container_minute {
    top: 150px !important;
  }
  #lp-pom-form-12 .form_elem_hour {
    width: 130px;
  }
  #lp-pom-form-12 .form_elem_day_of_the_week { 
    width: 270px !important;
  }
  #lp-pom-form-12 .form_elem_minute { 
    width: 130px !important;
  }  
@media only screen and (max-width: 600px){
  #lp-pom-form-12 .form_elem_hour {
    font-size: 14px !important;
   	top: 20px;
   	width: 130px !important;
  }  
  #lp-pom-form-12 .form_elem_minute {
    font-size: 14px !important;
  }  
  #lp-pom-form-12 .form_elem_day_of_the_week {
      font-size: 14px;
  }
  #lp-pom-form-12 #container_hour {
    top: 150px !important;
  }
  #lp-pom-form-12 #container_minute {
    width: 140px !important;
    left: 140px !important;
    top: 151px !important;
  }  
}
</style>

Reply