Categories
Programming

HTML custom input with type hidden and number1 min read

I don’t think it is possible to a create an element like

<input type="hidden | number" />

Solution is to create a custom element like this

<hidden-num value="0"></hidden-num>

MDN – Using Custom ELements (https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)

class InputHiddenNum extends HTMLElement {
    constructor() {
        super();
    }
    connectedCallback() {
        this.style.display = "none";
    }
    get value() {
        return this.intern_value;
    }
    set value(v) {
        this.intern_value = typeof v !== "number" ? parseFloat(v) : v;
        this.setAttribute("value", this.intern_value.toString());
    }
}

customElements.define("hidden-num", InputHiddenNum);

To add a property of value to our <hidden-num> element, we must add the get value()  and set value(val)  methods.

An attribute of value is also added to element if the value property is changed. A number  value is returned if the elem.value  is accessed.

Custom element is registered with

customElements.define(“hidden-num”, InputHiddenNum);

Built in element can also be extended like

<input is=”hidden-num” />

Refer – MDN – Customized built-in Elements (https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#Customized_built-in_elements)

Categories
Programming

An HTML5 desktop app with electron4 min read

Electron let’s you build cross platform desktop apps using HTML5 and javascript. Let’s make a  demo desktop app with electron.

If you haven’t  heard of electron from GitHub, you should go through this Build cross platform desktop apps with JavaScript, HTML, and CSS before continuing.

Without wasting our time let’s get started with the app.

Prerequisites

The app

We shall call our app Dapp – Demo App.

Let’s also create a project folder named dapp and open command window inside the folder.

All the commands have to be run inside this folder.

The first command would be

npm init

Input the required information and our package.json file would be created.

Now we have to install electron which is available as an npm package.

npm install electron-prebuilt --save-dev

We are installing it as developer dependency.

The package.json file has to be modified to run electron at start

{
    "name": "dapp",
    "version": "1.0.0",
    "description": "A demo app made with electron.",
    "main": "index.js",
    "scripts": {
        "start": "electron ."
    },
    "keywords": [
        "electron",
        "demo"
    ],
    "build": {
        "appId": "in.jijnasu.dapp"
    },
    "author": "Hrishikesh M K <hrishikesh.mk1@gmail.com> (https://jijnasu.in)",
    "license": "ISC",
    "devDependencies": {
        "electron-prebuilt": "^1.4.10"
    }
}

Add a start script as shown above.

We have to create a file called index.js, as we have provided it in the package file with the key main.

Let’s just copy the index.js file provided in the electron website.

const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win

function createWindow () {
  // Create the browser window.
  win = new BrowserWindow({width: 800, height: 600})

  // and load the index.html of the app.
  win.loadURL(url.format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }))

  // Open the DevTools.
  win.webContents.openDevTools()

  // Emitted when the window is closed.
  win.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    win = null
  })
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
})

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

The index.js file would load an HTML file called index.html in our main window.

So let’s create one and save it in the same directory.

<!doctype html>
<html>

<head>
    <title>Dapp</title>
    <style>
        html,
        body {
            width: 100%;
            height: 100%;
            position: relative;
            margin: 0;
            padding: 0;
            font-family: Avant Garde, Avantgarde, Century Gothic, CenturyGothic, AppleGothic, sans-serif;
            background-color: #2c3e50;
            color: #ffffff;
        }
        
        #cont {
            text-align: center;
            font-size: 30px;
            height: 300px;
            position: absolute;
            width: 300px;
            left: 50%;
            top: 50%;
            margin-left: -150px;
            margin-top: -150px;
            border: dashed;
        }
    </style>
</head>

<body>
    <div id="cont">
        <h1>Dapp</h1>
        <p id="time">...</p>
        <p id="date">...</p>
    </div>
    <script>
        var time_elem = document.getElementById('time');
        var date_elem = document.getElementById('date');
        window.setInterval(function() {
            var date = new Date();
            date_elem.innerHTML = date.toDateString();
            time_elem.innerHTML = make_valid(date.getHours()) + ':' + make_valid(date.getMinutes()) + ':' + make_valid(date.getSeconds());
        }, 200);

        function make_valid(num) {
            num = parseInt(num);
            if (num < 10) {
                return '0' + num;
            }
            return num;
        }
    </script>
</body>

</html>

Now our app is ready to run. Try it out with this command

npm start

Please note that the command window should be open inside the project folder throughout this tutorial.

Now that our app is ready, the only thing left is to package it.

Packaging electron applications

For packaging we use electron-builder.

First install electron-builder through npm.

npm install electron-builder --save-dev

The documentation for electron-builder tells us to modify the package.json file to add the pack and dist scripts.

A unique app id is also required for the build.

So let’s the modify the file.

{
    "name": "dapp",
    "version": "1.0.0",
    "description": "A demo app made with electron.",
    "main": "index.js",
    "scripts": {
        "start": "electron .",
        "pack": "build --dir",
        "dist": "build",
        "postinstall": "install-app-deps"
    },
    "keywords": [
        "electron",
        "demo"
    ],
    "build": {
        "appId": "in.jijnasu.dapp"
    },
    "author": "Hrishikesh M K <hrishikesh.mk1@gmail.com> (https://jijnasu.in)",
    "license": "ISC",
    "devDependencies": {
        "electron-builder": "^10.4.1",
        "electron-prebuilt": "^1.4.10"
    }
}

And finally run

npm run dist

Comment below if something went wrong.

✌️

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