1560870406
How to write simple modern JavaScript apps with Webpack and progressive web techniques - Have you thought about making modern JavaScript applications with the simplest setup possible for your next project?
If so, you have come to the right place!
JavaScript frameworks exist to help us build applications in a generalized way with most of the common features. But most of the applications may not need all the powerful features of a framework. It may be overkill to just use a framework for specific requirements (especially small to medium scale projects).
Today I am going to show an approach to how you can use modern features and build your own customized Web Applications. You can also build your own framework on top of the sample applications if you want to. That is purely optional. The power of Vanilla JavaScript enables us to follow our own coding style irrespective of the tools used.
Before starting out, let us quickly skim through the features we need.
To ensure fast loading and consistent experiences, we’ll use the following patterns:
We need a good custom build setup, so we will be using Webpack with the following requirements:
We will be touching on minimal JavaScript features to get us off the ground and produce the output we require. I will show you how we can use existing JavaScript ES6 features in our day to day vanilla applications. Here they are:
At the end of this article there is a sample application demo along with its source code on GitHub. Let’s dig deeper, shall we? 🙌
The advent of Progressive Web Applications has helped bring new architectures in order to make our first paint more effective. Combining App Shell and PRPL patterns can result in consistent responsiveness and app-like experiences.
App Shell is an architectural pattern for building Progressive Web Applications where you ship the minimal critical resourcesin order to load your site. This basically consists of all the necessary resources for the first paint. You may cache the critical resources as well using a service worker.
PRPL refers to the following:
The App Shell and PRPL pattern are both used together to achieve the best practices.
The App shell looks somewhat like the following piece of code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<!-- Critical Styles -->
<style>
html {
box-sizing: border-box;
}
*,
*:after,
*:before {
box-sizing: inherit;
}
body {
margin: 0;
padding: 0;
font: 18px 'Oxygen', Helvetica;
background: #ececec;
}
header {
height: 60px;
background: #512DA8;
color: #fff;
display: flex;
align-items: center;
padding: 0 40px;
box-shadow: 1px 2px 6px 0px #777;
}
h1 {
margin: 0;
}
.banner {
text-decoration: none;
color: #fff;
cursor: pointer;
}
main {
display: flex;
justify-content: center;
height: calc(100vh - 140px);
padding: 20px 40px;
overflow-y: auto;
}
button {
background: #512DA8;
border: 2px solid #512DA8;
cursor: pointer;
box-shadow: 1px 1px 3px 0px #777;
color: #fff;
padding: 10px 15px;
border-radius: 20px;
}
.button {
display: flex;
justify-content: center;
}
button:hover {
box-shadow: none;
}
footer {
height: 40px;
background: #2d3850;
color: #fff;
display: flex;
align-items: center;
padding: 40px;
}
</style>
<title>Vanilla Todos PWA</title>
</head>
<body>
<body>
<!-- Main Application Section -->
<header>
<h3><a class="banner"> Vanilla Todos PWA </a></h3>
</header>
<main id="app"></main>
<footer>
<span>© 2019 Anurag Majumdar - Vanilla Todos SPA</span>
</footer>
<!-- Critical Scripts -->
<script async src="<%= htmlWebpackPlugin.files.chunks.main.entry %>"></script>
<noscript>
This site uses JavaScript. Please enable JavaScript in your browser.
</noscript>
</body>
</body>
</html>
Lines 4–17: The install event of service workers helps to cache all the static assets. Here, you can cache the app shell resources (CSS, JavaScript, images, etc.) for the first route (as per App shell). Also, you can cache the remainder of the assets of the application ensuring the whole app can run offline too. This caching of static assets apart from the main app shell ensures the Pre-cache stage of the PRPL pattern.
Lines 19–38: The activate event is the place for cleaning up of unused caches.
Lines 40–63: These lines of code help in fetching resources from the cache if they are in cache or go to network. Also, if a network call is made, then the resource is not in cache and put into a new separate cache. This scenario helps to cache all dynamic data for an application.
All in all, most of the parts of the architecture have been covered. The only part left is the Lazy loading stage of the PRPL pattern. I will discuss this with regards to JavaScript.
What is a good architectural structure without a build setup? Webpack to the rescue. There are other tools like Parcel, Rollup etc. out there, but whatever concepts we apply to Webpack can be applied to any such tool.
I will map the concepts used to the plugins so that you can get hold of the basics used for setting up of the workflow. This is the most important step to getting started with a good reusable build config for your own application for the future.
I know how difficult it is for developers like us to configure Webpack or any tool for that matter from scratch. The following article was an inspiration which helped me to create my own build setup:
A tale of Webpack 4 and how to finally configure it in the right way. Updated.
Do refer to the above link if you get stuck anywhere with the build setup. For now let us check out the concepts required for the build.
Babel is a popular transpiler which is there to help us with transpiling ES6 features down to ES5. We will need the following packages to enable babel working with webpack:
Here is a sample babelrc for reference:
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
During babel setup, we need to feed the following 2nd line in presets to enable babel to transpile ES6 down to ES5 and the 3rd line inplugins to enable the dynamic import support with Webpack.
Here is how babel is used with Webpack:
module.exports = {
entry: {
// Mention Entry File
},
output: {
// Mention Output Filenames
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: [
// Plugins
]
};
Lines 10–17: The babel loader is used to set up the babel transpilation process in webpack.config.js. For simplicity, the other parts of the config have been eliminated or commented out.
For setting up SASS and CSS you need the following packages:
Here is how the config looks like:
module.exports = {
entry: {
// Mention Entry File
},
output: {
// Mention Output Filenames
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.scss$/,
use: [
'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
}),
]
};
Lines 17–25: This is the area where the loaders are registered.
Lines 29–31: Since we are using a plugin to extract a CSS file, we are using the MiniCssExtractPlugin here.
This is the most important section of the build process. We all know that we need a development and production build setup for developing applications and also deploying the final distributable to the web.
Here are the packages that will get used:
Here is the final Webpack config file:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const WebpackMd5Hash = require('webpack-md5-hash');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = (env, argv) => ({
entry: {
main: './src/main.js'
},
devtool: argv.mode === 'production' ? false : 'source-map',
output: {
path: path.resolve(__dirname, 'dist'),
chunkFilename:
argv.mode === 'production'
? 'chunks/[name].[chunkhash].js'
: 'chunks/[name].js',
filename:
argv.mode === 'production' ? '[name].[chunkhash].js' : '[name].js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.scss$/,
use: [
'style-loader',
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin('dist', {}),
new MiniCssExtractPlugin({
filename:
argv.mode === 'production'
? '[name].[contenthash].css'
: '[name].css'
}),
new HtmlWebpackPlugin({
inject: false,
hash: true,
template: './index.html',
filename: 'index.html'
}),
new WebpackMd5Hash(),
new CopyWebpackPlugin([
// {
// from: './src/assets',
// to: './assets'
// },
// {
// from: 'manifest.json',
// to: 'manifest.json'
// }
]),
new CompressionPlugin({
algorithm: 'gzip'
})
],
devServer: {
contentBase: 'dist',
watchContentBase: true,
port: 1000
}
});
Lines 9–77: The entire webpack config is a function which takes two arguments. Here I have used the argv i.e., the arguments sent while running the webpack or webpack-dev-server commands.
The below image shows the scripts section in package.json.
Accordingly, if we run npm run build it will trigger a production build, and if we run npm run serve it will trigger a development flow with a local development server.
Lines 44–77: These lines show how the plugins and the development server config needs to be setup.
Lines 59–66: These lines are any resources or static assets which need to be copied over from the application source.
Since we all know how tedious it is to write the names of all the files again for caching, I made a custom service worker build script for catching hold of the files in the dist folder and then adding them as contents of the cache in the service worker template. Finally, the service worker file will get written to the dist folder.
The concepts regarding the service worker file we talked about will be the same. Here is the script in action:
const glob = require('glob');
const fs = require('fs');
const dest = 'dist/sw.js';
const staticAssetsCacheName = 'todo-assets-v1';
const dynamicCacheName = 'todo-dynamic-v1';
let staticAssetsCacheFiles = glob
.sync('dist/**/*')
.map((path) => {
return path.slice(5);
})
.filter((file) => {
if (/\.gz$/.test(file)) return false;
if (/sw\.js$/.test(file)) return false;
if (!/\.+/.test(file)) return false;
return true;
});
const stringFileCachesArray = JSON.stringify(staticAssetsCacheFiles);
const serviceWorkerScript = `var staticAssetsCacheName = '${staticAssetsCacheName}';
var dynamicCacheName = '${dynamicCacheName}';
self.addEventListener('install', function (event) {
self.skipWaiting();
event.waitUntil(
caches.open(staticAssetsCacheName).then(function (cache) {
cache.addAll([
'/',
${stringFileCachesArray.slice(1, stringFileCachesArray.length - 1)}
]
);
}).catch((error) => {
console.log('Error caching static assets:', error);
})
);
});
self.addEventListener('activate', function (event) {
if (self.clients && clients.claim) {
clients.claim();
}
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.filter(function (cacheName) {
return (cacheName.startsWith('todo-')) && cacheName !== staticAssetsCacheName;
})
.map(function (cacheName) {
return caches.delete(cacheName);
})
).catch((error) => {
console.log('Some error occurred while removing existing cache:', error);
});
}).catch((error) => {
console.log('Some error occurred while removing existing cache:', error);
}));
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request)
.then((fetchResponse) => {
return cacheDynamicRequestData(dynamicCacheName, event.request.url, fetchResponse.clone());
}).catch((error) => {
console.log(error);
});
}).catch((error) => {
console.log(error);
})
);
});
function cacheDynamicRequestData(dynamicCacheName, url, fetchResponse) {
return caches.open(dynamicCacheName)
.then((cache) => {
cache.put(url, fetchResponse.clone());
return fetchResponse;
}).catch((error) => {
console.log(error);
});
}
`;
fs.writeFile(dest, serviceWorkerScript, function(error) {
if (error) return;
console.log('Service Worker Write success');
});
Lines 8–18: This is the place where all the contents of the dist folder are captured as an array staticAssetsCacheFiles.
Lines 24–87: This is the service worker template we talked about before. The concepts are exactly the same, just that we are introducing variables into the template so that we can reuse the service worker template and make it handy for future use. This template was also required since we needed to add dist folder contents to the cache as per line 33.
Lines 89–92: Finally, a new service worker file will get written to the dist folder along with its contents from the service worker template serviceWorkerScript.
The command to run the above script is node build-sw and it should be run after webpack --mode production is done.
This service worker build script really helped me a lot in caching files easily. I am currently using this for my own side projects due to its simplicity and great ease of tackling the caching problem.
If you guys want to use a library for Progressive Web Application related features, you can go for Workbox. This library does some real neat stuff and has amazing features which you can take control of.
Here is a sample package.json file with all dependencies:
{
"name": "vanilla-todos-pwa",
"version": "1.0.0",
"description": "A simple todo application using ES6 and Webpack",
"main": "src/main.js",
"scripts": {
"build": "webpack --mode production && node build-sw",
"serve": "webpack-dev-server --mode=development --hot"
},
"keywords": [],
"author": "Anurag Majumdar",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"autoprefixer": "^9.4.5",
"babel-core": "^6.26.3",
"babel-loader": "^8.0.4",
"babel-preset-env": "^1.7.0",
"clean-webpack-plugin": "^1.0.0",
"compression-webpack-plugin": "^2.0.0",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^2.1.0",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"terser": "^3.14.1",
"webpack": "^4.28.4",
"webpack-cli": "^3.2.1",
"webpack-dev-server": "^3.1.14",
"webpack-md5-hash": "0.0.6"
}
}
Remember that Webpack gets updated frequently, and changes keep happening in the community with new plugins replacing existing ones. So it is important to keep a note of the concepts required for a build setup rather than the actual packages used.
We all have a choice: either to write our own framework for certain features to be used by our application such as change detection, routing, storage patterns, redux etc, or pull already existing packages for such features.
Now I’ll speak about the bare minimum features required in order to structure the layout of our application and get it going. Later on you can add your own frameworks or packages to the application.
We will use ES6 import and export statements and treat each file as an ES6 module. This feature is commonly used by popular frameworks like Angular and React and is pretty handy. With the power of our Webpack config, we can fully utilize the power of import and export statements.
import { appTemplate } from './app.template';
import { AppModel } from './app.model';
export const AppComponent = {
// App Component code here...
};
Building components is a very important part of our application. We can choose to go with the latest web standards like Web Components too, but to keep things simple we can go ahead and use object literal syntax or ES6 class syntax.
The only thing with class syntax is that we need to instantiate it and then export it. So to keep things even simpler, I went ahead with object literal syntax for component architecture.
import { appTemplate } from './app.template';
import { AppModel } from './app.model';
export const AppComponent = {
init() {
this.appElement = document.querySelector('#app');
this.initEvents();
this.render();
},
initEvents() {
this.appElement.addEventListener('click', event => {
if (event.target.className === 'btn-todo') {
import( /* webpackChunkName: "todo" */ './todo/todo.module')
.then(lazyModule => {
lazyModule.TodoModule.init();
})
.catch(error => 'An error occurred while loading Module');
}
});
document.querySelector('.banner').addEventListener('click', event => {
event.preventDefault();
this.render();
});
},
render() {
this.appElement.innerHTML = appTemplate(AppModel);
}
};
Lines 4–32: We export an object called AppComponent which is immediately available for use in other parts of our application.
You can go ahead and use ES6 class syntax or standard Web Components too and achieve a more declarative way of writing code here. For simplicity’s sake, I chose to write the demo application in a more imperative approach.
Remember I talked about missing out on the “L” of the PRPL pattern? Dynamic import is the way to go ahead and lazy load our components or modules. Since we used the App Shell and PRPL together to cache the shell and other route assets, dynamic imports import the lazy component or module from the cache instead of the network.
Note that if we only used App Shell architecture, the remaining assets of the application i.e., the contents of chunksfolder, would not have been cached.
Lines 15–19: Refer to App Component code; this is the place where dynamic imports shine. If we click on a button having the class btn-todo, then only this TodoModule gets loaded. By the way, TodoModule is just another JavaScript file which consists of a set of object components.
Arrow functions should be used especially where we want to make sure of the this keyword inside the function, which should refer to the surrounding context where the arrow function is declared. Apart from that, these functions really help in creating neat shorthand syntax.
export const appTemplate = model => `
<section class="app">
<h3> ${model.title} </h3>
<section class="button">
<button class="btn-todo"> Todo Module </button>
</section>
</section>
`;
The above example is a template function defined as an arrow function which accepts a model and returns an HTML string consisting of the model data in it. String interpolation is carried out with the help of ES6 template literals. The real benefit of template literals is multi-line strings and interpolation of model data into the string.
Here’s a micro tip for handling component templating and generation of reusable components: use the reducefunction to accumulate all HTML strings as per the following example:
const WorkModel = [
{
id: 1,
src: '',
alt: '',
designation: '',
period: '',
description: ''
},
{
id: 2,
src: '',
alt: '',
designation: '',
period: '',
description: ''
},
//...
];
const workCardTemplate = (cardModel) => `
<section id="${cardModel.id}" class="work-card">
<section class="work__image">
<img class="work__image-content" type="image/svg+xml" src="${
cardModel.src
}" alt="${cardModel.alt}" />
</section>
<section class="work__designation">${cardModel.designation}</section>
<section class="work__period">${cardModel.period}</section>
<section class="work__content">
<section class="work__content-text">
${cardModel.description}
</section>
</section>
</section>
`;
export const workTemplate = (model) => `
<section class="work__section">
<section class="work-text">
<header class="header-text">
<span class="work-text__header"> Work </span>
</header>
<section class="work-text__content content-text">
<p class="work-text__content-para">
This area signifies work experience
</p>
</section>
</section>
<section class="work-cards">
${model.reduce((html, card) => html + workCardTemplate(card), '')}
</section>
</section>
`;
The above piece of code does a great deal of work indeed. Simple yet intuitive. It does follow a little inspiration from the frameworks out there.
Lines 1–10: This is a sample model array on which the reduce function can run in order to give the reusable template feature.
Line 32: This line does all the magic in generating multiple reusable components into one HTML string. The reduce function takes in the accumulator as the first argument, and each value of the array as the second argument.
Thanks to these simple features, we already have an application structure in place. The best way to learn a feature is to put it in action they say, so here we are. 😎
Here is a demo of the to-do application built with all the features as discussed in this article. Click here to visit the site.
Click here for the link to GitHub repository. Feel free to clone the repository and go through the code for a better understanding of the conceptual examples mentioned in the article.
The production site is a portfolio which was designed, developed and engineered from scratch using the exact features as specified in this article. The Single Page Application is broken down into custom modules and components. The flexibility and power that comes with Vanilla JavaScript is something unique and does help in producing some astonishing results.
Click here to go to the site. Here is the site in action:
Do visit the site to get a feel for it. The colors are not accurately produced in the demo here. The engineering put into this site produced the following results:
Never scored a perfect 100 before in any subject. 🙏
There are several projects we might like to build using Vanilla JavaScript instead of frameworks in order to achieve certain results quickly. I wrote this article to help developers use a simple custom setup to build their future projects.
The best part about the Vanilla framework is that developers have the freedom to shape their engineering thought patterns according to various use cases. Be it Imperative or Declarative style of programming, creating or using of latest existing features. As long as we produce consistent and performant applications with good code maintainability, our job is done for the day.
Happy hacking! 😄How to write simple modern JavaScript apps with Webpack and progressive web techniques - Have you thought about making modern JavaScript applications with the simplest setup possible for your next project?
===============================================
Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter
☞ Svelte.js - The Complete Guide
☞ The Complete JavaScript Course 2019: Build Real Projects!
☞ Advanced CSS and Sass: Flexbox, Grid, Animations and More!
☞ CSS - The Complete Guide (incl. Flexbox, Grid & Sass)
#javascript #webpack
1674813120
In this article, we will see how to validate multi step form wizard using jquery. Here, we will learn to validate the multi step form using jquery. First, we create the multi step form using bootstrap. Also, in this example, we are not using any jquery plugin for multi step form wizard.
So, let's see jquery multi step form with validation, how to create multi step form, multi step form wizard with jquery validation, bootstrap 4 multi step form wizard with validation, multi step form bootstrap 5, and jQuery multi step form with validation and next previous navigation.
Add HTML:
<html lang="en">
</head>
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700&display=swap" rel="stylesheet">
</head>
<body>
<div class="main">
<h3>How To Validate Multi Step Form Using jQuery - Websolutionstuff</h3>
<form id="multistep_form">
<!-- progressbar -->
<ul id="progress_header">
<li class="active"></li>
<li></li>
<li></li>
</ul>
<!-- Step 01 -->
<div class="multistep-box">
<div class="title-box">
<h2>Create your account</h2>
</div>
<p>
<input type="text" name="email" placeholder="Email" id="email">
<span id="error-email"></span>
</p>
<p>
<input type="password" name="pass" placeholder="Password" id="pass">
<span id="error-pass"></span>
</p>
<p>
<input type="password" name="cpass" placeholder="Confirm Password" id="cpass">
<span id="error-cpass"></span>
</p>
<p class="nxt-prev-button"><input type="button" name="next" class="fs_next_btn action-button" value="Next" /></p>
</div>
<!-- Step 02 -->
<div class="multistep-box">
<div class="title-box">
<h2>Social Profiles</h2>
</div>
<p>
<input type="text" name="twitter" placeholder="Twitter" id="twitter">
<span id="error-twitter"></span>
</p>
<p>
<input type="text" name="facebook" placeholder="Facebook" id="facebook">
<span id="error-facebook"></span>
</p>
<p>
<input type="text" name="linkedin" placeholder="Linkedin" id="linkedin">
<span id="error-linkedin"></span>
</p>
<p class="nxt-prev-button">
<input type="button" name="previous" class="previous action-button" value="Previous" />
<input type="button" name="next" class="ss_next_btn action-button" value="Next" />
</p>
</div>
<!-- Step 03 -->
<div class="multistep-box">
<div class="title-box">
<h2>Personal Details</h2>
</div>
<p>
<input type="text" name="fname" placeholder="First Name" id="fname">
<span id="error-fname"></span>
</p>
<p>
<input type="text" name="lname" placeholder="Last Name" id="lname">
<span id="error-lname"></span>
</p>
<p>
<input type="text" name="phone" placeholder="Phone" id="phone">
<span id="error-phone"></span>
</p>
<p>
<textarea name="address" placeholder="Address" id="address"></textarea>
<span id="error-address"></span>
</p>
<p class="nxt-prev-button"><input type="button" name="previous" class="previous action-button" value="Previous" />
<input type="submit" name="submit" class="submit_btn ts_next_btn action-button" value="Submit" />
</p>
</div>
</form>
<h1>You are successfully logged in</h1>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.0/jquery.easing.js" type="text/javascript"></script>
</body>
</html>
Add CSS:
body {
display: inline-block;
width: 100%;
height: 100vh;
overflow: hidden;
background-image: url("https://img1.akspic.com/image/80377-gadget-numeric_keypad-input_device-electronic_device-space_bar-3840x2160.jpg");
background-repeat: no-repeat;
background-size: cover;
position: relative;
margin: 0;
font-weight: 400;
font-family: 'Roboto', sans-serif;
}
body:before {
content: "";
display: block;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.main {
position: absolute;
left: 0;
right: 0;
top: 30px;
margin: 0 auto;
height: 515px;
}
input:-internal-autofill-selected {
background-color: #fff !important;
}
#multistep_form {
width: 550px;
margin: 0 auto;
text-align: center;
position: relative;
height: 100%;
z-index: 999;
opacity: 1;
visibility: visible;
}
/*progress header*/
#progress_header {
overflow: hidden;
margin: 0 auto 30px;
padding: 0;
}
#progress_header li {
list-style-type: none;
width: 33.33%;
float: left;
position: relative;
font-size: 16px;
font-weight: bold;
font-family: monospace;
color: #fff;
text-transform: uppercase;
}
#progress_header li:after {
width: 35px;
line-height: 35px;
display: block;
font-size: 22px;
color: #888;
font-family: monospace;
background-color: #fff;
border-radius: 100px;
margin: 0 auto;
background-repeat: no-repeat;
font-family: 'Roboto', sans-serif;
}
#progress_header li:nth-child(1):after {
content: "1";
}
#progress_header li:nth-child(2):after {
content: "2";
}
#progress_header li:nth-child(3):after {
content: "3";
}
#progress_header li:before {
content: '';
width: 100%;
height: 5px;
background: #fff;
position: absolute;
left: -50%;
top: 50%;
z-index: -1;
}
#progress_header li:first-child:before {
content: none;
}
#progress_header li.active:before,
#progress_header li.active:after {
background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
color: #fff !important;
transition: all 0.5s;
}
/*title*/
.title-box {
width: 100%;
margin: 0 0 30px 0;
}
.title-box h2 {
font-size: 22px;
text-transform: uppercase;
color: #2C3E50;
margin: 0;
font-family: cursive;
display: inline-block;
position: relative;
padding: 0 0 10px 0;
font-family: 'Roboto', sans-serif;
}
.title-box h2:before {
content: "";
background: #6ddc8b;
width: 70px;
height: 2px;
position: absolute;
bottom: 0;
left: 0;
right: 0;
margin: 0 auto;
display: block;
}
.title-box h2:after {
content: "";
background: #6ddc8b;
width: 50px;
height: 2px;
position: absolute;
bottom: -5px;
left: 0;
right: 0;
margin: 0 auto;
display: block;
}
/*Input and Button*/
.multistep-box {
background: white;
border: 0 none;
border-radius: 3px;
box-shadow: 1px 1px 55px 3px rgba(255, 255, 255, 0.4);
padding: 30px 30px;
box-sizing: border-box;
width: 80%;
margin: 0 10%;
position: absolute;
}
.multistep-box:not(:first-of-type) {
display: none;
}
.multistep-box p {
margin: 0 0 12px 0;
text-align: left;
}
.multistep-box span {
font-size: 12px;
color: #FF0000;
}
input, textarea {
padding: 15px;
border: 1px solid #ccc;
border-radius: 3px;
margin: 0;
width: 100%;
box-sizing: border-box;
font-family: 'Roboto', sans-serif;
color: #2C3E50;
font-size: 13px;
transition: all 0.5s;
outline: none;
}
input:focus, textarea:focus {
box-shadow: inset 0px 0px 50px 2px rgb(0,0,0,0.1);
}
input.box_error, textarea.box_error {
border-color: #FF0000;
box-shadow: inset 0px 0px 50px 2px rgb(255,0,0,0.1);
}
input.box_error:focus, textarea.box_error:focus {
box-shadow: inset 0px 0px 50px 2px rgb(255,0,0,0.1);
}
p.nxt-prev-button {
margin: 25px 0 0 0;
text-align: center;
}
.action-button {
width: 100px;
font-weight: bold;
color: white;
border: 0 none;
border-radius: 1px;
cursor: pointer;
padding: 10px 5px;
margin: 0 5px;
background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b);
transition: all 0.5s;
}
.action-button:hover,
.action-button:focus {
box-shadow: 0 0 0 2px white, 0 0 0 3px #6ce199;
}
.form_submited #multistep_form {
opacity: 0;
visibility: hidden;
}
.form_submited h1 {
-webkit-background-clip: text;
transform: translate(0%, 0%);
-webkit-transform: translate(0%, 0%);
transition: all 0.3s ease;
opacity: 1;
visibility: visible;
}
h1 {
margin: 0;
text-align: center;
font-size: 90px;
background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
background-image: linear-gradient(to right top, #35e8c3, #36edbb, #3df2b2, #4af7a7, #59fb9b) !important;
color: transparent;
-webkit-background-clip: text;
-webkit-background-clip: text;
transform: translate(0%, -80%);
-webkit-transform: translate(0%, -80%);
transition: all 0.3s ease;
opacity: 0;
visibility: hidden;
position: absolute;
left: 0;
right: 0;
margin: 0 auto;
text-align: center;
top: 50%;
}
h3{
color:#fff;
text-align:center;
margin-bottom:20px;
}
Add jQuery:
var current_slide, next_slide, previous_slide;
var left, opacity, scale;
var animation;
var error = false;
// email validation
$("#email").keyup(function() {
var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
if (!emailReg.test($("#email").val())) {
$("#error-email").text('Please enter an Email addres.');
$("#email").addClass("box_error");
error = true;
} else {
$("#error-email").text('');
error = false;
$("#email").removeClass("box_error");
}
});
// password validation
$("#pass").keyup(function() {
var pass = $("#pass").val();
var cpass = $("#cpass").val();
if (pass != '') {
$("#error-pass").text('');
error = false;
$("#pass").removeClass("box_error");
}
if (pass != cpass && cpass != '') {
$("#error-cpass").text('Password and Confirm Password is not matched.');
error = true;
} else {
$("#error-cpass").text('');
error = false;
}
});
// confirm password validation
$("#cpass").keyup(function() {
var pass = $("#pass").val();
var cpass = $("#cpass").val();
if (pass != cpass) {
$("#error-cpass").text('Please enter the same Password as above.');
$("#cpass").addClass("box_error");
error = true;
} else {
$("#error-cpass").text('');
error = false;
$("#cpass").removeClass("box_error");
}
});
// twitter
$("#twitter").keyup(function() {
var twitterReg = /https?:\/\/twitter\.com\/(#!\/)?[a-z0-9_]+$/;
if (!twitterReg.test($("#twitter").val())) {
$("#error-twitter").text('Twitter link is not valid.');
$("#twitter").addClass("box_error");
error = true;
} else {
$("#error-twitter").text('');
error = false;
$("#twitter").removeClass("box_error");
}
});
// facebook
$("#facebook").keyup(function() {
var facebookReg = /^(https?:\/\/)?(www\.)?facebook.com\/[a-zA-Z0-9(\.\?)?]/;
if (!facebookReg.test($("#facebook").val())) {
$("#error-facebook").text('Facebook link is not valid.');
$("#facebook").addClass("box_error");
error = true;
} else {
$("#error-facebook").text('');
error = false;
$("#facebook").removeClass("box_error");
}
});
// linkedin
$("#linkedin").keyup(function() {
var linkedinReg = /(ftp|http|https):\/\/?(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
if (!linkedinReg.test($("#linkedin").val())) {
$("#error-linkedin").text('Linkedin link is not valid.');
$("#linkedin").addClass("box_error");
error = true;
} else {
$("#error-linkedin").text('');
error = false;
$("#linkedin").removeClass("box_error");
}
});
// first name
$("#fname").keyup(function() {
var fname = $("#fname").val();
if (fname == '') {
$("#error-fname").text('Enter your First name.');
$("#fname").addClass("box_error");
error = true;
} else {
$("#error-fname").text('');
error = false;
}
if ((fname.length <= 2) || (fname.length > 20)) {
$("#error-fname").text("User length must be between 2 and 20 Characters.");
$("#fname").addClass("box_error");
error = true;
}
if (!isNaN(fname)) {
$("#error-fname").text("Only Characters are allowed.");
$("#fname").addClass("box_error");
error = true;
} else {
$("#fname").removeClass("box_error");
}
});
// last name
$("#lname").keyup(function() {
var lname = $("#lname").val();
if (lname != lname) {
$("#error-lname").text('Enter your Last name.');
$("#lname").addClass("box_error");
error = true;
} else {
$("#error-lname").text('');
error = false;
}
if ((lname.length <= 2) || (lname.length > 20)) {
$("#error-lname").text("User length must be between 2 and 20 Characters.");
$("#lname").addClass("box_error");
error = true;
}
if (!isNaN(lname)) {
$("#error-lname").text("Only Characters are allowed.");
$("#lname").addClass("box_error");
error = true;
} else {
$("#lname").removeClass("box_error");
}
});
// phone
$("#phone").keyup(function() {
var phone = $("#phone").val();
if (phone != phone) {
$("#error-phone").text('Enter your Phone number.');
$("#phone").addClass("box_error");
error = true;
} else {
$("#error-phone").text('');
error = false;
}
if (phone.length != 10) {
$("#error-phone").text("Mobile number must be of 10 Digits only.");
$("#phone").addClass("box_error");
error = true;
} else {
$("#phone").removeClass("box_error");
}
});
// address
$("#address").keyup(function() {
var address = $("#address").val();
if (address != address) {
$("#error-address").text('Enter your Address.');
$("#address").addClass("box_error");
error = true;
} else {
$("#error-address").text('');
error = false;
$("#address").removeClass("box_error");
}
});
// first step validation
$(".fs_next_btn").click(function() {
// email
if ($("#email").val() == '') {
$("#error-email").text('Please enter an email address.');
$("#email").addClass("box_error");
error = true;
} else {
var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
if (!emailReg.test($("#email").val())) {
$("#error-email").text('Please insert a valid email address.');
error = true;
} else {
$("#error-email").text('');
$("#email").removeClass("box_error");
}
}
// password
if ($("#pass").val() == '') {
$("#error-pass").text('Please enter a password.');
$("#pass").addClass("box_error");
error = true;
}
if ($("#cpass").val() == '') {
$("#error-cpass").text('Please enter a Confirm password.');
$("#cpass").addClass("box_error");
error = true;
} else {
var pass = $("#pass").val();
var cpass = $("#cpass").val();
if (pass != cpass) {
$("#error-cpass").text('Please enter the same password as above.');
error = true;
} else {
$("#error-cpass").text('');
$("#pass").removeClass("box_error");
$("#cpass").removeClass("box_error");
}
}
// animation
if (!error) {
if (animation) return false;
animation = true;
current_slide = $(this).parent().parent();
next_slide = $(this).parent().parent().next();
$("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");
next_slide.show();
current_slide.animate({
opacity: 0
}, {
step: function(now, mx) {
scale = 1 - (1 - now) * 0.2;
left = (now * 50) + "%";
opacity = 1 - now;
current_slide.css({
'transform': 'scale(' + scale + ')'
});
next_slide.css({
'left': left,
'opacity': opacity
});
},
duration: 800,
complete: function() {
current_slide.hide();
animation = false;
},
easing: 'easeInOutBack'
});
}
});
// second step validation
$(".ss_next_btn").click(function() {
// twitter
if ($("#twitter").val() == '') {
$("#error-twitter").text('twitter link is required.');
$("#twitter").addClass("box_error");
error = true;
} else {
var twitterReg = /https?:\/\/twitter\.com\/(#!\/)?[a-z0-9_]+$/;
if (!twitterReg.test($("#twitter").val())) {
$("#error-twitter").text('Twitter link is not valid.');
error = true;
} else {
$("#error-twitter").text('');
$("#twitter").removeClass("box_error");
}
}
// facebook
if ($("#facebook").val() == '') {
$("#error-facebook").text('Facebook link is required.');
$("#facebook").addClass("box_error");
error = true;
} else {
var facebookReg = /^(https?:\/\/)?(www\.)?facebook.com\/[a-zA-Z0-9(\.\?)?]/;
if (!facebookReg.test($("#facebook").val())) {
$("#error-facebook").text('Facebook link is not valid.');
error = true;
$("#facebook").addClass("box_error");
} else {
$("#error-facebook").text('');
}
}
// linkedin
if ($("#linkedin").val() == '') {
$("#error-linkedin").text('Linkedin link is required.');
$("#linkedin").addClass("box_error");
error = true;
} else {
var linkedinReg = /(ftp|http|https):\/\/?(?:www\.)?linkedin.com(\w+:{0,1}\w*@)?(\S+)(:([0-9])+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
if (!linkedinReg.test($("#linkedin").val())) {
$("#error-linkedin").text('Linkedin link is not valid.');
error = true;
} else {
$("#error-linkedin").text('');
$("#linkedin").removeClass("box_error");
}
}
if (!error) {
if (animation) return false;
animation = true;
current_slide = $(this).parent().parent();
next_slide = $(this).parent().parent().next();
$("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");
next_slide.show();
current_slide.animate({
opacity: 0
}, {
step: function(now, mx) {
scale = 1 - (1 - now) * 0.2;
left = (now * 50) + "%";
opacity = 1 - now;
current_slide.css({
'transform': 'scale(' + scale + ')'
});
next_slide.css({
'left': left,
'opacity': opacity
});
},
duration: 800,
complete: function() {
current_slide.hide();
animation = false;
},
easing: 'easeInOutBack'
});
}
});
// third step validation
$(".ts_next_btn").click(function() {
// first name
if ($("#fname").val() == '') {
$("#error-fname").text('Enter your First name.');
$("#fname").addClass("box_error");
error = true;
} else {
var fname = $("#fname").val();
if (fname != fname) {
$("#error-fname").text('First name is required.');
error = true;
} else {
$("#error-fname").text('');
error = false;
$("#fname").removeClass("box_error");
}
if ((fname.length <= 2) || (fname.length > 20)) {
$("#error-fname").text("User length must be between 2 and 20 Characters.");
error = true;
}
if (!isNaN(fname)) {
$("#error-fname").text("Only Characters are allowed.");
error = true;
} else {
$("#fname").removeClass("box_error");
}
}
// last name
if ($("#lname").val() == '') {
$("#error-lname").text('Enter your Last name.');
$("#lname").addClass("box_error");
error = true;
} else {
var lname = $("#lname").val();
if (lname != lname) {
$("#error-lname").text('Last name is required.');
error = true;
} else {
$("#error-lname").text('');
error = false;
}
if ((lname.length <= 2) || (lname.length > 20)) {
$("#error-lname").text("User length must be between 2 and 20 Characters.");
error = true;
}
if (!isNaN(lname)) {
$("#error-lname").text("Only Characters are allowed.");
error = true;
} else {
$("#lname").removeClass("box_error");
}
}
// phone
if ($("#phone").val() == '') {
$("#error-phone").text('Enter your Phone number.');
$("#phone").addClass("box_error");
error = true;
} else {
var phone = $("#phone").val();
if (phone != phone) {
$("#error-phone").text('Phone number is required.');
error = true;
} else {
$("#error-phone").text('');
error = false;
}
if (phone.length != 10) {
$("#error-phone").text("Mobile number must be of 10 Digits only.");
error = true;
} else {
$("#phone").removeClass("box_error");
}
}
// address
if ($("#address").val() == '') {
$("#error-address").text('Enter your Address.');
$("#address").addClass("box_error");
error = true;
} else {
var address = $("#address").val();
if (address != address) {
$("#error-address").text('Address is required.');
error = true;
} else {
$("#error-address").text('');
$("#address").removeClass("box_error");
error = false;
}
}
if (!error) {
if (animation) return false;
animation = true;
current_slide = $(this).parent().parent();
next_slide = $(this).parent().parent().next();
$("#progress_header li").eq($(".multistep-box").index(next_slide)).addClass("active");
next_slide.show();
current_slide.animate({
opacity: 0
}, {
step: function(now, mx) {
scale = 1 - (1 - now) * 0.2;
left = (now * 50) + "%";
opacity = 1 - now;
current_slide.css({
'transform': 'scale(' + scale + ')'
});
next_slide.css({
'left': left,
'opacity': opacity
});
},
duration: 800,
complete: function() {
current_slide.hide();
animation = false;
},
easing: 'easeInOutBack'
});
}
});
// previous
$(".previous").click(function() {
if (animation) return false;
animation = true;
current_slide = $(this).parent().parent();
previous_slide = $(this).parent().parent().prev();
$("#progress_header li").eq($(".multistep-box").index(current_slide)).removeClass("active");
previous_slide.show();
current_slide.animate({
opacity: 0
}, {
step: function(now, mx) {
scale = 0.8 + (1 - now) * 0.2;
left = ((1 - now) * 50) + "%";
opacity = 1 - now;
current_slide.css({
'left': left
});
previous_slide.css({
'transform': 'scale(' + scale + ')',
'opacity': opacity
});
},
duration: 800,
complete: function() {
current_slide.hide();
animation = false;
},
easing: 'easeInOutBack'
});
});
$(".submit_btn").click(function() {
if (!error){
$(".main").addClass("form_submited");
}
return false;
})
Output:
Original article source at: https://websolutionstuff.com/
1625717787
A Progressive Web App is a type of application software that is delivered through the web. It is built using standard web technologies HTML, CSS, and JavaScript. It is expected to work on any platform that uses a standards-compliant browser, including desktop and mobile.
It is also known as a type of web page or web server. With the help of these PWA web pages, you can reach more customers for a business by marketing and promoting your business through web pages. You can also gain more customers by developing PWA by hiring a progressive web app development company. Nevina Infotech is a company that can help you to build your progressive web app with the help of its enthusiastic developers.
#progressive web app development company #progressive web app developers #progressive web app platform #progressive web app development services #progressive web app development
1620187126
Progressive Web App or PWA is a type of application software that is delivered over the web. It is built using standard web technologies, including HTML, CSS, and JavaScript. It is designed to work on any platform that uses a standards-compliant browser. It can work on both desktop and mobile devices.
PWA is a type of website or web page known as a web application. It doesn’t require separate bundling or distribution.
Are you searching for a progressive web app development company to develop your custom PWA at an affordable rate? Then Nevina Infotech is the best suitable choice for you. We have experienced and dedicated developers who will help you to develop your custom PWA as per your requirement.
#progressive web app development company #progressive web app developers #progressive web app platform #progressive web app development services #progressive web app development
1626327771
Suppose you are looking for an app that can be run on any platform, whether on a desktop or a smartphone. Progressive web app development can provide this functionality to your app. It’s made using the most common web technologies, including HTML, CSS, and JavaScript.
To get a PWA for your business requirements, you can find a progressive web app development company that can understand your vision and give you the best app that you deserve. Nevina Infotech is one of the best PWA development companies, which have the most dedicated developers with the experience of app development that can help you achieve your goals.
#progressive web app development company #progressive web app developers #progressive web app platform #progressive web app development services #progressive web app development
1617354704
AppClues Infotech is one of the leading progressive web app development company in USA. We offer the best progressive web app development & design solution to create high-performance & secure PWA.
For more info:
Website: https://www.appcluesinfotech.com/
Email: info@appcluesinfotech.com
Call: +1-978-309-9910
#top progressive web app development company in usa #hire progressive web app developers in usa #best pwa development company in usa #custom progressive web app development company #progressive web apps development #progressive web app development services