Categories
Programming

Javascript load more plugin6 min read

When searching the internet for a Javascript load more plugin, I came across a Jquery load more plugin tutorial in this youtube channel – Codecourse.

I highly recommend that the reader watch the Codecourse (formerly PHPacademy) tutorial and subscribe to their channel before continuing.

Link to the tutorial – Load More jQuery Plugin.

The tutorial was a really good and the plugin was very useful. But, with the aim of getting better at javascript and programming in general I tried to customize it.

So I decided to redo it in pure javascript. Here is how it went.

Demo link – Javascript load more plugin demo.

Please refer the above tutorial for creating a server side JSON API. The API would look like this Load more JSON API.

Now let’s get started with our plugin.

How to use?

<script src="lmore.js"</script>
<script>
  var itemload=new lmore({elem:cont,source:'json-api-load-more-plugin/',count:3});
</script>

Parameters

  • elem: The main container.
  • source: JSON API page.
  • count: Number of results to load per request.

HTML

The plugin requires an HTML template to work with and the template would look like this.

<div id="cont">
  <span class="rescont" >
    <div id="ent_cont" class="items" >
      <div class="item" >
        <p data-field="id" class="hide" ></p>
        <h2 data-field="title" ></h2>
        <p data-field="description" ></p>
      </div>
    </div>
  </span>
  <div id="no_result" class="nores" >No results to display.</div>
  <button id="load_more" class="load_more" >Load more</button>
</div>

Elements included are

  • #cont – Main Container.
  • .rescont – Container where results have to be loaded.
  • .items – Container for .item
  • .item – Individual data container which would be cloned with each result set.
  • [data-field] – The value of this attribute would be matched with the keys in the JSON object and corresponding values would be filled.
  • .nores – The element to be shown when there are no results. .rescont would be hidden and .nores would be shown if there are no results.
  • .load_more – Button which on click would load more results.

JAVASCRIPT

The plugin would take 3 inputs as given in the usage section.

It would have two main functions

  1. load – A function to load data from the JSON API page using ajax.
  2. append – A function to append the loaded data to the DOM.

The plugin

Wrapper
var lmore = function(options) {
  this.append = function() {

  }
  this.load = function() {

  }
}
Now we have to set the necessary properties.
//Default options
this.def_option = {
  elem: NaN,
  source: NaN,
  count: 3
};

//set the properties
if (options) {
  for (key in options) {
    if (this.def_option.hasOwnProperty(key)) {
      this[key] = options[key];
    }
  }
}

//Http request variable
this.http = 0;

//Starting point
this.start = 1;

//Load more button
this.load_more = false;

//Whether it load more button was clicked or not
this.is_load_more = false;
Getting load more button and saving it and it’s display property
  //set load more button
  if (this.elem.getElementsByClassName('load_more')[0]) {
    this.load_more = this.elem.getElementsByClassName('load_more')[0];
    this.load_more_display = this.load_more.style.display;
    this.load_more.style.display = 'none';
  }
Cloning .item element and removing it from the DOM.
//Clone .item and remove it from DOM
this.items = this.elem.getElementsByClassName('items')[0];
this.itemo = this.items.getElementsByClassName('item')[0];
this.item = this.itemo.cloneNode(true);
this.items.removeChild(this.itemo);
this.nores = this.elem.getElementsByClassName('nores')[0];
this.rescont = this.elem.getElementsByClassName('rescont')[0];
this.resdis = this.rescont.style.display;

//function to remove .item from DOM
this.remove = function() {
  while (this.items.firstChild) {
    this.items.removeChild(this.items.firstChild);
  }
}
The load function
this.load = function() {
  //Check if load more button was clicked
  if (!this.is_load_more) {
    this.start = 1;
    this.remove();
  } else {
    this.start += this.count;
  }


  this.is_load_more = false;

  //Abort pending ajax calls if any
  if (this.http != 0) {
    this.http.abort();
  }

  //Ajax request
  this.http = new XMLHttpRequest();
  this.http.onreadystatechange = function() {
    if (this.http.readyState == 4 && this.http.status == 200) {
      res = JSON.parse(this.http.responseText);
      resp = res.items;

      //If last result hide load more button else show
      if (this.load_more) {
        if (!res.last) {
          this.load_more.style.display = this.load_more_display;
        } else {
          this.load_more.style.display = 'none';
        }
      }

      //Check if there are results
      //If not the no results text is shown
      if (undefined !== resp && resp != null && resp.length) {
        //Loop through each result set
        //and pass it to append function
        for (var i = 0, q = resp.length; i < q; i++) {
          this.append(resp[i]);
        }
        this.nores.style.display = 'none';
        this.rescont.style.display = this.resdis;
      } else {
        this.nores.style.display = 'block';
        this.rescont.style.display = 'none';
      }
    }
  }.bind(this);
  //.bind is used so that 'this' is set
  //to lmore object

  //create query string with 
  //start and count variables
  var qs = this.source + '?lmore_start=' + this.start + '&lmore_count=' + this.count;
  this.http.open('GET', qs);
  this.http.send();
};
The append function
//Append API data to DOM
this.append = function(value) {

  //Create a clone of the .item 
  var clone = this.item.cloneNode(true);

  //select all elements in .item
  //with data-field attribute
  var subs = clone.querySelectorAll('[data-field]');

  //loop through API result
  for (name in value) {
    if (value.hasOwnProperty(name)) {

      //loop through elements
      //with data-field attr
      for (var i = 0, q = subs.length; i < q; i++) {

        //id attr = key of object
        //set value
        if (subs[i].getAttribute('data-field') == name) {
          var ptext = value[name];
          var text = document.createTextNode(ptext);
          this.set_val(subs[i], ptext);
        }
      }
    }
  }

  //append the cloned element
  //to .items
  this.items.appendChild(clone);
};
Function to set value of any element
//Function to set value of any element
//params el - element to set value to
//val - the value to be set
this.set_val = function(el, val) {
  var node_type = el.nodeName.toLowerCase();
  if (node_type != 'input' && node_type != 'select' && node_type != 'textarea') {
    el.innerHTML = val;
    return true;
  }
  if (node_type == 'select' || (node_type == 'input' && el.type != 'checkbox') || node_type == 'textarea') {
    if (el.hasAttribute('multiple')) {
      this.multi_set(el, val);
      return true;
    }
    el.value = val;
    return true;
  } else if (el.type == 'checkbox') {
    el.checked = (val == '1') ? true : false;
    return true;
  }
  return false;
}

//Function to set value of select multiple
this.multi_set = function(sel, values) {
  var vals = Array.isArray(values) ? values : String(values).split(',');
  var opt;
  for (var i = 0, len = sel.options.length; i < len; i++) {
    opt = sel.options[i];
    opt.selected = false;
    for (var x = 0, q = vals.length; x < q; x++) {
      if (opt.value == vals[x]) {
        opt.selected = true;
      }
    }
  }
}
Initial loading

Also setting event listener on load more button to load data on click

//Load first set of data on page load
this.load('');

//The load more button on click would load
//the nextr set of results
if (this.load_more) {
  this.load_more.addEventListener('click', function() {

    //is_load_more is set to true
    //so that start is not reset
    this.is_load_more = true;
    this.load();
  }.bind(this));
}

And finally the plugin is complete ????.

Download link – lmore.js.

Leave your suggestions below.

Categories
Programming

Styling HTML forms 23 min read

In a previous post we looked at styling HTML forms. In this one let’s try to take it further and mess around with it.

What are we going to do?

The aim is to add placeholder text to text inputs. If the element is not in focus we want to show only the placeholder text and hide the label text. But if in focus we want to hide the placeholder text and show the label text.

If that explanation didn’t work, here is a working example –

See the Pen Styling HTML forms 2 by hrishikesh mk (@avastava) on CodePen.0

We can add this effect with a few lines of extra javascript. The javascript in the  previous post looked like this –

var inputfade = document.querySelectorAll('.fi input,.fi select,.fi textarea');
var ifln = inputfade.length;
 
function input_fade(elem, on) {
  var span_elem = elem.parentNode.getElementsByTagName('span');
  if (span_elem.length) {
    span_elem[0].style.color = on ? 'rgb(0, 188, 215)' : '#000';
  }
}
for (var i = 0; i < ifln; i++) {
  inputfade[i].onfocus = function() {
    input_fade(this, true);
  }
  inputfade[i].onblur = function() {
    input_fade(this, false);
  }
}

It is to be noted that the desired effect works only with text inputs or elements that have the placeholder property. Ex – input[type=”text”],[type=”password”],[type=”number”], textarea element.

We have to create a collection of elements containing input elements ( excluding the types submit, checkbox and radio ) and textarea elements.

So let’s create a function which would help us identify the required elements. The function takes an element as input and tells us whether it is a required element or not.

function is_text_input(elem) {
  el_name = elem.nodeName.toLowerCase();
  el_type = elem.getAttribute('type');
  if ((el_name == 'input' && el_type != 'checkbox' && el_type != 'radio' && el_type != 'submit') || el_name == 'textarea') {
    return true;
  }
  return false;
}

Upgrading previous code

We need to add place holders for the input elements. So inside the for loop we add the following code

var elem = inputfade[i];
var span_elem = elem.parentNode.getElementsByTagName('span')[0];
if (is_text_input(elem)) {
  elem.setAttribute('placeholder', span_elem.innerHTML);
  if (!elem.value) {
    span_elem.style.opacity = '0';
  }
}

We take the label text and set it as the placeholder after checking if the element is of the required type. The label text is also hidden by setting the opacity property to zero. We are using the opacity property instead of display: none so that we can animate it using css transitions.

We used the input_fade function in the loop to change color of label text on focus. We have to slightly modify this function so that on focus the label text of the element becomes visible. But on blur if the value of the input field is empty we have to hide the label text and show the placeholder text instead.

function input_fade(elem, on) {
  var span_elem = elem.parentNode.getElementsByTagName('span')[0];
  if (span_elem) {
    span_elem.style.color = on ? 'rgb(0, 188, 215)' : '#000';
  }
  if (is_text_input(elem)) {
    if (on) {
      elem.setAttribute('placeholder', '');
      span_elem.style.opacity = '100';
    } else {
      if (!elem.value) {
        elem.setAttribute('placeholder', span_elem.innerHTML);
        span_elem.style.opacity = '0';
      }
    }
  }
}

 

CSS

Our aim was to create a beautiful form, but for this one to look good we need to add some more lines of code.

Styling placeholder text

The placeholder text should look the same as our label text.

::-webkit-input-placeholder {
    color: black;
    font-size: 17px;
    font-weight: 600;
}
:-moz-placeholder {
    /* Firefox 18- */
    
    color: black;
    font-size: 17px;
    font-weight: 600;
}
::-moz-placeholder {
    /* Firefox 19+ */
    
    color: black;
    font-size: 17px;
    font-weight: 600;
}
:-ms-input-placeholder {
    color: black;
    font-size: 17px;
    font-weight: 600;
}

Source – CSS Tricks

Adding a fading effect

Add this line of code to label text element and your beautiful form is ready. 🙂

transition: opacity 0.5s;
Categories
Programming

Creating a Javascript form validation plugin8 min read

After my HTML Form designs are complete the next problem was to validate the input data before sending it to the servers. I don’t use Jquery or any other javascript frameworks. Also i did not find the inbuilt browser validation working well with the user interface i had in mind. So i had to come up with a new “Javascript form validation plugin” in pure vanilla javascript.

OK maybe i should have named it better.

Let’s get started

So where do i start ?

Here are the thing i wanted it to do

  • Set min, max and other properties on the input element and the plugin should check if the input text satisfies the conditions
  • Show an error message with a user recognizable field name and reason why it failed the test.
  • Scroll to the element which broke the rule on submit.
  • Do not submit data if no changes are made in an edit data form.
  • Generate a JSON string from the form data.

I am no expert in javascript but it seemed simple and i had to give it a try.

So i decided to add 2 custom attributes to my input elements.

  • data-sname: A replacement for the name attribute for inputs. This is for the servers to understand what field the data should be inserted to. Ex – If it is the username field data-sname attribute would be “username”.
    <input data-sname="username" />
    
  • data-lname: A field name that the end user would understand. Ex – If any required field was left blank, the error message would be {data-lname of elem} is required.
    <input data-lname="username" data-sname="username" />
    

To start with let us add 3 rules

  1. min – Min value of the input.
  2. max – Max value of the input.
  3. required – whether the field is mandatory.

More rules can be added to improve upon our “Javascript form validation plugin”.  Yup SEO.

Here is the form we have created in this article with our new found attributes added.

<form id="add_form" class="fiform" novalidate >
  <h1>New User</h1>
  <div class="fi" >
    <label>
      <span>User Type</span>
      <select id="user_type" data-lname="User Type" required data-sname="user_type" >
        <option value="1" >Single</option>
        <option value="1" >Team</option>
      </select>
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Username</span>
    <input id="username" required min="2" max="50" data-sname="username" data-lname="Username" />
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Password</span>
    <input id="password" type="password" required min="6" max="50" data-sname="pass" data-lname="Password" />
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Address</span>
    <textarea id="add" required min="6" max="50" data-sname="addr" data-lname="Address" ></textarea>
    </label>
  </div>
  <div class="fi" >
    <label>
    <input type="checkbox" checked data-sname="notify" />
    <span>Recieve Notifications</span>
    </label>
  </div>
  <div class="fi" >
    <label>
    <input type="submit" value="Add" id="submit" />
    </label>
  </div>
</form>

Javascript

Let us create a validator object.

var validator = function(elem) {
    this.form = elem;
    this.error = [false];
    this.qs = '';
    this.result = {};
    this.changed = false;
    this.changes = [];
    this.check_changes = false;
}

It takes the form element which has to be validated as input.

Now let us add the functions that would validate the input.

var validator = function(elem) {
  this.form = elem;
  this.error = [false];
  this.qs = '';
  this.result = {};
  this.changed = false;
  this.changes = [];
  this.check_changes = false;
  this.valid_input = {
    'min': function(input, val, type) {
      if (type == 'number') {
        if (parseFloat(input) < val) {
          return [false, 'Min value of {fieldname} is ' + val];
        }
      } else {
        if (input.length < val) {
          return [false, 'Min length of {fieldname} is ' + val];
        }
      }
      return [true];
    },
    'max': function(input, val, type) {
      if (type == 'number') {
        if (parseFloat(input) > val) {
          return [false, 'Max value of {fieldname} is ' + val];
        }
      } else {
        if (input.length > val) {
          return [false, 'Max length of {fieldname} is ' + val];
        }
      }
      return [true];
    },
    'required': function(input, val, type) {
      if (!input.length) {
        return [false, '{fieldname} is required'];
      } else {
        return [true];
      }
    }
  };
}

The validation functions take 3 inputs

  1. The inputted value.
  2. The value against it has to be checked.
  3. The type of the input element.

The functions return an array. Array key position 1 gives the validity of the input against the condition provided as Boolean. Position 2 gives the error message if any as string.

Now lets add a message box to show the error message.

if (document.getElementById('form_err')) {
  this.emsg = document.getElementById('form_err');
} else {
  this.emsg = document.createElement('div');
  this.emsg.style.padding = '10px 20px';
  this.emsg.id = 'form_err';
  this.emsg.style.background = 'rgb(0, 0, 16)';
  this.emsg.style.color = 'white';
  this.emsg.style.fontSize = '16px';
  this.emsg.style.position = 'absolute';
  this.emsg.style.zIndex = '100';
  this.emsg.style.display = 'none';
  this.emsg.style.top = '10px';
  this.emsg.style.left = '10px';
  this.emsg.style.opacity = '1';
  this.emsg.style.transition = 'opacity 1s';
  this.emsg.style.height = 'auto';
  this.emsg.style.borderRadius = '3px';
  this.emsg.style.boxSizing = 'border-box';
  var pointer = document.createElement('div');
  document.body.appendChild(this.emsg);
}

We also need a function to show this message box.

this.showerr = function(msg, input) {
  this.emsg.innerHTML = msg;
  this.emsg.style.display = 'inline-block';
  this.emsg.style.position = 'fixed'
  this.emsg.style.opacity = '1';
  setTimeout(function() {
    this.emsg.style.opacity = 0;
  }.bind(this), 5000);
  setTimeout(function() {
    this.emsg.style.display = 'none';
    this.emsg.style.position = 'absolute'
  }.bind(this), 6000);
};

And a scroll to element function

this.scrollt = function() {
  var scroll_speed = 15;
  if (this.left != this.leftlimit) {
    if (this.left < this.leftlimit) {
      this.left += scroll_speed;
    } else {
      this.left -= scroll_speed;
    }
  }
  if (this.top != this.toplimit) {
    if (this.top < this.toplimit) {
      this.top += scroll_speed;
    } else {
      this.top -= scroll_speed;
    }
  }
  window.scrollTo(this.left, this.top);
  this.animator = requestAnimationFrame(this.scrollt.bind(this));
  if ((this.top - this.toplimit) > -scroll_speed && (this.top - this.toplimit) < scroll_speed) {
    cancelAnimationFrame(this.animator);
  }
};

Before that we need a function that would return the value of a given element regardless of its type.

function multi_select(sel, string) {
  var opts = [],
    opt;
  for (var i = 0, len = sel.options.length; i < len; i++) {
    opt = sel.options[i];
    if (opt.selected) {
      opts.push(opt.value);
    }
  }
  return (string) ? opts.join() : opts;
}
get_val = function(el) {
  var node_type = el.nodeName.toLowerCase();
  if (node_type != 'input' && node_type != 'select' && node_type != 'textarea') {
    return el.innerHTML;
  }
  if (node_type == 'select' || (node_type == 'input' && el.type != 'checkbox') || node_type == 'textarea') {
    if (el.hasAttribute('multiple')) {
      return multi_select(el, true);
    }
    return el.value.trim();
  } else if (el.type == 'checkbox') {
    return (el.checked) ? 1 : 0;
  }
  return '';
}

Now the validate function

this.validate = function(no_same) {
  var input = [];
  var inputlen = 0;
  var childrenall = this.form.getElementsByTagName('*');
  for (var i = 0, q = childrenall.length; i < q; i++) {
    if (childrenall[i].hasAttribute('data-sname')) {
      if (childrenall[i].hasAttribute('data-active')) {
        if (childrenall[i].getAttribute('data-active') == 'true') {
          input.push(childrenall[i]);
          inputlen++;
        }
      } else {
        input.push(childrenall[i]);
        inputlen++;
      }
      if (childrenall[i].hasAttribute('data-init')) {
        this.check_changes = true;
        if (childrenall[i].getAttribute('multiple') != null) {
          if (childrenall[i].getAttribute('data-init') != multi_select(childrenall[i]).join()) {
            if (!this.changed) {
              this.changed = true;
            }
            this.changes.push(childrenall[i].getAttribute('data-lname').toLowerCase());
          }
        } else {
          if (childrenall[i].getAttribute('data-init') != get_val(childrenall[i])) {
            if (!this.changed) {
              this.changed = true;
            }
            this.changes.push(childrenall[i].getAttribute('data-lname').toLowerCase());
          }
        }
      }
    }
  }
  if ((this.check_changes && !this.changed) || inputlen == 0) {
    this.showerr('No changes made');
    return [false];
  }
  for (var i = 0, q = inputlen; i < q; i++) {
    for (rule in this.valid_input) {
      if (this.valid_input.hasOwnProperty(rule)) {
        if (input[i].hasAttribute(rule) && (input[i].required || input[i].value != '')) {
          var er_check = this.valid_input[rule](get_val(input[i]), input[i].getAttribute(rule), input[i].type);
          if (!er_check[0]) {
            this.left = this.scrollp.scrollLeft;
            this.top = this.scrollp.scrollTop;
            this.toplimit = input[i].offsetTop - 50;
            this.leftlimit = input[i].offsetLeft - 50;
            this.animator = requestAnimationFrame(this.scrollt.bind(this));
            input[i].focus();
            this.showerr(er_check[1].replace('{fieldname}', input[i].getAttribute('data-lname')), input[i]);
            return [false, input[i]];
          }
        }
      }
    }
    this.result[input[i].getAttribute('data-sname')] = encodeURIComponent(get_val(input[i]));
    if (this.changes.length) {
      this.result['changes'] = this.changes.join();
    }
  }
  return [true, this.result];
}

The function loops through all elements of the given form and creates an array of elements which has an attribute of data-sname.

As you can see if an attribute of data-init is set to an element, the current value of the element is checked against the value of this attribute to see if any changes were made.

On error our validator shows a message and focuses on the element which broke the rule. Finally if no errors are found it returns an object with data-sname as key and value of element as value.

Now let’s put our validator to work

document.getElementById('add_form').onsubmit = function(e) {
  e.preventDefault();
  var val = new validator(this).validate();
  if (!val[0]) {
    return;
  }
  alert(JSON.stringify(val[1]));
}

The returned object is converted to a JSON string, and is ready to be send to our servers for further validation.

Download validator.js

You can download the js file from the above link and use it as shown in  code above.

Here is a working example

See the Pen XjzRrg by hrishikesh mk (@avastava) on CodePen.0