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) { (function (should, commandr, bindr, Container) {
var app = { var app = {
views: {} views: {},
models: {}
}; };
var commandHandlers = {}; var commandHandlers = {};
@@ -23,11 +24,8 @@
app.service = app.component; 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) { app.command = function(name, handler) {
var cmd = commandHandlers[name]; var cmd = commandHandlers[name];
@@ -58,34 +56,15 @@
}; };
app.bootstrap = function(rootViewElement) { app.bootstrap = function(rootViewElement) {
this.rootViewElement = rootViewElement;
invokeRunObservers(); invokeRunObservers();
bindr.bindControllers(rootViewElement, app.di);
}; };
function invokeRunObservers() { function invokeRunObservers() {
runObservers.forEach(function(o){ o(); }); 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.app = app;
})(window.should, window.commandr, window.bindr, window.Container); })(window.should, window.commandr, window.bindr, window.Container);

View File

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

View File

@@ -20,7 +20,11 @@
clear: function (){ clear: function (){
this.viewElement.innerHTML = ''; this.viewElement.innerHTML = '';
}, },
insert: function (htmlElement) { display: function (htmlElement) {
if(typeof htmlElement.tagName == "undefined") {
htmlElement = app.buildViewFor(htmlElement);
}
var vw = this.viewElement; var vw = this.viewElement;
if(vw.childNodes.length == 0) { if(vw.childNodes.length == 0) {
vw.appendChild(htmlElement); 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', { app.service('html', {
builder: function () { builder: function () {
return new HtmlBuilder(); 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); })(window.app, window.HtmlBuilder);

View File

@@ -4,42 +4,37 @@
var formatter = app.service('formatter'); var formatter = app.service('formatter');
var calc = app.service('calc'); var calc = app.service('calc');
function ExpressionView(expression) { app.modelView(app.models.BitwiseOperation, {
this.expression = expression; $html:null,
} renderView: function(model) {
return renderCalculableExpression(model, this.$html.builder());
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.BitwiseNumbers, {
$html:null,
renderView: function(model) {
return renderListOfNumbers(model.numbers, this.$html.builder());
}
});
function renderCalculableExpression(expr, hb) { function renderCalculableExpression(expr, hb) {
var result = expr.calculate(), var maxLen = calc.maxNumberOfBits([expr.operand1, expr.operand2, expr.result]);
maxLen = calc.maxNumberOfBits([expr.operand1, expr.operand2, result]);
hb.element('table', { class: "expression", cellspacing: "0"}, function () { hb.element('table', { class: "expression", cellspacing: "0"}, function () {
buildRow(hb, expr.operand1, formatter.toBinaryString(expr.operand1, maxLen)); buildRow(hb, expr.operand1, formatter.toBinaryString(expr.operand1, maxLen));
buildRow(hb, expr.operand2, formatter.toBinaryString(expr.operand2, 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(); return hb.toHtmlElement();
} }
function renderListOfNumbers(expr, hb) { function renderListOfNumbers(numbers, hb) {
var maxLen = calc.maxNumberOfBits(expr.operands); var maxLen = calc.maxNumberOfBits(numbers);
hb.element('table', { class: "expression", cellspacing: "0"}, function () { hb.element('table', { class: "expression", cellspacing: "0"}, function () {
expr.operands.forEach(function(o){ numbers.forEach(function(o){
buildRow(hb, o, formatter.toBinaryString(o, maxLen)); buildRow(hb, o, formatter.toBinaryString(o, maxLen));
}); });
}); });
@@ -62,27 +57,18 @@
} }
} }
app.views.ExpressionView = ExpressionView; app.modelView(app.models.HelpResult, {
$html: null,
})(window.app); renderView: function(model) {
var hb = this.$html.builder();
// Help View var commands = model.commands;
(function(app){ hb.element('ul', { class: 'result' }, function() {
function HelpView(commands) { commands.forEach(function(c) {
this.commands = commands; hb.element('li', c.name + " — " + c.description);
} });});
return hb.toHtmlElement();
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;
})(window.app); })(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) { function bindInput(model, intput, propertyName) {
intput.addEventListener('keyup', function(e){ intput.addEventListener('keyup', function(e){
model[propertyName] = e.srcElement.value; 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 (){ HtmlBuilder.prototype.toHtmlElement = function (){
return HtmlBuilder.createElement(this.toString());
};
HtmlBuilder.createElement = function(html) {
var el = document.createElement('div'); var el = document.createElement('div');
el.innerHTML = this.toString(); el.innerHTML = html;
return el.children[0]; 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/bindr.js"></script>
<script type="text/javascript" src="components/commandr.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/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="components/container.js"></script>
<script type="text/javascript" src="app/app.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/calc.js"></script>
<script type="text/javascript" src="app/bitwise/expression.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/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/views.js"></script>
<script type="text/javascript" src="app/dispatcher.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/controllers.js"></script>
<script type="text/javascript" src="app/services.js"></script> <script type="text/javascript" src="app/services.js"></script>
@@ -50,8 +54,7 @@
return; return;
} }
var expressionView = new window.app.views.ExpressionView(expr); this.$resultView.display(expr);
this.$resultView.insert(expressionView.render());
cmdArgs.commandHandled = true; cmdArgs.commandHandled = true;
} }
@@ -68,9 +71,9 @@
{ name: 'clear', description: 'Clears console'} { 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; cmdArgs.commandHandled = true;
}); });
@@ -90,12 +93,13 @@
var hb = html.builder(); var hb = html.builder();
hb.element('div', { class: "result error", html: "Unknown expression: " + cmdArgs.input }); hb.element('div', { class: "result error", html: "Unknown expression: " + cmdArgs.input });
resultView.insert(hb.toHtmlElement()); resultView.display(hb.toHtmlElement());
cmdArgs.commandHandled = true; cmdArgs.commandHandled = true;
}); });
app.bootstrap(document.getElementById('rootView')); app.bootstrap(document.getElementById('rootView'));
})(); })();
</script> </script>