Organizing form elements with Pug - frontend tips

Organizing form elements with pug

in Front-end | November 12, 2019
Organizing form elements with Pug

Hi, developer!

In order to layout faster and to support the project in an easier way, we have made a pug mixin for form elements. It looks like a big sausage code:

mixin form_element(config)
	//- default values
	- config.class_array = config.class_array || {}
	- config.class_array.block_class_mod = config.class_array.block_class_mod || ''
	- config.class_array.class_mod = config.class_array.class_mod || ''
	- config.class_array.elem_class = config.class_array.elem_class || ''
	- config.add_class = config.add_class || ''
	- config.placeholder = config.placeholder || ''
	if !config.id
		- config.id = config.title.replace(/\s/g, '_').replace(/[.,:;(){}/?!@#$%&*]/g, '')
	else
		- config.id = config.id.replace(/\s/g, '_').replace(/[.,:;(){}/?!@#$%&*]/g, '')
	if config.type === 'text' || config.type === 'password' || config.type === 'email' || config.type === 'textarea' || config.type === 'select' || config.type === 'file'
		dl.form_cell(class=config.class_array.block_class_mod)
			dt.form_c_hline(class=config.class_array.class_mod)
				label(for=config.id) #{config.title}
			dd.form_c_f_w(class=config.class_array.class_mod)
				if config.type === 'text' || config.type === 'password' || config.type === 'email'
					if config.value
						input.f_c_field(value=config.value type=config.type id=config.id class='#{config.class_array.elem_class} #{config.add_class}' placeholder=config.placeholder)
						block
					else
						input.f_c_field(type=config.type id=config.id class='#{config.class_array.elem_class} #{config.add_class}' placeholder=config.placeholder)
						block
				if config.type === 'textarea'
					textarea.f_c_field(type=config.type id=config.id class='#{config.class_array.elem_class} #{config.add_class}' placeholder=config.placeholder) #{config.value}
					block
				if config.type === 'file'
					input.f_c_field(type=config.type id=config.id class='#{config.class_array.elem_class} #{config.add_class}' placeholder=config.placeholder)
					.f_c_field_file_field
						span.f_c_field_file_text #{config.title}
						span.f_c_field_file_butt Choose File
				if config.type === 'select'
					select.f_c_field(id=config.id class='#{config.class_array.elem_class} #{config.add_class}' data-placeholder=config.placeholder)
						each option in config.options
							option(value=option)= option
					block
	if config.type === 'checkbox' || config.type === 'radio'
		label.lbl_rb_ch_block(class=config.class_array.block_class_mod)
			if config.checked
				input.lbl_inp_rb_ch(name=config.name type=config.type class='#{config.class_array.elem_class} #{config.add_class}' checked='checked')
			else
				input.lbl_inp_rb_ch(name=config.name type=config.type class='#{config.class_array.elem_class} #{config.add_class}')
			span.lbl_rb_ch_text(class=config.class_mod) #{config.title}

However, in reality it is a very simple mixin to use. With it, we can do the following types of form elements:

  • input(type=”text”)
  • input(type=”password”)
  • input(type=”email”)
  • input(type=”checkbox”)
  • input(type=”radio”)
  • select
  • textarea

One example of a fetch:

+form_element({
    type:'text',
    class_array:default_field,
    title:'some title',
    id:'some'
})

Let’s first see how mixin works.

Note that the mixin has one parameter – config. This is the object that will contain our options. It the beginning, we declare the defaults. These values will be further used in mixin. If the value is not specified, we replace it with an empty string:

//- default values
- config.class_array = config.class_array || {}
- config.class_array.block_class_mod = config.class_array.block_class_mod || ''
- config.class_array.class_mod = config.class_array.class_mod || ''
- config.class_array.elem_class = config.class_array.elem_class || ''
- config.add_class = config.add_class || ''
- config.placeholder = config.placeholder || ''

Next, we edit the identifier of our field. If it is not specified, it will be equal to the title parameter.

if !config.id
		- config.id = config.title.replace(/\s/g, '_').replace(/[.,:;(){}/?!@#$%&*]/g, '')
	else
		- config.id = config.id.replace(/\s/g, '_').replace(/[.,:;(){}/?!@#$%&*]/g, '')

The essence of this operation is to bring the field id into a valid form. Let’s look at these transformations using an example (before-after):

'some text of id'➞'some_text_of_id'
'#some-?text of id?'➞'some-text_of_id'

After that, mixin is divided into 2 main parts depending on the type of field. If it is input (type = “text”), input (type = “password”), input (type = “email”), select, textarea we will use the structure dl, dt, dd:

<dl>
    <dt>
        <label for="some">Title of form element</label>
    </dt>
    <dd>
        <input type="text" id="some">
    </dd>
</dl>

We use this structure because it is the most semantic. You can read more about it in an article on To add modifier classes in a mixin, there is a class_array parameter (an array of classes). This is how a class is added to mixin:

dt.form_c_hline(class=config.class_array.class_mod)

Here is an example of a fetching along with the definition of an array of classes.

- var default_field = {block_class_mod:'form_cell_v1_mod',class_mod:'form_v1_mod', elem_class:'default_mod'}

+form_element({
    type:'text',
    class_array:default_field,
    title:'some title',
    id:'#some'
})

This is necessary in order to create several types of fields and then boldly reuse them. For example, you have 3 types of fields on your site (for example, input (type = “text”)). You just create 3 such arrays and use them:

- var default_field = {block_class_mod:'form_cell_v1_mod',class_mod:'form_v1_mod', elem_class:'default_mod'}
- var default_field = {block_class_mod:'form_cell_v2_mod',class_mod:'form_v2_mod', elem_class:'second_mod'}
- var default_field = {block_class_mod:'form_cell_v3_mod',class_mod:'form_v3_mod', elem_class:'third_mod'}

Next, let’s consider the following code snippet:

if config.type === 'text' || config.type === 'password' || config.type === 'email'
    if config.value
        input.f_c_field(value=config.value type=config.type id=config.id class='#{config.class_array.elem_class} #{config.add_class}' placeholder=config.placeholder)
        block
    else
        input.f_c_field(type=config.type id=config.id class='#{config.class_array.elem_class} #{config.add_class}' placeholder=config.placeholder)
        block
if config.type === 'textarea'
    textarea.f_c_field(type=config.type id=config.id class='#{config.class_array.elem_class} #{config.add_class}' placeholder=config.placeholder) #{config.value}
    block

The separation is by field type, as well as by the value parameter. Pay attention to the expression ‘block’, it allows you to insert any elements after the field. Example link:

+form_element({
	type:'text',
	class_array:default_field,
	title:'some title',
	id:'#some'
})
    a(href="#") some link after input

As a result, we get the following html:

<dl class="form_cell form_cell_v1_mod">
    <dt class="form_c_hline form_v1_mod">
        <label for="some">some title</label>
    </dt>
    <dd class="form_c_f_w form_v1_mod">
        <input type="text" id="some" placeholder="" class="f_c_field default_mod ">
        <a href="#">some link after input</a>
    </dd>
</dl>

The link was inserted after input. For input (type = “file”), the following structure is used:

if config.type === 'file'
	input.f_c_field(type=config.type id=config.id class='#{config.class_array.elem_class} #{config.add_class}' placeholder=config.placeholder)
	.f_c_field_file_field
		span.f_c_field_file_text #{config.title}
		span.f_c_field_file_butt Choose File

Everything is simple, may vary depending on the layout. For select the following structure is used:

if config.type === 'select'
    select.f_c_field(id=config.id class='#{config.class_array.elem_class} #{config.add_class}' data-placeholder=config.placeholder)
        each option in config.options
            option(value=option)= option
    block

In other words, you need to pass an array of options for the select in the mixin fetch. Here is the example of the fetching:

+form_element({
    type: 'select',
    class_array: default_field,
    title: 'some title',
    id: 'id',
    options: ['Option 1', 'Option 2', 'Option 3']
})

The structure for checkboxes and radio buttons is

if config.type === 'checkbox' || config.type === 'radio'
    label.lbl_rb_ch_block(class=config.class_array.block_class_mod)
        if config.checked
            input.lbl_inp_rb_ch(name=config.name type=config.type class='#{config.class_array.elem_class} #{config.add_class}' checked='checked')
        else
            input.lbl_inp_rb_ch(name=config.name type=config.type class='#{config.class_array.elem_class} #{config.add_class}')
        span.lbl_rb_ch_text(class=config.class_mod) #{config.title}

This structure is most easily styled. Here, the separation goes by the parameter ‘checked’. This is where the mixin is completed.

Fetch examples

input(type=”text”)

pug:

+form_element({
    type:'text',
    class_array:default_field,
    title:'some title',
    id:'#some'
})

html:

<dl class="form_cell form_cell_v1_mod">
  <dt class="form_c_hline form_v1_mod">
    <label for="some">some title</label>
  </dt>
  <dd class="form_c_f_w form_v1_mod">
    <input type="text" id="some" placeholder="" class="f_c_field default_mod ">
  </dd>
</dl>

input(type=”password”)

pug:

+form_element({
	type:'password',
	class_array:default_field,
	title:'some title',
	id:'#some'
})

html:

<dl class="form_cell form_cell_v1_mod">
  <dt class="form_c_hline form_v1_mod">
    <label for="some">some title</label>
  </dt>
  <dd class="form_c_f_w form_v1_mod">
    <input type="password" id="some" placeholder="" class="f_c_field default_mod ">
  </dd>
</dl>

input(type=”email”)

pug:

+form_element({
	type:'email',
	class_array:default_field,
	title:'some title',
	id:'#some'
})

html:

<dl class="form_cell form_cell_v1_mod">
  <dt class="form_c_hline form_v1_mod">
    <label for="some">some title</label>
  </dt>
  <dd class="form_c_f_w form_v1_mod">
    <input type="email" id="some" placeholder="" class="f_c_field default_mod ">
  </dd>
</dl>

textarea

pug:

+form_element({
	type:'textarea',
	class_array:default_field,
	title:'some title',
	id:'#some'
})

html:

<dl class="form_cell form_cell_v1_mod">
  <dt class="form_c_hline form_v1_mod">
    <label for="some">some title</label>
  </dt>
  <dd class="form_c_f_w form_v1_mod">
    <textarea type="textarea" id="some" placeholder="" class="f_c_field default_mod "></textarea>
  </dd>
</dl>

input(type=”checkbox”)

pug:

+form_element({
	type: 'checkbox',
	class_array: default_field,
	title: 'some title',
	id: 'id',
	add_class: 'someclass',
	name: 'radiobtn',
	checked: true
})

html:

<label class="lbl_rb_ch_block form_cell_v1_mod">
  <input name="radiobtn" type="checkbox" checked="checked" class="lbl_inp_rb_ch default_mod someclass"><span class="lbl_rb_ch_text">some title</span>
</label>

input(type=”radio”)

pug:

+form_element({
	type: 'radio',
	class_array: default_field,
	title: 'some title',
	id: 'id',
	add_class: 'someclass',
	name:'radiobtn',
	checked:true
})

html:

<label class="lbl_rb_ch_block form_cell_v1_mod">
  <input name="radiobtn" type="radio" checked="checked" class="lbl_inp_rb_ch default_mod someclass"><span class="lbl_rb_ch_text">some title</span>
</label>

select

pug:

+form_element({
	type: 'select',
	class_array: default_field,
	title: 'some title',
	id: 'id',
	add_class: 'someclass',
	options: ['Option 1', 'Option 2', 'Option 3']
})

html:

<dl class="form_cell form_cell_v1_mod">
  <dt class="form_c_hline form_v1_mod">
    <label for="id">some title</label>
  </dt>
  <dd class="form_c_f_w form_v1_mod">
    <select id="id" data-placeholder="" class="f_c_field default_mod someclass">
      <option value="Option 1">Option 1</option>
      <option value="Option 2">Option 2</option>
      <option value="Option 3">Option 3</option>
    </select>
  </dd>
</dl>

All options

  • type – field type. Possible values are ‘text’, ’textarea’, ’email’, ’password’, ’checkbox’, ’radio’, ’select’.Mandatory field
  • title – field name. Usually is located in dt, except checkbox and radio. Mandatory field
  • id – field identifier.
  • class_array – an array of modifier classes.
  • add_class – additional class for the field.
  • value – the value attribute for the field.
  • placeholder – placeholder attribute for the field. For selects, it is added to the data-placeholder.
  • options – an array of options. Only for selects.
  • name – the name of the field. Only for checkbox and radio.
  • checked – the checked attribute. Only for checkbox and radio.

That’s all.