Extracted mode views and controllers into separate features.

This commit is contained in:
Borys Levytskyi
2015-04-03 17:15:51 +03:00
parent fd03747475
commit 97eed3fa1e
11 changed files with 215 additions and 123 deletions

View File

@@ -1,7 +1,8 @@
(function (should, commandr, bindr, Container) {
var app = {
views: {}
views: {},
models: {}
};
var commandHandlers = {};
@@ -23,11 +24,8 @@
app.service = app.component;
app.controller = function(name, inst) {
this.addControllerMixin(inst);
this.di.register(name, inst);
};
// TODO: introduce command feature
app.command = function(name, handler) {
var cmd = commandHandlers[name];
@@ -58,34 +56,15 @@
};
app.bootstrap = function(rootViewElement) {
this.rootViewElement = rootViewElement;
invokeRunObservers();
bindr.bindControllers(rootViewElement, app.di);
};
function invokeRunObservers() {
runObservers.forEach(function(o){ o(); });
}
app.addControllerMixin = function (component) {
component.attachView = function(viewElement) {
this.viewElement = viewElement;
if(typeof component.onViewAttached == 'function') {
component.onViewAttached(viewElement);
}
};
component.detachView = function() {
this.viewElement = null;
if(typeof component.onViewDetached == 'function') {
component.onViewDetached(viewElement);
}
};
}
window.app = app;
})(window.should, window.commandr, window.bindr, window.Container);

View File

@@ -24,15 +24,14 @@
var o1 = parseInt(matches[1], 10);
var o2 = parseInt(matches[3], 10);
return {
string: matches.input,
operand1: o1,
sign: matches[2],
operand2: o2,
calculate: function() {
return eval(this.string);
}
}
var m = new app.models.BitwiseOperation();
m.operand1 = o1;
m.operand2 = o2;
m.sing = matches[2];
m.string = matches.input;
m.result = eval(matches.input);
return m;
}
function createListOfNumbersExpression(matches) {
@@ -42,10 +41,7 @@
numbers.push(parseInt(matches[i], 10));
}
return {
string:matches.input,
operands: numbers
}
return app.models.BitwiseNumbers(numbers);
}
})(window.app);

View File

@@ -20,7 +20,11 @@
clear: function (){
this.viewElement.innerHTML = '';
},
insert: function (htmlElement) {
display: function (htmlElement) {
if(typeof htmlElement.tagName == "undefined") {
htmlElement = app.buildViewFor(htmlElement);
}
var vw = this.viewElement;
if(vw.childNodes.length == 0) {
vw.appendChild(htmlElement);

27
app/models.js Normal file
View File

@@ -0,0 +1,27 @@
(function(app) {
function BitwiseOperation () {
}
BitwiseOperation.prototype.calculate = function () {
return eval(this.string);
};
function BitwiseNumbers(numbers) {
this.numbers = numbers;
}
function ErrorResult(message) {
this.message = message;
}
function HelpResult (commands) {
this.commands = commands;
}
app.models.BitwiseOperation = BitwiseOperation;
app.models.BitwiseNumbers = BitwiseNumbers;
app.models.ErrorResult = ErrorResult;
app.models.HelpResult = HelpResult;
})(window.app);

View File

@@ -3,7 +3,45 @@
app.service('html', {
builder: function () {
return new HtmlBuilder();
},
view: function (tml, model) {
var func = template.compile(tml);
return HtmlBuilder.createElement(func(model));
}
});
var template = {
compile: function (template) {
var regex = /(?:{([^}]+)})/g;
var sb = [];
sb.push('(function() {')
sb.push('return function (model) { ')
sb.push('\tvar html = [];')
sb.push('\twith (model) { ')
var m, index = 0;
while ((m = regex.exec(template)) !== null) {
if(m.index > index) {
sb.push("\t\thtml.push('" + normalize(template.substr(index, m.index - index)) + "');");
}
sb.push('\t\thtml.push(' + m[1] + ');');
index = m.index + m[0].length;
}
if(index < template.length - 1) {
sb.push("\t\thtml.push('" + normalize(template.substr(index, template.length - index)) + "');");
}
sb.push('\t}');
sb.push("\treturn html.join('');");
sb.push('}');
sb.push('})()')
console.log(sb.join('\r\n'));
return eval(sb.join('\r\n'));
}
};
function normalize(str) {
return str.replace(/(\r|\n)+/g, '').replace("'", "\\\'");
}
})(window.app, window.HtmlBuilder);

View File

@@ -4,42 +4,37 @@
var formatter = app.service('formatter');
var calc = app.service('calc');
function ExpressionView(expression) {
this.expression = expression;
}
ExpressionView.prototype.render = function () {
var expr = this.expression,
hb = app.service('html').builder();
console.log('Rendering expression: ', expr)
if(typeof expr.calculate == "function") {
return renderCalculableExpression(expr, hb);
} else {
return renderListOfNumbers(expr, hb);
app.modelView(app.models.BitwiseOperation, {
$html:null,
renderView: function(model) {
return renderCalculableExpression(model, this.$html.builder());
}
});
};
app.modelView(app.models.BitwiseNumbers, {
$html:null,
renderView: function(model) {
return renderListOfNumbers(model.numbers, this.$html.builder());
}
});
function renderCalculableExpression(expr, hb) {
var result = expr.calculate(),
maxLen = calc.maxNumberOfBits([expr.operand1, expr.operand2, result]);
var maxLen = calc.maxNumberOfBits([expr.operand1, expr.operand2, expr.result]);
hb.element('table', { class: "expression", cellspacing: "0"}, function () {
buildRow(hb, expr.operand1, formatter.toBinaryString(expr.operand1, maxLen));
buildRow(hb, expr.operand2, formatter.toBinaryString(expr.operand2, maxLen));
buildRow(hb, expr.string, formatter.toBinaryString(result, maxLen), { class: 'result'});
buildRow(hb, expr.result, formatter.toBinaryString(expr.result, maxLen), { class: 'result'});
});
return hb.toHtmlElement();
}
function renderListOfNumbers(expr, hb) {
var maxLen = calc.maxNumberOfBits(expr.operands);
function renderListOfNumbers(numbers, hb) {
var maxLen = calc.maxNumberOfBits(numbers);
hb.element('table', { class: "expression", cellspacing: "0"}, function () {
expr.operands.forEach(function(o){
numbers.forEach(function(o){
buildRow(hb, o, formatter.toBinaryString(o, maxLen));
});
});
@@ -62,27 +57,18 @@
}
}
app.views.ExpressionView = ExpressionView;
})(window.app);
// Help View
(function(app){
function HelpView(commands) {
this.commands = commands;
}
HelpView.prototype.render = function() {
var hb = app.service('html').builder();
var commands = this.commands;
hb.element('ul', { class: 'result' }, function() {
commands.forEach(function(c) {
hb.element('li', c.name + " — " + c.description);
});});
return hb.toHtmlElement();
};
app.views.HelpView = HelpView;
app.modelView(app.models.HelpResult, {
$html: null,
renderView: function(model) {
var hb = this.$html.builder();
var commands = model.commands;
hb.element('ul', { class: 'result' }, function() {
commands.forEach(function(c) {
hb.element('li', c.name + " — " + c.description);
});});
return hb.toHtmlElement();
}
});
})(window.app);

View File

@@ -72,40 +72,6 @@
};
bindr.bindControllers = function (rootViewElement, container) {
var elements = rootViewElement.querySelectorAll('[data-controller]'),
i = 0, l = elements.length, ctrlName, ctrl, attached;
for(;i<l;i++){
var element = elements[i];
ctrlName = element.getAttribute('data-controller');
ctrl = container.resolve(ctrlName);
attached = [];
if(ctrl == null) {
console.warn(ctrlName + ' controller wasn\'t found');
continue;
}
ctrl.attachView(element);
attached.push(ctrl);
console.log(ctrlName + ' Controller: view attached');
if(typeof ctrl.detachView != "function") {
continue;
}
element.addEventListener('DOMNodeRemoved', function (evt) {
if(element === evt.target) {
ctrl.detachView();
}
console.log(ctrlName + ' Controller: view detached');
});
}
};
function bindInput(model, intput, propertyName) {
intput.addEventListener('keyup', function(e){
model[propertyName] = e.srcElement.value;

View File

@@ -0,0 +1,67 @@
(function(app) {
app.controller = function(name, inst) {
addControllerMixin(inst);
this.di.register(name, inst);
};
function addControllerMixin(component) {
component.attachView = function(viewElement) {
this.viewElement = viewElement;
if(typeof component.onViewAttached == 'function') {
component.onViewAttached(viewElement);
}
};
component.detachView = function() {
this.viewElement = null;
if(typeof component.onViewDetached == 'function') {
component.onViewDetached(viewElement);
}
};
}
app.run(function(){
attachControllers(app.rootViewElement, app.di);
});
function attachControllers(rootViewElement, di) {
var elements = rootViewElement.querySelectorAll('[data-controller]'),
i = 0, l = elements.length,
ctrlName,
ctrl, element;
for(;i<l;i++){
element = elements[i];
ctrlName = element.getAttribute('data-controller');
ctrl = di.resolve(ctrlName);
if(ctrl == null) {
console.warn(ctrlName + ' controller wasn\'t found');
continue;
}
ctrl.attachView(element);
console.log(ctrlName + ' Controller: view attached');
if(typeof ctrl.detachView != "function") {
continue;
}
// TODO: get rid from closure
element.addEventListener('DOMNodeRemoved', function (evt) {
if(element === evt.target) {
ctrl.detachView();
}
console.log(ctrlName + ' Controller: view detached');
});
}
}
})(window.app);

View File

@@ -35,8 +35,12 @@
};
HtmlBuilder.prototype.toHtmlElement = function (){
return HtmlBuilder.createElement(this.toString());
};
HtmlBuilder.createElement = function(html) {
var el = document.createElement('div');
el.innerHTML = this.toString();
el.innerHTML = html;
return el.children[0];
};

View File

@@ -0,0 +1,21 @@
(function(app){
app.modelView = function (modelCtor, builder) {
var name = getKey(modelCtor);
app.di.register(name, builder);
};
app.buildViewFor = function(model) {
var key = getKey(model.constructor);
var builder = this.di.resolve(key);
return builder.renderView(model);
};
function getKey(modelCtor) {
return getFunctionName(modelCtor) + "ViewBuilder";
}
function getFunctionName(func) {
var str = func.toString();
return str.substr(8, str.indexOf('(') - 8).trim();
}
})(window.app);

View File

@@ -8,15 +8,19 @@
<script type="text/javascript" src="components/bindr.js"></script>
<script type="text/javascript" src="components/commandr.js"></script>
<script type="text/javascript" src="components/should.js"></script>
<script type="text/javascript" src="components/html.js"></script>
<script type="text/javascript" src="components/htmlBuilder.js"></script>
<script type="text/javascript" src="components/container.js"></script>
<script type="text/javascript" src="app/app.js"></script>
<script type="text/javascript" src="components/controllersFeature.js"></script>
<script type="text/javascript" src="components/modelViewsFeature.js"></script>
<script type="text/javascript" src="app/bitwise/calc.js"></script>
<script type="text/javascript" src="app/bitwise/expression.js"></script>
<script type="text/javascript" src="app/bitwise/formatter.js"></script>
<script type="text/javascript" src="app/models.js"></script>
<script type="text/javascript" src="app/views.js"></script>
<script type="text/javascript" src="app/dispatcher.js"></script>
<script type="text/javascript" src="app/controllers.js"></script>
<script type="text/javascript" src="app/services.js"></script>
@@ -50,8 +54,7 @@
return;
}
var expressionView = new window.app.views.ExpressionView(expr);
this.$resultView.insert(expressionView.render());
this.$resultView.display(expr);
cmdArgs.commandHandled = true;
}
@@ -68,9 +71,9 @@
{ name: 'clear', description: 'Clears console'}
];
var helpView = new app.views.HelpView(commands);
var model = new app.models.HelpResult(commands);
resultView.insert(helpView.render());
resultView.display(model);
cmdArgs.commandHandled = true;
});
@@ -90,12 +93,13 @@
var hb = html.builder();
hb.element('div', { class: "result error", html: "Unknown expression: " + cmdArgs.input });
resultView.insert(hb.toHtmlElement());
resultView.display(hb.toHtmlElement());
cmdArgs.commandHandled = true;
});
app.bootstrap(document.getElementById('rootView'));
})();
</script>