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)
console.log("doFoo()")
console.log(" └☑ doBar()")
console.log(" ├☑ doBar()")
console.log(" └☒ doBaz(): no key provided")
square = (x,freq) => ( x & freq )
function clampSteps(steps,max,x){
let stepSize = max/steps
return Math.round( x/stepSize ) * stepSize )
}
clampSteps(8,360,40) // 45
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
hyphenate = (str) => str.replace(/[^a-zA-Z0-9]/g,'-')
let once = ((c) => (str) => c.called++ ? false : console.log(str) )({called:0})
once("hello") // output: hello
once("hello") // output:
once("hello") // output:
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.
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})
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}
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>
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()
}
<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]}
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 )
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()
})
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
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: 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)
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
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()
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
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 = 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()
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 )
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
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
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
})
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]
)
})
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
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()
})
}
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}
// 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]})
/*
* 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( 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 }
}
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")
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')
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]]"
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]}}
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
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
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
}
// 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);
}
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)
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 });
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()
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');
/*
* 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));
};
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 = (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}
}
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)
looks great in monospace font for UI's
function futurize(str){
return str
.toUpperCase()
.replace(/E/g,'Ξ')
.replace(/A/g,'Λ')
}
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");
}