#StackBounty: #javascript #performance #object-oriented #node.js #database Making job listings project more modular and flexible

Bounty: 50

I am creating a pet project where candidates can apply and recruiters can post their listings:

db.js

var pg = require('pg');

var config = {
  host: 'localhost',
  user: 'v',
  password: 'a',
  database: 'j',
};

var pool = new pg.Pool(config);

pool.connect(function(err, client, done) {
  if(err) {
    return console.error('error fetching client from pool', err);
  }
  client.query('SELECT $1::int AS number', ['1'], function(err, result) {
    done(err);

    if(err) {
      return console.error('error running query', err);
    }
    console.log(result.rows[0].number);
    //output: 1
  });
});

pool.on('error', function (err, client) {
  // if an error is encountered by a client while it sits idle in the pool
  // the pool itself will emit an error event with both the error and
  // the client which emitted the original error
  // this is a rare occurrence but can happen if there is a network partition
  // between your application and the database, the database restarts, etc.
  // and so you might want to handle it and at least log it out
  console.error('idle client error', err.message, err.stack)
});

module.exports = pool;

index.js

'use strict';

var express = require('express');
var Promise = require('promise');
var app = express();
var router = express.Router();
var PORT = 3000;
var pool = require('./db.js');

app.use(function (req, res, next) {
  pool.connect(function(error, client, done) {
    // Handle connection errors
    if(error) {
      done();
      console.log(error.message);
      return res.status(500).json({success: false, data: error});
    }
    req.client = client;
    next();
  });
});

router.get('/topActiveUsers', (req, res) => {
  topActiveUsers(req, res);
});

router.get('/users', (req, res) => {
  userInfo(req, res);
});

app.use(router);

app.get('*', function (req, res) {
  res.status(400).send('Invalid route');
});

app.listen(PORT, function () {
  console.log('App listening on port ' + PORT);
});

var topActiveUsers = function topActiveUsers(req, res) {
};

var userInfo = function userInfo(req, res) {
  User.getById(req)
      .then(function (user) {
        return user;
      })
      .then(function getCompanies(user) {
        return user.companies(req);
      })
      .then(function getListings(user) {
        return user.listings(req);
      })
      .then(function getApplications(user) {
        return user.applications(req);
      })
      .then(function success(user) {
        res.json({
          id: user.id,
          name: user.name,
          createdAt: user.createdAt,
          companies: user._companies,
          listings: user._listings,
          applications: user._applications
        });
      })
      .catch(function error(error) {
        console.log('In catch');
        console.log('error', error.message);
        res.end();
        throw error;
      });
};

/**
 * User m2m Company
 * User o2m Listing
 * User m2m applications
 */
function User(opt_data) {
  var data = opt_data || {};

  this.id = data['id'] || null;
  this.name = data['name'] || '';
  this.createdAt = data['created_at'] || new Date();
  this._companies = [];
  this._listings = [];
  this._applications = [];
}
User._RESOURCE_LIMIT = 5;
User._TABLE_NAME = 'users';

User.getById = function getById(req, callback) {
  var client = req.client;
  client.connection.on('message', function(msg) {
    console.log(msg)
    //console.log(msg.name)
  });
  var queryString = 'select * from users where id = $1::int';
  return new Promise(function _promise(resolve, reject) {
    client.query(
        queryString, [req.query.id],
        function result(error, result) {
      if (error) {
        console.log(error.message);
        return reject(error);
      }

      //console.log(result.rows);

      /*
      client.end(function (error) {
        if (error) throw error;
      });*/
      resolve(new User(result.rows[0]));
    });
  });
};

var UserProto = User.prototype;

UserProto.companies = function companies(req) {
  var client = req.client;
  var queryString = 'select c.id, c.name, t.contact_user '+
    'from companies c, teams t '+
    'where t.user_id = $1::int and t.company_id = c.id '+
    'limit $2::int';
  var self = this;
  return new Promise(function _promise(resolve, reject) {
    client.query(
        queryString,
        [req.query.id, User._RESOURCE_LIMIT],
        function result(error, result) {
      if (error) return reject(error);

      //console.log(result.rows);

      /*
      client.end(function (error) {
        if (error) throw error;
      });*/
      self._companies = result.rows.map(function (data) {
        return new Company(data);
      });
      resolve(self);
    });
  });
};

UserProto.listings = function listings(req, callback) {
  var client = req.client;
  var queryString = 'select * from listings '+
    'where created_by = $1::int '+
    'limit $2::int';
  var self = this;
  return new Promise(function _promise(resolve, reject) {
    client.query(
        queryString,
        [req.query.id, User._RESOURCE_LIMIT],
        function result(error, result) {
      if (error) {
        return reject(error);
      }

      //console.log(result.rows);

      /*
      client.end(function (error) {
        if (error) throw error;
      });*/
      self._listings = result.rows.map(function (data) {
        return new Listing(data);
      });
      //console.log('Got listings', self);
      resolve(self);
    });
  });
};

UserProto.applications = function applications(req, callback) {
  var client = req.client;
  var queryString = 'select a.id as app_id, a.created_at, a.cover_letter, '+
    'l.id as list_id, l.name, l.description '+
    'from applications a, listings l '+
    'where a.user_id = $1::int and a.listing_id = l.id '+
    'limit $2::int';
  var self = this;
  return new Promise(function _promise(resolve, reject) {
    client.query(
        queryString,
        [req.query.id, User._RESOURCE_LIMIT],
        function result(error, result) {
      if (error) return reject(error);

      console.log(result.rows);

      /*
      client.end(function (error) {
        if (error) throw error;
      });*/
      self._applications = result.rows.map(function (data) {
        return new Application(data);
      });
      resolve(self);
    });
  });
};

function Company(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.name = data['name'] || '';
  this.isContact = false;
}

function Listing(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.name = data['name'] || '';
  this.description = data['description'] || '';
}

function Application(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.listing = data['listing'] || null;
  this.coverLetter = data['cover_letter'] || '';
}

data.sql

insert into users (id, created_at, name) values
    (1, '2015-01-13 15:30', 'Mark'),
    (2, '2015-01-13 15:30', 'John'),
    (3, '2016-01-01 10:30', 'Melinda'),
    (4, '2016-01-17 23:30', 'Carl'),
    (5, '2016-02-02 16:30', 'Tim'),
    (6, '2016-02-02 16:30', 'Jessica')
;

insert into companies (id, created_at, name) values
    (1, '2015-01-13 15:00', 'Facewall'),
    (2, '2015-01-17 15:00', 'Carl & Co')
;

insert into teams (company_id, user_id, contact_user) values
    (1, 1, TRUE),
    (2, 3, FALSE),
    (2, 4, TRUE)
;

insert into listings (id, created_at, created_by, name, description) values
    (1, '2015-01-15 11:00', 1, 'Join us conquering the world!', 'This is your best chance to be on the right side of the equation...')
;

insert into applications (created_at, user_id, listing_id, cover_letter) values
    ('2015-01-16 12:00', 2, 1, 'Hello, ...')
;

Notes:

  1. I need to apply dependency injection from start. I don’t have enough experience in NodeJS but I have tried one approach here. I would like to know if there is a better approach.
  2. Is the code flexible and maintainable? I see some repetitive logic. How can I refactor it?
  3. Can my code handle thousands of requests? If not, what changes do I need to make?
  4. What if I give this code as part of an interview process? What are the points where I can get rejected?

Update

Here is my updated code:

index.js

'use strict';

var express = require('express');
var Promise = require('promise');
var router = express.Router();
var app = express();

var pool = require('./db.js')();
var User = require('./models');

var PORT = 3000;

app.use(function (req, res, next) {
  pool.connect(function(error, client, done) {
    // Handle connection errors
    if (error) {
      done(error);
      console.log(error.message);
      return res.status(500)
          .json({success: false, data: error});
    }
    req.client = client;
    req.done = done;
    next();
  });
});
**index.js**

'use strict';

var express = require('express');
var Promise = require('promise');
var router = express.Router();
var app = express();

var pool = require('./db.js')();
var User = require('./models');

var PORT = 3000;

app.use(function (req, res, next) {
  pool.connect(function(error, client, done) {
    // Handle connection errors
    if (error) {
      done(error);
      console.log(error.message);
      return res.status(500)
          .json({success: false, data: error});
    }
    req.client = client;
    req.done = done;
    next();
  });
});

router.get('/topActiveUsers', (req, res) => {
  topActiveUsers(req, res);
});

router.get('/users', (req, res) => {
  userInfo(req, res);
});

app.use(router);

app.get('*', function (req, res) {
  res.status(400).send('Invalid route');
});

app.listen(PORT, function () {
  console.log('App listening on port ' + PORT);
});

var topActiveUsers = function topActiveUsers(req, res) {
  var ENTRIES_PER_PAGE = 3;
  var startIndex = 0;
  var total = 0;
  req.query.page = +req.query.page || 0;

  var pageNum = req.query.page > 0 ? req.query.page : 0;
  if (pageNum > 0) {
    startIndex = ENTRIES_PER_PAGE * (pageNum - 1);
  }
  total = ENTRIES_PER_PAGE * (pageNum + 1);

  User.topActiveUsers(req)
      .then(function fullfilled(users) {
        if (users.length < startIndex) {
          throw new Error('Invalid pagination offset');
        }
        if (users.length > total) {
          users = users.slice(startIndex, startIndex + ENTRIES_PER_PAGE);
        } else {
          users = users.splice(startIndex);
        }
        return Promise.all(users.map(function (user) {
          return user.applicationListings(req);
        }));
      })
      .then(function fullfilled(users) {
        var result = users.map(function (user) {
          return {
            id: user.id,
            name: user.name,
            count: user._appliedListings.length,
            createdAt: user.createdAt,
            listings: user._appliedListings
          };
        });
        res.json(result);
      })
      .catch(function rejected(error) {
        console.log(error.message);
        throw error;
      })
      .finally(function () {
        res.end();
      });
};

var userInfo = function userInfo(req, res) {
  User.getById(req)
      // run companies/listings/applications in "parallel"
      .then(function fullfilled(user) {
        return Promise.all([
          user.id,
          user.name,
          user.createdAt,
          user.companies(req),
          user.listings(req),
          user.applications(req)
        ]);
      })
      .then(function fullfilled([
            id, name, createdAt, companies, listings, applications]) {
        res.json({
          id: id,
          name: name,
          createdAt: createdAt,
          companies: companies,
          listings: listings,
          applications: applications
        });
      })
      .catch(function rejected(error) {
        console.log('error', error.message);
        throw error;
      })
      .finally(function () {
        res.end();
      });
};

models/index.js

var Promise = require('promise');

module.exports = User;

/**
 * User m2m Company
 * User o2m Listing
 * User m2m applications
 */
function User(opt_data) {
  var data = opt_data || {};

  this.id = data['id'] || null;
  this.name = data['name'] || '';
  this.createdAt = data['created_at'] || new Date();
  this._companies = [];
  this._listings = [];
  this._applications = [];
  this._appliedListings = [];
}
User._RESOURCE_LIMIT = 5;
var UserProto = User.prototype;

User.topActiveUsers = function topActiveUsers(req) {
  var queryString = "select * from users u inner join "+
      "(select user_id, count(id) cnt from applications "+
      "where id in (select id from applications where "+
      "created_at > current_date - interval '1 week') "+
      "group by user_id) a on u.id = a.user_id order by a.cnt desc";
  return queryPromise(req, queryString)
      .then(function fullfilled(result) {
        return result.rows.map(function(row) {
          return new User(row);
        });
      });
};

User.getById = function getById(req) {
  var queryString = 'select * from users where id = $1::int';
  return queryPromise(req, queryString, [req.query.id])
      .then(function fullfilled(result) {
        return new User(result.rows[0]);
      });
};

UserProto.companies = function companies(req) {
  var queryString = 'select c.id, c.name, t.contact_user '+
      'from companies c, teams t '+
      'where t.user_id = $1::int and t.company_id = c.id '+
      'limit $2::int';
  return queryPromise(req, queryString, [this.id, User._RESOURCE_LIMIT])
    .then(function fullfilled(result) {
      return result.rows.map(function (data) {
        return new Company(data);
      });
    });
};

UserProto.listings = function listings(req) {
  var queryString = 'select * from listings '+
      'where created_by = $1::int '+
      'limit $2::int';
  return queryPromise(req, queryString, [this.id, User._RESOURCE_LIMIT])
    .then(function fullfilled(result) {
      return result.rows.map(function (data) {
        return new Listing(data);
      });
    });
};

UserProto.applicationListings = function applications(req) {
  var queryString = "select * from listings l inner join "+
      "(select listing_id, user_id, created_at from applications) a "+
      "on a.listing_id = l.id "+
      "where a.user_id = $1::int order by a.created_at desc limit 3";
  var self = this;
  return queryPromise(req, queryString, [this.id])
      .then(function fullfilled(result) {
        self._appliedListings = result.rows.map(function (data) {
          return new Listing(data);
        });
        return self;
      });
};

UserProto.applications = function applications(req) {
  var queryString = 'select a.id as app_id, a.created_at, a.cover_letter, '+
    'l.id as list_id, l.name, l.description '+
    'from applications a, listings l '+
    'where a.user_id = $1::int and a.listing_id = l.id '+
    'limit $2::int';
  return queryPromise(req, queryString, [this.id, User._RESOURCE_LIMIT])
      .then(function fullfilled(result) {
        return result.rows.map(function (data) {
          return new Application(data);
        });
      });
};

function Company(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.name = data['name'] || '';
  this.isContact = false;
}

function Listing(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.name = data['name'] || '';
  this.description = data['description'] || '';
}

function Application(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.listing = data['listing'] || null;
  this.coverLetter = data['cover_letter'] || '';
}

function queryPromise(req, queryString, queryParams, debug) {
  if (debug) {
    console.log(queryString, queryParams);
    req.client.connection.on('message', function(msg) {
      console.log(msg)
    });
  }
  return new Promise(function _promise(resolve, reject) {
    req.client.query(
        queryString,
        queryParams || [],
        function result(error, result) {
      req.done(error);

      if (error) {
        console.log('error ' + error.message);
        return reject(error);
      }
      resolve(result);
    });
  });
};

db.js

var pg = require('pg');

module.exports = function() {
  var config = {
    port: 5432,
    max: 10,
    idleTimeoutMillis: 30000
  };
  switch (process.env.NODE_ENV) {
    case 'development':
      config.host = 'localhost';
      config.user = 'xxxx';
      config.password = 'xxxx';
      config.database = 'xxxx';
      break;
    case 'production':
      config.user = 'xxxx';
      config.database = 'xxxx';
      config.password = 'xxxx';
      config.host = 'xxxx'
      break;
    default:
      throw new Error('Invalid enviroment');
  }

  var pool = new pg.Pool(config);

  pool.connect(function(err, client, done) {
    if(err) {
      return console.error('error fetching client from pool', err);
    }
    client.query('SELECT $1::int AS number', ['1'], function(err, result) {
      done(err);

      if(err) {
        return console.error('error running query', err);
      }
      console.log(result.rows[0].number);
      //output: 1
    });
  });

  pool.on('error', function (err, client) {
    // if an error is encountered by a client while it sits idle in the pool
    // the pool itself will emit an error event with both the error and
    // the client which emitted the original error
    // this is a rare occurrence but can happen if there is a network partition
    // between your application and the database, the database restarts, etc.
    // and so you might want to handle it and at least log it out
    console.error('idle client error', err.message, err.stack)
  });

  return pool;
};

tables.sql

create table users (
    id serial primary key,
    created_at timestamp default current_timestamp,
    name character varying(64)
);

create table companies (
    id serial primary key,
    created_at timestamp default current_timestamp,
    name character varying(64)
);

create table teams (
    id serial primary key,
    company_id integer references companies (id),
    user_id integer references users (id),
    contact_user boolean default false
);

create table listings (
    id serial primary key,
    created_at timestamp default current_timestamp,
    created_by integer references users (id),
    name character varying(64),
    description text
);

create table applications (
    id serial primary key,
    created_at timestamp default current_timestamp,
    user_id integer references users (id),
    listing_id integer references listings (id),
    cover_letter text
);
router.get('/topActiveUsers', (req, res) => {
  topActiveUsers(req, res);
});

router.get('/users', (req, res) => {
  userInfo(req, res);
});

app.use(router);

app.get('*', function (req, res) {
  res.status(400).send('Invalid route');
});

app.listen(PORT, function () {
  console.log('App listening on port ' + PORT);
});

var topActiveUsers = function topActiveUsers(req, res) {
  var ENTRIES_PER_PAGE = 3;
  var startIndex = 0;
  var total = 0;
  req.query.page = +req.query.page || 0;

  var pageNum = req.query.page > 0 ? req.query.page : 0;
  if (pageNum > 0) {
    startIndex = ENTRIES_PER_PAGE * (pageNum - 1);
  }
  total = ENTRIES_PER_PAGE * (pageNum + 1);

  User.topActiveUsers(req)
      .then(function fullfilled(users) {
        if (users.length < startIndex) {
          throw new Error('Invalid pagination offset');
        }
        if (users.length > total) {
          users = users.slice(startIndex, startIndex + ENTRIES_PER_PAGE);
        } else {
          users = users.splice(startIndex);
        }
        return Promise.all(users.map(function (user) {
          return user.applicationListings(req);
        }));
      })
      .then(function fullfilled(users) {
        var result = users.map(function (user) {
          return {
            id: user.id,
            name: user.name,
            count: user._appliedListings.length,
            createdAt: user.createdAt,
            listings: user._appliedListings
          };
        });
        res.json(result);
      })
      .catch(function rejected(error) {
        console.log(error.message);
        throw error;
      })
      .finally(function () {
        res.end();
      });
};

var userInfo = function userInfo(req, res) {
  User.getById(req)
      // run companies/listings/applications in "parallel"
      .then(function fullfilled(user) {
        return Promise.all([
          user.id,
          user.name,
          user.createdAt,
          user.companies(req),
          user.listings(req),
          user.applications(req)
        ]);
      })
      .then(function fullfilled([
            id, name, createdAt, companies, listings, applications]) {
        res.json({
          id: id,
          name: name,
          createdAt: createdAt,
          companies: companies,
          listings: listings,
          applications: applications
        });
      })
      .catch(function rejected(error) {
        console.log('error', error.message);
        throw error;
      })
      .finally(function () {
        res.end();
      });
};

models/index.js

var Promise = require('promise');

module.exports = User;

/**
 * User m2m Company
 * User o2m Listing
 * User m2m applications
 */
function User(opt_data) {
  var data = opt_data || {};

  this.id = data['id'] || null;
  this.name = data['name'] || '';
  this.createdAt = data['created_at'] || new Date();
  this._companies = [];
  this._listings = [];
  this._applications = [];
  this._appliedListings = [];
}
User._RESOURCE_LIMIT = 5;
var UserProto = User.prototype;

User.topActiveUsers = function topActiveUsers(req) {
  var queryString = "select * from users u inner join "+
      "(select user_id, count(id) cnt from applications "+
      "where id in (select id from applications where "+
      "created_at > current_date - interval '1 week') "+
      "group by user_id) a on u.id = a.user_id order by a.cnt desc";
  return queryPromise(req, queryString)
      .then(function fullfilled(result) {
        return result.rows.map(function(row) {
          return new User(row);
        });
      });
};

User.getById = function getById(req) {
  var queryString = 'select * from users where id = $1::int';
  return queryPromise(req, queryString, [req.query.id])
      .then(function fullfilled(result) {
        return new User(result.rows[0]);
      });
};

UserProto.companies = function companies(req) {
  var queryString = 'select c.id, c.name, t.contact_user '+
      'from companies c, teams t '+
      'where t.user_id = $1::int and t.company_id = c.id '+
      'limit $2::int';
  return queryPromise(req, queryString, [this.id, User._RESOURCE_LIMIT])
    .then(function fullfilled(result) {
      return result.rows.map(function (data) {
        return new Company(data);
      });
    });
};

UserProto.listings = function listings(req) {
  var queryString = 'select * from listings '+
      'where created_by = $1::int '+
      'limit $2::int';
  return queryPromise(req, queryString, [this.id, User._RESOURCE_LIMIT])
    .then(function fullfilled(result) {
      return result.rows.map(function (data) {
        return new Listing(data);
      });
    });
};

UserProto.applicationListings = function applications(req) {
  var queryString = "select * from listings l inner join "+
      "(select listing_id, user_id, created_at from applications) a "+
      "on a.listing_id = l.id "+
      "where a.user_id = $1::int order by a.created_at desc limit 3";
  var self = this;
  return queryPromise(req, queryString, [this.id])
      .then(function fullfilled(result) {
        self._appliedListings = result.rows.map(function (data) {
          return new Listing(data);
        });
        return self;
      });
};

UserProto.applications = function applications(req) {
  var queryString = 'select a.id as app_id, a.created_at, a.cover_letter, '+
    'l.id as list_id, l.name, l.description '+
    'from applications a, listings l '+
    'where a.user_id = $1::int and a.listing_id = l.id '+
    'limit $2::int';
  return queryPromise(req, queryString, [this.id, User._RESOURCE_LIMIT])
      .then(function fullfilled(result) {
        return result.rows.map(function (data) {
          return new Application(data);
        });
      });
};

function Company(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.name = data['name'] || '';
  this.isContact = false;
}

function Listing(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.name = data['name'] || '';
  this.description = data['description'] || '';
}

function Application(opt_data) {
  var data = opt_data || {};
  this.id = data['id'] || null;
  this.createdAt = data['created_at'] || new Date();
  this.listing = data['listing'] || null;
  this.coverLetter = data['cover_letter'] || '';
}

function queryPromise(req, queryString, queryParams, debug) {
  if (debug) {
    console.log(queryString, queryParams);
    req.client.connection.on('message', function(msg) {
      console.log(msg)
    });
  }
  return new Promise(function _promise(resolve, reject) {
    req.client.query(
        queryString,
        queryParams || [],
        function result(error, result) {
      req.done(error);

      if (error) {
        console.log('error ' + error.message);
        return reject(error);
      }
      resolve(result);
    });
  });
};

db.js

var pg = require('pg');

module.exports = function() {
  var config = {
    port: 5432,
    max: 10,
    idleTimeoutMillis: 30000
  };
  switch (process.env.NODE_ENV) {
    case 'development':
      config.host = 'localhost';
      config.user = 'vivek';
      config.password = 'admin';
      config.database = 'jobbatical';
      break;
    case 'production':
      config.user = 'rragdkrc37';
      config.database = 'rragdkrc37_db';
      config.password = 'cxbrqnn8wp';
      config.host = 'assignment.codsssqklool.eu-central-1.rds.amazonaws.com'
      break;
    default:
      throw new Error('Invalid enviroment');
  }

  var pool = new pg.Pool(config);

  pool.connect(function(err, client, done) {
    if(err) {
      return console.error('error fetching client from pool', err);
    }
    client.query('SELECT $1::int AS number', ['1'], function(err, result) {
      done(err);

      if(err) {
        return console.error('error running query', err);
      }
      console.log(result.rows[0].number);
      //output: 1
    });
  });

  pool.on('error', function (err, client) {
    // if an error is encountered by a client while it sits idle in the pool
    // the pool itself will emit an error event with both the error and
    // the client which emitted the original error
    // this is a rare occurrence but can happen if there is a network partition
    // between your application and the database, the database restarts, etc.
    // and so you might want to handle it and at least log it out
    console.error('idle client error', err.message, err.stack)
  });

  return pool;
};

tables.sql

create table users (
    id serial primary key,
    created_at timestamp default current_timestamp,
    name character varying(64)
);

create table companies (
    id serial primary key,
    created_at timestamp default current_timestamp,
    name character varying(64)
);

create table teams (
    id serial primary key,
    company_id integer references companies (id),
    user_id integer references users (id),
    contact_user boolean default false
);

create table listings (
    id serial primary key,
    created_at timestamp default current_timestamp,
    created_by integer references users (id),
    name character varying(64),
    description text
);

create table applications (
    id serial primary key,
    created_at timestamp default current_timestamp,
    user_id integer references users (id),
    listing_id integer references listings (id),
    cover_letter text
);

So, finally I got rejected. I would really love to know what I did wrong.


Get this bounty!!!

#StackBounty: #javascript #html #google-chrome #email Chrome email field autocomplete options not showing for my website

Bounty: 100

As I visit many new websites for the first time, I see that:

  1. For some websites, putting my cursor in the email field of signup form immediately shows me email options from what I had entered in other websites.
  2. For other websites, putting my cursor in the email field does not give me any email options. And, I have to manually type every letter of the email.

I couldn’t find what piece of code differentiates the two cases. For my website, I am stuck with #2. I am trying to achieve #1, where user can just re-use emails entered in other websites.

I used some code like this:

<input type="email" name="email" id="frmEmailA" placeholder="name@example.com" required autocomplete="email">


Get this bounty!!!

#StackBounty: #javascript #node.js #apache #gulp #browser-sync Gulp browser-sync not monitoring and injecting files

Bounty: 50

I have following directory structure

workspace
  |--dev
      |--proj
          |--css 
              |--style.css
          |--js
              |--app.js
          |index.php
          |something.html
          |gulpfile.js
          |package.json

I had installed vhost named as dev.local on ...workspacedev. As you can see I have created a gulpfile.js in my proj directory.

Now if I run gulp browser-sync command my browser window is open showing following url http://dev.local:3000/proj/. It perfectly opens my index.php page but if I do any modification in my files they are not monitored and are not injected into my page. So there is no auto reload of my page.

Here is my gulpfile.js

var gulp = require('gulp');
var bs = require('browser-sync').create(); // create a browser sync instance.

gulp.task('browser-sync', function() {
    bs.init({
        open: 'external',
        host: 'dev.local',
        proxy: 'dev.local/proj'
    });
});

gulp.task('watch', ['browser-sync'], function () {
    gulp.watch("*.html,*.php,css/*.css,js/*.js").on('change', bs.reload);
});

Here is the output of my terminal

gulp browser-sync
[12:14:12] Using gulpfile ~/Documents/workspace/dev/proj/gulpfile.js
[12:14:12] Starting 'browser-sync'...
[12:14:12] Finished 'browser-sync' after 15 ms
[BS] Proxying: http://dev.local
[BS] Access URLs:
 ------------------------------------------------
       Local: http://localhost:3000/proj
    External: http://dev.local:3000/proj
 ------------------------------------------------
          UI: http://localhost:3001
 UI External: http://dev.local:3001
 ------------------------------------------------

I had already searched SO for various solutions but of no avail. Please help I am stuck.

UPDATE

I am using BrowserSync version 2.18.8 and gulp version 3.9.1


Get this bounty!!!

#StackBounty: #authentication #web-application #javascript #multi-factor #fido From a credential flow perspective, whats the difference…

Bounty: 50

The FIDO Standard allows for devices and authentication schemes to be certified as UAF or U2F. This allows for flexible unified authentication, and optional second factor enrollment and registration.

Deployment:

Question

How different is the W3C FIDO 2.0 Web API‘s from the current enrollment for Relying Parties as described here and in this script?


Get this bounty!!!

#StackBounty: Lazy load content of slides inside carousel (content is oEmbeds/iframes)

Bounty: 100

I am loading in ajax a modal/popin with a Bootstrap carousel in which each slide is (not an image as in many question about lazy loading) but an iframe from oEmbed from various social networks (facebook, instagram, twitter, …).

The issue is that when I click the button that laods the modal, ALL the slides content get loaded, that is to say 15 to 20 oembeds(eahc of them loading content tetx, image and and javascript…).

I would like to be clever about it and only “lazy loading” slide by slide or even smarter 3 slides by 3 slides.

I am just also mentioning for the sake of information that I am using scrollMonitor and Hubspot Messenger. But i’d rather use Bootstrap slide events to trigger the apparition/load of each slide or any suggestion you would have.

I’m using ruby on rails as back end language

The url of the oEmbed programmatically change as they are inputed from a Admin Backoffice and change on each Article/page but you’ll find below an example :

Page.html

//button to click to make modal appear
<a class="btn btn-primary" onclick="loadModal()" id="socialStoryModal">
      load modal
</a>

Load Modal with Hubspot Messenger in page.js

function loadModal() {  
      var msg;
      msg = Messenger().post({
        message:  'modal.html.erb',/see below the carousel
        showCloseButton: true,
        hideAfter: false
      }); 
    }

modal.html.erb
= Modal with the carousel and the social embeds (here can be up to 50 of them)

I’ve seen tons of libraries to lazy load images and even sometimes scripts/iframes but they all need to have directly add certain classes I n the block, which is of no help for me as I use oembed above and I have nowhere to put these lazy classes.

I need to make it work with these oEmbeds iframes.


Get this bounty!!!

#StackBounty: Google Maps API Winding path within polygon with rotate/grow/shrink/order options

Bounty: 50

I’m not certain what I’m trying to achieve is possible, but I know there are some creative people here and admittedly, I’m going to need some hand holding on this one.

Here is a mockup of what I’d like to add:
Example

Using these links below for reference of what’s possible. I need to apply each of these features to every user-drawn polygon.

  1. Auto-generating/filling a shape inside a polygon. For my purpose, I don’t need boxes, I need a single winding path to fill every user-created polygon. I need that winding path to hug the boundaries of the polygon as in my picture above.
    Draw small boxes inside a polygon

  2. Rotating a shape. The solution for rotating a shape 90 degrees offered by Vadim Gremyachev. Except, instead of rotating the polygon, I need to be able to rotate the path within the polygon and have it recalculate to maintain the winding path as in my pic above, only now with a different oritentation.
    Google Maps Rotate Polygon

  3. Another awesome example of drawing inside a polygon by geocodezip
    jsfiddle

  4. As in my mockup, I also need to be able to widen and shrink the path (grow = less winding and shrink = more winding). Arrows in mockup are just to illustrate the grow/shrink points.

  5. I need to be able to designate the order of paths via some way of passing the ability to number the final paths to the user.

  6. Finally, I need to be able to collect (save) all user generated paths in the correct sequence order. This includes any manually drawn paths as well as all of the paths auto-generated within each user-drawn polygon.

Here is my code I’m trying to apply these features to:

//debugger;

/////////////////////////////////////////////////////////////
//Map Specifications

function initialize() {
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 18,
    center: new google.maps.LatLng(33.27144940863937, -117.2983479390361),
    mapTypeId: google.maps.MapTypeId.SATELLITE,
    mapTypeId: google.maps.MapTypeId.HYBRID,
    tilt: 0,
    disableDefaultUI: true,
    zoomControl: true,
    mapTypeControl: false,
    scaleControl: true,
    streetViewControl: true,
    rotateControl: true,
    fullscreenControl: false
  });

  // Creates a drawing manager attached to the map that allows the user to draw
  // markers, lines, and shapes.
  drawingManager = new google.maps.drawing.DrawingManager({
    drawingControlOptions: {
      position: google.maps.ControlPosition.TOP_CENTER,
      drawingModes: [
        google.maps.drawing.OverlayType.POLYLINE,
        google.maps.drawing.OverlayType.POLYGON
      ]
    },
    markerOptions: {
      draggable: false
    },
    //https://developers.google.com/maps/documentation/javascript/reference#PolygonOptions
    polygonOptions: {
      clickable: true,
      draggable: false,
      editable: true,
      fillColor: '#00FF00',
      fillOpacity: 0.45,
      geodesic: false,
      strokeColor: '#000000',
      strokeOpacity: 08,
      //strokePosition: CENTER,
      strokeWeight: 3,
      visible: true,
      zIndex: 0
    },
    //https://developers.google.com/maps/documentation/javascript/reference#PolylineOptions
    polylineOptions: {
      clickable: true,
      draggable: false,
      editable: true,
      geodesic: false,
      //icons: ,
      strokeColor: '#FF00FF',
      strokeOpacity: 0.8,
      strokeWeight: 3,
      visible: true,
      zIndex: 0
    }
  });

  ////////////////////////////////////////////////////////////////////////////////
  var drawingManager;
  var deleteSelectedShape;
  var selectedShape;

  function clearSelection() {
    if (selectedShape) {
      if (selectedShape.type !== 'marker') {
        selectedShape.setEditable(false);
      }

      selectedShape = null;
    }
  }

  function setSelection(shape) {
    if (shape.type !== 'marker') {
      clearSelection();
      shape.setEditable(true);
    }

    selectedShape = shape;
  }
  DeleteShape = function deleteSelectedShape() {
    if (selectedShape) {
      selectedShape.setMap(null);
    }
    if (selectedShape.type == 'polygon') {
      document.getElementById("action_gon").value = 'adds, moves, deletions'
    } else if (selectedShape.type == 'polyline') {
      document.getElementById("action_line").value = 'adds, moves, deletions'
    }
  };

  /////////////////////////////////////////////////////////////
  //Populate textboxes with geo data when new polygon and polyline shape added

  drawingManager.setMap(map);

  google.maps.event.addDomListener(drawingManager, 'markercomplete', function(marker) {
    document.getElementById("action").value += "#markern";
    document.getElementById("action").value += marker.getPosition() + "n";
  });

  google.maps.event.addDomListener(drawingManager, 'polylinecomplete', function(line) {
    path = line.getPath();
    //document.getElementById("action_line").value = ''
    document.getElementById("action_line").value = "#polyline shape addedn";
    for (var i = 0; i < path.length; i++) {
      document.getElementById("action_line").value += path.getAt(i) + "n";
    }
  });

  google.maps.event.addDomListener(drawingManager, 'polygoncomplete', function(polygon) {
    var markerCnt = 0;
    path = polygon.getPath();
    //document.getElementById("action_gon").value = ''
    document.getElementById("action_gon").value = "#polygon shape addedn";
    for (var i = 0; i < path.length; i++) {
      document.getElementById("action_gon").value += path.getAt(i) + 'n';
    }
  });

  //////////////////////////////////////////////////////////////////////

  google.maps.event.addListener(drawingManager, 'overlaycomplete', function(e) {

    var newShape = e.overlay;
    newShape.type = e.type;

    if (e.type !== google.maps.drawing.OverlayType.MARKER) {
      // Switch back to non-drawing mode after drawing a shape.
      drawingManager.setDrawingMode(null);

      if (e.type == google.maps.drawing.OverlayType.POLYGON) {
        var coordinatesArray = e.overlay.getPath().getArray();
        document.getElementById("count_gon").value += "#n";
        document.getElementById("count_gon").value += coordinatesArray + "n";
      }

      //Catch vertex modifications (moves)
      function processVertices(e) {
        var ele;
        if (newShape.type == "polygon") {
          ele = document.getElementById("action_gon");
          //ele.value = "Modified vertex: "+e+"n"+this.getAt(e)+"nPolygon coords :n";
          ele.value = "#polygon vertex " + e + " movedn" + this.getAt(e) + "n";
        } else if (newShape.type == "polyline") {
          ele = document.getElementById("action_line");
          //ele.value = "Modified vertex: "+e+"n"+this.getAt(e)+"nPolyline coords :n";
          ele.value = "#polyline vertex " + e + " movedn" + this.getAt(e) + "n";
        } else return;
        for (var i = 0; i < newShape.getPath().getLength(); i++) {
          ele.value += newShape.getPath().getAt(i) + 'n';
        };
      };

      google.maps.event.addListener(newShape.getPath(), 'set_at', processVertices);
      google.maps.event.addListener(newShape.getPath(), 'insert_at', processVertices);

      /////////////////////////////////////////////////////////////
      // Add an event listener that selects the newly-drawn shape when the user clicks it.
      google.maps.event.addListener(newShape, 'click', function(e) {
        if (e.vertex !== undefined) {
          if (newShape.type === google.maps.drawing.OverlayType.POLYGON) {
            var path = newShape.getPaths().getAt(e.path);
            path.removeAt(e.vertex);

            /////////////////////////////////////////////////////////////
            //Update textboxes with geo data when polygon vertex deleted
            document.getElementById("action_gon").value = "#polygon vertex deletedn";
            for (var i = 0; i < path.length; i++) {
              document.getElementById("action_gon").value += path.getAt(i) + 'n';
            }

            if (path.length < 3) {
              newShape.setMap(null);
              document.getElementById("action_gon").value = 'This box shows updated coords for POLYGONS based on user interactions (adds, moves, deletions).'
            }
          }

          if (newShape.type === google.maps.drawing.OverlayType.POLYLINE) {
            var path = newShape.getPath();
            path.removeAt(e.vertex);
            /////////////////////////////////////////////////////////////
            //Update textboxes with geo data when polyline vertex deleted
            document.getElementById("action_line").value = "#polyline vertex deletedn";
            for (var i = 0; i < path.length; i++) {
              document.getElementById("action_line").value += path.getAt(i) + 'n';
            }

            if (path.length < 2) {
              newShape.setMap(null);
              document.getElementById("action_line").value = 'This box shows updated coords for POLYLINES based on user interactions (adds, moves, deletions).'
            }
          }
        }

        setSelection(newShape);
      });
      setSelection(newShape);
    } else {
      google.maps.event.addListener(newShape, 'click', function(e) {
        setSelection(newShape);
      });
      setSelection(newShape);
    }
  });

  // Link delete button to the UI element.
  var delbtn = /** @type {HTMLInputElement} */ (
    document.getElementById('delete-button'));
  map.controls[google.maps.ControlPosition.TOP_RIGHT].push(delbtn);

  // Clear the current selection when the drawing mode is changed, or when the
  // map is clicked.
  google.maps.event.addListener(drawingManager, 'drawingmode_changed', clearSelection);
  google.maps.event.addListener(map, 'click', clearSelection);

  // Listen for delete button click.
  google.maps.event.addDomListener(document.getElementById('delete-button'), 'click', deleteSelectedShape);

  /////////////////////////////////////////////////////////////////////

  //Places Search Box Setup
  var markers = [];
  var input = /** @type {HTMLInputElement} */ (
    document.getElementById('pac-input'));
  map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);

  var searchBox = new google.maps.places.SearchBox(
    /** @type {HTMLInputElement} */
    (input));

  // [START region_getplaces]
  // Listen for the event fired when the user selects an item from the
  // pick list. Retrieve the matching places for that item.
  google.maps.event.addListener(searchBox, 'places_changed', function() {
    var places = searchBox.getPlaces();

    if (places.length == 0) {
      return;
    }
    for (var i = 0, marker; marker = markers[i]; i++) {
      marker.setMap(null);
    }

    // For each place, get the icon, place name, and location.
    markers = [];
    var bounds = new google.maps.LatLngBounds();
    for (var i = 0, place; place = places[i]; i++) {
      var image = {
        url: place.icon,
        size: new google.maps.Size(71, 71),
        origin: new google.maps.Point(0, 0),
        anchor: new google.maps.Point(17, 34),
        scaledSize: new google.maps.Size(25, 25)
      };

      bounds.extend(place.geometry.location);
    }

    map.fitBounds(bounds);
  });
}

google.maps.event.addDomListener(window, 'load', initialize);
#map,
html,
body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
}
#geoinfoboxes {
  display: none;
}
#delete-button {
  background: #0084ff;
  background-image: -webkit-linear-gradient(top, #0084ff, #000000);
  background-image: -moz-linear-gradient(top, #0084ff, #000000);
  background-image: -o-linear-gradient(top, #0084ff, #000000);
  background-image: linear-gradient(to bottom, #0084ff, #000000);
  border-radius: 30px;
  text-shadow: 0px 1px 3px #cfcdcf;
  -webkit-box-shadow: 0px 1px 3px #666666;
  -moz-box-shadow: 0px 1px 3px #666666;
  box-shadow: 0px 1px 3px #666666;
  font-family: Arial;
  margin-top: 5px;
  right: 0.5%;
  color: #ffffff;
  font-size: 15px;
  padding: 8px 10px 8px 10px;
  border: solid #a8a8a8 2px;
  text-decoration: none;
}
#delete-button:hover {
  background: #09ff00;
  background-image: -webkit-linear-gradient(top, #09ff00, #000000);
  background-image: -moz-linear-gradient(top, #09ff00, #000000);
  background-image: -o-linear-gradient(top, #09ff00, #000000);
  background-image: linear-gradient(to bottom, #09ff00, #000000);
  text-decoration: none;
}
.controls {
  border: 1px solid transparent;
  border-radius: 30px 30px 30px 30px;
  box-sizing: border-box;
  -moz-box-sizing: border-box;
  height: 32px;
  outline: none;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
  margin-top: 5px;
}
#pac-input {
  background-color: #fff;
  font-family: Roboto;
  font-size: 15px;
  font-weight: 300;
  margin-left: 12px;
  padding: 0 11px 0 13px;
  text-overflow: ellipsis;
  width: 400px;
}
#pac-input:focus {
  border-color: #4d90fe;
}
.pac-container {
  font-family: Roboto;
}
<!DOCTYPE html>
<!-- saved from url=(0014)about:internet -->
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=Edge" />
  <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
  <meta charset="UTF-8">
  <title>Test</title>
  http://maps.google.com/maps/api/js?key=AIzaSyBgmfaITmUDhXxk-0V33IPmNPd43mMd4ZU&libraries=drawing,places
</head>
<!-- -->

<body>
  <input id="pac-input" class="controls" type="text" placeholder="Search...">
  <input id="delete-button" onclick="DeleteShape();" type=button value="Delete Selected Shape">
  
</body> </html>

I realize this is a tall order, so I plan to add a bounty if I can’t get there with general help. Also, willing to work with someone offline.

EDIT: Looks like the grid-filled polygon from my image has been done using the Google API. Here’s an exact example of the grid type I’m wanting to create (skip to 2:10): https://www.youtube.com/watch?v=u-k8ax2JGC4


Get this bounty!!!

#StackBounty: Writting a JS library to create a HTML form

Bounty: 50

I’m currently writting a JS Library in order to create a form in HTML.
This library need to be :

  • Cross-browser, with compatibility with at least IE10 (earlier version can be considered as bonus)
  • Multi language : you should be able to change the form language
  • Able to load external javascript dependencies*
  • Communicate with my API
  • Fast
  • As simple to use as possible

So, here is what I’ve done for now :

window.MyBasicLibrary = (function() 
{
'use strict';

    // Utilitarian function not to loose the scope 
    function bind(context, name){
        return function(){
            return context[name].apply(context, arguments);
        };
    }

    // Utilitarian function to get DOM by class
    function getElementsByClassName(node, classname) 
    {
        var a = [];
        var re = new RegExp('(^| )'+ classname +'( |$)');
        var els = node.getElementsByTagName("*");

        for (var i = 0, j = els.length; i < j; i++)
        {
            if (re.test(els[i].className))
                a.push(els[i]);
        }
        return (a);
    }

    /*
    ** Utilitarian function to serialize a form
    ** See : https://code.google.com/archive/p/form-serialize/
    */
    function serialize(form) 
    {
        if (!form || form.nodeName !== "FORM") {
            return;
        }
        var i, j, q = [];
        for (i = form.elements.length - 1; i >= 0; i = i - 1) {
            if (form.elements[i].name === "") {
                continue;
            }
            switch (form.elements[i].nodeName) {
            case 'INPUT':
                switch (form.elements[i].type) {
                case 'text':
                case 'hidden':
                case 'password':
                case 'button':
                case 'reset':
                case 'submit':
                    q.push(form.elements[i].name + "=" + encodeURIComponent(form.elements[i].value));
                    break;
                case 'checkbox':
                case 'radio':
                    if (form.elements[i].checked) {
                        q.push(form.elements[i].name + "=" + encodeURIComponent(form.elements[i].value));
                    }                       
                    break;
                case 'file':
                    break;
                }
                break;           
            case 'TEXTAREA':
                q.push(form.elements[i].name + "=" + encodeURIComponent(form.elements[i].value));
                break;
            case 'SELECT':
                switch (form.elements[i].type) {
                case 'select-one':
                    q.push(form.elements[i].name + "=" + encodeURIComponent(form.elements[i].value));
                    break;
                case 'select-multiple':
                    for (j = form.elements[i].options.length - 1; j >= 0; j = j - 1) {
                        if (form.elements[i].options[j].selected) {
                            q.push(form.elements[i].name + "=" + encodeURIComponent(form.elements[i].options[j].value));
                        }
                    }
                    break;
                }
                break;
            case 'BUTTON':
                switch (form.elements[i].type) {
                case 'reset':
                case 'submit':
                case 'button':
                    q.push(form.elements[i].name + "=" + encodeURIComponent(form.elements[i].value));
                    break;
                }
                break;
            }
        }
        return q.join("&");
    }

    // Element should be the result of getElementByID.
    var mb = function(element)
    {
        this.mainContainer = element;
    };


    mb.prototype.regional = {
        mcta_1: "Form A <This One Works>",
        mcta_2: "Form B",
        mcta_3: "Form C",
        submitFormA: "Submit"
    };

    mb.prototype.mainContainer = undefined;
    mb.prototype.currentForm = undefined;
    mb.prototype.APIEndpoint = 'https://www.example.com/api/';

    mb.prototype.initialize =  function()
    {
        this.createMainPageHTML();
    };

    mb.prototype.hideContainers = function()
    {
        var list = getElementsByClassName(this.mainContainer, "mbform_container");
        for (var i = 0; i < list.length; i++)
        {
            list[i].style["display"] = "none";
        }
    }

    mb.prototype.createMainPageHTML = function()
    {
        var element = document.getElementById('mbform_mainpage');

        this.hideContainers();

        if (typeof(element) != 'undefined' && element != null)
        {
            element.style["display"] = "block";
        }
        else
        {
            var mcta1 = '
'+ this.regional.mcta_1 +'
'; var mcta2 = '
'+ this.regional.mcta_2 +'
'; var mcta3 = '
'+ this.regional.mcta_3 +'
'; var mpHTML = '
'+mcta1 + mcta2 + mcta3 +'
'; // TODO Load CSS // Add HTML to DOM this.mainContainer.innerHTML = mpHTML; // takes an HTML string // TODO Load JS Dependencies // Add Event listener document.getElementById('mbform_mcta1').addEventListener("click", bind(this, "createFormA"), false); document.getElementById('mbform_mcta2').addEventListener("click", bind(this, "createFormB"), false); document.getElementById('mbform_mcta3').addEventListener("click", bind(this, "createFormC"), false); } }; mb.prototype.createFormA = function() { var element = document.getElementById('mbform_FormAContainer'); this.hideContainers(); this.currentForm = "mbform_FormA"; if (typeof(element) != 'undefined' && element != null) { element.style["display"] = "block"; } else { var iemail = '<input type="text" value="" name="email" id="mbform_iemail"/><br/>'; var ifirstname = '<input type="text" value="" name="firstname" id="mbform_ifirstname"/><br/>'; var ilastname = '<input type="text" value="" name="lastname" id="mbform_ilastname"/><br/>'; var formHTML = '<form id="mbform_FormA">'+iemail + ifirstname + ilastname + '</form>' // TODO : Disable form default bahaviour (Submit when Enter Keypress + submit button), button is placed outside form for testing purpose. var submitButton = '<button id="mbform_FormASubmit">'+ this.regional.submitFormA +'</button>'; var formContainerHTML = '
'+ formHTML + submitButton +'
'; this.mainContainer.innerHTML += formContainerHTML; // Append HTML to existing document.getElementById('mbform_FormASubmit').addEventListener("click", bind(this, "saveForm"), false); } }; mb.prototype.createFormB = function() { console.log(this.mainContainer); // Is scope ok ? }; mb.prototype.createFormC = function() { console.log(this.mainContainer); // Is scope ok ? }; mb.prototype.getPostFromCurrentForm = function() { var data = serialize(document.getElementById(this.currentForm)); console.log(data); return (data); } mb.prototype.saveForm = function() { var xhr = new XMLHttpRequest(); var params = this.getPostFromCurrentForm(); xhr.open('POST', this.APIEndpoint, true); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.onload = function () { console.log("Loading..."); }; // Current APIEndpoint is example.com, do not send the form. //xhr.send(params); } return (mb); })();

And here is how you initialize the library

(function() {
            var dom = document.getElementById('myForm');
            var mb = new MyBasicLibrary(dom);
            mb.initialize();
})();

I did my best to respect javascript best practice but I’m not sure if I’ve done it well. What do you think about it ?
Also, having raw HTML inside variable seems to be a bad practice, however I need to minimize the number of request made to the server for performance purpose. It means that I can’t store my HTML server side and send it through AJAX. Is it the best solution ?

I’ve created a JSFiddle in case you want to tweak it.
https://jsfiddle.net/xd4ojka2/


Get this bounty!!!

Fetch GET parameters in JS/jQuery

If you have a URL with some GET parameters as follows:

www.test.com/t.html?a=1&b=3&c=m2-m3-m4-m5 

and need to get the values of each parameters then below is a nifty piece of code solving your requirement.

JavaScript has nothing built in for handling query string parameters.

You could access location.search, which would give you from the ? character on to the end of the URL or the start of the fragment identifier (#foo), whichever comes first.

You can then access QueryString.c