Мля, а круто получается, вот весь код (пока быдлокод рефакторинг дело последнее)
<script>
//noinspection JSCheckFunctionSignatures
(function(window, document, undefined) {
var ui = {};
ui.module = {};
document.addEventListener('DOMContentLoaded', function() {
initModules();
});
function initModules(appContainer, scope) {
appContainer = appContainer || document.body;
scope = scope || {};
var children = appContainer.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
var uiAttr = child.getAttribute('ui');
// мы наткнулись на декларацию модуля
if (uiAttr && /^\w+$/.test(uiAttr)) {
var name = uiAttr;
if (!ui.module[name]) continue;
var container = child;
var widgets = findWidgets(child, uiAttr);
var newScope = Object.create(scope);
var module = new ui.module[name](newScope, widgets, container);
ui[name] = ui[name] || [];
ui[name].push(module);
}
initModules(child, scope);
}
}
function findWidgets(moduleElement, moduleName) {
function widget(name, handler) {
if (widget[name]) widget[name].forEach(handler);
}
findIn(moduleElement);
function findIn(element) {
var children = element.children;
for (var i = 0; i < children.length; i++) {
var child = children[i];
var uiAttr = child.getAttribute('ui');
var regExp = new RegExp(moduleName + ':(\\w+)');
if (uiAttr === moduleName) continue;
if (uiAttr && regExp.test(uiAttr)) {
var widgetName = regExp.exec(uiAttr)[1];
widget[widgetName] = widget[widgetName] || [];
widget[widgetName].push(child);
}
findIn(child);
}
}
return widget;
}
window['ui'] = ui;
})(window, document);
ui.module['window'] = function($scope, $widget, $moduleElement) {
var animationId;
var dragging = false;
var clickX = 0, clickY = 0;
var x = 0, y = 0;
$widget('close', function(close) {
close.addEventListener('click', function() {
$moduleElement.style.display = 'none';
});
});
$widget('title', function(title) {
title.addEventListener('mousedown', function(event) {
var rect = $moduleElement.getBoundingClientRect();
clickX = event.clientX - rect.left;
clickY = event.clientY - rect.top;
dragging = true;
event.preventDefault();
render();
});
});
window.addEventListener('mousemove', function(event) {
x = event.clientX;
y = event.clientY;
});
window.addEventListener('mouseup', function() {
dragging = false;
cancelRequestAnimationFrame(animationId);
});
function render() {
animationId = requestAnimationFrame(render, $moduleElement);
if (!dragging) return;
$moduleElement.style.left = x - clickX + 'px';
$moduleElement.style.top = y - clickY + 'px';
}
};
ui.module['chat'] = function($scope, $widgets, $moduleElement) {
$widgets('input', function(input) {
input.addEventListener('input', function(event) {
$widgets('post', function(post) {
post.innerHTML = event.target.value;
});
});
});
};
</script>
<style type="text/css">
.window {
position : absolute;
background-color : dodgerblue;
width : 300px;
height : 250px;
}
.window_header {
cursor : pointer;
background-color : coral;
}
</style>
<div ui="window" class="window">
<h1 ui="window:title" class="window_header">Заголовок окна</h1>
<button ui="window:close">Закрыть</button>
<h2 ui="window:title" class="window_header">Тоже заголовок окна</h2>
<div ui="chat">
<div ui="chat:post"></div>
<input ui="chat:input">
</div>
</div>