@ -0,0 +1,5 @@ | |||
# Revision history for UnitJS | |||
## 0.1.0 -- 2020-05-17 | |||
* Release UnitJS as a [SJW](https://git.marvid.fr/Tissevert/SJW) package still useable as a standalone script. |
@ -0,0 +1,43 @@ | |||
# UnitJS | |||
UnitJS is a tiny Javascript framework to write web applications. It provides four essential modules to deal with asynchronous functions (technically, it's a monad obtained by currifying the CPS functions of Javascript like `setTimeout`), value caching, DOM manipulation and basic functional needs like composition or object projections. | |||
## Usage | |||
### With [SJW](https://git.marvid.fr/Tissevert/SJW) | |||
UnitJS comes as a SJW package. You can install it by copying the `src/` directory of this repository to your package database by issuing the following command from the current directory : | |||
``` | |||
$ cp -r src/ ~/.sjw/unitJS | |||
``` | |||
Then, using unitJS in your projects is as simple as writing : | |||
``` | |||
import * as Async from UnitJS.Async; | |||
``` | |||
in any module if you want to use the `Async` module for instance and compile your code using the `--include` option of `sjw` with package `unitJS` : | |||
``` | |||
$ sjw -I unitJS your/code/src -o your/code/main.js | |||
``` | |||
Note that all modules have their path prefixed by a common `UnitJS` component and that SJW will only include the modules actually used in your code, not all of UnitJS. | |||
### As a standalone JS script | |||
If you don't want to or can't use SJW, it's still possible to use UnitJS by generating a single script that can then be loaded from your web page like this : | |||
``` | |||
<script src="/path/to/unit.js"></script> | |||
``` | |||
The file `unit.js` can be easily generated with `make`, which actually just calls the `unit.js.tpl` script. Note that you can still generate custom «partial» versions of `unit.js` if you don't use all of it by overriding the `SRC` variable of the `Makefile` like so : | |||
``` | |||
make SRC="src/UnitJS/Dom.js src/UnitJS/Cache.js" | |||
``` | |||
or by manually calling `./unit.js.tpl` with the files you want, like the `Makefile` does. |
@ -1,131 +0,0 @@ | |||
function Async() { | |||
return { | |||
apply: apply, | |||
bind: bind, | |||
fail: fail, | |||
http: http, | |||
map: map, | |||
parallel: parallel, | |||
run: run, | |||
sequence: sequence, | |||
wait: wait, | |||
wrap: wrap | |||
}; | |||
function apply(f, x) { | |||
return function(g) { | |||
g(f(x)); | |||
}; | |||
} | |||
function bind() { | |||
var m, steps, i; | |||
if(arguments.length < 1) { | |||
return wrap(); | |||
} | |||
m = arguments[0]; | |||
steps = arguments; | |||
i = 1; | |||
return function(f) { | |||
var step = function(x) { | |||
if(i < steps.length) { | |||
steps[i++](x)(step); | |||
} else { | |||
return f(x); | |||
} | |||
} | |||
m(step); | |||
}; | |||
} | |||
function fail(message) { | |||
return function(f) { | |||
console.log(message); | |||
} | |||
} | |||
function map(mapper) { | |||
return function(x) { | |||
return function(f) { | |||
f(mapper(x)); | |||
}; | |||
}; | |||
} | |||
function parallel() { | |||
var threads = arguments; | |||
var pending = threads.length; | |||
var results = []; | |||
var returned = []; | |||
return function(f) { | |||
var useResult = function(i) { | |||
return function(x) { | |||
if(!returned[i]) { | |||
results[i] = x; | |||
returned[i] = true; | |||
pending--; | |||
if(pending < 1) { | |||
f(results); | |||
} | |||
} | |||
}; | |||
}; | |||
for(var i = 0; i < threads.length; i++) { | |||
threads[i](useResult(i)); | |||
} | |||
}; | |||
} | |||
function run() { | |||
var m; | |||
if(arguments.length == 1) { | |||
m = arguments[0]; | |||
} else { | |||
m = sequence.apply(null, arguments); | |||
} | |||
m(function() {}); | |||
} | |||
function sequence() { | |||
var steps = arguments; | |||
var i = 0; | |||
return function(f) { | |||
var step = function(x) { | |||
if(i < steps.length) { | |||
steps[i++](step); | |||
} else { | |||
f(x); | |||
} | |||
} | |||
step(); | |||
}; | |||
} | |||
function wait(delay) { | |||
return function(f) { | |||
setTimeout(f, delay); | |||
}; | |||
} | |||
function wrap(x) { | |||
return function(f) { | |||
f(x); | |||
}; | |||
} | |||
function http(query) { | |||
return function(f) { | |||
var xhr = new XMLHttpRequest(); | |||
xhr.addEventListener('load', function() { | |||
f(this); | |||
}); | |||
xhr.open(query.method, query.url); | |||
if(query.method == 'POST') { | |||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | |||
} | |||
xhr.send(query.body); | |||
}; | |||
} | |||
} |
@ -1,54 +0,0 @@ | |||
function Cache() { | |||
function Cache(loader) { | |||
this.loader = loader; | |||
this.loaded = {}; | |||
this.loading = {}; | |||
} | |||
Cache.prototype.get = function(key) { | |||
return function(f) { | |||
if(this.loaded[key] != undefined) { | |||
f(this.loaded[key]); | |||
} else { | |||
this.startLoading(key); | |||
this.loading[key].push(f); | |||
this.loader(key)(this.store(key)); | |||
} | |||
}.bind(this); | |||
}; | |||
Cache.prototype.warmUp = function(key) { | |||
if(this.loaded[key] == undefined) { | |||
this.startLoading(key); | |||
this.loader(key)(this.store(key)); | |||
} | |||
}; | |||
Cache.prototype.startLoading = function(key) { | |||
if(this.loading[key] == undefined) { | |||
this.loading[key] = []; | |||
} | |||
}; | |||
Cache.prototype.store = function(key) { | |||
return function(value) { | |||
this.loaded[key] = value; | |||
for(var i = 0; i < this.loading[key].length; i++) { | |||
this.loading[key][i](value); | |||
} | |||
this.loading[key] = null; | |||
}.bind(this); | |||
}; | |||
return { | |||
make: make | |||
}; | |||
function make(loader) { | |||
var cache = new Cache(loader); | |||
return { | |||
get: cache.get.bind(cache), | |||
warmUp: cache.warmUp.bind(cache) | |||
}; | |||
} | |||
} |
@ -1,39 +0,0 @@ | |||
function Dom() { | |||
return { | |||
clear: clear, | |||
make: make | |||
} | |||
function clear(elem) { | |||
while(elem.firstChild) { | |||
elem.removeChild(elem.firstChild); | |||
} | |||
} | |||
function make(tag, properties, children) { | |||
var e = document.createElement(tag); | |||
properties = properties || {}; | |||
children = children || []; | |||
for(key in properties) { | |||
var value = properties[key]; | |||
switch(key) { | |||
case "class": | |||
e.className = Array.isArray(value) ? value.join(' ') : value; | |||
break;; | |||
case "maxlength": | |||
e.setAttribute("maxlength", value); | |||
break; | |||
case "onClick": | |||
e.addEventListener("click", value); | |||
break;; | |||
default: | |||
e[key] = value; | |||
} | |||
} | |||
for(var i = 0; i < children.length; i++) { | |||
e.appendChild(children[i]); | |||
} | |||
return e; | |||
} | |||
} |
@ -1,74 +0,0 @@ | |||
function Fun() { | |||
return { | |||
compare: compare, | |||
compose: compose, | |||
defined: defined, | |||
id: id, | |||
insert: insert, | |||
map: map, | |||
mapFilter: mapFilter, | |||
of: of, | |||
proj: proj | |||
}; | |||
function compare(on) { | |||
on = on || id; | |||
return function(a, b) { | |||
var va = on(a), vb = on(b); | |||
return va < vb ? -1 : (va > vb ? 1 : 0) | |||
}; | |||
} | |||
function compose(f, g) { | |||
return function() { | |||
return f(g.apply(null, arguments)); | |||
}; | |||
} | |||
function defined(x) { | |||
return x != undefined; | |||
} | |||
function id(x) { | |||
return x; | |||
} | |||
function insert(obj, t, comparer, min, max) { | |||
min = defined(min) ? min : 0; | |||
max = defined(max) ? max : t.length; | |||
comparer = comparer || compare(); | |||
if(max - min < 1) { | |||
return min; | |||
} | |||
var avg = Math.floor((max + min) / 2); | |||
if (compare(obj, t[avg]) < 0) { | |||
return insert(obj, t, compare, min, avg); | |||
} else { | |||
return insert(obj, t, compare, avg+1, max); | |||
} | |||
} | |||
function map(mapper, f) { | |||
return function() { | |||
var args = Array.prototype.map.call(arguments, mapper); | |||
return f.apply(null, args); | |||
} | |||
} | |||
function mapFilter(mapper, predicate) { | |||
return function(array) { | |||
return array.reduce(function(accumulator, elem) { | |||
var v = mapper(elem); | |||
return predicate(v) ? accumulator.concat(v) : accumulator; | |||
}, []); | |||
}; | |||
} | |||
function of(o) { | |||
return function(key) {return o[key];}; | |||
} | |||
function proj(key) { | |||
return function(o) {return o[key];}; | |||
} | |||
} |
@ -0,0 +1,128 @@ | |||
return { | |||
apply: apply, | |||
bind: bind, | |||
fail: fail, | |||
http: http, | |||
map: map, | |||
parallel: parallel, | |||
run: run, | |||
sequence: sequence, | |||
wait: wait, | |||
wrap: wrap | |||
}; | |||
function apply(f, x) { | |||
return function(g) { | |||
g(f(x)); | |||
}; | |||
} | |||
function bind() { | |||
var m, steps, i; | |||
if(arguments.length < 1) { | |||
return wrap(); | |||
} | |||
m = arguments[0]; | |||
steps = arguments; | |||
i = 1; | |||
return function(f) { | |||
var step = function(x) { | |||
if(i < steps.length) { | |||
steps[i++](x)(step); | |||
} else { | |||
return f(x); | |||
} | |||
} | |||
m(step); | |||
}; | |||
} | |||
function fail(message) { | |||
return function(f) { | |||
console.log(message); | |||
} | |||
} | |||
function map(mapper) { | |||
return function(x) { | |||
return function(f) { | |||
f(mapper(x)); | |||
}; | |||
}; | |||
} | |||
function parallel() { | |||
var threads = arguments; | |||
var pending = threads.length; | |||
var results = []; | |||
var returned = []; | |||
return function(f) { | |||
var useResult = function(i) { | |||
return function(x) { | |||
if(!returned[i]) { | |||
results[i] = x; | |||
returned[i] = true; | |||
pending--; | |||
if(pending < 1) { | |||
f(results); | |||
} | |||
} | |||
}; | |||
}; | |||
for(var i = 0; i < threads.length; i++) { | |||
threads[i](useResult(i)); | |||
} | |||
}; | |||
} | |||
function run() { | |||
var m; | |||
if(arguments.length == 1) { | |||
m = arguments[0]; | |||
} else { | |||
m = sequence.apply(null, arguments); | |||
} | |||
m(function() {}); | |||
} | |||
function sequence() { | |||
var steps = arguments; | |||
var i = 0; | |||
return function(f) { | |||
var step = function(x) { | |||
if(i < steps.length) { | |||
steps[i++](step); | |||
} else { | |||
f(x); | |||
} | |||
} | |||
step(); | |||
}; | |||
} | |||
function wait(delay) { | |||
return function(f) { | |||
setTimeout(f, delay); | |||
}; | |||
} | |||
function wrap(x) { | |||
return function(f) { | |||
f(x); | |||
}; | |||
} | |||
function http(query) { | |||
return function(f) { | |||
var xhr = new XMLHttpRequest(); | |||
xhr.addEventListener('load', function() { | |||
f(this); | |||
}); | |||
xhr.open(query.method, query.url); | |||
if(query.method == 'POST') { | |||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); | |||
} | |||
xhr.send(query.body); | |||
}; | |||
} |
@ -0,0 +1,52 @@ | |||
function Cache(loader) { | |||
this.loader = loader; | |||
this.loaded = {}; | |||
this.loading = {}; | |||
} | |||
Cache.prototype.get = function(key) { | |||
return function(f) { | |||
if(this.loaded[key] != undefined) { | |||
f(this.loaded[key]); | |||
} else { | |||
this.startLoading(key); | |||
this.loading[key].push(f); | |||
this.loader(key)(this.store(key)); | |||
} | |||
}.bind(this); | |||
}; | |||
Cache.prototype.warmUp = function(key) { | |||
if(this.loaded[key] == undefined) { | |||
this.startLoading(key); | |||
this.loader(key)(this.store(key)); | |||
} | |||
}; | |||
Cache.prototype.startLoading = function(key) { | |||
if(this.loading[key] == undefined) { | |||
this.loading[key] = []; | |||
} | |||
}; | |||
Cache.prototype.store = function(key) { | |||
return function(value) { | |||
this.loaded[key] = value; | |||
for(var i = 0; i < this.loading[key].length; i++) { | |||
this.loading[key][i](value); | |||
} | |||
this.loading[key] = null; | |||
}.bind(this); | |||
}; | |||
return { | |||
make: make | |||
}; | |||
function make(loader) { | |||
var cache = new Cache(loader); | |||
return { | |||
get: cache.get.bind(cache), | |||
warmUp: cache.warmUp.bind(cache) | |||
}; | |||
} |
@ -0,0 +1,36 @@ | |||
return { | |||
clear: clear, | |||
make: make | |||
} | |||
function clear(elem) { | |||
while(elem.firstChild) { | |||
elem.removeChild(elem.firstChild); | |||
} | |||
} | |||
function make(tag, properties, children) { | |||
var e = document.createElement(tag); | |||
properties = properties || {}; | |||
children = children || []; | |||
for(key in properties) { | |||
var value = properties[key]; | |||
switch(key) { | |||
case "class": | |||
e.className = Array.isArray(value) ? value.join(' ') : value; | |||
break;; | |||
case "maxlength": | |||
e.setAttribute("maxlength", value); | |||
break; | |||
case "onClick": | |||
e.addEventListener("click", value); | |||
break;; | |||
default: | |||
e[key] = value; | |||
} | |||
} | |||
for(var i = 0; i < children.length; i++) { | |||
e.appendChild(children[i]); | |||
} | |||
return e; | |||
} |
@ -0,0 +1,72 @@ | |||
return { | |||
compare: compare, | |||
compose: compose, | |||
defined: defined, | |||
id: id, | |||
insert: insert, | |||
map: map, | |||
mapFilter: mapFilter, | |||
of: of, | |||
proj: proj | |||
}; | |||
function compare(on) { | |||
on = on || id; | |||
return function(a, b) { | |||
var va = on(a), vb = on(b); | |||
return va < vb ? -1 : (va > vb ? 1 : 0) | |||
}; | |||
} | |||
function compose(f, g) { | |||
return function() { | |||
return f(g.apply(null, arguments)); | |||
}; | |||
} | |||
function defined(x) { | |||
return x != undefined; | |||
} | |||
function id(x) { | |||
return x; | |||
} | |||
function insert(obj, t, comparer, min, max) { | |||
min = defined(min) ? min : 0; | |||
max = defined(max) ? max : t.length; | |||
comparer = comparer || compare(); | |||
if(max - min < 1) { | |||
return min; | |||
} | |||
var avg = Math.floor((max + min) / 2); | |||
if (compare(obj, t[avg]) < 0) { | |||
return insert(obj, t, compare, min, avg); | |||
} else { | |||
return insert(obj, t, compare, avg+1, max); | |||
} | |||
} | |||
function map(mapper, f) { | |||
return function() { | |||
var args = Array.prototype.map.call(arguments, mapper); | |||
return f.apply(null, args); | |||
} | |||
} | |||
function mapFilter(mapper, predicate) { | |||
return function(array) { | |||
return array.reduce(function(accumulator, elem) { | |||
var v = mapper(elem); | |||
return predicate(v) ? accumulator.concat(v) : accumulator; | |||
}, []); | |||
}; | |||
} | |||
function of(o) { | |||
return function(key) {return o[key];}; | |||
} | |||
function proj(key) { | |||
return function(o) {return o[key];}; | |||
} |