Skip to content

Instantly share code, notes, and snippets.

@coderofsalvation
Last active June 4, 2024 10:00
Show Gist options
  • Save coderofsalvation/5605dbf346378ceaed4047fd342c1003 to your computer and use it in GitHub Desktop.
Save coderofsalvation/5605dbf346378ceaed4047fd342c1003 to your computer and use it in GitHub Desktop.
My favorite Javascript patterns

Useful JS Patterns/Functions

NOTE: for 3D JS patterns see my 3D patterns-page

Besides all the classical designpatterns in js, decorating lodash with new mixins, or a simple underscore object can also be useful.

reactive components #vanillajs #clean #noreact #novue #nosvelte #noframeworks #justproxies #eventdelegation

vanilla DOM components reactified using Proxies, see codepen here

// <div id="app">
//   <h1 id="title"></h1>
//   <button id="toggle" onclick="$app.toggle()">toggle</button>
//   <ul id="menu"></ul>
// </div>

myMenu = el => new Proxy({
  css:    `<style>#menu{border:1px solid red;}</style>`,
  render(data){ 
    el.innerHTML = this.css +
      `<ul>
           ${ data.show ? `<li>${data.buttons.join('</li><li>')}</li>` : '' }
       </ul>`
  }
},{})

myApp = el => new Proxy({
  buttons:     [1,2],
  show:        true,
  title:       'foo',
  $title:      el.querySelector('#title'),
  $menu:       myMenu( el.querySelector('#menu') ),
  
  toggle()   { this.show != this.show     },
  click(id,e){ console.log(id+" clicked") },
  init(){
    // delegate events to functions and dispatch ready event
    (['click']).map( (e) => el.addEventListener(e, (ev) => this[e] && this[e](ev.target.id,ev) ) )
    document.dispatchEvent( new CustomEvent("myapp:ready", {detail:this} ) )
    return this
  },
}, {
  get(me,k,v){ return me[k] },
  set(me,k,v){ 
    me[k] = v
    let {$title,$menu} = me    
    switch( k ){
      case "title":    $title.innerText = me.title;  break;
      case "buttons":  $menu.render(me);             break;      
    }
  }
})

// DEMO USAGE
$app = myApp( document.querySelector('#app') ).init() // reactify DOM element

setInterval( () => { // update to see autorender!
  $app.buttons = $app.buttons.concat([ Math.random(), Math.random()]).slice(-4)
  $app.title   = Math.random()  
}, 50)

readable hierarchical logs

console.log("doFoo()")
console.log(" └☑ doBar()")
console.log("   ├☑ doBar()")
console.log("   └☒ doBaz(): no key provided")

square wave

square = (x,freq) => ( x & freq )

clamp steps

function clampSteps(steps,max,x){
  let stepSize = max/steps
  return Math.round( x/stepSize ) * stepSize )
}
clampSteps(8,360,40) // 45

Smoothing values

Sometimes sensors are too noisy (accelerometer/gyroscope), therefore smoothing comes in handy:

function smooth(coef){
  this.x = 0
  return (x) => (this.x = this.x * coef + x * (1.0-coef))
}

var s = new smooth(0.9)
s(0.1) // 0.009 
s(0.8) // 0.088
s(0.8) // 0.160

bruteforce text-to-url hyphenate

hyphenate = (str) => str.replace(/[^a-zA-Z0-9]/g,'-')

function cache

let once = ((c) => (str) => c.called++ ? false : console.log(str) )({called:0})

once("hello") // output: hello
once("hello") // output:
once("hello") // output:

function state

var update = ((s) => (b) => s = {...s,...b} )({a:1})       // s = state
update({foo:1})    // output: {a:1,foo:1}
update({bar:1})    // output: {a:1,foo:1,bar:1}
update({foo:2})    // output: {a:1,foo:2,bar:1}

for mini-store, statemanager e.g.

function-call chomping

var receive = ((args) => (a) => {
  if( a ) args = {...args,...a}
  if( !args.a || !args.b || !args.c ) return
  console.dir(args)
})({})

receive({a:1}) // output:
receive({c:1}) // output:
receive({b:1}) // output: console.dir({a:1,b:1,c:1})

function batch

var batch = ((q) => (a) => {
  q.push(a)
  if( q.length < 3 && a ) return
  console.dir(q)
  q=[]
})([])

batch({a:1}) // output:
batch({c:1}) // output:
batch({b:1}) // output: {a:1,b:1,c:1} 
batch({d:1}) // output:
// last call to flush
batch()      // output: {d:1}

oneliner browser apps

copy/paste this into your urlbar, press enter, and bookmark it:

data:text/html, <html contenteditable><script>alert("hi");</script><style>*{ font-family: Helvetica; }</style></html>

human readable random name generator (browser)

   randomName = () => {
      var names = []
      let add = (s) => s.length < 6 && !s.match(/[0-9$]/) && !s.match(/_/) ? names.push(s) : false
      for ( var i in window             ) add(i)
      for ( var i in Object.prototype   ) add(i)
      for ( var i in Function.prototype ) add(i)
      for ( var i in Array.prototype    ) add(i)
      for ( var i in String.prototype   ) add(i) 
      var a = names[Math.floor(Math.random() * names.length)];
      var b = names[Math.floor(Math.random() * names.length)];
      var c = names[Math.floor(Math.random() * names.length)];
      return String(`${a}-${b}-${c}`).toLowerCase()
    }

html to js trickle-down data

<div id="foo" data-js="pos:[1,2,3]"></div>
<script>
var hopts = document.querySelector("#foo").getAttribute("data-js")
var  opts = (new Function(`return {${hopts}}`))()
console.log(opts)
</script>

result: {pos:[1,2,3]}

Daisychaining

function Car(opts){
	this.opts = opts

	this.init = function (opts) {
		this.opts = Object.assign( this.opts, opts )
		return this;
	}
	this.dump = function (){
		console.dir( this.opts )
	}
	return this
}

// usage:
var mycar = new Car({name:"Harry"})                  
mycar
.init({brand:"BMW"})
.init({foo:{bar:1}})
.dump()

Useful when you can't initialize an object in one run (think async calls inbetween)

Another method is using a mixin:

_.mixin({
	chainify: function(scope,fn){
	    var f = scope[fn]
	    scope[fn] = function(){
	       f.apply(arguments)
	       return scope
	    }
	}
})

chainify(mycar,'dump') // dump().init() is now possible

It also allows promise-chaining:

somePromise()
.then( (mycar) => mycar.init({brand:"mercedes"}) )
.then( (mycar) => mycar.dump() )
.then( (mycar) => console.dir )

get IP

  fetch('https://api.duckduckgo.com/?q=my+ip&format=json')
        .then( (res) => res.json() )
        .then( (res) => {
            const ipRegex = /Your IP address is ([0-9]+[.][0-9]+[.][0-9]+[.][0-9]+)/g;
            const ip = ipRegex.exec(res.Answer)[1] 
            this.link = this.link.replace(/localhost/, ip )
            resolve()
        })

Async middleware

function middleware(){
    var me = this
    this.middleware = [];
    this.use = (cb) => this.middleware.push(cb)
    this.process = function(input,cb){
      let i=0;  
      var next = function(){
        return (me.middleware[i+=1] != null) ? me.middleware[i](input,next) : cb(input,i)
      }
      return me.middleware[i](input,next)
    }.bind(this)
    return this
}

// USAGE 
var m = new middleware()
m.use( function(data,next){ console.log("1"); next() })
m.use( function(data,next){ console.log("2"); next() })
m.process({foo:1}, () => console.log("done") ) // OUTPUTS: 1,2,done

This allows dynamically adding functions to a ASYNC pipeline of functions

Pipe

Analogous to compose, promises & middleware there is one more familiar method attributed to unix is pipe which takes an array of functions to produce a new composed function.

const pipe = function(fns) {
 return function(item) {
  return fns.reduce(function(prev, fn) {
   return fn(prev);
  }, item);
 }
}
function add1(a) {
 return a + 1;
}
function multiply5(a) {
 return a * 5;
}
var add1multiply5 = pipe([add1, multiply5]);
console.log(add1multiply5(6)); //35

Clone

clone: function(obj,copy_refs){
    return copy_refs ? Object.assign({},obj) : JSON.parse( JSON.stringify(obj) )
}

Also see _.clone in lodash

Usage: var obj2 = _.clone(obj)

Throttle / Debounce

function throttle(f,delay,opts){
    opts = opts || {}
    return function(){
       if( !f.tid && opts.leading && (f.tid = 1) ) return f.apply(null,arguments) 
       clearTimeout(f.tid)
       f.tid = setTimeout( () => f.apply(null,arguments), delay )
    }
}

Usage: var h = throttle( (a,b) => console.log(a+b), 1000 ); h(1,2); h(1,2) // output: 3 ...(1 sec later)... 3

DOM essentials

window.$    = document.querySelector.bind(document)
$.all    = (s) => Array.prototype.slice.call( document.querySelectorAll(s) )
$.c      = function(e,v,n,c,r){r=e[c='className'].replace(eval('/ *\\b'+n+'\\b/g'),'');return'has'==v?r!=e[c]:e[c]={add:1,toggle:r==e[c]}[v]?r+' '+n:r},

// optional: shortcut to create tags
$.create = (opts /*{tag:'b',styles:{},events:{},attributeName:value*/) => {
    var t = document.createElement(opts.tag)
    if( opts.events ) for( var i in opts.events ) t.addEventListener(i,events[i])
    if( opts.styles ) for( var i in opts.styles ) t.style[i] = opts.styles[i]
    Object.keys(opts)
    .map( (i) => {
        if( !i.match(/(styles|events)/) ) t.setAttribute(i,opts[i])
    })
    return t
}

usage: $.all('#drinks').map( $.toggle('myclass´) )

see bless()

Get / Set / Pluck / Omit (nested) variable

These are the lodash classics:

var _ = {

    get: function get(xs,x,fallback) {
        return String(x).split('.').reduce(function(acc, x) {
            if (acc == null || acc == undefined ) return fallback;
            return new Function("x","acc","return acc['" + x.split(".").join("']['") +"']" )(x, acc) || fallback
        }, xs)
    },
    set: function set(obj, path, value) {
	  var last
	  var o = obj
	  path = String(path)
	  var vars = path.split(".")
	  var lastVar = vars[vars.length - 1]
	  vars.map(function(v) {
	      if (lastVar == v) return
	      o = (new Function("o","return o." + v)(o) || new Function("o","return o."+v+" = {}")(o))
	      last = v
	  })
	  new Function("o","v","o." + lastVar + " = v")(o, value)
    },    
    pluck: function pluck(arr,obj){
        var o = {}
        arr.map( (l) => o[l] = _.get(obj,l) )
        return o
    },
    omit: function omit(arr,obj) {
        var o = JSON.parse(JSON.stringify(obj))
        arr.map((l) => delete o[l])
        return o;
    }
}

Usage: bar = _.get({foo:{bar:1}},'foo.bar',0) // returns 1 if exist, otherwise 0

Usage: bar = _.pluck(['a','b'],{a:1,b:2,c:3}) // returns {a:1,b:2}

Usage: bar = _.omit( ['a','b'],{a:1,b:2,c:3}) // returns {c:3}

This saves lots of if/else & boilerplate code, as well as 'variable x is undefined'-crashes

See bless()

unsafe tiny get: var get = (o,k,def) => (new Function('o','return o.'+k))(o) || def || undefined

Functorize

Hasslefree 'smart' arrays and objects, without ES6 classes or ES5 prototype risks. Less boilerplate, more functional.

/*
 * Extends & wraps prototype-functions (by cloning/wrapping them) 
 *
 */
  
  function functorize(data,prototype){
    data           = data || {}
    var prot       = {}
    var prot_old   = data.__proto__
    for( var i in prot_old  ) prot[i] = prot_old[i]    // copy original prototype
    for( var i in prototype ) prot[i] = function(f){   // copy custom prototype
      var res
      var args = Array.prototype.slice.apply(arguments).slice(1)
      if( prot_old[i] ) args.unshift(prot_old[i].bind(data) )
      return f.apply(data,args)
    }.bind(data,prototype[i])
    data.__proto__ = prot
    return data
  }

Example:

  var arr = []
  var myModel = {
     toggle: () => console.log("toggle!")
  }
   
  functorize( arr, {
    push: function(old,item){
      old( functorize(item, myModel) )
      return this
    }
  })

  arr.push({foo:123})
     .push({foo:143})
  arr[0].toggle()
  

NOTE: see https://www.npmjs.com/package/functorize or even better bless()

Set nested variable

_.set = function(obj, path, value) {
  var last
  var o = obj
  path = String(path)
  var vars = path.split(".")
  var lastVar = vars[vars.length - 1]
  vars.map(function(v) {
      if (lastVar == v) return
      o = (new Function("o","return o." + v)(o) || new Function("o","return o."+v+" = {}")(o))
      last = v
  })
  new Function("o","v","o." + lastVar + " = v")(o, value)
  $.pub(path, value)
  return $
}

Usage: _.set(foo,'some.nested.path',123)

This will prevent you reading this (less readable) code:

if( !foo.some ) foo.some = {}
if( !foo.some.nested ) foo.some.nested = {}
if( !foo.some.nested.path ) foo.some.nested.path = {}
foo.some.nested.path = 123

see bless()

Async test framework

function test(opts){
      this.node   = typeof process != undefined && typeof process != "undefined"
      this.tests  = this.tests || []
      this.errors = 0
      this.error  = (msg) => { this.errors += 1; console.error("> error: "+msg) }
      this.add    = (description, cb) => this.tests.push({ description, cb })
      this.done   = (ready) => { console.log("\n> tests : "+this.tests.length+"\n> errors: "+this.errors); if( this.node ) process.exit( this.errors == 0 ? 0 : 1); ready(this) }
      this.run    = (ready) => {
          var p = Promise.resolve()
          var runTest = (i) => {
              return new Promise( (resolve, reject) => {
                  var test = this.tests[i]
                  if( !test ) return this.done(ready)
                  var printf  = this.node ? process.stdout.write.bind(process.stdout) : console.log
                  if( this.node ) printf("[ ] "+test.description+"\r")
                  var onError = (err) => { this.error(err); this.done(ready) }
                  var _next   = () => { printf("[✓] "+test.description+"\n"); p.then(runTest(i+1)) }
                  try { test.cb(_next, onError ) } catch (e) { onError(e) }
              })
          }
          p.then( runTest(0) )
      }
      return this
  }
  
  var t = new test()
  
  t.add("testing _.flow",  function(next, error){
    // error("something went wrong")
    next()
  })

  t.run( alert )

Spy function

function spy (fn) {
  if (!fn) fn = function() {};

  function proxy() {
    var args = Array.prototype.slice.call(arguments);
    proxy.calls.push(args);
    proxy.called = true;
    fn.apply(this, args);
  }

  proxy.prototype = fn.prototype;
  proxy.calls = [];
  proxy.called = false;

  return proxy;
}

// usage
console.log = spy(console.log)
console.log("foo")
console.log( console.log.called ) // true

This one is very handy in tests

Async map

map() is great, but its synchronous for specific reasons (which makes it hard to mix with async functions). Here's a async friendly map:

var map = async (arr,cb,onerror) => {
  var res = []
  for( var i = 0; arr[i] != undefined; i++ ){
    try{ res.push( await cb(arr[i],i) ) }catch(e){ onerror(e,i,res) }
  }
  return res
}

Example:

var arr = [{id:1},{id:2}]

await map( arr, async (v,k) => {
  v.id *= 10
  await somethingAsync()
  return v
})

NOTE: below is for ES3/ES4/ES5-only, otherwise use: promise-each

  function amap(arr, cb, done) {
    if( !arr || arr.length == 0 ) done() 
    var f, funcs, i, k, v;
    funcs = [];
    i = 0;
    for (k in arr) {
      v = arr[k];
      f = function(i, v) {
        return function() {
          var e, error;
          try {
            if (funcs[i + 1] != null) return cb(v, i, funcs[i + 1]);
            else return cb(v, i, done);
          } catch (error) {
            e = error;
            return done(new Error(e));
          }
        }
      }
      funcs.push(f(i++, v))
    }
    return funcs[0]()
  }

Usage: amap( myarr, function(data,nextt){ next() }, function(){ alert("done!") })

better use async/await if possible

Monkeypatching

Plug/unplug code without altering its sourcecode. This has pros and cons, but for me usually more pros (extend opensource libraries with features without forking it).

function monkeypatch(obj, method, handler, context) {
        var original = obj[method];

        // Unpatch first if already patched.
        if (original.unpatch) {
                original = original.unpatch();
        }

        // Patch the function.
        obj[method] = function() {
                var ctx  = context || this;
                var args = [].slice.call(arguments);
                args.unshift(original.bind(ctx));
                return handler.apply(ctx, args);
        };

        // Provide "unpatch" function.
        obj[method].unpatch = function() {
                obj[method] = original;
                return original;
        };

        // Return the original.
        return original;
}

Or with lodash:

_.wrap( foo.myfunction, function(original){
  var args = Array.prototype.slice.call(arguments,[1])
  original(arg) // do stuff before or after calling the original
})

Monkeypatch class method

Bye bye pullrequests, welcome monkeypatched methods:

class mySomeClass {
    monkeyPatchedFunction() { // functionname+args should match
       ... // your implementation
    }
};

var engine    = new SomeClass()
this.engine   = new Proxy(mySomeClass, () => {
  get: (target, property) => (
    target[property] || engine[property]
  )
})

Tasklist: Fire series of async functions

var TaskList = function(){
  var arr = []

  arr.push = function(push,a){
    delete this.copy
    push.call(this,a)
  }.bind(arr, Array.prototype.push)

  arr.run = function(opts){
    if( this.copy && this.copy.length == 0 ) return
    opts = opts || {}
    if( !this.copy ) this.copy = this.slice(0).reverse()
    let cb = this.copy.pop()
    try{
      (async () => await cb())()
    }catch(e){
      if( opts.halt_error ) throw e
    }
    this.run(opts)
  }.bind(arr)

  return arr
}

// USAGE
var T = new TaskList()
T.push( async () => console.log("1") )
T.push( async () => console.log("2") )
T.run()
// 1
// 2

Lazy browser loading

Slow pageload due to a mess of remote script/link-tags? Sometimes a tablespoon of async promises can do magic:

window.include = function(url,type){
	return new Promise(function(resolve, reject) {
		type = type == 'js' ? 'js' : 'css'
		var tag = document.createElement( type == 'js' ? 'script' : 'link' );
		if( type == 'css' ) tag.rel = "stylesheet"
		tag[ type == 'js' ? 'src' : 'href' ] = url;
		tag.addEventListener('load', function() {
			resolve(tag);
		}, false);
		tag.addEventListener('error', function() {
			reject(tag);
			console.log('require('+url+') failed')
		}, false);
		document.body.appendChild(tag);
	})
} 

// optional: you probably dont want to do this
window.evalScriptTags = function(selector){
	return new Promise( (resolve, reject) => {
		var scripts = window.document.querySelectorAll('.container script') || []
		scripts = Array.prototype.slice.call(scripts)
		var promises = []
		scripts.map( (script) => {
			if( script.src ) promises.push( window.gflo.require(script.src) )
			if( script.innerText ) new Function(script.innerText)()
		})
		if( promises.length ) Promise.all(promises).then(resolve).catch(reject)
		else resolve()
	})
}

Flatten nested object

Great for pushing random data into database-tables or spreadsheets

function flatten(arr,separator) {
	separator = separator || '.'
	function dive(currentKey, into, target) {
		for (var i in into) {
			if (into.hasOwnProperty(i)) {
				var newKey = i;
				var newVal = into[i];
				
				if (currentKey.length > 0) {
					newKey = currentKey + separator + i;
				}
				
				if (typeof newVal === "object") {
					console.dir(newVal)
					dive(newKey, newVal, target);
				} else {
					target[newKey] = newVal;
				}
			}
		}
	}
	var newObj = {};
    dive("", arr, newObj);
    return newObj;
}

console.dir( flatten({foo:1,bar:{a:1}}, '_') )
// outputs {foo:1,bar_a:1}

deepExtend

// from: https://gist.github.com/anvk/cf5630fab5cde626d42a
var deepExtend = function(out) {
  out = out || {};
  for (var i = 1, len = arguments.length; i < len; ++i) {
    var obj = arguments[i];
    if (!obj) continue;
    for (var key in obj) {
      if (!obj.hasOwnProperty(key)) {
        continue;
      }
      // based on https://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
      if (Object.prototype.toString.call(obj[key]) === '[object Object]') {
        out[key] = deepExtend(out[key], obj[key]);
        continue;
      }
      out[key] = obj[key];
    }
  }
  return out;
};

Example:

var o = {f:1,foo:{a:3}}
deepExtend(o,{foo:{bar:[1,2]}})
// {f:1,foo:{a:1,bar:[1,2]})

Simple pubsub bus in the browser

/*
 * tiny pubsub                 
 *
 * usage:   var foo = {}; 
 *          new pubsub(foo);   
 *          foo.on('f', console.log); 
 *          foo.trigger('f', 1); 
 *          foo.off('f', console.log)
 */ 
function pubsub($){            
    var e = $.e = {};          
    $.trigger = function(window, name, data){ 
        //window.setTimeout( function(){ console.log("$.trigger('"+name+"',...)") },5)
        (e[name] = e[name] || new window.Event(name)).data = data;
        window.dispatchEvent(e[name]);  
    }.bind(window, window)
    
    $.on = function(name, handler, context) {
        if( window.navigator ) console.log("$.on('" + name + "')")
        var ehandler = function(e) {    
            handler(e.data)    
        }
        window.addEventListener(name, ehandler.bind(context), $.passive);
    }
  
    $.off = function(name, handler, context) {
        removeEventListener(name, handler.bind(context));
    }
}	

.on() : reactivity / eventbus

/* _.on( fn, fnew )
 *
* event handling / function wrapping / reactivity
*
* **Example:**
* ```
* var a = _.object({foo:1})
* this.foo = (k,v) => a[k] = v
*
* var undo = a.on('foo', (foo,k,v) => 
*   alert(v) 
*   //foo()
* })
* a.foo('b','c') // alert 'c'    
* undo()
* a.foo('b','c') // a.b = c
* ```
*/
window._ = {}
_.on   = function( fn, fnew ){
	var me   = this
	var orig = this[fn].bind(this)
        /* async version:
         * this[fn] = async (a,b,c,d,e,f,g) => {
         *   var args = [orig,a,b,c,d,e,f,g]
         *   return fnew.constructor.name === "AsyncFunction" ? await fnew.apply( me, args )
         *                                                    : fnew.apply( me, args )
         * } 
	 */
	this[fn] = function(){
		var args = [orig].concat( Array.prototype.slice.call(arguments) )
		return fnew.apply( null, args )
	}
	return function(){ me[fn] = orig }
}

rule() : a micro async rule engine

Based on the _.on-function (above):

_.rule = function rule(o,opts){
    if( !o.on ){
      o.conditions = ( ) => o.triggers().filter( (f) => f.match(/^is_/) )
      o.actions    = ( ) => o.triggers().filter( (f) => f.match(/^do_/) )
      o.on         = _.on.bind(o) // use the async version 
      o.triggers = function(){
        var t = [];
        for( var i in this ) if( typeof this[i] == 'function' ) t.push(i)
        return t;
      }
    }

    o.on( opts.at, async (at,a,b,c,d,e,f) => {
      let out,pout;
      if( at.constructor.name === "AsyncFunction" ) out = await at(a,b,c,d,e,f)
      else out = at(a,b,c,d,e,f)
      if( opts.if == undefined || opts.if.length == 0 || o[ opts.if[0] ].apply( o, opts.if.slice(1) ) ){
        if( o.log ) o.log(opts.title)
        let pipeout = o[ opts.then[0] ].apply( o, opts.then.slice(1).concat([out]) )
        return opts.pipe ? pipeout : out
      }
    })
}
var x = {a:1}
x.foo  = function(f){ return "foo!" }                                                                                                                                                               

// add composable conditions & actions
x.is_date  = async (date) => true // fictional date check
x.do_thing = async (a,b)  => { x.a = a; x.b = b; return a; }
// optional: add schemas for gui composability
x.is_date.schema  = [{type:"string",format:"date",title:"Date"}]
x.do_thing.schema = [{type:"string",title:"Info"}]
// add json rule
let myrule = {
  title:   'at specific date do_thing',
  at:      'foo',
  if:      ['is_date','2017-21-01'],  
  then:    ['do_thing','thing happened!']	
}
_.rule( x, myrule )
let out = await x.foo()                                                                                                                                                                                   

// tests
if( x.triggers().length   != 7 ) return error("not enough triggers")
if( x.conditions().length != 1 ) return error("not enough conditions")
if( x.actions().length    != 1 ) return error("not enough actions")
if( out != "foo!"              ) return error("foo bad output: "+out)
if( x.a != "thing happened!" || x.b != "foo!" ) return error("bad output")                                                                                  

synchronous npm require in the browser

 var require
  require = function(package){
        if( !require.cache ) require.cache = {}
        if( require.cache[package] ) return require.cache[package]
  
        var url = package.substr(0,2) == "./" ? package.substr(2) : `https://unpkg.com/${package}`
        console.log("require('"+url+"')")                                               
        
        var requireFile = (path) => {                                                          
            request = new XMLHttpRequest();                                                    
            // `false` makes the request synchronous                                           
            request.open('GET', path, false);                                                  
            request.send();                                                                    
            if (request.status === 200) {                                                      
                //newline keeps non-falsy result                                               
                return (request.responseText || request.response) + '\n';                      
            }else throw request                                                                
        }                                                                                      
        var module = {exports:{}}
        var code = requireFile(url)
        new Function("module","exports",code)(module,module.exports)
        require.cache[package] = module.exports
        return module.exports
  }  
  window.require = require 

Usage: require('three')

Vector math

mmultiply  = (a, b) => a.map(x => transpose(b).map(y => dotproduct(x, y)));
dotproduct = (a, b) => a.map((x, i) => a[i] * b[i]).reduce((m, n) => m + n);
dot        = (a, b) => a.map((x, i) => (a[i] || 1) * (b[i] || 1) )
transpose  = a      => a[0].map((x, i) => a.map(y => y[i]));

Example:

a = [1,1,0.5]
b = [1,1,2]

dotproduct(a,b) // 3
dot(a,b)        // [1,1,1]

a = [[1,2,3],[4,5,6]]
b = [[7,8],[9,10],[11,12]]

c = [[1,2,3],[4,5,6],[7,8,9]]
d = [[10,11,12],[13,14,15],[16,17,18]]

JSON.stringify(mmultiply(a,b)) // "[[58,64],[139,154]]"
JSON.stringify(mmultiply(c,d)) // "[[84,90,96],[201,216,231],[318,342,366]]"

mergeDeep

function mergeDeep(current, updates) {
  for (key of Object.keys(updates)) {
    if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
    else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key]) 
    else mergeDeep(current[key], updates[key]);
  }
  return current;
};

usage:

console.log(mergeDeep({ a: { a: [1] } }, { a: { a: [2] } }))
// {a:{a:[1,2]}}

Associative array to ascii table

function table(rows){
  let size    
	let cols  = Object.keys(rows[0])   // get cols
  let colw  = {}
  cols.map( (c) => colw[c] = c.length )     
  rows.map( (r) => {                 // get cols max width
    cols.map( (c) => {
    	if( (size = String(r[c]).length) > colw[c] ) colw[c] = size
    })
  })
  const print_row = (row) => {
    let res = ''
    for( var i in row ){
      let chars = String(row[i]).split('')      
      let str = new Array( colw[i]+2 ).join(' ').split('')
      for( j in chars ) str[j] = chars[j] 
      res += str.join("")
    }
    return res
  }
  let header = [{},{}]
  for( var i in rows[0] ) header[0][i] = i, header[1][i] = '---'
  let str = header.concat(rows).map( print_row )
  return str.join("\n")
}

var data = [{foo:"asdlkfjasldkfj",bar:"8"},{foo:1,bar:"asdfsff"}]
console.log( table(data) )
// output:
//
// foo            bar     
// ---            ---     
// asdlkfjasldkfj 8       
// 1              asdfsff 

Simple template literals for es5

function template(a,b){ // es6 style string evaluator
  var regex = /\${([^}]*)}/g
  var bb = b ? JSON.parse( JSON.stringify(b) ) : {}
  for( var i in bb ) bb[ i.replace(/ /g,'_') ] = bb[i]
  var aa = a.replace(regex, function(x){
    return x.replace(/ /g,'_')   
  })
  return function(c,d){return String(aa).replace(regex,function(a,e){ return Function("x","try{ with(x)return "+e+" }catch(f){ return ''}").call(c,d||bb||{})})}
}

Usage:

this.foo = 'bar'
var text = "hello ${foo} {$n}"
var mytemplate = template(text,this)
console.log(mytemplate({n:1))        // hello bar 1

concat every X

Seam to array's into one

	concatEvery: (a, b, freq ) => {
		var x = []
		var i = 0
		var j = 0
		a.map( (k) => {
			x.push(k)
			if( (i % freq) == 0 && b.length > j ) x.push( b[j++] )
			i+=1
		})
		for( ; j < b.length; j++ ) x.push(b[j])
		return x
	}

State-manager / (nested) Observables using javascript proxy

// simple State-manager / Observables using ES proxy
// 
// Usage:
//    var x = {
//      model: {name:"i am a leaf"},
//      foo: (a) => console.log("foo("+a+")")
//    }
//    var p = observe(x)//, (property, value) => console.info(property, value));
//    p.on('name', (v) => console.log('name changed: '+v) )
//    p.on('foo', (v) => console.log("foo called with: "+v) )
//    p.foo(123)
//    p.model.name = 'Tesla';
//    console.log(p.model.name)
//
// OUTPUT:
//    foo called with: 123
//    foo(123)
//    name changed: Tesla
//    Tesla

export function observe(o, callback) {
  let cbs = {}    
  function buildProxy(prefix, o) {
    let p
    p = new Proxy(o, {
      set(target, property, value) {
        // same as above, but add prefix
        if( callback && !property.match(/(on|cbs)$/) ) callback(prefix + property, value);
        target[property] = value;
        for( let i in (cbs[property]||{}) ) cbs[property][i](value)  
        return true
      },
      get(target, property) {      
        // return a new proxy if possible, add to prefix
        const out = target[property];
        if( String(property).match(/(on|cbs|prototype)$/) ) return out
        if (out instanceof Object) return buildProxy(prefix + property + '.', out);
        if( typeof out == "function" )
          for( let i in (cbs[property]||{}) ) cbs[property][i](value)  
        return out;  // primitive, ignore
      },
      apply(target, thisArg, argumentsList) {
        for( let i in (cbs[target.name]||{}) ) 
          cbs[target.name][i].apply(thisArg,argumentsList) 
	return target.apply(thisArg, argumentsList )
      }
    })

    p.on = (property,cb) => {
      cbs[property] = cbs[property] || []
      cbs[property].push(cb)   
    }
    return p
  }

  return buildProxy('', o);
}

Async (DOT) execution graph

var graph = (g)   => {
  var execute   = (g,node,links) => async (i) => {
    g.output = []
    await node(i) 
    for( let y in links ){ 
      let x    = Object.assign({},i)  
      await links[y]( x )
      g.output[y] = x
    } 
    return i;
  }
  g.only = Object.assign({},g)
  g.wires.split(';').map( (nodes) => {
    nodes = nodes.split("->").map( (n) => n.trim() )
    for( let i = nodes.length-1; i >= 1; i-- ){
      var links           = g[nodes[i-1]].links || []
      links[nodes[i]] = g.only[nodes[i]]
      g[nodes[i-1]]       = execute( g, g.only[nodes[i-1]], links)
      g[nodes[i-1]].links = links
    }
  })
  return g
} 
var wait = () => new Promise( (r,rt) => setTimeout(r,1000))

var g = graph({
  a: (i) => i.k+='a',
  b: (i) => i.k+='b',
  c: (i) => { i.k+='c'; console.log(i.k) },
  d: async (i) => {
    await wait()
    i.f = 'abc'
    i.k+='d'
    console.log(i.k)
  },
  wires: `
    a -> b -> c;
         b -> d;
  `
})

await g.b({k:'@'})         // @b
console.dir(g.output)      // [ 'c': {k:'@bc'}, 'd': {k:'@bd',f:'abc'} ]

await g.only.b({k:'@'})    // @b  (linked nodes are not executed)

textarea as javascript sandbox

function sandboxify() {    
  let run = (t) => {
    let $res = t.parentElement.children[1]
    function log(res){
      $res.innerHTML = typeof res == 'string' ? res : JSON.stringify(res)
    }
    let _log = console.log
    console.log = log
    try{
      eval(t.value);    
    }catch(e){ log(e) }finally{ console.log = _log }
  }    
  const textareas = document.querySelectorAll("textarea.sandboxify");
  textareas.forEach(t => {
    t.addEventListener("input", function (event) {
      try {
        run(t)
      } catch (error) {
        console.error(error);
      }
    });
    run(t)
  });    
}

// call sandboxify once on page load
sandboxify();

// set up a MutationObserver to call sandboxify on any new textareas added to the DOM
const observer = new MutationObserver(mutations => {
  mutations.forEach(mutation => {
    if (mutation.addedNodes && mutation.addedNodes.length > 0) {
      const addedNodes = mutation.addedNodes;
      addedNodes.forEach(addedNode => {
        if (addedNode.tagName === "TEXTAREA" && addedNode.classList.contains("sandboxify")) {
          sandboxify();
        }
      });
    }
  });
});

observer.observe(document, { childList: true, subtree: true });

popup window

win = window.open("", "window title", [
  "toolbar=no",
  "location=no",
  "directories=no",
  "status=no",
  "menubar=no",
  "scrollbars=yes",
  "resizable=yes",
  "width=" + 300,
  "height=" + 300,
  "top=" + 100,
  "left=" + 100].join(','))
win.document.open()
win.document.write(["<h1>hello</h1>"].join(''))
win.document.close()

run function as webworker

function functionAsWorker(code){
	// URL.createObjectURL
  window.URL = window.URL || window.webkitURL;
  let response = "self.onmessage="+code.toString()
  let blob;
  try {
      blob = new Blob([response], {type: 'application/javascript'});
  } catch (e) { // Backwards-compatibility
      window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
      blob = new BlobBuilder();
      blob.append(response);
      blob = blob.getBlob();
  }
  return new Worker(URL.createObjectURL(blob));
}


function f(){
  let O = function(){
    return this
  }
  O.prototype.foo = function(){
    return "foooo"
  }
  let o = new O()
  o.foo()
  postMessage( o.foo() )
}

let w = functionAsWorker(f)
w.onmessage = function(e) {
  alert('Response: ' + e.data);
};
w.postMessage('Test');

proxy object which persists to jsonfile

/*
 * deadsimple jsonfile-as-proxy-object-object with mongo-style queries:
 *
 * const db = require('./app/db')({file:'db.json', ratelimit:1500})
 * db.accounts = {a:[{foo:1},{foo:2}]}
 * let result  = db.find('accounts.a',{foo:{$lt:2}}) )
 */

const fs   = require("fs");
const sift = require("sift")

function readFile(file) {
  let parsed;
  try {
    parsed = JSON.parse(fs.readFileSync(file, "utf8"));
  } catch (err) {
    if (err.code === "ENOENT") return {}
    throw err;
  }
  if (typeof parsed === "object" && parsed && !Array.isArray(parsed)) return parsed;
  throw new Error("File does not contain a JSON object");
}

class ProxyHandler {
  constructor(opts) {
    opts.ratelimit = opts.ratelimit || 1500
    this.opts     = opts;
    this.saveLast = null
    this.target   = {}
    this.save = () => {
      clearTimeout(this.saveLast)
      setTimeout( () => fs.writeFileSync(this.opts.file, JSON.stringify(this.target,null,2),"utf-8"), this.opts.ratelimit)
    }
    this.getpath = function get(xs,x,fallback) {
                        return String(x).split('.').reduce(function(acc, x) {
                                if (acc == null || acc == undefined ) return fallback;
                                return new Function("x","acc","return acc['" + x.split(".").join("']['") +"']" )(x, acc) || fallback
                        }, xs)
                }
    this.find = (path, query) => {
                        let arr = this.getpath(this.target,path)
                        return arr.filter ? arr.filter( sift(query) ) : []
    }
    this.findOne = (path, query) => {
                        let arr = this.find(path,query)
                        return arr.filter && arr.length ? arr[0] : undefined
    }
  }

  get(target, key) {
                if( this[key] ) return this[key]
    return target[key];
  }

  set(target, key, value) {
    target[key] = value;
    this.target = target
    this.save()
    return value;
  }

  deleteProperty(target, key) {
    delete target[key];
    this.target = target 
    this.save()
    return 
  }
}

module.exports = function jsonObject(options) {
  return new Proxy(readFile(options.file), new ProxyHandler(options));
};

typerify: type a new value in an animated way

function typerify(element, config, prop) {
  const { speed, sleep, lines } = config;
  let currentLine = 0;

  function typeLine(line) {
    return new Promise(resolve => {
      let count = 0;
      const intervalId = setInterval(() => {
        if (count >= line.length) {
          clearInterval(intervalId);
          setTimeout(resolve, sleep);
        } else {
          element[prop] += line[count];
          count++;
        }
      }, speed);
    });
  }

  async function typeText() {
    if (currentLine >= lines.length) {
      currentLine = 0;
    }
    const line = lines[currentLine];
    element[prop] = "";
    await typeLine(line);
    currentLine++;
    typeText();
  }

  typeText();
}

const config = {
  speed: 100,
  sleep: 1000,
  lines: [
    "this is a line    ",
    "another line"
  ]
};
el = document.querySelector('#q')
typerify(el, config, 'placeholder');

parseURL

parseUrl = (url) => {
  let urlObj,file
  try{
    urlObj = new URL( url.match(/:\/\//) ? url : String(`https://fake.com/${url}`).replace(/\/\//,'/') )
    file = urlObj.pathname.substring(urlObj.pathname.lastIndexOf('/') + 1);
  }catch(e){ file = '' }
  let   dir  = url.substring(0, url.lastIndexOf('/') + 1)
  const hash = url.match(/#/) ? url.replace(/.*#/,'') : ''
  const ext  = file.split('.').pop()
  let   store = {}
  let   search = urlObj.search.substr(1).split("&")
  let   hashmap = urlObj.hash.substr(1).split("&")
  for( let i in search )  store[  (search[i].split("=")[0])  ]  = search[i].split("=")[1] || ''
  for( let i in hashmap ) store[  (hashmap[i].split("=")[0]) ]  = hashmap[i].split("=")[1] || ''
  return {urlObj,dir,file,hash,ext,store}
}

AFRAME debug console in-VR

    document.body.innerHTML += `
      <textarea style="background:transparent;position:absolute;z-index:1000;border:none;padding:15px;color:red;left:0;bottom:0;right:0;top:0;pointer-events:none">flop</textarea>
    `
    let patch = function(old){
      return function(){
        old.apply(console,arguments)
        document.querySelector("body>textarea").innerHTML += [...arguments].join(" ")+"\n"
      }
    }
    console.log   = patch(console.log)
    console.error = patch(console.error)
    console.warn  = patch(console.warn)

future text

looks great in monospace font for UI's

function futurize(str){
  return str
  .toUpperCase()
  .replace(/E/g,'Ξ')
  .replace(/A/g,'Λ')
}

console HTML overlay #threejs

const consoleOverlay = () => {
  // add onscreen console
  let divConsole = document.createElement('pre')
  divConsole.style.position = 'fixed'
  divConsole.style.overflow = 'auto'
  divConsole.style.top = divConsole.style.left = divConsole.style.bottom = divConsole.style.right = '0px'
  divConsole.style.height = '98.5vh';
  divConsole.style.width = '100%'
  divConsole.id = 'console'
  document.body.appendChild(divConsole)
  const wrapper = (scope, fn, name) => {
    return function(msg) {
      divConsole.innerHTML += `[${name}] ${msg}<br>`;
      if( name == 'err'){
        let err = new Error()
        String(err.stack).split("\n").slice(2).map( (l) => divConsole.innerHTML += ` └☑ ${l}\n` )
      }
      divConsole.scrollTop = divConsole.scrollHeight;
      fn.call(scope,msg);
    };
  }
  window.console.log   = wrapper(console, console.log, "log");
  window.console.warn  = wrapper(console, console.warn, "wrn");
  window.console.error = wrapper(console, console.error, "err");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment