bungee
Version:
Bungee is a declarative language engine to run inside a browser. The node module contains the offline compiler.
1,676 lines (1,393 loc) • 191 kB
JavaScript
;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
(function(){'use strict';
var Bungee = require('./index.js');
// since we are in the browser, register it into the global namespace
window.Bungee = Bungee;
})()
},{"./index.js":2}],3:[function(require,module,exports){
// nothing to see here... no file methods for the browser
},{}],2:[function(require,module,exports){
/*
**************************************************
* Bungee.js
*
* (c) 2012-2013 Johannes Zellner
*
* Bungee may be freely distributed under the MIT license.
* For all details and documentation:
* http://bungeejs.org
**************************************************
*/
"use strict";
var fs = require('fs');
module.exports = (function () {
var ret = {};
ret.Tokenizer = require('./src/tokenizer.js');
ret.Compiler = require('./src/compiler.js');
ret.Engine = require('./src/engine.js');
ret.Elements = require('./src/dom.js');
ret.Animation = require('./src/animation.js');
function mixin(obj) {
for (var e in obj) {
if (obj.hasOwnProperty(e)) {
ret[e] = obj[e];
}
}
}
mixin(ret.Engine);
mixin(ret.Elements);
mixin(ret.Animation);
ret.compileFile = function(file, options, callback) {
fs.readFile(file, 'utf8', function (error, data) {
if (error) {
callback(error);
} else {
ret.compile(data, options, callback);
}
});
};
ret.compile = function(source, options, callback) {
var tokens = ret.Tokenizer.parse(source);
ret.Compiler.compileAndRender(tokens, options, function (error, result) {
callback(error, result);
});
};
ret.debug = false;
ret.verbose = false;
function ensureEngine(engine) {
if (!engine) {
console.log('[Bungee] Using default engine with DOM renderer.');
engine = new ret.Engine(new ret.RendererDOM());
}
return engine;
}
ret.jump = function (engine) {
engine = ensureEngine(engine);
ret.useQueryFlags();
ret.compileScriptTags(engine);
engine.start();
};
ret.useQueryFlags = function() {
// TODO improve detection
ret.verbose = (window.location.href.indexOf("verbose") >= 0);
ret.debug = (window.location.href.indexOf("debug") >= 0);
};
ret.compileScriptTagElement = function(engine, script) {
engine = ensureEngine(engine);
var tokens = ret.Tokenizer.parse(script.text);
var moduleName = script.attributes.module && script.attributes.module.textContent;
var o, n;
ret.Compiler.compileAndRender(tokens, { module: moduleName }, function (error, result) {
if (error) {
console.error("Bungee compile error: " + error.line + ": " + error.message);
console.error(" -- " + error.context);
} else {
if (ret.verbose || ret.debug) {
console.log("----------------------");
console.log(result);
console.log("----------------------");
console.log("eval...");
o = new Date();
}
if (result.indexOf('module.exports = ') === 0) {
if (!ret.Modules) {
ret.Modules = {};
}
result = result.replace('module.exports = ', 'Bungee.Modules.' + moduleName + ' = ');
eval(result);
} else {
var tmp = eval(result);
tmp(Bungee, engine);
}
if (ret.verbose || ret.debug) {
n = new Date();
console.log("done, eval took time: ", (n - o), "ms");
}
}
});
};
ret.compileScriptTags = function(engine, dom) {
engine = ensureEngine(engine);
for (var i = 0; i < window.document.scripts.length; ++i) {
var script = window.document.scripts[i];
if (script.type === "text/jmp" || script.type === "text/jump") {
ret.compileScriptTagElement(engine, script);
}
}
};
return ret;
}());
},{"./src/animation.js":8,"./src/compiler.js":5,"./src/dom.js":7,"./src/engine.js":6,"./src/tokenizer.js":4,"fs":3}],5:[function(require,module,exports){
(function(){/*
**************************************************
* Bungee.js
*
* (c) 2012-2013 Johannes Zellner
*
* Bungee may be freely distributed under the MIT license.
* For all details and documentation:
* http://bungeejs.org
**************************************************
*/
"use strict";
/*
**************************************************
* Compiler
**************************************************
*/
if (!Bungee) {
var Bungee = {};
}
var compiler = (function () {
// public compiler properties
var compiler = {};
// TODO sort out this kindof global variable mess
var ELEM_PREFIX = "e"; // just a define
var ELEM_NS = "Bungee."; // main namespace
var ENGINE_VAR = 'BungeeEngine';
var output; // output buffer used by all render functions
var index; // index used for tracking the indentation
var errorCodes = {
GENERIC: 0,
UNKNOWN_ELEMENT: 1,
NO_PROPERTY: 2,
NO_ELEMENTTYPE: 3,
NO_TYPENAME: 4,
NO_EXPRESSION: 5,
NO_COLON: 6,
INVALID_PROPERTY_NAME: 7,
UNEXPECTED_END: 8
};
// make error codes public
compiler.errorCodes = errorCodes;
var errorMessages = [];
errorMessages[errorCodes.GENERIC] = "generic error";
errorMessages[errorCodes.UNKNOWN_ELEMENT] = "Cannot create element.";
errorMessages[errorCodes.NO_PROPERTY] = "No property to assing expression.";
errorMessages[errorCodes.NO_ELEMENTTYPE] = "No type to create an element.";
errorMessages[errorCodes.NO_TYPENAME] = "No typename for the new type definition.";
errorMessages[errorCodes.NO_COLON] = "Property must be followed by a ':'.";
errorMessages[errorCodes.NO_EXPRESSION] = "No right-hand-side expression or element found.";
errorMessages[errorCodes.INVALID_PROPERTY_NAME] = "Invalid property name found.";
errorMessages[errorCodes.UNEXPECTED_END] = "Unexpected end of input.";
function error(code, token) {
var ret = {};
ret.code = code;
ret.context = token ? token.CONTEXT : undefined;
ret.message = errorMessages[code];
ret.line = token ? token.LINE : -1;
return ret;
}
function log(msg) {
if (Bungee.verbose) {
console.log(msg);
}
}
function isNumeric (c) {
return (c >= '0' && c <= '9');
}
/*
* adds current indentation to compiler output
*/
function addIndentation(additional) {
var indentLevel = index + (additional ? additional : 0);
var i;
for (i = indentLevel; i; --i) {
output += " ";
}
}
/*
* Renders the head of the javascript output
* Only called once
*/
function renderBegin(options) {
if (options.module) {
output += "module.exports = function (Bungee, " + ENGINE_VAR + ") {\n";
} else {
output += "(function () { return function (Bungee, " + ENGINE_VAR + ") {\n";
}
addIndentation();
output += "'use strict';\n\n";
if (Bungee.debug) {
addIndentation(1);
output += "debugger;\n";
}
// add pseudo parent
addIndentation();
output += "var " + ELEM_PREFIX + " = { \n";
addIndentation(1);
output += "children: [],\n";
addIndentation(1);
output += "addChild: function(child) {\n";
addIndentation(2);
output += "this[child.id] = child;\n";
addIndentation(2);
output += "for (var i in this.children) {\n";
addIndentation(2);
output += " if (this.children.hasOwnProperty(i)) {\n";
addIndentation(2);
output += " this.children[i][child.id] = child;\n";
addIndentation(2);
output += " child[this.children[i].id] = this.children[i];\n";
addIndentation(2);
output += " }\n";
addIndentation(2);
output += "}\n";
addIndentation(2);
output += ELEM_PREFIX + ".children.push(child);\n";
addIndentation(2);
output += ENGINE_VAR + ".addElement(child);\n";
addIndentation(2);
output += "return child;\n";
addIndentation(1);
output += "},\n";
addIndentation(1);
output += "initializeBindings: function() {\n";
addIndentation(2);
output += "for (var i = 0; i < " + ELEM_PREFIX + ".children.length; ++i) { " + ELEM_PREFIX + ".children[i].initializeBindings(); }\n";
addIndentation(1);
output += "},\n";
addIndentation(1);
output += "render: function() {\n";
addIndentation(2);
output += "for (var i = 0; i < " + ELEM_PREFIX + ".children.length; ++i) { " + ELEM_PREFIX + ".children[i].render(); }\n";
addIndentation(1);
output += "}\n";
addIndentation();
output += "};\n\n";
}
/*
* Render the end of the javascript output
* Only called once
*/
function renderEnd(options) {
addIndentation();
output += ELEM_PREFIX + ".initializeBindings();\n";
addIndentation();
output += ELEM_PREFIX + ".render();\n";
if (options.module) {
addIndentation();
output += "return " + ELEM_PREFIX + ";\n";
output += "};\n";
} else {
addIndentation();
output += "};\n";
output += "})();\n";
}
}
/*
* Renders the start of a new Element instance
* Called for each element instantiation in jump
*/
function renderBeginElement(type, id) {
addIndentation();
output += ELEM_PREFIX + ".addChild((function() {\n";
++index;
addIndentation();
output += "var " + ELEM_PREFIX + " = new " + ELEM_NS + type + "(";
output += ENGINE_VAR;
output += id ? ", \"" + id + "\"" : "";
output += ");\n";
}
/*
* Renders the end of a new Element instance
* Called for each element instantiation in jump
*/
function renderEndElement() {
addIndentation();
output += "return " + ELEM_PREFIX + ";\n";
--index;
addIndentation();
output += "})());\n";
}
/*
* Renders the start of a new Type definition
* Called for each type in jump
*/
function renderBeginType(type, inheritedType) {
addIndentation();
output += ELEM_NS + type + " = function (engine, id, parent) {\n";
++index;
addIndentation();
output += "var " + ELEM_PREFIX + " = new " + ELEM_NS + inheritedType + "(engine, id, parent);\n";
}
/*
* Renders the end of a new Type definition
* Called for each type in jump
*/
function renderEndType() {
addIndentation();
output += "return " + ELEM_PREFIX + ";\n";
--index;
addIndentation();
output += "};\n";
}
/*
* Renders an event handler for the current element/type in scope
* Event handlers will be generated whenever a property name
* begins with 'on' like 'onmousedown'
*/
function renderEventHandler(property, value) {
addIndentation();
output += ELEM_PREFIX + ".addEventHandler(\"" + property + "\", ";
output += "function () {\n";
addIndentation();
output += value + "\n";
addIndentation();
output += "});\n";
}
/*
* Renders a function for the current element/type in scope
* Functions will be generated whenever a property name
* contains a '(', a preceeding 'function ' is not necessary
* and will be stripped
*/
function renderFunction(property, value) {
var name = property.slice(property.indexOf(' ') + 1, property.indexOf('('));
var args = property.slice(property.indexOf('(') + 1, -1);
addIndentation();
output += ELEM_PREFIX + ".addFunction(\"" + name + "\", ";
output += "function (" + args + ") {\n";
addIndentation();
output += value + "\n";
addIndentation();
output += "});\n";
}
/*
* Renders a property for the current element/type in scope
*/
function renderProperty(property, value) {
// special case for ID
if (property === "id") {
return;
}
if (property.indexOf('on') === 0) {
renderEventHandler(property, value);
return;
}
if (property.indexOf('(') !== -1) {
renderFunction(property, value);
return;
}
addIndentation();
output += ELEM_PREFIX + ".addProperty(\"" + property + "\", ";
output += "function () {";
if (String(value).indexOf("return") !== -1) {
output += value + " ";
} else {
output += "return " + value + ";";
}
output += "});\n";
}
/*
* Renders a delegate for the current element/type in scope
*/
function renderDelegate(property, value) {
addIndentation();
output += ELEM_PREFIX + ".create" + property + " = function () {\n";
addIndentation(1);
output += "return new " + ELEM_NS + value + "(" + ENGINE_VAR + ");\n";
addIndentation();
output += "}\n";
}
/*
* Takes a TreeObject, containing either a Type or an Element
* and runs over the object's properties, types and children
* this is called recoursively
*/
function renderTreeObject(tree) {
var i;
if (tree.typeDefinition) {
renderBeginType(tree.typeDefinition, tree.type);
} else {
renderBeginElement(tree.type, tree.id);
}
for (i = 0; i < tree.properties.length; ++i) {
renderProperty(tree.properties[i].name, tree.properties[i].value);
}
for (i = 0; i < tree.delegates.length; ++i) {
renderDelegate(tree.delegates[i].name, tree.delegates[i].value);
}
for (i = 0; i < tree.types.length; ++i) {
renderTreeObject(tree.types[i]);
}
for (i = 0; i < tree.elements.length; ++i) {
renderTreeObject(tree.elements[i]);
}
if (tree.typeDefinition) {
renderEndType();
} else {
renderEndElement();
}
}
/*
* Starting point of the renderer
* Takes a TreeObject tree to render
* The first tree object is root and needs special treatment
*/
compiler.renderTree = function (tree, options, callback) {
var i;
index = 1;
output = "";
renderBegin(options);
for (i = 0; i < tree.types.length; ++i) {
renderTreeObject(tree.types[i]);
}
for (i = 0; i < tree.elements.length; ++i) {
renderTreeObject(tree.elements[i]);
}
renderEnd(options);
callback(null, output);
};
/*
* Dump out the current object tree to the console
*/
function dumpObjectTree(tree, indent) {
var i;
if (indent === undefined) {
indent = 0;
}
function niceLog(msg) {
var j;
var out = "";
for (j = 0; j < indent; ++j) {
out += " ";
}
console.log(out + msg);
}
niceLog("+ Element:");
niceLog("|- type: " + tree.type);
niceLog("|- type definition: " + tree.typeDefinition);
niceLog("|+ Properties:");
for (i = 0; i < tree.properties.length; ++i) {
niceLog("|--> " + tree.properties[i].name);
}
niceLog("|+ Delegates:");
for (i = 0; i < tree.delegates.length; ++i) {
niceLog("|--> " + tree.delegates[i].name + " : " + tree.delegates[i].value);
}
if (tree.types.length) {
niceLog("|+ Types:");
for (i = 0; i < tree.types.length; ++i) {
dumpObjectTree(tree.types[i], indent + 2);
}
}
if (tree.elements.length) {
niceLog("|+ Elements: ");
for (i = 0; i < tree.elements.length; ++i) {
dumpObjectTree(tree.elements[i], indent + 2);
}
}
}
/*
* Take all tokens and compile it to a object tree, which can be rendered
*/
compiler.createObjectTree = function (tok, options, callback) {
var property;
var tokens = tok;
var token_length = tokens.length;
var elementType;
var elementTypeDefinition;
var i, j;
// TreeObject is a helper to pass information to the renderer
var TreeObject = function (parent) {
this.id = undefined;
this.type = undefined;
this.typeDefinition = undefined;
this.parent = parent;
this.types = [];
this.elements = [];
this.properties = [];
this.delegates = [];
};
var objectTreeRoot = new TreeObject();
objectTreeRoot.type = "RootObject";
var objectTree = objectTreeRoot;
for (i = 0; i < token_length; i += 1) {
var token = tokens[i];
if (token.TOKEN === "IS_A") {
if (elementType) {
elementTypeDefinition = elementType;
elementType = undefined;
} else {
callback(error(errorCodes.NO_TYPENAME, token), null);
return;
}
}
if (token.TOKEN === "ELEMENT") {
elementType = token.DATA;
}
if (token.TOKEN === "SCOPE_START") {
log("start element description");
// only if elementType was found previously
if (elementType) {
// we found a element definition, so add one to create an element instance
var tmp = new TreeObject(objectTree);
tmp.type = elementType;
// check if we have a type definition or an element
if (elementTypeDefinition) {
tmp.typeDefinition = elementTypeDefinition;
objectTree.types.push(tmp);
} else {
objectTree.elements.push(tmp);
}
objectTree = tmp;
elementType = undefined;
elementTypeDefinition = undefined;
} else {
callback(error(errorCodes.NO_ELEMENTTYPE, token), null);
}
}
if (token.TOKEN === "SCOPE_END") {
log("end element description");
// scope end, so reset the objectTree pointer
objectTree = objectTree.parent;
}
if (token.TOKEN === "EXPRESSION") {
// next token must be COLON
var next_token = (i + 1 < token_length) ? tokens[i + 1] : undefined;
if (next_token && next_token.TOKEN === "COLON") {
property = token.DATA;
log("property found '" + property + "'");
// check for valid property names
if (isNumeric(property[0])) {
log("property name '" + property + "' is invalid.");
callback(error(errorCodes.INVALID_PROPERTY_NAME, token), null);
return;
}
i += 1;
next_token = undefined;
} else {
callback(error(errorCodes.NO_COLON, token), null);
return;
}
// next token must be EXPRESSION or ELEMENT
next_token = (i + 1 < token_length) ? tokens[i + 1] : undefined;
if (next_token && next_token.TOKEN === "EXPRESSION") {
log("right-hand-side expression found for property '" + property + "' '" + next_token.DATA + "'");
// special treatment for element IDs they are no real properties
if (property === "id") {
objectTree.id = next_token.DATA;
} else {
objectTree.properties.push({name: property, value: next_token.DATA});
}
i += 1;
property = undefined;
} else if (next_token && next_token.TOKEN === "ELEMENT") {
log("right-hand-side element found for property", property, next_token.DATA);
objectTree.delegates.push({name: property, value: next_token.DATA});
i += 1;
property = undefined;
} else {
callback(error(errorCodes.NO_EXPRESSION, next_token), null);
return;
}
}
}
if (objectTree !== objectTreeRoot) {
callback(error(errorCodes.UNEXPECTED_END, null), null);
return;
}
callback(null, objectTreeRoot);
};
/*
* Take all tokens, compile it to a object tree and render it
* options:
* - 'dump' dump object tree
*/
compiler.compileAndRender = function (tok, options, callback) {
compiler.createObjectTree(tok, options, function (error, result) {
if (error) {
callback(error, null);
return;
}
if (options.dump) {
dumpObjectTree(result);
}
compiler.renderTree(result, options, callback);
});
};
return compiler;
}());
module.exports = compiler;
})()
},{}],6:[function(require,module,exports){
/*
**************************************************
* Bungee.js
*
* (c) 2012-2013 Johannes Zellner
* (c) 2013 Simon Turvey
*
* Bungee may be freely distributed under the MIT license.
* For all details and documentation:
* http://bungeejs.org
**************************************************
*/
"use strict";
var ret = {};
/*
**************************************************
* Bungee engine
**************************************************
*
* Handles mainly toplevel elements and detects bindings.
* This should contain as less as possible!
*
*/
function Engine(renderer) {
this.getterCalled = {};
this._dirtyElements = {};
this.renderer = renderer;
this.verbose = false;
this._elementIndex = 0;
this.createElement = renderer.createElement;
this.addElement = renderer.addElement;
this.addElements = renderer.addElements;
this.renderElement = renderer.renderElement;
this.removeElement = renderer.removeElement;
/**
* Dynamically replaced function responsible for instrumenting bindings
* during the binding evaluation stage.
*/
this.maybeReportGetterCalled = function () {};
// TODO should be part of the dom renderer?
this.renderInterval = undefined;
this.fps = {};
this.fps.d = Date.now();
this.fps.l = 0;
}
Engine.prototype.log = function (msg, error) {
if (this.verbose || error) {
console.log("[Bungee.Engine] " + msg);
}
};
// begin binding detection
Engine.prototype.enterMagicBindingState = function () {
var that = this;
this.log("enterMagicBindingState");
this.getterCalled = {};
this.maybeReportGetterCalled = function (silent, name) {
if (!silent) {
that.addCalledGetter(this, name);
}
};
};
// end binding detection
Engine.prototype.exitMagicBindingState = function () {
this.log("exitMagicBindingState\n\n");
this.maybeReportGetterCalled = function () {};
return this.getterCalled;
};
Engine.prototype.start = function () {
var that = this;
this.renderInterval = window.setInterval(function () {
that.advance();
}, 1000/60.0);
};
Engine.prototype.stop = function () {
window.clearInterval(this.renderInterval);
};
Engine.prototype.dirty = function (element, property) {
// ignore properties prefixed with _
if (property[0] === '_') {
return;
}
element._dirtyProperties[property] = true;
if (!this._dirtyElements[element._internalIndex]) {
this._dirtyElements[element._internalIndex] = element;
}
};
Engine.prototype.addCalledGetter = function (element, property) {
this.getterCalled[element.id + "." + property] = {
element: element,
property: property
};
};
Engine.prototype.advance = function () {
// cache keys and length as we wont modify the array (see jsperf)
var keys = Object.keys(this._dirtyElements);
var keys_length = keys.length;
for (var i = 0; i < keys_length; ++i) {
this._dirtyElements[keys[i]].render();
}
this._dirtyElements = {};
if (this.verbose) {
var fps = this.fps;
if ((Date.now() - fps.d) >= 2000) {
console.log("FPS: " + fps.l / 2.0);
fps.d = Date.now();
fps.l = 0;
} else {
++(fps.l);
}
}
};
/*
**************************************************
* Basic Element
**************************************************
*
* The main element, which handles its connections
* and properties. It also calls into the renderer
* by using render hooks.
*
*/
function Element(engine, id, parent, typeHint) {
console.assert(engine instanceof Engine);
this.engine = engine;
this.id = id;
this.typeHint = typeHint;
this.parent = parent;
if (typeHint !== "object") {
this.element = this.engine.createElement(typeHint, this);
} else {
this.element = null;
}
// internal use only
this._internalIndex = this.engine._elementIndex++;
this._dirtyProperties = {};
this._properties = {};
this._connections = {};
this._children = {};
this._bound = {};
this._isInitialized = false;
this._initializeBindingsStep = false;
if (this.parent) {
this.parent.addChild(this);
}
}
Element.prototype.children = function () {
this.engine.maybeReportGetterCalled.call(this, false, 'children');
return this._children;
};
// TODO both removes need to break the bindings for the children as well
Element.prototype.removeChild = function(child) {
this.engine.removeElement(child, this);
delete this._children[child._internalIndex];
this.emit("children");
};
Element.prototype.removeChildren = function () {
for (var i in this._children) {
if (this._children.hasOwnProperty(i)) {
// TODO do we leak things here? elements are still referenced so maybe a delete?
this.engine.removeElement(this._children[i], this);
}
}
this._children = {};
this.emit("children");
};
Element.prototype.addChild = function (child) {
// adds child id to the namespace
if (child.id)
this[child.id] = child;
// adds the parents id to the child
if (this.id)
child[this.id] = this;
// add child to siblings scope and vice versa
for (var i in this._children) {
if (this._children.hasOwnProperty(i)) {
if (child.id)
this._children[i][child.id] = child;
if (this._children[i].id)
child[this._children[i].id] = this._children[i];
}
}
// add newly added child to internal children array
this._children[child._internalIndex] = child;
child.parent = this;
this.engine.addElement(child, this);
this.emit("children");
return child;
};
Element.prototype.render = function () {
this.engine.renderElement(this);
};
Element.prototype.addChanged = function (signal, callback) {
if (!this._connections[signal]) {
this._connections[signal] = [];
}
this._connections[signal].push(callback);
// console.log("connections for " + signal + " " + this._connections[signal].length);
};
Element.prototype.removeChanged = function (obj, signal) {
var signalConnections = this._connections[signal];
// check if there are any connections for this signal
if (!signalConnections) {
return;
}
// TODO do implementation
// for (var i = 0; i < signalConnections.length; ++i) {
// }
};
Element.prototype.addBinding = function (name, value, property) {
var that = this;
var hasBinding = false;
var val, getters;
var bindingFunction;
// FIXME does not catch changing conditions in expression
// x: mouseArea.clicked ? a.y() : b:z();
this.engine.enterMagicBindingState();
if (typeof value === 'function') {
val = value.apply(this);
bindingFunction = function() {
that[name] = value.apply(that);
};
} else if (typeof value === 'object' && typeof property !== 'undefined') {
val = value[property];
bindingFunction = function() {
that[name] = value[property];
};
} else {
val = value;
}
getters = this.engine.exitMagicBindingState();
this.breakBindings(name);
// store found bindings
for (var getter in getters) {
if (getters.hasOwnProperty(getter)) {
var tmp = getters[getter];
// store bindings to this for breaking
this._bound[name][this._bound[name].length] = {
element: tmp.element,
property: tmp.property
};
tmp.element.addChanged(tmp.property, bindingFunction);
hasBinding = true;
}
}
return { hasBindings: hasBinding, value: val };
};
Element.prototype.addEventHandler = function (event, handler) {
var that = this;
var signal = event;
if (signal === "" || typeof handler !== 'function') {
return;
}
if (signal.indexOf('on') === 0) {
signal = signal.slice(2);
}
this.addChanged(signal, function () {
if (!that._initializeBindingsStep)
handler.apply(that);
});
};
// Breaks all bindings assigned to this property
Element.prototype.breakBindings = function (name) {
// break all previous bindings
if (this._bound[name]) {
for (var i = 0; i < this._bound[name].length; ++i) {
this._bound[name][i].element.removeChanged(this, name);
}
}
this._bound[name] = [];
};
// This allows to set the property without emit the change
// Does not break the binding!
Element.prototype.setSilent = function (name, value) {
var setter = this.__lookupSetter__(name);
if (typeof setter === 'function') {
setter.call(this, value, true);
}
};
// This allows to get the property without notify the get
Element.prototype.getSilent = function (name) {
var getter = this.__lookupGetter__(name);
if (typeof getter === 'function') {
return getter.call(this, true);
}
};
// This breaks all previous bindings and adds a new binding
Element.prototype.set = function (name, value) {
this.breakBindings(name);
if (typeof value === 'function') {
var ret = this.addBinding(name, value);
if (ret.hasBindings) {
this[name] = value;
} else {
this[name] = ret.value;
}
} else {
this[name] = value;
}
};
Element.prototype.addFunction = function (name, value) {
this[name] = value;
};
var defPropCount = 0;
var notdefPropCount = 0;
Element.prototype.addProperty = function (name, value) {
var that = this;
var valueStore;
// register property
this._properties[name] = value;
if (!this.hasOwnProperty(name)) {
Object.defineProperty(this, name, {
get: function (silent) {
// console.log("getter: ", that.id, name);
this.engine.maybeReportGetterCalled.call(that, silent, name);
if (typeof valueStore === 'function')
return valueStore.apply(that);
return valueStore;
},
set: function (val, silent) {
// console.log("setter: ", that.id, name, val);
if (valueStore === val)
return;
valueStore = val;
// connections are called like the properties
if (!silent) {
that.emit(name);
that.emit('changed');
}
this.engine.dirty(that, name);
}
});
}
};
// initial set of all properties and binding evaluation
// can only be called once
Element.prototype.initializeBindings = function (options) {
var name, i;
// prevent from multiple initializations
if (this._isInitialized) {
return;
}
this._isInitialized = true;
this._initializeBindingsStep = true;
for (name in this._properties) {
if (this._properties.hasOwnProperty(name)) {
var value = this._properties[name];
// console.log("Element.initializeBindings()", this.id, name, value);
// initial set and binding discovery
if (typeof value === 'function') {
var ret = this.addBinding(name, value);
if (ret.hasBindings) {
this[name] = value;
} else {
this[name] = ret.value;
}
} else {
this[name] = value;
}
}
}
// force property being set on the elements
if (!options || !options.deferRender) {
this.render();
}
for (i in this._children) {
if (this._children.hasOwnProperty(i)) {
this._children[i].initializeBindings(options);
}
}
this._initializeBindingsStep = false;
// this calls the onload slot, if defined
this.emit("load");
};
Element.prototype.emit = function (signal) {
if (signal in this._connections) {
var slots = this._connections[signal];
for (var i = 0; i < slots.length; ++i) {
slots[i].apply();
}
}
};
/*
**************************************************
* Basic non visual Elements
**************************************************
*/
function Collection (engine, id, parent) {
var elem = new Element(engine, id, parent, "object");
return elem;
}
module.exports = {
Engine: Engine,
Element: Element,
Collection: Collection
};
},{}],7:[function(require,module,exports){
(function(){/*
**************************************************
* Bungee.js
*
* (c) 2012-2013 Johannes Zellner
*
* Bungee may be freely distributed under the MIT license.
* For all details and documentation:
* http://bungeejs.org
**************************************************
*/
"use strict";
/*
**************************************************
* DOM Renderer and Elements
**************************************************
*/
var Bungee = require('./engine.js');
/*
**************************************************
* Predefined basic elements
**************************************************
*/
Bungee.Item = function (engine, id, parent, typeHint) {
var elem = new Bungee.Element(engine, id, parent, typeHint ? typeHint : "item");
elem.addProperty("className", "");
elem.addProperty("width", 100);
elem.addProperty("height", 100);
elem.addProperty("top", 0);
elem.addProperty("left", 0);
elem.addProperty("childrenWidth", function () {
var left = 0;
var right = 0;
var kids = this.children();
for (var i in kids) {
if (kids.hasOwnProperty(i)) {
var c = kids[i];
if (c.left < left) {
left = c.left;
}
if ((c.left + c.width) > right) {
right = c.left + c.width;
}
}
}
return (right - left);
});
elem.addProperty("childrenHeight", function () {
var top = 0;
var bottom = 0;
var kids = this.children();
for (var i in kids) {
if (kids.hasOwnProperty(i)) {
var c = kids[i];
if (c.top < top) {
top = c.top;
}
if ((c.top + c.height) > bottom) {
bottom = c.top + c.height;
}
}
}
return (bottom - top);
});
return elem;
};
Bungee.InputItem = function (engine, id, parent) {
var elem = new Bungee.Item(engine, id, parent, "InputItem");
// default to fill parent
elem.addProperty("width", function () { return this.parent ? this.parent.width : 100; });
elem.addProperty("height", function () { return this.parent ? this.parent.height : 100; });
elem.addProperty("mouseAbsX", 0);
elem.addProperty("mouseAbsY", 0);
elem.addProperty("mouseRelX", 0);
elem.addProperty("mouseRelY", 0);
elem.addProperty("mouseRelStartX", 0);
elem.addProperty("mouseRelStartY", 0);
elem.addProperty("mousePressed", false);
elem.addProperty("containsMouse", false);
// scrolling
elem.addProperty("scrollTop", 0);
elem.addProperty("scrollLeft", 0);
elem.addProperty("srollWidth", 0);
elem.addProperty("scrollHeight", 0);
return elem;
};
// FIXME global leak
var tmpTextElement;
Bungee.Text = function (engine, id, parent) {
var elem = new Bungee.Item(engine, id, parent);
elem.addProperty("mouseEnabled", false);
elem.addProperty("textWidth", 0);
elem.addProperty("textHeight", 0);
elem.addProperty("fontSize", "");
elem.addProperty("fontFamily", "");
elem.addProperty("text", "");
elem.addProperty("-text", function () { return this.text; });
elem.addProperty("width", function() { return this.textWidth; });
elem.addProperty("height", function() { return this.textHeight; });
// all this below for calculating the text width
if (!tmpTextElement) {
tmpTextElement = window.document.createElement("div");
tmpTextElement.style.position = "absolute";
tmpTextElement.style.visibility = "hidden";
tmpTextElement.style.width = "auto";
tmpTextElement.style.height = "auto";
tmpTextElement.style.left = -10000;
window.document.body.appendChild(tmpTextElement);
}
function relayout() {
var tmpProperty = elem.text;
var width = 0;
var height = 0;
tmpTextElement.style.fontSize = elem.fontSize;
tmpTextElement.style.fontFamily = elem.fontFamily;
if (tmpTextElement.innerHTML === tmpProperty) {
width = (tmpTextElement.clientWidth + 1);
height = (tmpTextElement.clientHeight + 1);
} else if (tmpProperty !== "") {
tmpTextElement.innerHTML = tmpProperty;
width = (tmpTextElement.clientWidth + 1);
height = (tmpTextElement.clientHeight + 1);
}
elem.textWidth = width;
elem.textHeight = height;
}
elem.addChanged("text", relayout);
return elem;
};
Bungee.Window = function (engine, id, parent) {
var elem = new Bungee.Element(engine, id, parent);
elem.addProperty("innerWidth", window.innerWidth);
elem.addProperty("innerHeight", window.innerHeight);
elem.addProperty("width", function () { return this.innerWidth; });
elem.addProperty("height", function () { return this.innerHeight; });
elem.addEventHandler("load", function () {
var that = this;
window.addEventListener("resize", function (event) {
that.innerWidth = event.srcElement.innerWidth;
that.innerHeight = event.srcElement.innerHeight;
});
});
return elem;
};
Bungee.Rectangle = function (engine, id, parent) {
var elem = new Bungee.Item(engine, id, parent);
elem.addProperty("backgroundColor", "white");
elem.addProperty("borderColor", "black");
elem.addProperty("borderStyle", "solid");
elem.addProperty("borderWidth", 1);
elem.addProperty("borderRadius", 0);
return elem;
};
Bungee.BackgroundImage = function (engine, id, parent) {
var elem = new Bungee.Item(engine, id, parent);
elem.addProperty("src", "");
elem.addProperty("backgroundImage", function () {
if (!this.src) {
return "";
}
if (this.src.indexOf("url('") === 0) {
return this.src;
}
return "url('" + this.src + "')";
});
elem.addProperty("backgroundPosition", "center");
elem.addProperty("backgroundRepeat", "no-repeat");
return elem;
};
Bungee.Image = function (engine, id, parent) {
var elem = new Bungee.Item(engine, id, parent, "image");
elem.addProperty("src", "");
elem.addProperty("-image-src", function () {
return this.src;
});
return elem;
};
Bungee.Input = function (engine, id, parent) {
var elem = new Bungee.Item(engine, id, parent, "input");
elem.addProperty("-webkit-user-select", "auto");
elem.addProperty("userSelect", "auto");
elem.addProperty("text", function() {
return this.element.value;
});
elem.addProperty("placeholder", "");
return elem;
};
/*
**************************************************
* DOM renderer
**************************************************
*/
Bungee.RendererDOM = function () {
this.currentMouseElement = undefined;
};
Bungee.RendererDOM.prototype.createElement = function (typeHint, object) {
var elem;
var that = this;
if (typeHint === 'input') {
elem = document.createElement('input');
} else if (typeHint === 'image') {
elem = document.createElement('img');
} else {
elem = document.createElement('div');
}
// initialize basic css attributes
elem.style.position = 'absolute';
// set id attribute
if (object.id) {
elem.id = object.id;
}
function handleTouchStartEvents(event) {
that.currentMouseElement = this;
if (that.currentScrollElement) {
that.currentScrollElementTopStart = that.currentScrollElement.scrollTop;
that.currentScrollElementLeftStart = that.currentScrollElement.scrollLeft;
}
object.mousePressed = true;
object.emit('mousedown');
}
function handleTouchEndEvents(event) {
object.mousePressed = false;
object.mouseRelStartX = 0;
object.mouseRelStartY = 0;
object.emit('mouseup');
if (that.currentMouseElement === this) {
object.emit('activated');
}
that.currentMouseElement = undefined;
}
function handleTouchMoveEvents(event) {
object.mouseAbsX = event.clientX || event.targetTouches[0].clientX;
object.mouseAbsY = event.clientY || event.targetTouches[0].clientY;
object.mouseRelX = event.layerX || event.targetTouches[0].layerX;
object.mouseRelY = event.layerY || event.targetTouches[0].layerY;
object.emit('mousemove');
}
function handleMouseDownEvents(event) {
if (!event.used) {
that.currentMouseElement = this;
event.used = true;
}
object.mousePressed = true;
object.mouseRelStartX = event.layerX;
object.mouseRelStartY = event.layerY;
object.emit('mousedown');
}
function handleMouseUpEvents(event) {
object.mousePressed = false;
object.mouseRelStartX = 0;
object.mouseRelStartY = 0;
object.emit('mouseup');
if (that.currentMouseElement === this) {
object.emit('activated');
}
that.currentMouseElement = undefined;
}
function handleMouseMoveEvents(event) {
object.mouseAbsX = event.clientX;
object.mouseAbsY = event.clientY;
object.mouseRelX = event.layerX;
object.mouseRelY = event.layerY;
object.emit('mousemove');
}
function handleMouseOverEvents(event) {
object.containsMouse = true;
object.emit('mouseover');
}
function handleMouseOutEvents(event) {
object.containsMouse = false;
object.emit('mouseout');
}
function handleScrollEvents(event) {
if (that.currentScrollElement !== object) {
that.currentScrollElement = object;
that.currentScrollElementTopStart = event.target.scrollTop;
that.currentScrollElementLeftStart = event.target.scrollLeft;
}
if (Math.abs(that.currentScrollElementTopStart - event.target.scrollTop) > 20 || Math.abs(that.currentScrollElementLeftStart - event.target.scrollLeft) > 20 ) {
that.currentMouseElement = undefined;
}
object.scrollTop = event.target.scrollTop;
object.scrollLeft = event.target.scrollLeft;
object.srollWidth = event.target.scrollWidth;
object.scrollHeight = event.target.scrollHeight;
}
elem.addEventListener("scroll", handleScrollEvents, false);
if (typeHint === "InputItem") {
if ('ontouchstart' in document.documentElement) {
if (window.navigator.msPointerEnabled) {
elem.addEventListener("MSPointerDown", handleTouchStartEvents, false);
elem.addEventListener("MSPointerMove", handleTouchMoveEvents, false);
elem.addEventListener("MSPointerUp", handleTouchEndEvents, false);
} else {
elem.addEventListener("touchstart", handleTouchStartEvents, false);
elem.addEventListener("touchmove", handleTouchMoveEvents, false);
elem.addEventListener("touchend", handleTouchEndEvents, false);
}
} else {
elem.addEventListener("mousedown", handleMouseDownEvents, false);
elem.addEventListener("mouseup", handleMouseUpEvents, false);
elem.addEventListener("mousemove", handleMouseMoveEvents, false);
elem.addEventListener("mouseover", handleMouseOverEvents, false);
elem.addEventListener("mouseout", handleMouseOutEvents, false);
}
}
return elem;
};
Bungee.RendererDOM.prototype.addElement = function (element, parent) {
// in case we have no visual element, just return
if (!element.element) {
return;
}
if (parent && parent.element) {
parent.element.appendChild(element.element);
} else {
document.body.appendChild(element.element);
}
};
Bungee.RendererDOM.prototype.removeElement = function (element, parent) {
// in case we have no visual element, just return
if (!element.element) {
return;
}
if (parent && parent.element) {
parent.element.removeChild(element.element);
} else {
document.body.removeChild(element.element);
}
};
Bungee.RendererDOM.prototype.addElements = function (elements, parent) {
var fragment = document.createDocumentFragment();
for (var i = 0; i < elements.length; ++i) {
if (!elements[i].element) {
continue;
}
fragment.appendChild(elements[i].element);
}
if (parent && parent.element) {
parent.element.appendChild(fragment);
} else {
document.body.appendChild(fragment);
}
};
Bungee.RendererDOM.prototype.renderElement = function (element) {
// console.log("renderElement: " + element.id + " properties: " + Object.keys(element.properties).length);
var name;
// in case we have no visual element, just return
if (!element.element) {
return;
}
for (name in element._dirtyProperties) {
if (name === 'className' && element[name] !== '') {
element.element.className = element[name];
} else if (name === 'scale') {
var s = element.scale.toFixed(10);
var tmp = "scale(" + s + ", " + s + ")";
element.element.style['-webkit-transform'] = tmp;
element.element.style['transform'] = tmp;
} else if (name === '-text') {
element.element.innerHTML = element[name];
} else if (name === '-image-src') {
element.element.src = element[name];
} else if (name === 'placeholder') {
element.element.placeholder = element[name];
} else {
element.element.style[name] = element[name];
}
}
element._dirtyProperties = {};
};
module.exports = Bungee;
})()
},{"./engine.js":6}],8:[function(require,module,exports){
/*
**************************************************
* Bungee.js
*
* (c) 2012-2013 Johannes Zellner
*
* Bungee may be freely distributed under the MIT license.
* For all details and documentation:
* http://bungeej