UNPKG

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
;(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