Skip to content

Instantly share code, notes, and snippets.

@the0neWhoKnocks
Last active May 24, 2024 18:28
Show Gist options
  • Save the0neWhoKnocks/2f1b8e9f9b7408210e0a246d088323e5 to your computer and use it in GitHub Desktop.
Save the0neWhoKnocks/2f1b8e9f9b7408210e0a246d088323e5 to your computer and use it in GitHub Desktop.
Javascript Snippets

Javascript Snippets


Pad a string with a token

This is supported in newer versions of ES via padStart or padEnd

The example below demonstrates how to remove some of the boilerplate by having a default token.

const pad = (num, token = '00') => `${num}`.padStart(token.length, token);

pad(4);
// returns '04'

pad(4, 'XXXX');
// returns 'XXX4'

Formatting a timestamp

Newer format with localized times

const [month, day, year] = (new Date()).toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', year: 'numeric', timeZone: 'America/Los_Angeles' }).split('/');
const [time, meridiem] = (new Date()).toLocaleDateString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'America/Los_Angeles' }).split(', ')[1].split(' ');
console.log(`${year}-${month}-${day} ${time}${meridiem.toLowerCase()}`);

// logs '2021-05-31 01:29pm'

Couldn't find more info on the options on MDN, but this article shows all the possible options for each type: https://medium.com/swlh/use-tolocaledatestring-to-format-javascript-dates-2959108ea020.

Function with formatting options

const timestamp = ({
  date = new Date(),
  format = '[y]-[mo]-[d] [h]:[mi]:[s][md]',
  timeZone = 'America/Los_Angeles',
} = {}) => {
  // NOTE: Since the format positioning changes per locale, sticking with this one.
  // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString#using_locales
  const langLocale = 'en-US';
  // Format values: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
  const [month, day, year] = date.toLocaleDateString(langLocale, { day: '2-digit', month: '2-digit', year: 'numeric', timeZone }).split('/');
  const [time, meridiem] = date.toLocaleDateString(langLocale, { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone }).split(', ')[1].split(' ');
  const [hour, minutes, seconds] = time.split(':');
  const tokens = {
    d: day,
    h: hour,
    mi: minutes,
    mo: month,
    md: meridiem.toLowerCase(),
    raw: date,
    s: seconds,
    y: year,
  };
  
  return Object.keys(tokens).reduce((str, token) => {
    return str.replace(new RegExp(`\\[${token}\\]`, 'g'), tokens[token]);
  }, format);
};

console.log( timestamp() );
console.log( timestamp({ format: '[y]-[mo]-[d] [h]:[mi][md]' }) );
console.log( timestamp({ format: 'Month: "[mo]" Day: "[d]"' }) );
var d = new Date();
d.setDate(d.getDate()-1);
console.log( timestamp({ date: d }) );

Old format

const timestamp = '1525877727373'; // would be string from server
const d = new Date(+timestamp);
const hour = d.getHours();
const strMonth = pad(d.getMonth() + 1); // month is zero based, but everything else is 1 based
const strDate = pad(d.getDate());
const strHour = hour > 12 ? pad(hour-12): pad(hour);
const strMins = pad(d.getMinutes());
const meridiem = hour >= 12 ? 'pm' : 'am';
att.value = `${strMonth}/${strDate}/${d.getFullYear()} ${strHour}:${strMins}${meridiem}`;

// returns '04/09/2018 07:55am'

Convert a String to Camelcase

const camelCase = (str) => str
  .toLocaleLowerCase()
  // kill any non alpha-numeric chars (but leave spaces)
  .replace(/[^a-zA-Z0-9 ]/g, '')
  // capitalize any words with a leading space (and remove the space)
  .replace(/\s+(\w)?/gi, (m, l) => l.toUpperCase());

console.log(camelCase('SOME (rAndom) wurdz!'));
// 'someRandomWurdz'

Convert a String to Pascalcase

const pascalCase = (str, del = ' ') => str
  .split(del)
  .map((word) => word.replace(/^\w/, c => c.toUpperCase()))
  .join('');

Convert a String to Kebabcase

const kebabCase = (str) => str
  .trim()
  .toLocaleLowerCase()
  // kill any non alpha-numeric chars (but leave spaces)
  .replace(/[^a-zA-Z0-9 ]/g, '')
  // capitalize any words with a leading space (and replace the space with a hyphen)
  .replace(/\s+(\w)?/gi, (m, l) => `-${l.toLowerCase()}`);

console.log(kebabCase('SOME (rAndom) wurdz!'));
// 'some-random-wurdz'

Convert Hex to RGB

const hexToRGB = (hex) => {
  const hasAlpha = hex.length === 5 || hex.length === 9;
  let r = 0, g = 0, b = 0, a = 1;
  
  // 3 or 4 digits
  if (hex.length === 4 || hex.length === 5) {
    r = `0x${hex[1] + hex[1]}`;
    g = `0x${hex[2] + hex[2]}`;
    b = `0x${hex[3] + hex[3]}`;
    if (hasAlpha) a = `0x${hex[4] + hex[4]}`;
  }
  // 6 or 8 digits
  else if (hex.length === 7 || hex.length === 9) {
    r = `0x${hex[1] + hex[2]}`;
    g = `0x${hex[3] + hex[4]}`;
    b = `0x${hex[5] + hex[6]}`;
    if (hasAlpha) a = `0x${hex[7] + hex[8]}`;
  }
  
  if (hasAlpha) a = +(a / 255).toFixed(2);
  
  return (hasAlpha) ? `rgba(${+r}, ${+g}, ${+b}, ${a})` : `rgb(${+r}, ${+g}, ${+b})`;
}

// usage
hexToRGB('#f1b71f'); // result: 'rgb(241, 183, 31)'
hexToRGB('#f1b71f80'); // result: 'rgba(241, 183, 31, 0.5)'

Convert RGB to Hex

const rgbToHex = (r, g, b, a) => {
  if (typeof r === 'string' && r.startsWith('rgb')) {
    const arr = r.replace(/rgba?\(|\)/g, '').split(',').map(s => s.trim());
    r = +arr[0];
    g = +arr[1];
    b = +arr[2];
    if (arr.length === 4) a = +arr[3];
  }
  
  r = r.toString(16);
  if (r.length === 1) r = `0${r}`;
  
  g = g.toString(16);
  if (g.length === 1) g = `0${g}`;
  
  b = b.toString(16);
  if (b.length === 1) b = `0${b}`;
  
  if (a !== undefined) {
    a = Math.round(a * 255).toString(16);
    if (a.length === 1) a = `0${a}`;
  }

  return (a !== undefined) ? `#${r + g + b + a}` : `#${r + g + b}`;
}

// usage
rgbToHex('rgb(241, 183, 31)'); // result: '#f1b71f'
rgbToHex(241, 183, 31); // result: '#f1b71f'
rgbToHex('rgba(241, 183, 31, 0.5)'); // result: '#f1b71f80'
rgbToHex(241, 183, 31, 0.5); // result: '#f1b71f80'

Random Number Within Range

const randomNumber = (min, max) => Math.floor(Math.random() * (max - min + 1) + min); 

Random Color

const randomColor = () => {
  return `#${Math.floor(Math.random()*16777215).toString(16).padEnd(6, 'E')}`;
};

Generate an Array of Objects from a specific count

const arr = Array(100).fill().map(() => ({ fu: 'bar' }));

Generate an Array of Numbers from a range

const genRange = (start, end) => Array(end - (start - 1)).fill(0).map((_, ndx) => start + ndx);

// usage
genRange(5, 8); // results in [5, 6, 7, 8]

Generate a String of a Duplicated Token with a Specific Length

const genString = (token='-', count=10) => Array(count).fill().reduce((str) => `${str}${token}`, '');

// usage
genString(); // results in '----------'
genString('=', 20); // results in '========================================'

Pre-Loading an Image

Markup

<!-- NOTE: a transparent image is being used as a placeholder, you could use a gif of a spinner -->
<img
  src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
  data-src="https://cdn.com/some/image1.jpg"
>
<img
  src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
  data-src="https://cdn.com/some/image2.jpg"
>

Utils

/**
 * Checks whether or not an image (that we're getting ready to load) has already
 * been loaded & cached.
 *
 * @param {String} imgPath - The path to the image.
 * @returns {Boolean}
 */
const checkIfImageCached = (imgPath) => {
  const img = document.createElement('img');
  img.src = imgPath;

  return img.complete;
};

/**
 * Allows for calling a callback once an image has been loaded into cache.
 *
 * @param {String} imgPath - The path to the image.
 * @param {Function} cb - A callback to be executed once the image has loaded.
 */
const loadImage = (imgPath, cb) => {
  const img = new Image();
  img.addEventListener('load', cb);
  img.src = imgPath;
};

App Code

const handleLoadedImage = (img) => {
  img.src = img.dataset.src;
};
const imgs = document.querySelectorAll('img[data-src]');

for(let i=0; i<imgs.length; i++){
  const img = imgs[i];
  const src = img.dataset.src;
  
  (checkIfImageCached(src))
    ? handleLoadedImage(img)
    : loadImage(src, handleLoadedImage.bind(null, img));
}

Lazy-Load an Image

This isn't JS, but since I've been using JS (IntersectionObserver) to do handle it for so many years, adding it as a reminder. If you need to have the image load immediately change the attribute to eager.

<!-- load only when in view -->
<img
  src="https://cdn.com/some/image1.jpg""
  loading="lazy"
/>

Sort an Object by keys

const obj = {
  fu: 'bar',
  bar: 'fu',
  arr: ['str'],
  obj: { fu: 'bar', bar: 'fu' },
};
const sortObjByKeys = (obj) => {
  return Object.keys(obj).sort().reduce((sorted, prop) => {
    const curr = obj[prop];
    sorted[prop] = (!Array.isArray(curr) && curr !== null && typeof curr === 'object')
      ? sortObjByKeys(curr)
      : curr;
    return sorted;
  }, {});
};

Sort an Object by key values

Imagine you have an Object that's sorted by it's keys, but you need an Object sorted by values for display purposes.

/**
 * Sorts an Object by it's key values.
 *
 * @param {Object} obj - An Object that needs sorting.
 * @return {Object}
 */
const sortByValues = (obj) => {
  const sorted = {};

  Object.keys(obj)
    .sort((a, b) => {
      const optA = obj[a].toLowerCase();
      const optB = obj[b].toLowerCase()
      if (optA < optB) return -1;
      if (optA > optB) return 1;
      return 0;
    })
    .forEach((key) => {
      sorted[key] = obj[key];
    });

  return sorted;
};

// small snippet that demonstrates order
const countryOpts = {
  ae: 'United Arab Emirates',
  at: 'Austria',
  au: 'Australia',
  be: 'Belgium',
  bg: 'Bulgaria',
  ca: 'Canada',
  ch: 'Switzerland',
};

console.log( sortByValues(countryOpts) );

Sort an Array by Object Props

const sortArrayByProp = (prop) => (a, b) => {
  const _a = `${a[prop]}`.toLowerCase();
  const _b = `${b[prop]}`.toLowerCase();
  const subCheck = (_b > _a) ? -1 : 0;
  return (_a > _b) ? 1 : subCheck;
};

const arr = [{name: 'Blah2'}, {name: 'Blah1'}];
arr.sort(sortArrayByProp('name'));

Sort an Array of Objects by Object Prop Values

const sortArrayByPropVal = (arr, orderArr) => {
  const _arr = [...arr];
  const sortedArr = [];

  orderArr.forEach(([ prop, value ]) => {
    const tempArr = [];

    for (let i=_arr.length - 1; i>=0; i--) {
      if (_arr[i][prop] === value) {
        tempArr.push(_arr[i]);
        _arr.splice(i, 1);
      }
    }
    sortedArr.push(...tempArr.reverse());
  });

  return [...sortedArr, ..._arr];
};

const arr = [
  { p: '1', prop: 'content' },
  { p: '2', prop: 'title' },
  { p: '3', prop: 'content' },
  { p: '4', prop: 'title' },
  { p: '5', prop: 'fu' },
];
console.log(sortArrayByPropVal(arr, [['prop', 'title'], ['prop', 'fu']]));

Filter instances from an Array

There are times (like when sharing WebPack configs) where you'll want to remove instance items from an Array. By instance items I mean items that were created with the new operator.

const plugins = origPlugins.filter((plugin) => {
  switch (plugin.constructor.name) {
    case 'ProgressPlugin': return false;
    default: return true;
  }
});

Convert non-safe characters into HTML entities for HTML attributes

const sanitizeStringForAttr = (str) => str
  .replace(/&/g, "&amp;")
  .replace(/>/g, "&gt;")
  .replace(/</g, "&lt;")
  .replace(/"/g, "&quot;");

Check if a node is a child of a specific node type

/**
 * Will loop over a child nodes parent's until it either hits the specified
 * root element, or it's determined that the node is a child of a specific
 * node type.
 *
 * @param {HTMLElement} childEl - The node that you're trying to see if it's contained within a specific node type
 * @param {HTMLElement} rootEl - The top-most element where the loop should stop iterating
 * @param {String} nodeName - The node name that the child should be within
 * @return {Boolean}
 */
function childOf(childEl, rootEl, nodeName) {
  let currEl = childEl;

  while (currEl && currEl !== rootEl) {
    if (currEl.nodeName.toLowerCase() === nodeName.toLowerCase()) return true;
    currEl = currEl.parentNode;
  }

  return false;
}
// usage
childOf(ev.target, ev.currentTarget, 'a');

Prompt a User to save a file

/**
 * Allows you to prompt a User to save a file after they've clicked on something.
 *
 * @param {String} data - The text that'll be written to the file.
 * @param {String} name - The name of the file.
 * @param {String} type - The file type. Use one of `saveFile.FILE_TYPE__*`
 * @example
 * saveFile({
 *   data: JSON.stringify(someObj, null, 2),
 *   name: 'backup.json',
 *   type: saveFile.FILE_TYPE__JSON,
 * });
 */
function saveFile({ data, name, type }) {
  if (!data || !name || !type) throw Error(`You're missing a required param: data: "${data}" | name: "${name}" | type: "${type}"`);
  
  const file = new Blob([data], { type });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(file);
  a.download = name;
  a.click();
  a.remove();
}
saveFile.FILE_TYPE__JSON = 'application/json';
saveFile.FILE_TYPE__TEXT = 'text/plain';

Prompt a User to load a file

/**
 * Prompts a User to pick a file from their filesystem. Once it's loaded, the
 * file is read, and returned via a Promise.
 *
 * @returns {Promise}
 * @example
 * loadFile().then((data) => { console.log(data); });
 */
function loadFile() {
  return new Promise((resolve) => {
    const fileInput = document.createElement('input');
    fileInput.type = 'file';
    
    fileInput.addEventListener('change', (ev) => {
      const importedFile = ev.target.files[0];
      const reader = new FileReader();
      
      reader.addEventListener('load', (readEv) => {
        const content = readEv.target.result;
        resolve(content);
      });
      reader.readAsText(importedFile);
    });
    
    fileInput.click();
  });
}

Get More Info About Unhandled Promise Rejection

There may be times when you get random UnhandledRejection errors. This usually stems from a Promise not having a catch, but in a large codebase this can be difficult to track down. Just add the below snippet to the root entry of your app, and it should give you a more verbose error and point to the file causing the error.

process.on('unhandledRejection', (err) => {
  console.error('unhandledRejection', error.stack);
});

Parse Query Params to an Object

function parseQueryParams(params) {
  const ret = {};

  params.replace(/^\?/, '').split('&').forEach((param) => {
    const data = param.split('=');
    ret[data[0]] = data[1];
  });

  return ret;
}

const params = parseQueryParams(window.location.search);

Comparing characters in a String

There may come a time where you're trying to compare two Strings that visually look the same, but you get a result that says they're not equal. The below examples show a couple troubleshooting options to display the characters in the String.

// Displays all newline and carriage return characters as '[n]' and '[r]'
console.log(`${string1.replace(/\n/g, '[n]').replace(/\r/g, '[r]')}\n${string2.replace(/\n/g, '[n]').replace(/\r/g, '[r]')}`);
// Displays character codes for each character in a String
const val1 = string1.split('').map((char, ndx) => `[${char.replace('\n', '\\n').replace('\r', '\\r')}: ${string1.charCodeAt(ndx)}]`).join('');
const val2 = string2.split('').map((char, ndx) => `[${char.replace('\n', '\\n').replace('\r', '\\r')}: ${string2.charCodeAt(ndx)}]`).join('');
console.log(`${val1}\n${val2}`);

Download image with custom name

In the below, the image has already loaded into the DOM. I load the image first so the User has something to look at since the downloading of the image may or may not happen.

<!--
  The 'download' attribute will only work with local images. There's info that says it'll also work if a Server returns a specific disposition header but I think they mean if your local server returns that header.
-->
<a href="<IMG_SRC>" download="custom.jpg">
  <!--
    It's important that crossOrigin is 'anonymous', otherwise Chrome disallows creating blob URLs.
  -->
  <img src="<IMG_SRC>" crossOrigin="anonymous" />
</a>
// Top of script
window.blobURLs = [];

// Down in the script where you're processing things.
const a = document.querySelector('<ANCHOR_QUERY>');
const img = document.querySelector('<IMG_QUERY>');
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth; // `natural*` the actual dimensions of the image, not what it's currently sized to.
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);

canvas.toBlob((blob) => {
  const blobURL = URL.createObjectURL(blob);
  window.blobURLs.push(blobURL);
  a.href = blobURL;
}, 'image/jpeg', 1);

// For performance reasons you need to clean up the URLs once you're done with them, like if you have a SPA and clicking around loads different views, you'll need to remove these first.
if (window.blobURLs.length) {
  while (window.blobURLs.length) {
    const blobURL = window.blobURLs[window.blobURLs.length - 1];
    URL.revokeObjectURL(blobURL);
    window.blobURLs.pop();
    console.log(`Revoked Blob URL: "${blobURL}"`);
  }
}

Copy text from an element

The below is pretty flexible since the User can specify to the copyHandler what should be copied. So if you want to copy text from a node you'd use textContent, or value if from an input/textarea. There's also some added code to aid in styling copy states.

const copyEl = document.querySelector('<SELECTOR>');
const copyStatusHandler = (el, state) => (err) => {
  el.classList.add(state);
  err && alert(err.stack);
  setTimeout(() => { el.classList.remove(state); }, 2000);
};
const copyHandler = (dataFn) => (ev) => {
  const el = ev.currentTarget;
  const type = 'text/plain';
  const blob = new Blob([dataFn()], { type });
  const data = [new ClipboardItem({ [type]: blob })];
  
  navigator.clipboard.write(data).then(
    copyStatusHandler(el, 'success'),
    copyStatusHandler(el, 'error')
  );
}

navigator.permissions.query({ name: 'clipboard-write' }).then(({ state }) => {
  if (state == 'granted' || state == 'prompt') {
    copyEl.addEventListener('click', copyHandler(() => copyEl.textContent));
  }
  else {
    alert('[WARN] Access to clipboard not enabled, disabling feature.');
  }
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment