Last active
January 10, 2017 20:40
-
-
Save kissarat/8550959 to your computer and use it in GitHub Desktop.
Updated from https://github.com/kissarat/designer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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