Skip to content

Instantly share code, notes, and snippets.

@davidbgk

davidbgk/utils.js

Last active Feb 1, 2021
Embed
What would you like to do?
Let's start to reference some JS utils
// More resources: https://1loc.dev/ + https://htmldom.dev/ + https://thisthat.dev/ + https://vanillajstoolkit.com/
const qsa = (selector) => Array.from(document.querySelectorAll(selector))
// Another way inspired by fluorjs https://fluorjs.github.io/
function $(selector, root = document) {
if (selector instanceof Node) {
return [selector]
}
return root.querySelectorAll(selector)
}
function $$(selector, root = document) {
if (selector instanceof Node) {
return selector
}
return root.querySelector(selector)
}
// With exposure
$: (selector, root = rootNode) => $(selector, root)
$$: (selector, root = rootNode) => $$(selector, root)
const currentAnchor = () => document.location.hash ? document.location.hash.slice(1) : ''
async function copyToClipboard (codeElement, alert) => {
try {
await navigator.clipboard.writeText(codeElement.innerText)
alert.innerHTML = `<div class="code-block__alert">Code copied!</div>`
// Reset the alert element after 3 seconds,
// which should be enough time for folks to read
setTimeout(() => {
alert.innerHTML = ''
}, 3000);
} catch (ex) {
alert.innerHTML = ''
}
}
delete(event) {
if (window.confirm('Êtes-vous sûr·e de cette suppression ?')) {
return
} else {
event.preventDefault()
}
}
const ready = function (cb) {
document.readyState === 'loading'
// The document is still loading
? document.addEventListener('DOMContentLoaded', function (e) {
cb()
})
// The document is loaded completely
: cb()
}
ready(function() {
// Do something when the document is ready
...
})
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
if (prefersReducedMotion !== true) {
// do animation
}
// Extracted from https://framagit.org/drone/cardinal/-/blob/master/js/main.js
// Allows you to get the URL from a `fetch` error (non trivial…)
function request (url, options) {
return fetch(url, options)
.then(response => [response, response.json()])
.then(([response, data]) => {
if (response.ok) {
return data
} else {
const e = new ServerError(`${url} ${response.status} ${data}`)
e.status = response.status
e.url = url
e.data = data
throw e
}
})
.catch(error => {
if (error instanceof ServerError) throw error
const e = new Error(`${error.message} ${url}`)
e.url = url
throw e
})
}
function handleError (error) {
console.error(error)
const errorURL = new window.URL(error.url)
const userMessage = `
Le domaine ${errorURL.host} semble être inaccessible.
Nous en avons été informés, veuillez réessayer plus tard.
`
}
// For native fonction (not supported in IE but polyfill exists):
// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams
function toQueryParams (data) {
return Object.keys(data).map(k => `${k}=${data[k]}`).join('&')
}
function getBBox (map) {
const bounds = map.getBounds()
return {
west: bounds.getWest(),
south: bounds.getSouth(),
east: bounds.getEast(),
north: bounds.getNorth()
}
}
function utcIsoString (dateStr) {
return (new Date(new Date(dateStr || Date.now()).toUTCString())).toISOString()
}
function fromQueryParams () {
return new Map(document.location.search.slice(1).split('&').map(kv => kv.split('=')))
}
function mapToDict(map) {
const dict = {}
map.forEach((v, k) => { dict[k] = v })
return dict
}
// Usage:
const data = {
lat: lat,
lon: lng,
alt: 0
}
request(`${this.config.checkURL}?${toQueryParams(data)}`)
.then(response => { /* do something */})
.catch(handleError)
// Another request/response binding for fetch (from Hey!), splitted across many files.
// Kudos to them for sharing their source files from https://app.hey.com/sign_up/welcome
// lib/http/request.js
import { Response } from "./response"
import { getCookie } from "lib/cookie"
export class Request {
constructor(method, url, options = {}) {
this.method = method
this.url = url
this.options = options
}
async perform() {
const response = new Response(await fetch(this.url, this.fetchOptions))
if (response.unauthenticated && response.authenticationURL) {
return Promise.reject(window.location.href = response.authenticationURL)
} else {
return response
}
}
get fetchOptions() {
return {
method: this.method,
headers: this.headers,
body: this.body,
signal: this.signal,
credentials: "same-origin",
redirect: "follow"
}
}
get headers() {
return compact({
"X-Requested-With": "XMLHttpRequest",
"X-CSRF-Token": this.csrfToken,
"Content-Type": this.contentType,
"Accept": this.accept
})
}
get csrfToken() {
const csrfParam = document.head.querySelector("meta[name=csrf-param]")?.content
return csrfParam ? getCookie(csrfParam) : undefined
}
get contentType() {
if (this.options.contentType) {
return this.options.contentType
} else if (this.body == null || this.body instanceof FormData) {
return undefined
} else if (this.body instanceof File) {
return this.body.type
} else {
return "application/octet-stream"
}
}
get accept() {
switch (this.responseKind) {
case "html":
return "text/html, application/xhtml+xml"
case "json":
return "application/json"
default:
return "*/*"
}
}
get body() {
return this.options.body
}
get responseKind() {
return this.options.responseKind || "html"
}
get signal() {
return this.options.signal
}
}
function compact(object) {
const result = {}
for (const key in object) {
const value = object[key]
if (value !== undefined) {
result[key] = value
}
}
return result
}
// lib/http/response.js
class _Response {
constructor(response) {
this.response = response
}
get statusCode() {
return this.response.status
}
get ok() {
return this.response.ok
}
get unauthenticated() {
return this.statusCode == 401
}
get authenticationURL() {
return this.response.headers.get("WWW-Authenticate")
}
get contentType() {
const contentType = this.response.headers.get("Content-Type") || ""
return contentType.replace(/;.*$/, "")
}
get headers() {
return this.response.headers
}
get html() {
if (this.contentType.match(/^(application|text)\/(html|xhtml\+xml)$/)) {
return this.response.text()
} else {
return Promise.reject(`Expected an HTML response but got "${this.contentType}" instead`)
}
}
get json() {
if (this.contentType.match(/^application\/json/)) {
return this.response.json()
} else {
return Promise.reject(`Expected a JSON response but got "${this.contentType}" instead`)
}
}
get text() {
return this.response.text()
}
}
export { _Response as Response }
// helpers/request_helpers.js
import { Request } from "lib/http"
export async function request(method, url, options) {
const request = new Request(method, url, options)
const response = await request.perform()
if (!response.ok) throw new Error(response.statusCode)
return request.responseKind == "json"
? response.json
: response.text
}
[ "get", "post", "put", "delete" ].forEach(method => {
request[method] = (...args) => request(method, ...args)
})
request.getJSON = (url, options = {}) => request.get(url, { responseKind: "json", ...options })
// lib/cookie.js
export function getCookie(name) {
const cookies = document.cookie ? document.cookie.split("; ") : []
const prefix = `${encodeURIComponent(name)}=`
const cookie = cookies.find(cookie => cookie.startsWith(prefix))
if (cookie) {
const value = cookie.split("=").slice(1).join("=")
return value ? decodeURIComponent(value) : undefined
}
}
const twentyYears = 20 * 365 * 24 * 60 * 60 * 1000
export function setCookie(name, value) {
const body = [ name, value ].map(encodeURIComponent).join("=")
const expires = new Date(Date.now() + twentyYears).toUTCString()
const cookie = `${body}; path=/; expires=${expires}`
document.cookie = cookie
}
// Another implementation of `fetch` (this thing is truly half-baked) from
// https://framagit.org/drone/raccoon/-/blob/master/js/request.js
// Inspired by https://github.com/zellwk/zl-fetch
// See https://css-tricks.com/using-fetch/
function request (url, options = undefined) {
return fetch(url, optionsHandler(options))
.then(handleResponse)
.catch(error => {
const e = new Error(`${error.message} ${url}`)
Object.assign(e, error, {url})
throw e
})
}
function optionsHandler (options) {
const def = {
method: 'GET',
headers: {'Content-Type': 'application/json'}
}
if (!options) return def
let r = Object.assign({}, def, options)
// Deal with body, can be either a hash or a FormData,
// will generate a JSON string from it if in options.
delete r.body
if (options.body) {
// Allow to pass an empty hash too.
if (!(Object.getOwnPropertyNames(options.body).length === 0)) {
r.body = JSON.stringify(options.body)
} else if (options.body instanceof FormData) {
r.body = JSON.stringify(Array.from(options.body.entries()))
}
}
return r
}
const handlers = {
JSONResponseHandler (response) {
return response.json()
.then(json => {
if (response.ok) {
return json
} else {
return Promise.reject(Object.assign({}, json, {
status: response.status
}))
}
})
},
textResponseHandler (response) {
if (response.ok) {
return response.text()
} else {
return Promise.reject(Object.assign({}, {
status: response.status,
message: response.statusText
}))
}
}
}
function handleResponse (response) {
let contentType = response.headers.get('content-type')
if (contentType.includes('application/json')) {
return handlers.JSONResponseHandler(response)
} else if (contentType.includes('text/html')) {
return handlers.textResponseHandler(response)
} else {
throw new Error(`
Sorry, content-type '${contentType}' is not supported,
only 'application/json' and 'text/html' are.
`)
}
}
/* To build some kind of a plugins system:
https://github.com/simonw/datasette/issues/9
See also https://simonwillison.net/2021/Jan/3/weeknotes/
*/
var datasette = datasette || {};
datasette.plugins = (() => {
var registry = {};
return {
register: (hook, fn) => {
registry[hook] = registry[hook] || [];
registry[hook].push(fn);
},
call: (hook, args) => {
var results = (registry[hook] || []).map(fn => fn(args||{}));
return results;
}
};
})();
// And the register/call like this:
datasette.plugins.register('numbers', ({a, b}) => a + b)
datasette.plugins.register('numbers', o => o.a * o.b)
datasette.plugins.call('numbers', {a: 4, b: 6})
@davidbgk

This comment has been minimized.

Copy link
Owner Author

@davidbgk davidbgk commented Oct 1, 2020

An approach that might be useful to avoid polluting builtins: https://github.com/DavidBruant/tiretbas-natives

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment