Compare commits

...

5 Commits
goSJW ... main

13 changed files with 400 additions and 303 deletions

11
CHANGELOG.md Normal file
View File

@ -0,0 +1,11 @@
# Revision history for UnitJS
## 0.1.1 -- 2022-08-09
* Package UnitJS for [guix](https://guix.gnu.org/)
* Fix state bug in Async's sequences and bindings
* Generalize support for events in Dom element maker
## 0.1.0 -- 2020-05-17
* Release UnitJS as a [SJW](https://git.marvid.fr/Tissevert/SJW) package still useable as a standalone script.

View File

@ -1,6 +1,6 @@
BUILD_DIR=dist
TARGET=$(BUILD_DIR)/unit.js
SRC=$(wildcard *.js)
SRC=$(wildcard src/UnitJS/*.js)
TEMPLATE=unit.js.tpl
all: $(TARGET)

49
README.md Normal file
View File

@ -0,0 +1,49 @@
# 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.
## How to use it in your project
### As a [`SJW`](https://git.marvid.fr/Tissevert/SJW) package.
#### With [`guix`](https://guix.gnu.org/)
The easiest way to use it in your web projects is to simply [package them with
`guix`](https://git.marvid.fr/Tissevert/SJW#how-not-to-install).
#### Otherwise
You can follow [these
instructions](https://git.marvid.fr/Tissevert/SJW#when-guix-is-not-an-option)
to use it without `guix`.
### Without `SJW`
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.
When used that way, the `UnitJS` library will be available to your Javascript
code as a global variable `unitJS` containing each of the modules (`Async`,
`Dom`, etc.) as an attribute.

131
async.js
View File

@ -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);
};
}
}

View File

@ -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)
};
}
}

39
dom.js
View File

@ -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;
}
}

74
fun.js
View File

@ -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];};
}
}

24
guix.scm Normal file
View File

@ -0,0 +1,24 @@
(use-modules (guix build-system copy)
(guix gexp)
(guix git-download)
(guix licenses)
(guix packages))
(let ((%source-dir (dirname (current-filename))))
(package
(name "sjw-unitJS")
(version "devel")
(source (local-file %source-dir
#:recursive? #t
#:select? (git-predicate %source-dir)))
(build-system copy-build-system)
(arguments
'(#:install-plan '(("src" "lib/SJW/unitJS"))))
(home-page "https://git.marvid.fr/Tissevert/UnitJS")
(synopsis "The Simple Javascript Wrench.")
(description
"A collection of JS modules to write simple web applications. It covers
the basics, providing asynchronous operations without any need for
promises-support from the browser as well as primitives to create DOM
elements and basic functional-programming tooling.")
(license gpl3+)))

128
src/UnitJS/Async.js Normal file
View File

@ -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;
if(arguments.length < 1) {
return wrap();
}
m = arguments[0];
steps = arguments;
return function(f) {
var i = 1;
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;
return function(f) {
var i = 0;
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);
};
}

52
src/UnitJS/Cache.js Normal file
View File

@ -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)
};
}

38
src/UnitJS/Dom.js Normal file
View File

@ -0,0 +1,38 @@
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;
default:
var matched = key.match(/on([A-Z]\w+)/);
if(matched) {
e.addEventListener(matched[1].toLowerCase(), value);
} else {
e[key] = value;
}
}
}
for(var i = 0; i < children.length; i++) {
e.appendChild(children[i]);
}
return e;
}

72
src/UnitJS/Fun.js Normal file
View File

@ -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];};
}

View File

@ -2,15 +2,36 @@
indent()
{
local tabs="$(printf '\t%.0s' `seq 1 $1`)"
sed "s|^|${tabs}|"
sed "s|^\(.\)|\t\1|"
}
moduleName()
{
local fileName="${1##*/}"
printf "${fileName%.*}"
}
MODULES=""
for file in "${@}"
do
MODULES="${MODULES}:$(moduleName "${file}")"
done
MODULES="${MODULES#:}"
includeModule()
{
cat <<EOF
function $(moduleName "${1}")() {
$(cat "${1}" | indent)
}
EOF
}
cat <<EOF
var unitJS = (function() {
return {
$(echo "${@}" | sed -e 's| |,\n|g' -e 's|\([^.\n]\+\)\.js|\u\1: \u\1|g' | indent 2)
$(printf "${MODULES}" | sed -e 's|:|,\n|g' -e 's|[^,\n]\+|&: &()|g' | indent | indent)
};
$(cat "${@}" | indent 1)
$(for file in "${@}"; do includeModule "${file}"; done | indent)
})();
EOF