Better styling of forms : yet another framework

Many of us have been fighting with the lack of handlers that would enable to easily style forms. Combining multiple ideas, I propose you here a framework that will enable to add specific classes to form columns and form rows dynamically, using some JS that you can add once to your LP templates or in the embedded forms.

The idea is to leverage the fact that you can add a class to a field label and have the javascript replicate that class to any element in the DOM hierarchy of the label.

First, this how you can add some classes to field labels:

1-Access the form in edit mode and select a field (e.g. First name). Click the label advanced editor icon:

pastedImage_0.png

2-In the editor, open to the HTML mode:

pastedImage_1.png

3-And in the HTML, change the simple text into some HTML with a class:

pastedImage_4.png

You can use any class you want, provided that the JS knows how to identify it.

Second, use the following JS to duplicate the label classes to the rows and columns (I borrowed a couple of lines to Sanford Whiteman other solution to this problem, so thx for this 😞

<script>

    MktoForms2.whenRendered(function (form) {

      var ANCESTORS_STOR = '.mktoFormRow, .mktoFormCol',

          _forEach = Array.prototype.forEach;

      function addcustomclasses(formEl) {

            _forEach.call(formEl.querySelectorAll(ANCESTORS_STOR), function(ancestor) {

                    _forEach.call(ancestor.querySelectorAll('.mfc-twelve-twelve'), function(input) {

                    ancestor.classList.add('mfc-twelve-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-eleven-twelve'), function(input) {

                    ancestor.classList.add('mfc-eleven-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-ten-twelve'), function(input) {

                    ancestor.classList.add('mfc-ten-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-nine-twelve'), function(input) {

                    ancestor.classList.add('mfc-nine-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-eight-twelve'), function(input) {

                    ancestor.classList.add('mfc-eight-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-seven-twelve'), function(input) {

                    ancestor.classList.add('mfc-seven-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-six-twelve'), function(input) {

                    ancestor.classList.add('mfc-six-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-five-twelve'), function(input) {

                    ancestor.classList.add('mfc-five-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-four-twelve'), function(input) {

                    ancestor.classList.add('mfc-four-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-three-twelve'), function(input) {

                    ancestor.classList.add('mfc-three-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-two-twelve'), function(input) {

                    ancestor.classList.add('mfc-two-twelve');

                    });

                    _forEach.call(ancestor.querySelectorAll('.mfc-one-twelve'), function(input) {

                    ancestor.classList.add('mfc-one-twelve');

                    });

            });

        }

        var formEl = form.getFormElem()[0];

        addcustomclasses(formEl);

    });

</script>

I am quite sure that the JS code can be streamlined and enhanced, for instance to handle any class starting with "mfc-" instead of a fixed list of classes, but I would leave it to someone better at JS than I am . Another enhancement would be to also recopy the class from the label to the input zone. (Sanford Whiteman​, I know you turn this very basic code into gold)

Finally, you will need to add these classes to you CSS. you can use combined classes to manage specifically rows and columns.

For instance, I have prepared a CSS that uses the following set of classes to manage the columns width and responsive behavior on a multi-column form:

<style>

.mktoForm .mktoFormCol.mfc-twelve-twelve {

    width: 100%;

}

.mktoForm .mktoFormCol.mfc-eleven-twelve {

    width: 91.66%;

}

.mktoForm .mktoFormCol.mfc-ten-twelve {

    width: 83.33%;

}

.mktoForm .mktoFormCol.mfc-nine-twelve {

    width: 75%;

}

.mktoForm .mktoFormCol.mfc-eight-twelve {

    width: 66.66%;

}

.mktoForm .mktoFormCol.mfc-seven-twelve {

    width: 58.33%;

}

.mktoForm .mktoFormCol.mfc-six-twelve {

    width: 50%;

}

.mktoForm .mktoFormCol.mfc-five-twelve {

    width: 41.66%;

}

.mktoForm .mktoFormCol.mfc-four-twelve {

    width: 33.33%;

}

.mktoForm .mktoFormCol.mfc-three-twelve {

    width: 25%;

}

.mktoForm .mktoFormCol.mfc-two-twelve {

    width: 16%;

}

.mktoForm .mktoFormCol.mfc-one-twelve {

    width: 8.33%;

}

@media only screen and (max-width: 480px) {

    .mktoForm .mktoFormCol.mfc-twelve-twelve,

    .mktoForm .mktoFormCol.mfc-eleven-twelve,

    .mktoForm .mktoFormCol.mfc-ten-twelve,

    .mktoForm .mktoFormCol.mfc-nine-twelve,

    .mktoForm .mktoFormCol.mfc-eight-twelve,

    .mktoForm .mktoFormCol.mfc-seven-twelve,

    .mktoForm .mktoFormCol.mfc-six-twelve,

    .mktoForm .mktoFormCol.mfc-five-twelve,

    .mktoForm .mktoFormCol.mfc-four-twelve,

    .mktoForm .mktoFormCol.mfc-three-twelve,

    .mktoForm .mktoFormCol.mfc-two-twelve,

    .mktoForm .mktoFormCol.mfc-one-twelve, {

        width: 100%;

    }

}

</style>

The real advantage of this method is that you can design in advance a series of classes and document them so that your users are fully free to use them as they want in their forms.

-Greg

8371
10
10 Comments
SanfordWhiteman
Level 10 - Community Moderator

Hey Greg,

Your code's looking a little weird (in terms of display, like this in my Firefox):

pastedImage_0.png

Maybe a(nother) Jive regression? (Have you also noticed how abysmally slow the Nation is right now?)

Anyway, yes, I would generalize it like this (which also includes copying the class from <label> to <input>😞

var ANCESTORS_STOR = ".mktoFormRow, .mktoFormCol",

  COLUMN_LAYOUT_PREFIX = "mfc",

  COLUMN_LAYOUT_STOR = "LABEL[class|='" + COLUMN_LAYOUT_PREFIX + "']",

  COLUMN_LAYOUT_RE = new RegExp("^" + COLUMN_LAYOUT_PREFIX + "-"),

  arrayFrom = Function.prototype.call.bind(Array.prototype.slice);

arrayFrom(formEl.querySelectorAll(ANCESTORS_STOR))

  .forEach(function(ancestor) {

    arrayFrom(ancestor.querySelectorAll(COLUMN_LAYOUT_STOR))

      .forEach(function(label) {

        arrayFrom(label.classList)

          .filter(COLUMN_LAYOUT_RE.test.bind(COLUMN_LAYOUT_RE))

          .forEach(function(columnClassItem){

            [ancestor,window[label.htmlFor]].forEach(function(el){

              el.classList.add(columnClassItem);

          });

      });

  });

});

But I don't see this as a full replacement for the other method (tagging wrappers with their contained field names) because that's still necessary in order to style based on semantics of the field (highlight placeholders differently, etc.) not just for overall payout.

Grégoire_Miche2
Level 10

Hi Sandford,

the display thing is weird, I do not have such an issue on my own laptop (using Chrome, though)

I knew you would make the code much better Thx a lot.

-Greg

Grégoire_Miche2
Level 10

An also, agreed it's not a replacement for the wrappers with field names.

On the community being very slow, I posted a question in the "about community" group.

Grégoire_Miche2
Level 10

Hi again,

So here would be the global JS to use :

<script>

MktoForms2.whenRendered(function (form) {

  /*! @author Sanford Whiteman @license MIT */

  /* common vars & aliases */

  var ANCESTORS_STOR = '.mktoFormRow, .mktoFormCol',

      INPUTS_STOR = 'INPUT,SELECT,TEXTAREA,BUTTON',

      attrTag = 'data-wrapper-for',

      _forEach = Array.prototype.forEach,

      COLUMN_LAYOUT_PREFIX = "mfc",

      COLUMN_LAYOUT_STOR = "LABEL[class|='" + COLUMN_LAYOUT_PREFIX + "']",

      COLUMN_LAYOUT_RE = new RegExp("^" + COLUMN_LAYOUT_PREFIX + "-"),

      arrayFrom = Function.prototype.call.bind(Array.prototype.slice);

    /* utility fn to tag wrapper containers with inner form inputs */

    function tagMktoWrappers(formEl) {

      _forEach.call(formEl.querySelectorAll(ANCESTORS_STOR), function(ancestor) {

        ancestor.setAttribute(attrTag, '');

          _forEach.call(ancestor.querySelectorAll(INPUTS_STOR), function(input) {

            var currentTag = ancestor.getAttribute(attrTag);

            ancestor.setAttribute(attrTag, [currentTag ? currentTag : '', input.id, input.name != input.id ? input.name : ''].join(' ').trim());

          });

        });

      }

 

    /* utility that extracts all mfc label classes and add them to ancestors and input */

    function addcustomclasses(formEl) {

        arrayFrom(formEl.querySelectorAll(ANCESTORS_STOR))

          .forEach(function(ancestor) {

            arrayFrom(ancestor.querySelectorAll(COLUMN_LAYOUT_STOR))

              .forEach(function(label) {

                arrayFrom(label.classList)

                .filter(COLUMN_LAYOUT_RE.test.bind(COLUMN_LAYOUT_RE))

                .forEach(function(columnClassItem){

                  [ancestor,window[label.htmlFor]].forEach(function(el){

                    el.classList.add(columnClassItem);

                  });

                });

              });

        });

    }

 

    var formEl = form.getFormElem()[0];

    tagMktoWrappers(formEl);

    addcustomclasses(formEl);

 

});

</script>

-Greg

SanfordWhiteman
Level 10 - Community Moderator

That's mixing two different array functions, so more like so:

  /* common vars & aliases */

  var ANCESTORS_STOR = '.mktoFormRow, .mktoFormCol',

      INPUTS_STOR = 'INPUT,SELECT,TEXTAREA,BUTTON',

      wrapperAttrTag = 'data-wrapper-for',

      COLUMN_LAYOUT_PREFIX = "mfc",

      COLUMN_LAYOUT_STOR = "LABEL[class|='" + COLUMN_LAYOUT_PREFIX + "']",

      COLUMN_LAYOUT_RE = new RegExp("^" + COLUMN_LAYOUT_PREFIX + "-"),

      arrayFrom = Function.prototype.call.bind(Array.prototype.slice);

    /* utility fn to tag wrapper containers with inner form inputs */

    function tagMktoWrappers(formEl) {

      arrayFrom(formEl.querySelectorAll(ANCESTORS_STOR))

        .forEach(function(ancestor) {

          ancestor.setAttribute(wrapperAttrTag, '');

          arrayFrom(ancestor.querySelectorAll(INPUTS_STOR))

            .forEach(function(input) {

              var currentTag = ancestor.getAttribute(wrapperAttrTag);

              ancestor.setAttribute(wrapperAttrTag, [currentTag ? currentTag : '', input.id, input.name != input.id ? input.name : ''].join(' ').trim());

        });

      });

    }

    /* utility that extracts all mfc label classes and add them to ancestors and input */

    function addCustomClasses(formEl) {

        arrayFrom(formEl.querySelectorAll(ANCESTORS_STOR))

          .forEach(function(ancestor) {

            arrayFrom(ancestor.querySelectorAll(COLUMN_LAYOUT_STOR))

              .forEach(function(label) {

                arrayFrom(label.classList)

                .filter(COLUMN_LAYOUT_RE.test.bind(COLUMN_LAYOUT_RE))

                .forEach(function(columnClassItem){

                  [ancestor,window[label.htmlFor]].forEach(function(el){

                    el.classList.add(columnClassItem);

                  });

                });

              });

        });

    }

Grégoire_Miche2
Level 10

Hi Dave Roberts

Web designers for Marketo need to address 3 constraints :

  1. Basically, in most of the teams, the people who create templates and CSS are not the ones who create the forms. So you cannot expect the operational marketing team to be CSS fluent and neither can you expect that the designer will step in on every form (you would create bottlenecks and furthermore you would have serious issues with your shared templates and shared forms)
  2. Not all columns and rows should be treated the same and have the same format. The designer needs to be able to offer a way to style fields easily, without knowing in advance what field will be in what form. So you need to have a way for the form creator (operational marketing) to "tell" the CSS how a given field should be dealt with
  3. And you need to provide a system that will continue to work even when some new fields are added to your data schema and to forms over time. Hence the class and tag system that can work now and tomorrow and give operational marketing users the flexibility they need

-Greg

Grégoire_Miche2
Level 10

Also, all this would probably not be necessary of this idea was impemented

-Greg

Grégoire_Miche2
Level 10

Janet Dulsky​,

This blog post, as most of the post I write, would probably better located in the "Products" section, but I do not have the right to post in Products. Can you do something about it ?

-Greg

Dave_Roberts
Level 10

I agree with you that it'd be easier if you could just easily "tell" the CSS how to deal with a given field in some way that didn't need to involve coding. At the same time, Im not so sure that ALL CSS references need to be so literally tied to the form that they are inflexible for future additions / changes. It'd sure be nice to have a more consistent (or even responsive, by default) framework for the forms, or the ability to append the form row/column classes like you've mentioned, however.

To your first point, I've found that in a lot of cases you're absolutely correct. The big difference-maker I've noticed is preparation in anticipating use-cases and "future changes" and/or coming up with an agreeable set of 'rules' for how the form can/will behave. A good agency / partner is going to work with your team not only to provide a solution, but a workflow/process that is enabling for your team, wherever you're at - I hope that can become an expectation rather than an exception.

In the short term (until we can set this stuff up in the form editor more easily) - I've used the "fieldset" like wrapper to create an alternate style for fields - rather than targeting them via row(1) .mktoRadioList, row(2) .mktoLabel, etc. - it looks more like // fieldset .mktoRadioList { alternate set of styles -- that only applies to radio lists inside a fieldset go here }. This will at least let you target a few fields differently with a little flexibility and future-proofing, without the need to code anything add'l, but at least is an example of how you might set something up with more flexibility using CSS rather than JS for the sake of simplification (WYSIWYG).

Here's to hoping the future is way easier for everyone!

Grégoire_Miche2
Level 10

Hi Dave,

You can easily combine both approaches using the last code corrected by Sanford and get with a method that, if not as easy to use as it should be, at least will be scalable and preserve the future.

-Greg