Skip to content

Instantly share code, notes, and snippets.

@kissarat
Last active January 10, 2017 20:40
Show Gist options
  • Save kissarat/8550959 to your computer and use it in GitHub Desktop.
Save kissarat/8550959 to your computer and use it in GitHub Desktop.
/**
* Utilites library, like common.js
*/
"use strict";
var svgns = "http://www.w3.org/2000/svg";
var xlinkns = "http://www.w3.org/1999/xlink";
/**
* Register event listener, jQuery.on analog
* @param target Element or selector to register
* @param {string} [name=click] name of the event
* @param {function} call event callback
*/
function on(target, name, call) {
if (!call) {
call = name;
name = 'click';
}
if ('string' == typeof target) {
target = document.querySelectorAll(target);
for (var i = 0; i < target.length; i++)
target[i].addEventListener(name, call);
}
else
target.addEventListener(name, call);
}
/**
* Register many event listeners
* @param {Element|string} target Element or selector to register
* @param {object} events named events callbacks
* @see on
*/
function register(target, events) {
for (var name in events)
on(target, name, events[name]);
}
function off(target, name, call) {
if ('string' == typeof target)
target = $$(target);
if (!call) {
call = name;
name = 'click';
}
target.removeEventListener(name, call);
}
/**
* Removes excess whitespace nodes
* @param {Element} element
*/
function normilize(element) {
element.innerHTML = element.innerHTML.replace(/>[\s]*</mg, '><').trim();
}
/**
* Helper console log function used to investigation goals
*/
function l() {
console.log.apply(console, arguments);
}
/**
* Any container iteration, jQuery.each analog
* @param {Array|Element|NodeList|HTMLCollection} array - any iterable or Element
* @param {Object} [type] - type of item
* @param {function} call - callback(item)
*/
function each(array, type, call) {
if ('string' == typeof array) {
array = document.querySelectorAll(array);
if (!call) {
call = type;
type = Element;
}
}
else if (array instanceof Element)
array = array.childNodes;
if (call) {
Array.prototype.forEach.call(array, function (o) {
if (o instanceof type)
call(o);
});
}
else
Array.prototype.forEach.call(array, type);
}
/**
* Iterator for creating dictionaries
* @param array iterable or Element
* @param {Object} [type] optional type of item
* @param call callback(item)
* @callback call
* @returns {Object} created object (dictionary)
*/
function every(array, type, call) {
var result = {};
if (call)
call = call.bind(result);
else
type = type.bind(result);
each(array, type, call);
return result;
}
function forEach(obj, call) {
for(var key in obj)
call(obj[key], key);
}
function reverse(obj) {
var keys = Object.keys(obj);
if (keys.length <= 1)
return obj;
var result = {};
for(var i=keys.length - 1; i >=0; i--)
result[keys[i]] = obj[keys[i]];
return result;
}
function concat(a1, a2, a3) {
Array.prototype.concat.call(a1, a2, a3);
}
function map(array, call) {
Array.prototype.map.call(array, call);
}
function filter(array, call) {
Array.prototype.filter.call(array, call);
}
/**
* Split object (dictionary) to array with [key, value] elements
* @param {Object} object object (dictionary)
* @returns {Array} array of [key, value]
*/
function pair(object) {
var result = [];
for (var key in object)
result.push([key, object[key]])
return result;
}
/**
* Sorts any iterable
* @param array iterable
* @param call
* @returns {Array}
*/
function sort(array, call) {
array = Array.prototype.slice.call(array);
array.sort(call);
return array;
}
/**
* Detects index of element for any container
* @param {NodeList|Element} element
* @param {NodeList} [parent=element.parentNode]
* @returns {number}
*/
function indexOf(element, parent) {
if (!parent)
parent = element.parentNode;
if (parent instanceof Element)
parent = parent.childNodes;
return Array.prototype.indexOf.call(parent, element);
}
/**
* Finds key for value in object
* @param {Object} target
* @param {*} object
* @returns {string}
*/
function find(target, value) {
for (var key in target)
if (value === target[key])
return key;
}
/**
* OOP inheritance helper
* @param {Object} child
* @param {Object} parent
* @param {Object} [proto]
* @param {Object} [descriptor] - the same as second parameter Object.defineProperties
* @see Object.defineProperties
*/
function inherit(child, parent, proto, descriptor) {
if (!child)
child = function() {
parent.apply(this, arguments);
};
if (!descriptor)
descriptor = {};
if ('flags' in descriptor) {
descriptor.flags.forEach(function(flag) {
descriptor['is' + flag] = {
get: function() {
return this.$.classList.contains(flag);
},
set: function(value) {
this.$.classList[value ? 'add' : 'remove'](flag);
this.fire(flag, [value, this]);
}
}
});
delete descriptor.flags;
}
descriptor.base = {
value: parent,
enumerable: false,
writable: false
};
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
var names = proto ? Object.getOwnPropertyNames(proto) : [];
for (var i in names) {
var name = names[i];
descriptor[name] = Object.getOwnPropertyDescriptor(proto, name);
}
Object.defineProperties(child.prototype, descriptor);
child.descriptor = descriptor;
return child;
}
/**
* Merges arguments to single object
* @param args - array of objects
* @returns {Object}
*/
function merge(args) {
if (arguments.length > 1)
args = arguments;
var result = {};
for (var i = 0; i < args.length; i++) {
var obj = args[i];
if (obj)
for (var name in obj) {
result[name] = obj[name];
}
}
return result;
}
/**
* Setting default values to properties if it is not set in target
* @param {Object} target
* @param {Object} defaults
* @param {Object} [except] - properties that must be excluded from result
* @returns {Object}
*/
function mergeDefaults(target, defaults, except) {
if (!target)
target = {};
if (defaults)
for (var name in defaults) {
if (!(name in target))
target[name] = defaults[name]
}
if (except)
for (var i = 0; i < except.length; i++)
delete target[except[i]];
return target;
}
function construct(it, args, defaults) {
if (!defaults)
defaults = it.constructor.defaults;
if (1 == args.length)
it.extend(args[0]);
else if (args.length > 1) {
var keys = Object.keys(defaults);
if (args.length > keys.length)
throw 'Too many arguments';
for (var i = 0; i < keys.length; i++) {
if (i < args.length)
it[keys[i]] = args[i];
else
it[keys[i]] = defaults[keys[i]];
}
}
else
throw 'Zero arguments';
}
/**
* Extends existing objects, duplicate declaration is in common.js
* @param {Object} target - object to extend, prototype is extends if avaible
* @param {Object} extension - extension properties
*/
function ext(target, extension) {
if (target.prototype)
target = target.prototype;
for (var key in extension) {
if (key in target) {
console.log(key + 'is in target ', target);
continue;
}
var desc = Object.getOwnPropertyDescriptor(extension, key);
desc.enumerable = false;
Object.defineProperty(target, key, desc);
}
}
/**
* Creates and initilize object
* @param {Object} parent
* @returns {Object}
*/
function instantiate(parent) {
var child = {};
if (parent)
for(var key in parent)
child[key] = parent[key];
return child;
}
ext(Object, {
map: function(call) {
var result = [];
if (this.length)
for (var i = 0; i < this.length; i++)
result.push(call(this[i]));
else
for (var key in this)
result.push(call(this[key], key));
return result;
},
mapObject: function(call) {
var result = {};
for (var key in this)
result[key] = call(this[key], key);
return result;
}
});
ext(Array, {
/**
* Creates array of items first elements
* @returns {Array}
*/
first: function () {
return this.map(function (item) {
return item[0];
})
},
/**
* Creates indexed array
* @returns {Array} [[0,item1], [1,item2], ... [N,itemN]]
*/
index: function () {
return this.map(function (item, i) {
return [i, item[0]];
})
},
/**
* Sorts array by index
* @param {Array} indexes [[0,item1], [1,item2], ... [N,itemN]]
* @returns {Array}
*/
order: function (indexes) {
if (this.length != indexes.length)
throw "Array length is not equals to indexes length";
var result = new Array(this.length);
for (var i = 0; i < this.length; i++)
result[indexes[i]] = this[i];
//result[i] = this[indexes[i]];
return result;
},
sortIndexed: function (call) {
return this.sort(function (a, b) {
return call(a[1], b[1], a[0]);
});
},
/**
* Creates object from [key, value] array
* @returns {Object}
*/
toObject: function () {
var object = {};
this.forEach(function (item) {
object[item[0]] = item[1];
});
return object;
},
where: function(conditions) {
return this.filter(function(row) {
for(var key in conditions)
if (conditions[key] != row[key])
return false;
return true;
})
},
findAll: function(key, value) {
return this.filter(function(row) {
return value == row[key];
})
},
findFirst: function(key, value) {
for (var i = 0; i < this.length; i++)
if (value == this[i][key])
return this[i];
},
orderBy: function() {
var keys = slice(arguments);
return this.sort(function(a, b) {
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (a[key] == b[key])
continue;
if (a[key] < b[key])
return 1;
if (a[key] > b[key])
return -1;
}
return 0;
})
},
leftJoin: function(second, key1, key2) {
return this.map(function(row) {
return [row, second.findFirst(key2, row[key1])]
});
},
swap: function() {
for (var i = 0; i < this.length; i++)
this[i].reverse();
},
select: function(keys) {
return this.map(function(item) {
var result = [];
for (var i = 0; i < keys.length; i++) {
result.push(item[keys[i]])
}
return result;
});
},
update: function(condition, values) {
var objects = this;
if (values)
objects = this.where(condition);
else
values = condition;
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
for(var key in values)
object[key] = values[key];
}
return objects;
}
});
/**
* Gets first element of object
* @param {Object} object
* @returns {*}
*/
function first(object) {
for (var key in object)
return object[key];
}
/**
* Repeats n times of call
* @param n
* @param call
* @returns {Array}
*/
function repeat(n, call) {
var result = [];
for (var i = 0; i < n; i++)
result.push(call());
return result;
}
function mul(n) {
return function (a) {
return a * n;
}
}
/**
* Generates array of random integers
* @param {int} [max] - maximum value of integer
* @param {int} length - length of array and maximum value if max is not set
* @returns {Array}
*/
function randIntArray(max, length) {
if (!length)
length = max;
return repeat(length, Math.random).map(mul(max)).map(Math.floor);
}
/**
* Comparing properties of object
* @param a
* @param b
* @returns {boolean}
*/
function equals(a, b) {
for (var i in a)
if (a[i] != b[i])
return false;
return true;
}
/**
* checks if object is empty
* @param object
* @returns {boolean}
*/
function empty(object) {
return !!first(object);
}
/**
* Counts number of properties in object
* @param object
* @returns {Number}
*/
function count(object) {
return Object.keys(object).length;
}
/**
* Breaks array to pieces of fixed size
* @param array
* @param size - size of piece
* @returns {Array}
*/
function cut(array, size) {
size++;
var result = [];
var piece = [];
for (var i = 1; i < array.length; i++) {
if (i % size)
piece.push(array[i]);
else {
result.push(piece);
piece = [];
}
}
return result;
}
/**
* Swaps items [a, b] to [b, a] of array
* @param {Array} list
*/
function swap(list) {
for (var i = 0; i < list.length; i++) {
var temp = list[i][0];
list[i][0] = list[i][1];
list[i][1] = temp;
}
}
function tellme(me) {
return me;
}
function define(object, name, options) {
Object.defineProperty(object.prototype, name, options);
}
/**
* Generates random number from [min, max] interval
* @param min
* @param max
* @returns {number}
*/
function rand(min, max) {
return Math.round(min + Math.random()*(max - min));
}
/**
* Gets function that return random key of dictionary
* @param {Object} dict
* @returns {Function}
*/
function randDict(dict) {
var keys = Object.keys(dict);
return function () {
return keys[Math.floor(Math.random() * keys.length)];
}
}
/**
* Helper function for event investigation
* @param element
* @param {Array|string} events - array or category of events
* @param {boolean} prevent - preventDefault
*/
function spy(element, events, prevent) {
switch (events) {
case 'mouse':
events = ["mousedown", "mouseenter",
"mouseleave", "mousemove", "mouseout", "mouseover",
"mouseup", "mousewheel"];
break;
case 'drag':
events = ["drag", "dragdrop", "dragend",
"dragenter", "dragexit", "draggesture",
"dragleave", "dragover", "dragstart", "drop"];
break;
case 'focus':
events = ["blur", "change", "DOMFocusIn",
"DOMFocusOut", "focus", "focusin", "focusout"];
break;
case 'text':
events = ["compositionend", "compositionstart",
"compositionupdate", "copy", "cut", "paste",
"select", "text"];
break;
}
var event;
while (event = events.pop())
try {
on(element, event, function (e) {
l(e.type, e);
if (prevent)
e.preventDefault();
});
}
catch (e) {
console.error(e);
}
}
function join(array) {
return Array.prototype.join.call(array, ' ');
}
/**
* Translate text user language
* @param {string} text
* @returns {string}
*/
function t(text) {
var translation = uk[text];
return translation || text;
}
/**
* Loads the script
* @param {url} src
* @param {function} call
* @returns {HTMLElement}
*/
function loadScript(src, call) {
var script = document.createElement('script');
script.setAttribute('src', src);
script.onload = call;
document.body.appendChild(script);
return script;
}
function slice(array, begin, end) {
return Array.prototype.slice.call(array, begin, end)
}
var last = window.last || function(array) {
return array[array.length - 1];
};
function when(promises, event, call) {
for (var i = 0; i < promises.length; i++)
on(promises[i], event, function() {
if (promises.length > 0)
promises.pop();
if (0 == promises.length)
call();
});
}
function guid() {
return Math.round(Number.MAX_SAFE_INTEGER * Math.random()).toString(36);
}
function loadObjects(ajaxes) {
return ajaxes.map(function(call, url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
if ('string' == typeof call)
xhr.onload = function(e) {
window[call] = JSON.parse(e.target.responseText);
};
else
xhr.onload = call;
xhr.send(null);
return xhr;
});
}
ext(XMLHttpRequest, {
sendObject: function(object) {
this.setRequestHeader('Content-Type', 'application/json;charset=utf-8');
object = JSON.stringify(object);
this.send(object);
},
get responseObject() {
return JSON.parse(this.responseText);
}
});
function request(method, url, call) {
var xhr = new XMLHttpRequest();
var data;
var form;
if ('object' == typeof method) {
form = method;
if (2 == arguments.length) {
call = url;
url = form.action;
}
data = method instanceof HTMLFormElement
? every($all('[name]', form), function(input) {
var value = input.value;
if ('number' == input.type)
value = parseInt(value);
this[input.name] = value;
})
: form.data;
xhr.requestObject = data;
data = JSON.stringify(data);
method = form.getAttribute('method').toUpperCase();
}
xhr.open(method, url);
if (call) {
xhr.onloadend = call;
xhr.send(data);
}
}
/**
* Utilities and extensions for working with user interface
*/
"use strict";
/**
* Helper function for making movable elements
* @param {Element|selector} it - element for movement
* @param {Element} [root] - container
* @param call - movement implementation callback
*/
function movable(it, root, call) {
if ('string' == typeof it)
it = document.querySelector(it);
if (2 == arguments.length) {
call = root;
root = document.body;
}
if (call instanceof Element)
call = (function(e) {
this.addFloat('left', e.movementX);
this.addFloat('top', e.movementY);
}).bind(call);
it._mousemove = function(e) {
if (it.motionStart)
call.call(it, e);
};
it._mouseup = function(e) {
off(root, 'mouseleave', it._mouseup);
off(root, 'mouseup', it._mouseup);
off(root, 'mousemove', it._mousemove);
it.motionStart = null;
};
on(it, 'mousedown', function(e) {
on(root, 'mousemove', it._mousemove);
on(root, 'mouseup', it._mouseup);
on(root, 'mouseleave', it._mouseup);
it.motionStart = e;
});
it.style.cursor = 'move';
}
define(MouseEvent, 'box', {
/**
* Gets bounding client rect for event
* @returns {ClientRect}
* @see Element.prototype.getBoundingClientRect
*/
get: function() {
if (!this._box)
this._box = this.target.getBoundingClientRect();
return this._box
}
});
function add(element, name, value, min) {
value = parseInt(element.style[name]) + value;
if (value < (min || 0))
return false;
element.style[name] = Math.round(value) + 'px';
return true;
}
/**
* Shorthand for querySelector
* @param {string} selector
* @param {Element} [context]
* @returns {Element}
* @see Element.prototype.querySelector
*/
function $$(selector, context) {
return (context || document).querySelector(selector);
}
/**
* Shorthand for querySelectorAll
* @param {string} selector
* @param {Element} [context]
* @returns {NodeList}
* @see Element.prototype.querySelectorAll
*/
function $all(selector, context) {
return (context || document).querySelectorAll(selector);
}
function $id(id) {
return document.getElementById(id);
}
function $new(info) {
if (!info)
return console.warn('No information to create');
if ('string' == typeof info)
info = {_:'span', $:info};
else if(info instanceof Array) {
info = {
$: info
}
}
var _ = info._ || 'div';
if (_ instanceof Element)
_.detach();
else
_ = document.createElement(_);
delete info._;
if ('$' in info) {
if ('string' == typeof info.$)
_.innerHTML = info.$;
else if (info.$.length)
for (var i = 0; i < info.$.length; i++) {
var item = info.$[i];
if (item)
_.appendChild(item instanceof Element ? item : $new(item));
}
else {
_.innerHTML = '';
_.appendChild(info.$ instanceof Element ? info.$ : $new(info.$));
}
delete info.$;
}
_.update(info);
_.attach();
return _;
}
function $row(values, keys) {
var $tr = document.createElement('tr');
function append(value) {
var $cell = document.createElement('td');
if (null === value || undefined === value)
$cell.innerHTML = '';
else if ('object' != typeof value)
$cell.innerHTML = value;
else if(value instanceof Element)
$cell.appendChild(value);
else
$cell.appendChild($new(value));
$tr.appendChild($cell)
}
var i;
if (keys)
for (i = 0; i < keys.length; i++)
append(values[keys[i]])
else
for(i in values)
append(values[i]);
if ('uid' in values)
$tr.id = values['uid'];
return $tr;
}
function $editable(object, change) {
return object.mapObject(function(value, key) {
var input = {_:'input', name:key};
switch (typeof value) {
case 'boolean':
input.type = 'checkbox';
if (value)
input['checked'] = 'checked';
break;
case 'number':
input.type = 'number';
break;
default:
input.type = 'text';
}
if (!('value' in input) && null != value)
input.value = value;
input.onchange = change;
return $new(input);
});
}
var $busy;
on(document, 'DOMContentLoaded', function() {
$busy = $id('busy');
if ($busy) {
$busy.remove();
}
});
ext(Element, {
update: function(info) {
var _ = this;
if (info.checked)
_.setAttribute('checked', 'checked');
if (info.class instanceof Array)
info.class = info.class.join(' ');
if (info.data) {
for(var key in info.data)
_.dataset[key] = info.data[key];
delete info.data;
}
for(var key in info) {
var attr = info[key];
if (0 == key.indexOf('on') && attr)
on(_, key.slice(2), attr);
else if ('$' == key[0])
_.style.setProperty(key.slice(1), attr);
else
_.setAttribute(key, attr);
}
if (!info.checked)
_.removeAttribute('checked');
return _;
},
/**
* Initialize CSS properites for element
* @param properties
*/
initStyle: function initStyle(properties) {
if ('string' == typeof properties)
properties = properties.split(' ');
var style = getComputedStyle(this);
for(var i=0; i<properties.length; i++) {
var name = properties[i];
this.style.setProperty(name, style[name]);
}
},
/**
* Appends children to element
* @param list - iterable list of elements
*/
appendAll: function(list) {
this.detach();
for(var i in list)
this.appendChild(list[i]);
this.attach();
},
/**
* Temporary detaches element from DOM
*/
detach: function(animate, full) {
var parent_index = indexOf(this);
if (parent_index != this.parentNode.childNodes.length - 1)
this._parent_index = parent_index;
this._parent = this.parentNode;
this.remove();
if (animate && !$$('#busy')) {
this._busy = $busy;
if (full)
this._busy.classList.add('full');
else
this._busy.classList.remove('full');
if ('_parent_index' in this)
this._parent.insertBefore(this._busy, this._parent.childNodes[this._parent_index]);
else
this._parent.appendChild(this._busy);
this._busy.style.display = 'block';
}
return this;
},
/**
* Insert element in the same position that it was detached
*/
attach: function() {
if (this.parentNode || !this._parent)
return;
if (this._busy) {
this._parent.removeChild(this._busy);
this._busy.classList.toggle('full');
this._busy.style.removeProperty('display');
document.body.appendChild(this._busy);
delete this._busy;
}
if ('_parent_index' in this)
this._parent.insertBefore(this, this._parent.childNodes[this._parent_index]);
else
this._parent.appendChild(this);
delete this._parent;
delete this._parent_index;
},
getAncestorByTagName: function(tag) {
tag = tag.toUpperCase();
for(var current = this.parentNode || this._parent;
current; current = current.parentNode || current._parent)
if (tag == current.nodeName)
return current;
console.error('Parent node not found');
},
/**
* Simulates click
*/
click: function() {
this.dispatchEvent(new MouseEvent('click'));
},
describe: function() {
if (this.hasAttribute('id'))
return this.id;
var attributes = this.attributes.map(function(attr) {
return attr.nodeName + '="' + attr.value + '"';
});
return '<' + this.nodeName.toLowerCase() + ' ' + attributes.join(' ') + '>';
},
/**
* Computes CSS property and cast it to integer
* @param name - CSS integer property name
* @returns {Number}
*/
getInt: function(name) {
return parseInt(this.style[name] || getComputedStyle(this)[name]);
},
/**
* Setting CSS property assuming it is measured in pixels if suffix is not set
* @param {string} name - CSS integer property name
* @param {Number} value - integer value (without the suffix)
* @param {string} [suffix]
*/
setInt: function(name, value, suffix) {
this.style[name] = Math.round(value) + (suffix || 'px');
},
/**
* Adds integer value to CSS property, equivalent to Model.prototype.inc
* @param {string} name - CSS integer property name
* @param {Number} value - value to add
* @param {string} [suffix]
*/
addInt: function(name, value, suffix) {
this.setInt(name, this.getInt(name) + value, suffix);
},
/**
* Computes CSS property and cast it to float
* @param name - CSS float property name
* @returns {Number}
*/
getFloat: function(name) {
return parseFloat(this.style[name] || getComputedStyle(this)[name]);
},
/**
* Setting CSS property assuming it is measured in pixels if suffix is not set
* @param {string} name - CSS float property name
* @param {Number} value - float value (without the suffix)
* @param {string} [suffix]
*/
setFloat: function(name, value, suffix) {
this.style[name] = value + (suffix || 'px');
},
/**
* Adds float value to CSS property, equivalent to Model.prototype.inc
* @param {string} name - CSS float property name
* @param {Number} value - value to add
* @param {string} [suffix]
*/
addFloat: function(name, value, suffix) {
this.setFloat(name, this.getFloat(name) + value, suffix);
},
replace: function(newElement) {
if ('string' == typeof newElement)
newElement = $$(newElement);
else if (!(newElement instanceof Element))
newElement = $new(newElement);
this.parentNode.insertBefore(newElement, this);
this.remove();
return newElement;
},
insertNextSibling: function(newElement) {
newElement = this.replace(newElement);
newElement.parentNode.insertBefore(this, newElement);
}
});
ext(NodeList, {
on: function(event, call) {
for (var i = 0; i < this.length; i++)
on(this[i], event, call)
}
});
function click(selector, context) {
var $element = $$(selector, context);
if ($element)
$element.click();
return $element;
}
function submenu(menu, _submenu) {
function hideOnFocusLost() {
off('article', hideOnFocusLost);
_submenu.classList.remove('Visible');
}
return function(e) {
if ('mouseover' == e.type && !click('.Visible', menu))
return;
if (_submenu.classList.contains('Visible'))
hideOnFocusLost();
else {
_submenu.classList.add('Visible');
on('article', hideOnFocusLost);
}
};
}
function $svg(info) {
var doc = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
if (info)
doc.appendChild(info);
return doc;
}
function $icon(id) {
var use = document.createElementNS(svgns, 'use');
use.setAttributeNS(xlinkns, 'href', 'bag.svg#' + id);
var icon = $svg(use);
icon.setAttribute('width', '16');
icon.setAttribute('height', '16');
return icon;
}
function $menu($container, items) {
var hasContainer = !!items;
if (hasContainer) {
if ('string' == typeof $container)
$container = $$($container);
$container.detach();
}
else {
items = $container;
$container = $new({class:'menu'});
}
for(var name in items) {
var info = {$:[!hasContainer ? $icon(name) : null, name]};
var item = items[name];
if (item) {
if (item instanceof Function)
info.onclick = item;
else {
var $submenu = $menu(item);
info.onclick = info.onmouseover = submenu($container, $submenu);
info.$.push($submenu);
}
}
else
info.class = 'disable';
$container.appendChild($new(info));
}
if (hasContainer)
$container.attach();
return $container;
}
function $field(info) {
return $new({_:'div', $:[
{_:'label', $:info.title},
merge({_:'input'}, info)
]});
}
ext(HTMLFormElement, {
fill: function(obj) {
for(var key in obj) {
var input = $$('[name=' + key + ']', this);
if (input)
input.value = obj[key];
}
},
show: function(method, url, button, to_hide, call) {
if (method)
this.method = method;
if (url)
this.action = url;
var b = $$('[type=submit]', this);
if (button)
b.innerHTML = button;
replace(to_hide, this);
var it = this;
b.onclick = function(e) {
e.preventDefault();
request(it, function(e) {
replace(it, to_hide);
call(e);
});
};
}
});
function $select(array) {
var select = document.createElement('select');
var option;
if (array instanceof Array)
for (var i = 0; i < array.length; i++) {
option = document.createElement('option');
option.value = array[i];
option.innerHTML = array[i];
select.appendChild(option);
}
else
for(var key in array) {
option = document.createElement('option');
option.value = key;
option.innerHTML = array[key];
select.appendChild(option);
}
return select;
}
function $form(schema) {
var form = {_:'form', method:'post', $:[]};
var i=1;
for(var key in schema) {
var column = schema[key];
switch (typeof column) {
case 'string':
column = {
type: 'text',
title: column
};
break;
default:
break;
}
column.name = key;
if ('hidden' == column.type)
column = $new(merge({_:'input'}, column));
else {
if (!('title' in column))
column.title = key.slice(0, 1).toUpperCase() + key.slice(1);
column.tabindex = i++;
schema[key] = column;
column = $field(column);
}
form.$.push(column);
}
form.$.push({_:'button', type:'submit', $:'submit'});
form = $new(form);
form.onsubmit = function(e) {
e.preventDefault();
request(this, function() {
l(this.responseText);
form.dispatchEvent(new CustomEvent('success', {
detail:this.responseObject
}));
});
};
form.schema = schema;
return form;
}
function replace(a, b) {
a.classList.remove('visible');
b.classList.add('visible');
}
function $table(info) {
if (!(info.$ instanceof Array))
info.$ = [];
var table = {_:'table', $:[], class:['visible', 'table']};
var schema;
var edit;
var rows;
var page = 0;
var keys;
if (info.editable)
var add = {class:['button', 'glyphicon', 'glyphicon-plus'], onclick: function() {
var root = this.getAncestorByTagName('div');
edit.show('put', info.url, 'Створити', $$('table', root), Function());
}};
if ('schema' in info) {
edit = $form(info.schema);
if (info.editable) {
edit.action = info.url;
}
table.$.push({_:'thead', $:{_:'tr', $:info.schema.map(function(column) {
return {_:'th', class:'sorting', $:column.title, onclick:function() {
var old = $$('th:not(.sorting)');
if (old && this.innerHTML != old.innerHTML)
old.setAttribute('class', 'sorting');
rows.orderBy(this.innerHTML);
this.setAttribute('class', 'sorting_asc' == this.getAttribute('class')
? 'sorting_desc' : 'sorting_asc');
if ('sorting_desc' == this.getAttribute('class'))
rows.reverse();
render();
}};
})}});
schema = info.schema;
keys = Object.keys(schema);
delete info.schema;
}
function table_row(row) {
var tr = $row(row, keys);
if (info.onrow) {
tr.setAttribute('draggable', 'true');
register(tr, info.onrow);
}
if (info.editable) {
tr.appendChild($new({_: 'td', class: ['button', 'glyphicon', 'glyphicon-edit'], onclick: function () {
var tr = this.getAncestorByTagName('tr');
var root = this.getAncestorByTagName('div');
edit.fill(rows.findFirst('uid', tr.id));
edit.show('post', info.url + '/' + tr.id, 'Зберегти', $$('table', root), function (e) {
var object = e.target.requestObject;
//@todo uid must save in form
object.uid = row.uid;
tr.replace(table_row(object))
});
}}));
tr.appendChild($new({_: 'td', class: ['button', 'glyphicon', 'glyphicon-remove'], onclick: function () {
request('DELETE', info.url + '/' + row.uid, function () {
tr.remove();
});
}}));
}
return tr;
}
function render() {
var container = edit.getAncestorByTagName('div');
var tbody = $$('tbody', container);
if (!tbody) {
tbody = document.createElement('tbody');
$$('table', container).appendChild(tbody);
}
tbody.innerHTML = '';
var start = page * size.value;
var end = start + parseInt(size.value);
for(var i = start; i < end && i < rows.length; i++)
tbody.appendChild(table_row(rows[i]));
var page_count = Math.ceil(rows.length / size.value);
pages.innerHTML = '';
for(i = 0; i < page_count; i++) {
var page_info = {
_: 'li', $:{_:'a', $:(i + 1).toString(), onclick: function (e) {
e.preventDefault();
page = this.innerHTML - 1;
render();
}
}};
if (page == i)
page_info.class = 'active';
pages.appendChild($new(page_info))
}
}
var size = $select([10, 25, 50, 100]);
size.onchange = render;
var pages = $new({_:'ul', class: 'pagination'});
if (info.editable)
info.$.push(add);
info.$.push(size);
// if (info.editable)
info.$.push(edit);
info.$.push(table);
info.$.push(pages);
request('GET', info.url, function(e) {
rows = JSON.parse(e.target.responseText);
render();
});
return $new(info);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment