Commit b2365922 authored by Avris's avatar Avris

init

parents
{
"presets": ["env"]
}
/node_modules/*
# Vanillin.js – Almost like vanilla JavaScript
![Logo](img/logo.png)
[jQuery](https://jquery.com/) used to be virtually indispensable,
if you wanted to develop a cross-browser website without getting a headache.
Today, however, [you might not need jQuery](http://youmightnotneedjquery.com/),
especially, if you're developing a library and want to avoid unnecessary dependencies.
Still, some helpers could be useful...
Vanillin is an opinionated set of helpers
that I find most useful, a bare minimum to make life easier.
## Installation
You can either install Vanillin as a node module:
$ npm i --save avris-vanillin
or
$ yarn add avris-vanillin
require('avris-vanillin');
Use a CDN:
<script src="https://gitlab.com/Avris/Vanillin/raw/v0.0.1/dist/Vanillin.min.js"></script>
Or simply copy-paste whatever filters you need from the [source code](https://gitlab.com/Avris/Vanillin/blob/v0.0.1/src/Vanillin.js).
## Usage
### `_each(iterable, function (el, i))`
Iterate in a consistent way, whether it's over an array or an object,
without having to check for `hasOwnProperty` in every loop.
each(object, function (val, key) {
// ...
});
### `_el(html)`
Convert an HTML string into a DOM element with one simple helper.
const el = _el('<div class="lorem"><a href="/test">OK</a></div>');
### `_findParent(el, selector)`
Finds the first parent of `el` that matches `selector`.
parent = _el(el, 'section.important')
### `_on(selector, event, handler (e))`
Attaches the `handler` as an event listener or `event` for all elements that match `selector`,
even if they don't exist yet at that moment.
_on('a.foo', 'click', function (e) {
console.log('foo link clicked', this.attributes.href.value)
});
### `_request(method, url, data, headers)`
Performs an AJAX request to `url` using a specified HTTP `method`,
attaching `data` (they can be either `FormData`, an object, or a DOM element containing form data)
and using specified `headers`. Returns a `Promise`.
_request('POST', '/document/create', document.querySelector('form'), {Authorization: 'Basic YWxhZGRpbjpvcGVuc2VzYW1l'})
.then((data) => {
alert('Document created with ID: ' + JSON.parse(data).id);
})
.catch((xhr) => {
console.log(xhr);
alert('Error while creating a document');
};
### `_extend = (out, ...args)`
Extends `out` with data from `...args`, useful for resolving configs:
function Helper(options) {
this.config = _extend({}, defaultOptions, options);
}
## Development
To install dev dependencies:
yarn
To build a minified, babel-ified version:
yarn build
To run tests:
yarn test
and open [http://localhost:8777](http://localhost:8777).
## Copyright
* **Author:** Andre Prusinowski [(Avris.it)](https://avris.it)
* **Licence:** [MIT](https://mit.avris.it)
* **Logo**: [Sbrools](https://en.wikipedia.org/wiki/Vanillin#/media/File:Vanillin-3d.png) (CC-BY)
"use strict";window._each=function(n,e){for(var t in n)n.hasOwnProperty(t)&&e(n[t],t)},window._el=function(n){var e=document.createElement("div");return e.innerHTML=n.trim(),e.firstChild},window._findParent=function(n,e){do{if(n.matches(e))return n;n=n.parentNode}while(n&&n.matches)},window._on=function(n,e,t){document.addEventListener(e,function(e){var r=_findParent(e.target,n);r&&(!1===t.apply(r,[e])&&(e.preventDefault(),e.stopPropagation()))})},window._request=function(n,e,t,r){return new Promise(function(o,a){var i=new XMLHttpRequest;i.open(n,e,!0),_each(r||{},function(n,e){return i.setRequestHeader(e,n)}),i.onload=function(){i.status>=400?a(i):o(i.responseText,i)},i.onerror=function(){return a(i)};var u=void 0;t instanceof HTMLElement?u=new FormData(t):t instanceof FormData?u=t:(u=new FormData,_each(t,function(n,e){return u.append(e,n)})),i.send(u)})},window._extend=function(n){for(var e=arguments.length,t=Array(e>1?e-1:0),r=1;r<e;r++)t[r-1]=arguments[r];n=n||{};for(var o=1;o<t.length;o++){var a=t[o];a&&_each(a,function(e,t){n[t]=e})}return n};
\ No newline at end of file
var gulp = require('gulp');
var babel = require('gulp-babel');
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
gulp.task('default', function(){
return gulp.src('src/Vanillin.js')
.pipe(babel())
.pipe(uglify())
.pipe(rename({ suffix: '.min' }))
.pipe(gulp.dest('dist'));
});
{
"name": "avris-vanillin",
"version": "0.0.1",
"description": "Almost like vanilla JavaScript",
"keywords": [
"vanilla",
"simple",
"helper"
],
"homepage": "https://avris.it",
"author": "Andre Prusinowski <andre@avris.it>",
"repository": {
"type": "git",
"url": "git+https://gitlab.com/Avris/Vanillin.git"
},
"license": "MIT",
"scripts": {
"build": "./node_modules/.bin/gulp",
"test": "node server.js"
},
"main": "src/Vanillin.js",
"devDependencies": {
"babel-core": "^6.26.0",
"babel-preset-env": "^1.6.1",
"fs": "^0.0.1-security",
"gulp": "^3.9.1",
"gulp-babel": "^7.0.1",
"gulp-cli": "^2.0.1",
"gulp-rename": "^1.2.2",
"gulp-uglify": "^3.0.0",
"http": "^0.0.0",
"multiparty": "^4.1.3"
}
}
const http = require('http');
const fs = require('fs');
const multiparty = require('multiparty');
http.createServer(function (req, res) {
switch (req.url) {
case '/':
fs.readFile('tests.html', function(err, data) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
});
break;
case '/get':
res.writeHead(200, {'Content-Type': 'application/json'});
res.write(JSON.stringify({header: req.headers['x-test'] || '-'}));
res.end();
break;
case '/post':
if (req.method !== 'POST') {
res.writeHead(405, {'Content-Type': 'text/plaintext'});
res.write('Method not allowed');
res.end();
} else {
const form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
res.writeHead(200, {'Content-Type': 'application/json'});
res.write(JSON.stringify(fields).toUpperCase());
res.end();
});
}
break;
default:
fs.readFile(req.url.substr(1), function(err, data) {
if (err) {
res.writeHead(404);
res.write('Not found');
res.end();
} else {
res.writeHead(200);
res.write(data);
res.end();
}
});
}
}).listen(8777);
window._each = (obj, callable) => {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
callable(obj[key], key);
}
}
};
window._el = (html) => {
const div = document.createElement('div');
div.innerHTML = html.trim();
return div.firstChild;
};
window._findParent = (el, selector) => {
do {
if (el.matches(selector)) {
return el;
}
el = el.parentNode;
} while (el && el.matches);
};
window._on = (selector, event, handler) => {
document.addEventListener(event, function (e) {
const match = _findParent(e.target, selector);
if (match) {
const result = handler.apply(match, [e]);
if (result === false) {
e.preventDefault();
e.stopPropagation();
}
}
});
};
window._request = (method, url, data, headers) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url, true);
_each(headers || {}, (val, key) => xhr.setRequestHeader(key, val));
xhr.onload = () => {
if (xhr.status >= 400) {
reject(xhr);
} else {
resolve(xhr.responseText, xhr);
}
};
xhr.onerror = () => reject(xhr);
let formData;
if (data instanceof HTMLElement) {
formData = new FormData(data);
} else if (data instanceof FormData) {
formData = data;
} else {
formData = new FormData();
_each(data, (val, key) => formData.append(key, val));
}
xhr.send(formData);
});
};
window._extend = (out, ...args) => {
out = out || {};
for (let i = 1; i < args.length; i++) {
const obj = args[i];
if (!obj) {
continue;
}
_each(obj, (val, key) => {
out[key] = val;
});
}
return out;
};
@import url('https://fonts.googleapis.com/css?family=Ubuntu');
body {
font-family: 'Ubuntu', sans-serif;
}
.container {
width: 100%;
max-width: 400px;
margin: 0 auto;
}
.tests {
list-style-type: none;
padding-left: 0;
}
.test-result {
display: inline-block;
margin: 0 1rem;
width: 1rem;
text-align: center;
}
[data-result=running] .test-result {
animation: spin 2s infinite linear;
}
@keyframes spin {
0% { transform: rotate(0); }
100% { transform: rotate(359deg); }
}
[data-result=ok] {
color: limegreen;
}
[data-result=fail] {
color: red;
}
.workspace {
height: 1px;
overflow: hidden;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vanillin.js – Test suite</title>
<!-- Run `yarn test` and open http://localhost:8777 -->
<link rel="shortcut icon" href="./img/favicon.png" />
<link rel="stylesheet" href="./style/tests.css"/>
</head>
<body>
<div class="container">
<h1><img src="./img/logo.png" alt="Vanillin.js"></h1>
<h2>Test suite:</h2>
<ul class="tests"></ul>
</div>
<div class="workspace">
<div class="foo">aa</div>
<div class="foo">bb</div>
<div class="foo">cc</div>
<div class="bar-parent">
<div class="bar-parent"></div>
<div class="bar-parent">
<ul>
<li><span></span></li>
<li><span class="bar"></span></li>
<li><span></span></li>
</ul>
</div>
<div class="bar-parent"></div>
</div>
<form>
<input name="foo" value="FOO">
<select name="select[]" multiple>
<option value="a1" selected>A1</option>
<option value="a2">A2</option>
<option value="a3" selected>A3</option>
</select>
</form>
<div class="dynamic"></div>
</div>
<script src="./src/Vanillin.js"></script>
<script src="./tests/Vanillin.tests.js"></script>
</body>
</html>
const $tests = document.querySelector('.tests');
eq = (a, b) => {
if (JSON.stringify(a) === JSON.stringify(b)) {
return true;
}
console.error('Expected:', b, 'Actual:', a);
return false;
};
let alertShown = false;
test = (name, run) => {
const $el = _el(`<li class="test" data-name="${name}" data-result="running"><span class="test-result">✵</span> ${name}</li>`);
$tests.appendChild($el);
const ok = () => {
$el.querySelector('.test-result').innerHTML = '✔';
$el.dataset.result = 'ok';
};
const fail = (message) => {
console.error('Fail', name, message);
$el.querySelector('.test-result').innerHTML = '✘';
$el.dataset.result = 'fail';
if (!alertShown) {
alert('Some tests have failed, check the console.');
alertShown = true;
}
};
try {
run((assertion) => assertion ? ok() : fail(), ok, fail);
} catch (e) {
console.error(e);
fail();
}
};
// -----------------
test('each-array', (assert) => {
const out = [];
_each(['a', 'b', 'c'], (el, i) => out.push([el, i]));
assert(eq(out, [['a', '0'], ['b', '1'], ['c', '2']]));
});
test('each-object', (assert) => {
const out = [];
_each({'f0': 'a', 'f1': 'b', 'f2': 'c'}, (el, i) => out.push([el, i]));
assert(eq(out, [['a', 'f0'], ['b', 'f1'], ['c', 'f2']]));
});
test('each-element', (assert) => {
const out = [];
_each(document.querySelectorAll('.foo'), (el, i) => out.push([el.innerText, i]));
assert(eq(out, [['aa', '0'], ['bb', '1'], ['cc', '2']]));
});
test('each-scalar', (assert) => {
const out = [];
_each(8, (el, i) => out.push([el.innerText, i]));
assert(eq(out, []));
});
// -----------------
test('el', (assert, ok, fail) => {
const $el = _el('<div class="lorem"><a href="/test">OK</a></div>');
if (!$el instanceof HTMLElement) {
fail('$el not instanceof HTMLElement');
}
if ($el.tagName !== 'DIV') {
fail('$el not a div');
}
if (!eq(Array.from($el.classList), ['lorem'])) {
fail();
}
if ($el.children.length !== 1) {
fail('Unexpected number of $el children: ' + $el.children.length);
}
if ($el.firstChild.tagName !== 'A') {
fail('$el child not an A');
}
if ($el.innerText !== 'OK') {
fail('$el text not OK');
}
ok();
});
// -----------------
test('findParent', (assert) => {
const $expectedParent = document.querySelectorAll('.bar-parent')[2];
const $el = document.querySelector('.bar');
assert(_findParent($el, '.bar-parent') === $expectedParent);
});
test('findParent-not-found', (assert) => {
const $el = document.querySelector('.bar');
assert(_findParent($el, '.nope') === undefined);
});
// -----------------
test('on', (assert) => {
const $el = document.querySelector('.workspace .dynamic');
const $child1 = _el('<a href="#a1" class="catch-click"></a>');
const $child2 = _el('<a href="#a2"></a>');
const $child3 = _el('<a href="#a3" class="catch-click"></a>');
const $grandChild3 = _el('<span>A3-sub</span>');
$child3.appendChild($grandChild3);
const $child4 = _el('<a href="#a4"></a>');
const $grandChild4 = _el('<span>A4-sub</span>');
$child4.appendChild($grandChild4);
const out = [];
$el.appendChild($child1);
$el.appendChild($child2);
_on('.workspace .dynamic .catch-click', 'click', function (e) {
out.push(this.attributes.href.value);
return false;
});
$el.appendChild($child3);
$el.appendChild($child4);
const createClickEvent = () => {
const event = document.createEvent('HTMLEvents');
event.initEvent('click', true, false);
return event;
};
$child1.dispatchEvent(createClickEvent());
$child2.dispatchEvent(createClickEvent());
$child3.dispatchEvent(createClickEvent());
$grandChild3.dispatchEvent(createClickEvent());
$child4.dispatchEvent(createClickEvent());
$grandChild4.dispatchEvent(createClickEvent());
assert(eq(out, ['#a1', '#a3', '#a3']))
});
// -----------------
test('request-get', (assert, ok, fail) => {
_request('GET', '/get')
.then((data) => assert(eq(JSON.parse(data), {header: '-'})))
.catch(() => fail());
});
test('request-get-header', (assert, ok, fail) => {
_request('GET', '/get', {}, {'x-test': 'content'})
.then((data) => assert(eq(JSON.parse(data), {header: 'content'})))
.catch(() => fail());
});
test('request-post-form', (assert, ok, fail) => {
_request('POST', '/post', document.querySelector('form'))
.then((data) => assert(eq(JSON.parse(data), {FOO: ['FOO'], 'SELECT[]': ['A1', 'A3']})))
.catch((xhr) => fail(xhr));
});
test('request-post-formdata', (assert, ok, fail) => {
_request('POST', '/post', new FormData(document.querySelector('form')))
.then((data) => assert(eq(JSON.parse(data), {FOO: ['FOO'], 'SELECT[]': ['A1', 'A3']})))
.catch((xhr) => fail(xhr));
});
test('request-post-object', (assert, ok, fail) => {
_request('POST', '/post', {lorem: 'ipsum', foo: ['phoebe', 'buffay']})
.then((data) => assert(eq(JSON.parse(data), {LOREM: ['IPSUM'], FOO: ['PHOEBE,BUFFAY']})))
.catch((xhr) => fail(xhr));
});
test('request-not-found', (assert, ok, fail) => {
_request('GET', '/nope')
.then((data, xhr) => fail(xhr))
.catch((xhr) => assert(eq(xhr.status, 404)));
});
test('request-method-not-allowed', (assert, ok, fail) => {
_request('GET', '/post')
.then((data, xhr) => fail(xhr))
.catch((xhr) => assert(eq(xhr.status, 405)));
});
test('request-refused', (assert, ok, fail) => {
_request('GET', '//nonexistent.domain.hopefully/origin')
.then((data, xhr) => fail(xhr))
.catch((xhr) => ok());
});
// -----------------
test('extend', (assert) => {
const objA = {foo: 5, 'bar': {lorem: 1, ipsum: 2}};
const objB = {foo: 0, 'bar': {lorem: 0}, baz: 'xxx'};
const expected = {foo: 0, bar: {lorem: 0}, baz: 'xxx'};
assert(eq(
_extend({}, objA, objB),
expected
));
});
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment