👉 Important Update About Horizontal Forms 

  • 2 November 2017
  • 32 replies
  • 108 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

Userlevel 7
Badge +1

Hey Ken!

I’m going to send you a direct message because I’ll need a bit more information in regards to which page this is effecting.

I’ll also be able to find out if this is something that our Support team is more fit to handle. I’ve notified to team to keep a heads up for your message, however they take tickets in a first-come-first-serve basis.

Hang tight!

Hi, in the editor my forms are hosed. I cannot republish my pages with because the form field widths have all been automatically adjusted somehow and I cannot get them back to the right width. I need some help asap and cannot get anyone on phone or chat.

Ken

Userlevel 7
Badge +1

Hey Sean!

If the problem has resolved itself I would assume that you’re in the clear, however, to be sure, would you mind telling me which workaround you’ve implemented on your page?

I’ll keep an eye out for your response 🙂

Hi

were using multi part forms using a script the Unbounce team recommended
(but not an unbounce product), we did have some issues the other day but
seems ok now. Can we continue to use them?

Sean

Userlevel 7
Badge +1

Hey @Chris_Collis and @BrunoLifts!

Thanks for reaching out (and apologies for the delay in my response!)

We did a slow release of the horizontal forms in the builder. The process began on November 14 and will be available to everybody on November 30th.

If this is something you’d like to be enabled sooner than the listed dates, please send me a DM and I’ll get you set up. Sorry for any confusion!

We’re in the same boat as Chris. Was the horizontal form feature not rolled out on Nov 14th?

Hi
I cannot see the ability to build horizontal forms on our landing pages. Do i have to use a certain template or activate anything in order to use them?

Thanks
Chris

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>

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 @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!

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>

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

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

You guys rock! Thanks @Brian_Holt

Tx a ton Brian.

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!

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,

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?

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 @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 @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!

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 🙂

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

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

Userlevel 6
Badge +3

Awesome, I’ll be sure to test it out first 🙂

Reply