Categories
JavaScript APIs

Formatting Language-Sensitive Lists in JavaScript with ListFormat

With the Intl.ListFormat constructor, we can format language-sensitive lists with ease. It lets us set the locale in which to format a list. Also, it lets us set options for formatting lists such as the type of list or style of the list. To use it, we can create a new ListFormat object with the Intl.ListFormat constructor.

The constructor takes up to two arguments. The first argument is a string of the locale that you want to format the list for. The second argument takes an object with the options for formatting and styling the list. An example use of the Intl.ListFormat constructor is:

const arr = ['bus', 'car', 'train'];
const formatter = new Intl.ListFormat('en', {
  style: 'long',
  type: 'conjunction'
});
const formattedList = formatter.format(arr);
console.log(formattedList);

If we run the code above, we will see bus, car, and train logged from the console.log statement. The Intl.ListFormat constructor creates a formatter object that has the format method to convert an array into a list according to the locale and formatting options that we set.

In the example above, we set the locale to en for English, we set the style property to long, which formats an array of strings into something like A, B, and C or A, B, or C. The type property specifies the kind of list we want to format the list into. Conjunction means that we format a list into A, B, and C.

The arguments of the constructor are the locales that you want to format the list into. It can be a string or an array of strings containing the locale of the list you want to format it into. The locale string or strings should be a BCP 47 language tag. The locales argument is optional. An abridged list of BCP-47 language tags includes:

ar — Arabic

bg — Bulgarian

ca — Catalan

zh-Hans — Chinese, Han (Simplified variant)

cs — Czech

da — Danish

de — German

el — Modern Greek (1453 and later)

en — English

es — Spanish

fi — Finnish

fr — French

he — Hebrew

hu — Hungarian

is — Icelandic

it — Italian

ja — Japanese

ko — Korean

nl — Dutch

no — Norwegian

pl — Polish

pt — Portuguese

rm — Romansh

ro — Romanian

ru — Russian

hr — Croatian

sk — Slovak

sq — Albanian

sv — Swedish

th — Thai

tr — Turkish

ur — Urdu

id — Indonesian

The second argument for the constructor is an object that lets us set the options for how to format the list’s string. There are three properties for this object: localeMatcher, type, and style. The localeMatcher option specifies the locale-matching algorithm to use. The possible values are lookup and best fit.

The lookup algorithm searches for the locale until it finds the one that fits the character set of the strings that are being compared.

best fit finds the locale that is at least as, but possibly more, suitable than the lookup algorithm.

The type property can take on two possible values: conjunction, disjunction, or unit. conjunction means that the list is joined with an and, as in A, B, and C. This is the default option. disnjunction means the list is joined with an or, as in A, B, or C.

unit stands for a list of values with units.

The style property specifies the length of the formatted message. There are two possible values for this property. It can either be long (e.g., A, B, and C), short (e.g., A, B, C), or narrow (e.g., A B C). When style is short or narrow, unit is the only allowed value for this option.

For example, we can use it to format a list into a string that is joined with an and at the end. We can write the following to do so:

const arr = ['bus', 'car', 'bicycle'];
const formatter = new Intl.ListFormat('en', {
  style: 'long',
  type: 'conjunction'
});
const formattedList = formatter.format(arr);
console.log(formattedList);

If we log the formattedList constant like we did above, we get bus, car, and bicycle. We set the en locale to format the string for the English locale, with style long and as a conjunction, which means the list will be joined with an and at the end. If we want to get an or-based list, we can change the type property to a disjunction, as in the following code:

const arr = ['bus', 'car', 'bicycle'];
const formatter = new Intl.ListFormat('en', {
  style: 'long',
  type: 'disjunction'
});
const formattedList = formatter.format(arr);
console.log(formattedList);

If we run the code above, then we get bus, car, or bicycle logged in the console.log statement above.

We can convert it to a shorter list by using the short or narrow option for the style property. For example, we can write:

const arr = ['bus', 'car', 'bicycle'];
const formatter = new Intl.ListFormat('en', {
  style: 'short',
  type: 'conjunction'
});
const formattedList = formatter.format(arr);
console.log(formattedList);

Then we get bus, car, & bicycle in the console.log output when we run the code above. The short and disjunction combination is the same as the long and disjunction combination. If we write:

const arr = ['bus', 'car', 'bicycle'];
const formatter = new Intl.ListFormat('en', {
  style: 'short',
  type: 'disjunction'
});
const formattedList = formatter.format(arr);
console.log(formattedList);

Then we get bus, car, or bicycle. The narrow option would make it even shorter. For example, if we put:

const arr = ['bus', 'car', 'bicycle'];
const formatter = new Intl.ListFormat('en', {
  style: 'narrow',
  type: 'conjunction'
});
const formattedList = formatter.format(arr);
console.log(formattedList);

Then we get bus, car, bicycle logged in the console.log when we run the code above.

This also works for non-English locales. For example, if we want to format a list of Chinese strings into a list, we can write the following code:

const arr = ['日', '月', '星'];
const formatter = new Intl.ListFormat('zh-hant', {
  style: 'narrow',
  type: 'conjunction'
});
const formattedList = formatter.format(arr);
console.log(formattedList);

Then we get ‘日、月和星’, which means sun, moon, and stars. If we switch the style option to long or short, we would get the same thing because in Chinese there’s only one way to write a conjunction, unlike in English. disjunction also works with Chinese. For instance, if we have:

const arr = ['日', '月', '星'];
const formatter = new Intl.ListFormat('zh-hant', {
  style: 'long',
  type: 'disjunction'
});
const formattedList = formatter.format(arr);
console.log(formattedList);

Then we get ‘日、月或星’ which means sun, moon, or stars. If we switch the style option to long or short, as with Chinese conjunctions, we would get the same thing because in Chinese there’s only one way to write a disjunction, unlike in English.

formatToParts() Method

In addition to the format method, the Intl.ListFormat instance also has the formatToParts method, which formats an array into a conjunction or disjunction string and then returns it as an array of the parts of the formatted string.

For example, if we want to return an array of the parts of the formatted English string for a list, then we can write the following code:

const arr = ['bus', 'car', 'bicycle'];
const formatter = new Intl.ListFormat('en', {
  style: 'long',
  type: 'conjunction'
});
const formattedList = formatter.formatToParts(arr);
console.log(formattedList);

Then we get:

[
  {
    "type": "element",
    "value": "bus"
  },
  {
    "type": "literal",
    "value": ", "
  },
  {
    "type": "element",
    "value": "car"
  },
  {
    "type": "literal",
    "value": ", and "
  },
  {
    "type": "element",
    "value": "bicycle"
  }
]

from the console.log statement. These are the parts of the formatted string that we get with the format method, but broken apart into individual entries. This method is handy if we only want some parts of the formatted string.

With the Intl.ListFormat constructor, formatting language-sensitive lists is easy. The constructor takes a locale string or an array of locale strings as the first argument and an object with some options for the second argument. We can convert an array into a string that has the list formatted into conjunction or disjunction with the Intl.ListFormat instance’s format method, which takes a locale string and options for the length style of the formatted string and whether it’s a conjunction, disjunction, or unit string. It also has a formatToParts method to convert it to a formatted list and then break up the parts into an array.

Categories
JavaScript APIs

Create Custom JavaScript Error Objects

In JavaScript, there’s an Error class to let code throw exceptions. We can create custom error classes by extending the Error class with our own class and custom logic to throw more specific errors.

The Error class has the message , name , and stack files that we inherit from. Of course, like any other class, we can define our own fields and methods inside it.

Create a New Error Class

For example, to make data validation easier, we can make a ValidationError class which is thrown whenever our code encounters invalid data.

We can create a new class as follows:

class ValidationError extends Error {
  constructor(message, type) {
    super(message);
    this.name = "ValidationError";
    this.type = type;
  }
}

As we can see, we used the extends keyword to indicate that we want to inherit from the Error class.

Using Error Classes we Defined

After we defined our error classes, we can use our ValidationError class as follows:

try {
  let data = {
    email: 'abc'
  }
  if (!/[^@+@[^.+..+/.test(data.email)) {
    throw new ValidationError('invalid email', 'email error');
  }
} catch (ex) {
  console.log(ex);
}

In the console.log output, we should see ‘ValidationError: invalid email’ when we run the code.

We can also log the message , name , stack , and type properties individually:

try {
  let data = {
    email: 'abc'
  }
  if (!/[^@]+@[^.]+..+/.test(data.email)) {
    throw new ValidationError('invalid email', 'email error');
  }
} catch (ex) {
  const {
    message,
    name,
    stack,
    type
  } = ex;
  console.log(
    message,
    name,
    stack,
    type
  );
}

Then we should see:

  • invalid email logged for message
  • ValidationError logged for name
  • ValidationError: invalid email  at window.onload for stack
  • email error logged for type

As with another type of object, we can use the instanceof operator to check if it’s an instance of ValidationError as follows:

try {
  let data = {
    email: 'abc'
  }
  if (!/[^@]+@[^.]+..+/.test(data.email)) {
    throw new ValidationError('invalid email', 'email error');
  }
} catch (ex) {
  if (ex instanceof ValidationError){
   console.log('validation error occurred');
  }
}

Then we should see ‘validation error occurred’ logged since we threw an instance of the ValidationError class.

We can also look at the name property to discern different kinds of errors. For example, we can write:

try {
  let data = {
    email: 'abc'
  }
  if (!/[^@]+@[^.]+..+/.test(data.email)) {
    throw new ValidationError('invalid email', 'email error');
  }
} catch (ex) {
  if (ex.name === 'ValidationError') {
    console.log('validation error occurred');
  }
}

Then we get the same thing as above.

Wrapping Exceptions

We can wrap lower-level exceptions, which are of instances of a subclass of a parent exception constructor and create higher-level errors.

To do this, we can catch and rethrow lower-level exceptions and catch them in higher-level code.

First, we have to create a parent and child classes which extend from the Error class as follows:

class FormError extends Error {
  constructor(message, type) {
    super(message);
    this.name = "FormError";
  }
}

class ValidationError extends FormError {
  constructor(message, type) {
    super(message);
    this.name = "ValidationError";
    this.type = type;
  }
}

In the code above, the FormError class extends the Error class and act as the parent class of the ValidationError class.

Then we write some code which makes use of our exception classes that we created:

const checkEmail = (data) => {
  try {
    if (!/[^@]+@[^.]+..+/.test(data.email)) {
      throw new ValidationError('invalid email', 'email error');
    }
  } catch (ex) {
    if (ex instanceof ValidationError) {
      throw new FormError(ex.name)
    } else {
      throw ex;
    }
  }
}

try {
  let data = {
    email: 'abc'
  }
  checkEmail(data);
} catch (ex) {
  if (ex instanceof FormError) {
    throw new FormError(ex.name)
  } else {
    throw ex;
  }
}

In the code above, we have a checkEmail function that throws an error if the data.email property has an invalid email. If a ValidationError is thrown, then we throw a new error.

Then we create a try...catch block after the checkEmail function to wrap around the function call and then we catch the exception in the catch block. We check for FormError instances and then throw a FormError if the caught exception is an instance of the FormError .

Otherwise, we rethrow the caught exception directly. We have this pattern so that we only deal with one kind of error at the top level, instead of checking for all kinds of error in top-level code.

The alternative is to have if...else statements to check for all kinds of errors at the top-level, which is more complex.

We can inherit from the Error class to create our own error classes. Then we can throw new error instances with the throw keyword.

In the class we define, we can add our own instance fields and methods.

When we catch the errors, we can use the insranceof operator to check for the type of error that’s thrown. We can also use the name property to do the same thing.

In our lower level code, we can catch errors of type of the sub-class error type and then rethrow an error of the instance of the parent class. Then we can catch the error of the higher-level type instead of checking for error instances of the lower-level type.

Categories
JavaScript APIs

Drawing Shapes on HTML Canvas — Arcs and Rectangles

The HTML canvas element lets us draw shapes and do various things with them. The most basic things include adding shapes, changing their color, and drawing paths.

In this article, we’ll look at how to draw shapes on the canvas, including arcs and rectangles

The Grid

Everything on the canvas is located on a grid. The items are positioned by coordinates. The top left corner is the origin (0, 0) and everything is placed relative to that. Normally 1 unit in the grid is 1 pixel on the screen.

The x-coordinate is larger as we move to the right and the y-coordinate increases as we go down.

Drawing Rectangles

Canvas supports only 2 primitive shapes — the rectangle and paths.

We can draw rectangles by using the fillRect method of the rendering context. It takes 4 arguments, which are the x and y coordinates of the top left corner respectively, and the width and height of the rectangle. The rectangle will be filled with a color.

The strokeRect method takes the same arguments as the fillRect method and let us draw a rectangular outline.

Finally, there’s the clearRect method, which clears the specified rectangular area, making it fully transparent. It takes the same arguments as the other 2 methods.

For example, we can use these methods as follows:

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillRect(5, 5, 80, 80);
ctx.clearRect(15, 15, 60, 60);
ctx.strokeRect(50, 50, 50, 50);

given that we have the following HTML:

<canvas>

</canvas>

and CSS:

canvas {
  width: 200px;
  height: 200px;
  border: 1px solid black;
}

The fillRect draws a rectangle with the black background, which has a clear rectangle drawn with the clearRect method over it on the top left. The black rectangle has the top-left corner in (5, 5) and is 80×80 pixels large.

The clear rectangle has its top-left corner on (15, 15) and it’s 60×60 pixels large.

Then we have the rectangle drawn with the strokeRect method farther to the bottom right. Its top-left corner is at (50, 50) and it’s 50×50 pixels large.

In the end, we have the following result:

Drawing Paths

In addition to rectangles, we can draw paths, which are line segments that are connected together. It can be a different shape or color. The path can be closed.

We create paths with the following steps:

  1. We create the path
  2. Use drawing commands to draw into the path
  3. Then we can stroke or fill the path to render it.

There’re a few methods we need to call for drawing paths. We need to call the beginPath method to create a new path. Future drawing commands will be directed into the path and build it up.

Then we use 3 other methods to draw the path. We have the closePath method to add a straight line to the path, going to the start of the current subpath.

We use the stroke method to draw the shape by stroking its outline. Then we can use the fill method to fill the path’s content area if we wish.

We move to the coordinates of the screen with the moveTo method, which takes the x and y coordinates to move to as arguments. Then we can draw a line from that coordinate to another with the lineTo method, which takes the x and y coordinates to draw the line from wherever the current coordinate is.

For example, we can draw a triangle by writing:

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(100, 50);
ctx.lineTo(50, 100);
ctx.lineTo(150, 100);
ctx.fill();

We first start trying at (100, 50). Then we draw a line from (100, 50) to (50, 100) with the first lineTo call. Then we draw from (50, 100) to (150, 100) again with the lineTo method. Finally, we called fill to draw the shape by filling in the path’s content area.

In the end, we get:

moveTo Method and Arcs

The moveTo method is handy for moving the originating point of the drawing path without drawing anything.

It takes the x and y coordinates that we want to move to.

We can write:

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(125, 75);
ctx.arc(75, 75, 50, 0, 2 * Math.PI);
ctx.moveTo(135, 75);
ctx.arc(75, 75, 60, 0, 2 * Math.PI);
ctx.stroke();

To draw a circle, we move to the rightmost point of the circle with the moveTo method. Then we call the arc method to draw the arc. The first 2 arguments are the x and y coordinates of the center. The third argument is the radius, the 4th argument is the starting angle and the last argument if the ending angle.

In the code above, the starting angle is the 3 o’clock position. Clockwise or counterclockwise directions are relative to this point.

We can also pass in an additional boolean argument to specify if we want to draw counterclockwise.

In addition. There’s the arcTo method which takes the x and y coordinates of the starting point and the ending point respectively for the first 4 arguments. The 5th argument has the radius of the arc.

After writing that code, we get the following circles.

ctx.moveTo(125, 75);
ctx.arc(75, 75, 50, 0, 2 * Math.PI);

draws the smaller circle with a radius of 50 pixels, and:

ctx.moveTo(135, 75);
ctx.arc(75, 75, 60, 0, 2 * Math.PI);

draws the bigger circle with a radius of 60 pixels.

With the canvas element, we can draw shapes easily. There’re methods specifically for drawing rectangles. For curved shapes and lines, we can draw them with the arc and arcTo methods.

Shapes can be drawn filled or not depending on our preferences.

We can use the moveTo method to move to a given coordinate without drawing anything.

Categories
JavaScript APIs

Checking Network Status with the Network Information API

With the advent of mobile devices like phones and tablets, knowing the connection status is very important since it can change any time, affecting user experience in the process.

We also have to be aware of different kinds of Internet connections since they vary widely in speed.

Fortunately, we have the Network Information API built into browsers to check for Internet connection status.

This API is available for browsing and worker contexts.

In this article, we’ll look at how to use the API to get network connection type changes and connection status.

Detect Connection Changes

Detecting connection changes is simple. We can use the navigation.connection object to listen to network type changes as follows:

const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
let type = connection.effectiveType;

const updateConnectionStatus = () => {
  console.log(`Connection type changed from ${type} to ${connection.effectiveType}`);
  type = connection.effectiveType;
}

connection.addEventListener('change', updateConnectionStatus);

We can then test the connection type changes by going to the Chrome developer console by pressing F12. Then go to the Network tab, and then on the top right, there’s a dropdown to change the connection type.

If we switch between them, we should see something like:

Connection type changed from 4g to 3g
Connection type changed from 3g to 4g
Connection type changed from 4g to 2g

from the console.log output.

The connection object is a NetworkInformation object instance and it has the following properties:

  • downlink — effective bandwidth estimate in megabits per second rounded to the nearest 25 kbps.
  • downlinkMax — maximum downlink speed, in Mbps for the underlying connection technology
  • effectiveType — the type of connection determined by a combination of recently observed round-trip time and downlink values. It can be one of ‘slow-2g’, ‘2g’, ‘3g’, or ‘4g’.
  • rtt — estimated effective round-trip time of the current connection rounded to the nearest multiple of 25 milliseconds.
  • saveData — boolean indicating if reduced data usage optional has been set
  • type — type of connection to communicate with the network. It can be one of bluetooth, cellular, ethernet, none, wifi, wimax, other, unknown

Compatibility

This API is new and experimental, so we’ve to be careful when we use it. Chrome supports most properties out of the box since Chrome 61. Some options like downlinkMax and type are only available for Chrome OS. Firefox and Edge do not support this API.

It’s also available for use in other Chromium-based browsers like Opera, Android Webview, and Chrome for Android.

With the Network Information API, we can get information about the network connection. This is useful for detecting the connection type of mobile devices and lets us change the web page to accommodate slower connections accordingly.

Categories
JavaScript APIs

Getting Browser User Permission with the Permissions API

We have to get user consent when we do anything that changes the user experience. For example, we need to get user permission to show notifications or access their hardware.

Modern browsers are standardized in how permissions are obtained. It’s all in the Permissions API.

API that relies on the Permissions API for permissions include the Clipboard API, Notifications API, Push API, and Web MIDI API.

More and more APIs will use the Permissions API to obtain user consent in the future.

The navigator object has the permissions property in both the standard browser and worker contexts. It gives us a way to get the permissions.

The way for requesting permissions is different for each API.

This API is experimental so check if the browsers we want to support are supported before using this API.

Querying Permissions

The navigation.permissions object lets us query permissions set with the query method.

It returns a promise which gets us the state of the permission. For example, we can write the following to check for permission for the geolocation API:

(async () => {
  const result = await navigator.permissions.query({
    name: 'geolocation'
  });
  console.log(result.state);
})();

The possible values for result.state are 'granted', 'denied', or 'prompt'.

There’s also one for Web Workers that do the same thing.

The object that we passed into the query method is the permission descriptor object, which has the following properties:

  • name — name of the API which we want to get the permission for. Firefox only supports geolocation, notifications, push, and persistent-storage
  • userVisibleOnly — indicates whether we want to show notification for every message or be able to send silent push notifications. Default value is false .
  • sysex — applies to MIDI only. Indicates whether we need to receive system exclusive messages. Default value is false .

Requesting Permission

Requesting permission is different between APIs. For example, the Geolocation API will show a prompt for permission when we call getCurrentPosition() as follows:

navigator.geolocation.getCurrentPosition((position) => {
  console.log('Geolocation permissions granted');
  console.log(`Latitude: ${position.coords.latitude}`);
  console.log(`Longitude: ${position.coords.longitude}`);
});

The Notifications API has a requestPermission method to request permissions:

Notification.requestPermission((result) => {
  console.log(result);
});

Watching for Permission Changes

The navigator.permissions.query promise resolves to a PermissionStatus object. We can use it to set an onchange event handler function to watch for permission changes.

For example, we can expand on this example to add an event handler function to watch for changes for the permission of the Geolocation API:

(async () => {
  const result = await navigator.permissions.query({
    name: 'geolocation'
  });
  console.log(result.state);
})();

We can write:

(async () => {
  const permissionStatus = await navigator.permissions.query({
    name: 'geolocation'
  });
  permissionStatus.onchange = () => {
    console.log(permissionStatus.state);
  }
})();

The permissionStatus.state will have the permission for the Geolocation API.

It’ll log whenever the permission changes, such as when we change the permission setting in the browser.

Revoking Permissions

We can revoke permission by using the revoke method.

To call it, we can access it from the navigator.permissions object as follows:

navigator.permissions.revoke(descriptor);

Where descriptor is the permission descriptor which we described above.

It returns a Promise that resolves with the permission status object indicating the result of the request.

This method throws a TypeError if getting the permission descriptor failed or the permission doesn’t exist or is unsupported by browsers.

The name property of the permission descriptor can accept one of the following values 'geolocation', 'midi', 'notifications', and 'push'.

Other properties remain the same as the query method.

For example, we can use it by writing:

(async () => {
  const permissionStatus = await navigator.permissions.revoke({
    name: 'geolocation'
  });
  console.log(permissionStatus);
})();

Note that we’ve to set dom.permissions.revoke.enable to true in Firefox 51 or later to use this method. It’s not enabled in Chrome.

The Permissions API lets us check and revoke browser permissions in a uniform way.

Requesting permissions are different for each API. This isn’t changing any time soon.

This is an experimental API, so using it in production is probably premature.