Categories
JavaScript Best Practices

Maintainable JavaScript — Storing Config Data

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at ways to store config data.

Storing Configuration Data

There’re various ways to store the config data of our app.

Front end frameworks like React, Vue, and Angular have their own ways of storing environment-specific config data.

The CLI programs that we use to create, run, and build the projects can read from them and get the right config values into our build artifacts.

For instance, with Create React App, we can create our own config file

REACT_APP_NOT_SECRET_CODE=abcdef

in our .env file.

Then we can read the value with process.env.REACT_APP_NOT_SECRET_CODE in our app’s code.

Then when we run npm start to start the app and npm run build to build the app, the value will be added.

It can take one file for each environment.

We can have:

  • .env: default config file.
  • .env.local: local overrides
  • .env.development, .env.test, .env.production: environment-specific settings
  • .env.development.local, .env.test.local, .env.production.local — local overrides.

We check ib .env and ignore the local ones.

As long as the key starts with REACT_APP_ , then it can be read.

Vue CLI also has something similar.

It takes the following files:

  • .env — default file
  • .env.local — local override
  • .env.[mode] — override for a specific environment
  • .env.[mode].local — local override for a specific environment

Then in the .env files, we can write:

VUE_APP_NOT_SECRET_CODE=some_value

As long as the prefix of the config key is VUE_APP_ , Vue CLI will read it.

For Node apps, we can use the dotenv package.

We can install it by running:

npm i dotenv

Then we can create our config by writing:

DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3

and then we can use it by writing:

require('dotenv').config()

Then we can read the config by writing:

const db = require('db')
db.connect({
  host: process.env.DB_HOST,
  username: process.env.DB_USER,
  password: process.env.DB_PASS
})

to get the config values from process.env .

We can also store the values in JSON format.

For instance, we can write:

{
  "MESSAGE_INVALID_VALUE": "Invalid value",
  "URL_INVALID": "/errors/invalid"
}

to store the JSON file.

Then we can read the JSON file with fetch , jQuery’s getJSON or whatever we want.

JSON can also be imported directly with import if we use a module bundler like Webpack, Rollup, or Parcel.

For instance, we can just write:

import config from './config.json'

to import our JSON file.

It’ll be imported as if it’s a regular JavaScript object and we can use them as such.

In all the examples, we store the config outside our app.

This way, we can just change the config file, and the values will be reflected everywhere that they’re referenced.

Conclusion

We can store configuration data externally so that we can change the value in one place and they’ll be reflected everywhere.

We can have separate config files for each environment to make our app work in all environments.

Categories
JavaScript Best Practices

Maintainable JavaScript — Separate HTML and JavaScript

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at loose coupling between JavaScript and HTML.

Keep HTML Out of JavaScript

We should keep our HTML code out of our JavaScript.

Having HTML in our JavaScript code make the app hard to debug when there’re any text or structural issues in our code.

It’s almost impossible to trace issues with dynamic HTML in our JavaScript code.

Therefore, we shouldn’t write code like:

const div = document.querySelector("div");
div.innerHTML = "<h3>Error</h3> <p>Invalid date.</p>";

Embedding HTML strings in our JavaScript is a bad practice.

It complicates tracking down any text or structural issues,

It’s hard to look through the DOM inspector in the dev console of our browser to fund the issue since it’s updated dynamically.

Therefore, it’s harder to work with than tracking down simple DOM manipulation issues.

The 2nd problem is maintainability.

If we need to change the text or markup, we’ve to change the HTML and the JavaScript code to change the text.

This is more work than changing them all in the HTML code.

The markup in JavaScript isn’t accessible for changes.

This is because we don’t expect HTML in our JavaScript code.

It’s much more error-prone to edit markup than it is to edit JavaScript since the markup is in string.

So by putting HTML in our JavaScript code, then we complicate our problem.

There are ways to insert HTML with JavaScript in a loosely coupled manner.

One way is to use some templating system like Handlebars.

For instance, we can write:

<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>

<script>
  const template = Handlebars.compile("<p>{{firstname}} {{lastname}}</p>");

  console.log(template({
    firstname: "james",
    lastname: "smith"
  }));

</script>

We include the Handlebars library and then call Handlebars.compile to return a template function that lets us interpolate expressions in a template string.

If we want to use HTML with Handlebars, we add a special script tag:

<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>

<script id="entry-template" type="text/x-handlebars-template">
  <div class="entry">
    <p>{{firstname}} {{lastname}}</p>
  </div>
</script>

Then we can add the following JavaScript to compile the template into HTML:

const source = $("#entry-template").html();
const template = Handlebars.compile(source);
const context = {
  firstname: "james",
  lastname: "smith"
}
const output = template(context);
$("body").append(output);

The script tag has the text/x-handlebars-template type to distinguish it from a JavaScript script tag.

So we can compile the template into HTML.

The compilation is done by getting the script with jQuery.

Then we call Handlebars.compile to compile the source.

The context has the variables we want to interpolate in our template.

Then we call template with the context to compile the data.

Finally, we call append with the compiled template.

Alternatively, we can use frameworks like Vue, React, or Angular to structure our apps so that we won’t have to worry about mixing HTML and JavaScript is a disorganized way.

The frameworks make us write code in a way that there’s loose coupling between JavaScript and HTML.

Conclusion

We should keep our HTML code out of our JavaScript code.

This way, we won’t have to worry about tracking code that’s hard to debug.

We can separate them with templating systems like Handlebars or use frameworks like Angular, React, or Vue.

Categories
JavaScript Best Practices

Maintainable JavaScript — Null and Object Checks

erral)

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at how to check for various kinds of data.

typeof and null

The typeof operator returns 'object' if the operand is null .

This is one primitive value that we can’t use typeof to check for.

Comparing against null doesn’t give us enough information generally.

However, we can check for null if we use the === and !== operators.

For instance, we can write:

const element = document.querySelector("div");
if (element !== null) {
  element.className = "found";
}

to check if an element with the given selector exists.

querySelector returns null if the element doesn’t exist.

So we can use the !== or === operator to check for the value.

Detecting Reference Values

We can’t use the typeof operator to check for objects.

Therefore, we need another way to check for them.

Objects include object literals and objects created from constructors like Object , Array , Date and Error .

If we use the typeof operator, it always returns 'object' .

This means it won’t be of much use to us.

typeof null also returns 'object' so it’s also not useful for checking for null .

Instead, we can use the instanceof operator to detect values of a particular reference type.

The general syntax of instanceof is:

value instanceof constructor

where value is the value we’re checking for.

And constructor is the constructor that we want to check whether value os created from.

For example, we can use it to detect a Date instance by writing:

if (date instanceof Date) {
  console.log(date.getFullYear());
}

instanceof not only checks the constructor used to create the object.

It also checks for the constructor up the prototype chain.

Since almost all objects inherits from Object , date instanceof Object also returns true .

Therefore, we can’t use value instanceof Object to check for a particular type of object.

The instanceof operator also works with custom types that we defined ourselves.

For instance, we can write:

function Person(name) {
  this.name = name;
}

const jane = new Person("jane");
console.log(jane instanceof Object);
console.log(jane instanceof Person);

Then both console logs will be true .

The first one is true because jane inherits from Object.prototype .

And the 2nd one is true because jane is created from the Person constructor.

The instanceof operator is the only way to detect custom types in JavaScript.

However, it does have one limitation.

If we pass one object from one frame to another.

If we have:

frameAPerson instanceof frameAPerson

then that returns true .

But if we have:

frameAPerson instanceof frameBPerson

then that returns false .

Each frame has its own copy of Person and instanceof only considers the Person in the current frame.

This is the case even if they’re identical.

Conclusion

The instanceof operator lets us check for various kinds of object.

The only ones it can’t check are the ones passed from different frames.

Categories
JavaScript Best Practices

Maintainable JavaScript — Namespaces and Modules

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at namespacing.

Namespaces

It’s possible that we pollute our single global object even if we only created one.

Therefore, if we have one global variable, we organize our code inside it by namespacing the code.

For instance, we can write:

var Books = {};
Books.JavaScript = {};
Books.Ruby = {}

We have the Books global variable and we compartmentalize them with properties.

We have the JavaScript and Ruby properties to organize different things inside the object.

Also, we can add namespaces dynamically by adding properties to our object.

For instance, we can write:

var GlobalObj = {
  namespace(ns) {
    const parts = ns.split(".");
    let object = this;

    for (const part of parts) {
      if (!object[part]) {
        object[part] = {};
      }
      object = object[part];
    }
    return object;
  }
};

We have the namespace function that gets a string that’s separated by dots.

Then we check if the property exists.

If it doesn’t, then we create a property and set it to an empty object.

We do that until all the string parts and added.

And then we return the object .

This way, we can use it by writing:

GlobalObj.namespace('foo.bar.baz');

And we should see the foo property with bar and baz nested in it.

This method gives us freedom to create the namespace and assumes that it exists.

We just run the namespace method beforehand so that we can always use it.

Modules

Since ES6, modules have been part of the JavaScript language.

For instance, we can use them by exporting the members that we want from a module and importing them to use them.

To define a module, we write:

module.js

export const foo = 1;
export const bar = () => {};
export const baz = {};
export default function() {}

to create our JavaScript module with the filename module.js .

Then we can use it by using the import keyword:

import qux, { foo, bar, baz } from "./module";

console.log(qux, foo, bar, baz);

We import the default export as qux .

Default exports are always outside the curly braces and there can only be one of them.

The named exports are in the curly braces and are import with the names that they’re defined as.

We can change the names of default imports to anything.

And we can change the names of named exports with the as keyword.

So we can write:

import qux, { foo, bar, baz as quux } from "./module";

console.log(qux, foo, bar, quux);

The module name can be a relative path or an absolute path.

If it starts with ./ or ../ then it’s a relative path.

./ means relative to the current file.

../ means one directory level up.

Having none of those means it’s an absolute path.

Modules in the node_modules folder can be imported with just the module name.

If something isn’t exported from the module, then they won’t be accessible outside the module.

Conclusion

Namespaces can be added to compartmentalize our code.

Also, we can use modules to export only what we want to expose and keep everything else private.

We can also create smaller files with modules because of that.

Categories
JavaScript Best Practices

Maintainable JavaScript — JavaScript/HTML Coupling

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at loose coupling between JavaScript and HTML.

Keep JavaScript Out of HTML

We should keep JavaScript out of our HTML code.

It’s hard to debug JavaScript code that’s in our HTML files.

For instance, if we have:

<button onclick="doSomething()">click me</button>

then we call our doSomething function when we click on the button.

doSomething maybe in an external file or in a script tag.

We may run into issues when we click the button before the function is available, which causes a JavaScript error.

The resulting error may pop up and cause the button to appear to do nothing.

It’s also harder to maintain since changing the name would mean we’ve to also look for the name in the HTML file.

We may also want to change the code in the onclick attribu

Embedding JavaScript code in HTML makes changing the code easier.

Most of our JavaScript code should be in external files and included on the page with a script tag.

The on attributes shouldn’t ve used for attaching event handlers to HTML.

Instead, we should attach the event listeners to the element by getting the element with JavaScript and attaching the listener:

function onClick() {
  // ...
}

const btn = document.querySelector("button");
btn.addEventListener("click", onClick, false);

We set the onClick function as the click handler for the button.

The benefit of that is that onClick is defined in the same files as the code that attaches the event handler.

If the function needs to change, then we need to change the JavaScript file.

If the button should do something else when it’s clicked, then we just need to change the code in the file.

IE8 and earlier don’t have the addEventListener method.

Instead, we have to use the attachEvent method.

With jQuery, we can write:

$("button").on("click", onClick);

We can also embed JavaScript in HTML with a script element that has inline code.

But this isn’t good since it keeps the JavaScript code in the HTML and makes the code longer.

For instance, we shouldn’t write code like:

<script>
  doSomething();

</script>

It’s best to keep all JavaScript in external files and keep inline JavaScript out of our HTML.

This helps us debug the code easier.

When a script error occurs, we know the error is in a JavaScript file.

If the JavaScript code is in our HTML file, then we have to search through both kinds of files.

Conclusion

We should keep our JavaScript code out of our HTML code so that we can debug them easier.