Hemanth.HM

A Computer Polyglot, CLI + WEB ♥'r.

Contact Picker API

| Comments

Native mobile apps always had this privilege of using the contact picker with ease and now web can do that too!

First up, Check if the browser support this API:

1
const hasContactPicker = "contacts" in navigator && "ContactsManager" in window;

invoke select method:

1
2
3
4
5
6
7
8
9
10
11
12
13
const properties = "name, tel, email, address, icon".split(",");
const options = { multiple: true };

const fetchContact = async evn => {
  try {
    const contacts = await navigator.contacts.select(properties, options);
    // ^ [{},{}]
  } catch (err) {
    //bombed
  }
};

contactButton.addEventListener("click", evn => fetchContact(evn));

contacts would be an array of objects with keys being the requested properties and values being the corresponding contact details.

Note:

  • This API works only on top level window.

  • User gesture is required before invoking the select method!

View the demo here and the source below:

WebSocketStream API

| Comments

WebSocketStream API came into play to make things easier with WebScoket API, which has the below limitations:

  • Ergonomics isn't great.
  • The important feature of backpressure is missing.
  • The onmessage event will keep firing until the page becomes completely unresponsive.
  • To find out when it is safe to start sending messages again, it is necessary to poll bufferedAmount.

Goals of WebSocketStream API:

  • Provide a WebSocket API that supports backpressure
  • Provide a modern, ergonomic, easy-to-use WebSocket API
  • Allow for future extension

^ From the explainer.

Sample code:

1
2
3
4
5
6
7
8
(async () => {
  const wss = new WebSocketStream("wss://echo.websocket.org/");
  const { readable, writable } = await wss.connection;
  const reader = readable.getReader();
  await writable.getWriter().write("echo");
  const { value, done } = await reader.read();
  console.log(value, done);
})();

DEMO:

DOM Element Timing API

| Comments

Measuring and monitoring the rendering timestamps of DOM elements is an important measure to improve and avoid regressions in page loads.

The Element Timing API, helps us to measure this with ease and it supports the below elements:

  • img elements.

  • image elements inside an svg.

  • video element's poster image.

  • Elements with a background-image.

  • Groups of text nodes.

Make sure the DOM element of interest has elementtiming content attribute:

1
2
3
<img src="" elementtiming="cat-pic" />
<p elementtiming="imp-doc">I need to watch this</p>
<!-- So on -->

Observe the element:

1
2
3
4
5
6
7
8
9
10
11
12
const observer = new PerformanceObserver(list => {
  let perfEntries = list.getEntries().forEach(function(entry) {
    if (entry.identifier) {
      if (entry.renderTime) {
        console.log(`${entry.renderTime}ms to render!`);
      } else {
        console.log(`${entry.loadTime}ms to load!`);
      }
    }
  });
});
observer.observe({ type: "element", buffered: true });

renderTime and loadTime will be DOMHighResTimeStamp time stamp in milliseconds.

Note: renderTime may not be available if the element is an image and it's loaded cross-origin without the Timing-Allow-Origin header.

DEMO:

Detect Dark Mode

| Comments

Detecting if the user preference is light or dark color theme is pretty easy with the prefers-color-scheme CSS media feature, below code gives more details on the same.

Detecting the prefered color scheme in JS:

1
2
const isDarkMode = window.matchMedia
    && window.matchMedia('(prefers-color-scheme: dark)').matches;

Or maybe a more generic method:

1
2
3
4
5
6
7
8
9
const checkType = type => window.matchMedia
    && window.matchMedia(`(prefers-color-scheme: ${type})`).matches);

const mode = {
    is : {
        dark: checkType('dark'),
        light: checkType('light')
    }
};
1
2
mode.is.dark; // true
mode.is.light; // false

matchMedia(query) returns a new MediaQueryList object with the parsed results of the specified media query string.

Detecting the prefered color scheme in CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Light mode */
@media (prefers-color-scheme: light) {
    body {
        background-color: #000;
        color: white;
    }
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
    body {
        background-color: #000;
        color: white;
    }
}

Note for the spec:

User preferences can also vary by medium. For example, a user may prefer dark themes on a glowing screen, but light themes when printing (to save ink and/or because inked text on blank paper prints better than blank letterforms knocked out of an inked background). UAs are expected to take such variances into consideration so that prefers-color-scheme reflects preferences appropriate to the medium rather than preferences taken out of context.

Handling prefered color changes:

1
2
3
4
5
window.matchMedia('(prefers-color-scheme: dark)')
    .addListener((e) => {
        const isDarkMode = e.matches;
        console.log(`${isDarkMode ? '🌚' : '🌞'}.`);
    });

Force setting dark mode:

1
2
3
4
5
6
7
8
9
10
11
12
13
const forceDarkMode = () => [...document.styleSheets[0].rules].forEach(rule => {
     const mediaText = rule.media && rule.media.mediaText || [];
     const hasColorScheme = mediaText.includes("prefers-color-scheme");
     const hasLightScheme = hasColorScheme && mediaText.includes("light");

     // Remove light mode if.
     if (hasLightScheme) {
         rule.media.deleteMedium("(prefers-color-scheme: light)");
     }

     // Append Dark mdoe.
     rule.media && rule.media.appendMedium("(prefers-color-scheme: dark)");
 });

Tracking DOM Element Visibility

| Comments

We would normally track if a DOM element is visible in the viewport or not to understand user behaviour or to operate on the elment based on visibilty (animation, ajax calls etc).

We would normally apply a function like below to detect if an element is visible in the viewport:

1
2
const isInView = (elm, {top, height} = elm.getBoundingClientRect()) =>
    top <= innerHeight && top + height >= 0;

Later we would mostly add it on scroll events to trigger the checks as the user scroll, which in turn is a costly operation.

That's where the Intersection Observer API comes into rescue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// From the spec
const observer = new IntersectionObserver(changes => {
  for (const change of changes) {
    console.log(change.time);               // Timestamp when the change occurred
    console.log(change.rootBounds);         // Unclipped area of root
    console.log(change.boundingClientRect); // target.boundingClientRect()
    console.log(change.intersectionRect);   // boundingClientRect, clipped by its containing block ancestors, and intersected with rootBounds
    console.log(change.intersectionRatio);  // Ratio of intersectionRect area to boundingClientRect area
    console.log(change.target);             // the Element target
  }
}, {});

// Watch for intersection.
observer.observe(target);

// Stop watching.
observer.unobserve(target);

// Stop observing.
observer.disconnect();

Say, if we were to observe this element, #catBox for visiblity, we would end up with:

1
2
3
4
5
6
7
let options = {
  root: document.querySelector('#catBox'),
  rootMargin: '0px',
  threshold: 1.0
}

let observer = new IntersectionObserver(callback, options);

Portal Communication

| Comments

Portals takes the best from SPA and MPA to provide users with a top level portal browsing context which can embedded in an HTML document.

A portal is similar to an iframe, in that it allows another browsing context to be embedded. However, the portal browsing context hosted by a portal is part of a separate unit of related browsing contexts. The user agent is thus free to use a separate event loop for the browsing contexts,even if they are same origin-domain.

At times we would want to communicate with the intial host and the portal and vice versa, it can be easily achived with the below:

In the host:

1
2
3
4
5
6
const portal = document.querySelector('portal');
portal.activate({data: {foo: "bar"}}).then(() => {
  window.portalHost.onmessage = evn => {
     console.log(e.data); // data from the portal
  }
});

We can also get other information from the event:

1
2
3
4
5
6
7
8
9
10
11
12
{
      origin: evn.origin,
      data: evn.data,
      sourceIsPortalHost: evn.source === window.portalHost,
      gotUserActivation: !!evn.userActivation,
      userActivation: {
        isActive: evn.userActivation && evn.userActivation.isActive,
        hasBeenActive: evn.userActivation && evn.userActivation.hasBeenActive
      }
};

// ^ from WPT

In the portal:

1
2
3
4
5
6
window.addEventListener('portalactivate', (evt) => {
    let predecessor = evt.adoptPredecessor();
    evt.data
    predecessor.postMessage("data", "*");
  // ^ predecessor would be an instanceof HTMLPortalElement
});

With portal.activate we can basically pass the {data} to the portal on activation and the portal shall receive this data in the event object of the portalactivate callback.

evt.adoptPredecessor() will provide context to the originator of the portal and portalHost is exposed if the window has a portal browsing context.

createRequireFromPath in Node

| Comments

Let us consider an hypothetical scenario where we have project foo with the below directory structure:

1
2
3
4
5
6
7
.
├── index.js
├── lib
│   └── util.js
└── src
    └── component
        └── index.js

Let us say that src/component in dependent on lib/util.js, in current world one would have to do the below to require the util in their component

1
const util = require('../../lib/uitl.js');

The nesting for those realtive paths ../../ gets more complex as the directory structure gets more deeper.

For our rescue the module object of node got an addition of createRequireFromPath method in v10.12.0, which accepts a filenameto be used to construct the relative require function. and return a require function.

This can help in doing the below in our src/component, rather than dealing with relative paths.

1
2
3
const requireLib = require('module').createRequireFromPath('../../lib');

requireLib("util.js");

So, any files in ../../lib can now be just required as requireLib(<absPath>) instead of dealing with relative paths.

The heart of createRequireFromPath looks like:

1
2
3
4
5
6
Module.createRequireFromPath = (filename) => {
  const m = new Module(filename);
  m.filename = filename;
  m.paths = Module._nodeModulePaths(path.dirname(filename));
  return makeRequireFunction(m);
};

Meanwhile, MylesBorins has a proTip for us:

ES2019 Features

| Comments

So, it is time for an other post, this time it's ES2019 features.

Without wasting time with some placeholder content, the below are the features and examples:

Array#{flat,flatMap}

1
2
3
4
5
[1, 2, 3].flatMap((x) => [x, x * 2]);
// => [1, 2, 2, 4, 3, 6]

[1, [2, [3]]].flat(Infinity);
// => [1, 2, 3]

Object.fromEntries

1
2
3
4
5
6
const iterableOfEntries = new Map([
    ['cat', 'dog'],
    ['life', 42]
]);
const obj = Object.fromEntries(iterableOfEntries);
console.log(obj); // { cat: "dog", life: 42 }

String#{trimStart,trimEnd}

1
2
"    Hey JS!".trimStart(); // "Hey JS!"
    "Hey JS!    ".trimEnd(); // "Hey JS!"

Symbol#description

1
2
3
const symbol = Symbol('TC39');
console.log(symbol.description); // 'TC39'
console.log(symbol.hasOwnProperty('description')); // false

try { } catch {} // optional binding

1
2
3
4
5
try {
    throw new Error("End of life!");
} catch { // ✋
    console.log("^ no params for catch, you are dead anyway!");
}

JSON ⊂ ECMAScript

1
2
3
// Without the proposal:
"foo<U+2028>bar<U+2029>baz"
// → SyntaxError
1
2
3
// With the proposal:
"foo<U+2028>bar<U+2029>baz"
// does not throw

well-formed JSON.stringify

1
2
JSON.stringify('\uD800');
// → '"\\ud800"'

Function#toString

1
2
3
4
5
function /* this is bar */ bar () {}

bar.toString(); // 'function /* this is bar */ foo () {}'

// ^ perviously this was not the case.

Array#sort stability

1
2
3
4
5
6
7
8
9
10
11
12
[
  { name: "Jan",     age: 20 },
  { name: "Jhon",    age: 20 },
  { name: "David",   age: 18 },
  { name: "Ram",     age: 18 },
  { name: "Sita",    age: 18 },
  { name: "Ravan",   age: 18 },
  { name: "Asura",   age: 12 },
  { name: "Milly",   age: 12 },
].sort((m, n) => m.age - n.age));

// People with the same age retain their order.

Don't miss:

$ in Haskell

| Comments

Function composition plays a very important role in functional programming and in haskell things get better with the $ operator.

$ AKA Application operator helps us in avoiding parentheses during function, a quick type check relves:

1
2
Prelude> :t ($)
($) :: (a -> b) -> a -> b
1
($) :: forall r a (b :: TYPE r). (a -> b) -> a -> b infixr 0

That implies (f x) is same as (f $ x) but it helps us in rewriting f (g (h x)) as f $ g $ h $ x

So, say we have:

1
take 5 (reverse (filter odd [1..10]))

We can re-write it as:

1
take 5 $ reverse $ filter odd $ [1..10]

It gets interesting with the function composition operator .

1
2
Prelude> :t (.)
(.) :: (b -> c) -> (a -> b) -> a -> c

We can re-write the $ expression to:

1
take 5 . reverse . filter odd $ [1..10]

or

1
(take 5 . reverse . filter odd) [1..10]

Another example would be using the $ and . with interact.

1
2
Prelude> :t (interact)
(interact) :: (String -> String) -> IO ()

^ Input from the standard input device is passed to this function as its argument, and the resulting string is output on the standard output device.

Say, we need to accept multiple inputs from the standard input and add them up, we could do it as:

1
main = interact $ show . sum . map read . words

If we are trying the same in Prelude, you must use lines with it. (Better not to use interact in GHCi)

1
Prelude> interact $ show . sum . map read . words . head . lines

So, the thing to remeber is:

  • f $ x = f x

  • (f . g) x = f (g x)

Switch Cameras getUserMedia

| Comments

It statred with a simple tweet:

@_zouhir suggested facingMode

Which has the below mods:

  • "user": The video source is facing toward the user; this includes, for example, the front-facing camera on a smartphone.

  • "environment": The video source is facing away from the user, thereby viewing their environment. This is the back camera on a smartphone.

  • "left": The video source is facing toward the user but to their left, such as a camera aimed toward the user but over their left shoulder.

  • "right": The video source is facing toward the user but to their right, such as a camera aimed toward the user but over their right shoulder.

Here is some simple code to flip cameras:

Check if the browser supports facingMODE

1
2
3
4
let supports = navigator.mediaDevices.getSupportedConstraints();
if( supports['facingMode'] === true ) {
  flipBtn.disabled = false;
}

Set options for the getUserMedia

1
2
3
4
5
6
7
8
let shouldFaceUser = true; //Default is the front cam
let opts = {
  audio: true,
  video: true,
  {
    facingMode: shouldFaceUser ? 'user' : 'environment'
  }
}

Stream the video:

1
2
3
4
5
(async () => {
  const stream = await navigator.mediaDevices.getUserMedia(opts);
  videoElm.srcObject = stream;
  videoElm.play();
})();

Toggle cams:

1
2
3
4
5
6
7
8
flipBtn.addEventListener('click', function(){
  // we need to flip, stop everything
  videoElm.pause()
  videoElm.srcObject = null
  // toggle \ flip
  shouldFaceUser = !shouldFaceUser;
  capture();
})

Demo: