From bae8262524db34a3cc64bb5ed7bb1234f1602479 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Fri, 11 Mar 2011 00:13:23 +0100 Subject: [PATCH] too much to sumarize ;) --- htdocs/frontend/javascripts/entity.js | 63 ++++- htdocs/frontend/javascripts/functions.js | 125 ++++++++++ htdocs/frontend/javascripts/helper.js | 28 +-- htdocs/frontend/javascripts/init.js | 27 ++- .../javascripts/jquery/jquery-treeTable.js | 219 ++++++++++++++++++ htdocs/frontend/javascripts/property.js | 28 +-- htdocs/frontend/javascripts/uuid.js | 4 +- 7 files changed, 431 insertions(+), 63 deletions(-) create mode 100644 htdocs/frontend/javascripts/functions.js create mode 100644 htdocs/frontend/javascripts/jquery/jquery-treeTable.js diff --git a/htdocs/frontend/javascripts/entity.js b/htdocs/frontend/javascripts/entity.js index e790b63..e8bfda3 100644 --- a/htdocs/frontend/javascripts/entity.js +++ b/htdocs/frontend/javascripts/entity.js @@ -28,13 +28,19 @@ * Entity constructor * @todo add validation */ -var Entity = function(json) { +var Entity = function(json, parent) { $.extend(true, this, json); - + this.parent = parent; + if (this.children) { - for (var i in this.children) { - this.children[i] = new Entity(this.children[i]); - } + var children = new Array(); + for (var i = 0; i < this.children.length; i++) { + children.push(new Entity(this.children[i], this)); + }; + + this.children = children.sort(function(e1, e2) { + e1.title < e2.title; + }); } this.definition = vz.capabilities.definitions.get('entities', this.type); @@ -60,11 +66,11 @@ Entity.prototype.showDetails = function() { * @todo implement/test */ Entity.prototype.getDOM = function() { - var table = $('
KeyValue
'); + var table = $('
EigenschaftWert
'); var data = $(''); for (var property in this) { - if (this.hasOwnProperty(property) && !['data', 'definition', 'children'].contains(property)) { + if (this.hasOwnProperty(property) && !['data', 'definition', 'children', 'parent'].contains(property)) { switch(property) { case 'type': var title = 'Typ'; @@ -112,18 +118,51 @@ Entity.prototype.getDOM = function() { return table.append(data); }; +/** + * Add entity as child + */ +Entity.prototype.addChild = function(child) { + if (this.definition.model != 'Volkszaehler\\Model\\Aggregator') { + throw new Exception('EntityException', 'Entity is not an Aggregator'); + } + + vz.load({ + context: 'group', + identifier: this.uuid, + data: { + uuid: child.uuid + }, + type: 'post', + success: vz.wait($.noop, vz.entities.loadDetails, 'information') + }); +} + +/** + * Remove entity from children + */ +Entity.prototype.removeChild = function(child) { + vz.load({ + context: 'group', + identifier: this.uuid, + data: { + uuid: child.uuid, + operation: 'delete' + }, + success: vz.wait($.noop, vz.entities.loadDetails, 'information') + }); +}; + /** * Validate Entity for required and optional properties and their values + * * @return boolean * @todo implement/test */ Entity.prototype.validate = function() { - var def = getDefinition(vz.definitions.entities, entity.type); - - def.required.each(function(index, property) { - var property = getDefinition(vz.definitions.properties, property); + this.definition.required.each(function(index, property) { + var propertyDefinition = vz.capabilities.definitions.get('properties', property); if (!validateProperty(property, form.elements[property.name].value)) { - throw 'Invalid property: ' + property.name + ' = ' + form.elements[property.name].value; + throw new Exception('EntityException', 'Invalid property: ' + property.name + ' = ' + form.elements[property.name].value); } }); diff --git a/htdocs/frontend/javascripts/functions.js b/htdocs/frontend/javascripts/functions.js new file mode 100644 index 0000000..6108cd4 --- /dev/null +++ b/htdocs/frontend/javascripts/functions.js @@ -0,0 +1,125 @@ +/** + * Some general functions we need for the frontend + * + * @author Florian Ziegler + * @author Justin Otherguy + * @author Steffen Vogel + * @copyright Copyright (c) 2010, The volkszaehler.org project + * @package default + * @license http://opensource.org/licenses/gpl-license.php GNU Public License + */ +/* + * This file is part of volkzaehler.org + * + * volkzaehler.org is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 3 of the License, or any later version. + * + * volkzaehler.org is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * volkszaehler.org. If not, see . + */ + +/** + * Helper function to wait for multiple ajax requests to complete + */ +vz.wait = function(callback, finished, identifier) { + if (!vz.wait.counter) { vz.wait.counter = new Array(); } + if (!vz.wait.counter[identifier]) { vz.wait.counter[identifier] = 0; } + + vz.wait.counter[identifier]++; + + return function (data, textStatus) { + callback(data, textStatus); + + if (!--vz.wait.counter[identifier]) { + finished(); + } + }; +}; + +/** + * Universal helper for backend ajax requests with error handling + */ +vz.load = function(args) { + $.extend(args, { + url: this.options.backendUrl, + dataType: 'json', + error: function(xhr) { + json = JSON.parse(xhr.responseText); + vz.wui.dialogs.error(xhr.statusText, json.exception.message, xhr.status); + } + }); + + if (args.context) { + args.url += '/' + args.context; + } + if (args.identifier) { + args.url += '/' + args.identifier; + } + args.url += '.json'; + + $.ajax(args); +}; + +/** + * Parse URL GET parameters + */ +vz.parseUrlParams = function() { + var vars = $.getUrlParams(); + for (var key in vars) { + if (vars.hasOwnProperty(key)) { + switch (key) { + case 'uuid': // add optional uuid from url + var uuids = (typeof vars[key] == 'string') ? [vars[key]] : vars[key]; // handle multiple uuids + uuids.each(function(index, uuid) { + try { vz.uuids.add(uuid); } catch (exception) { /* ignore exception */ } + }); + break; + + case 'from': + vz.options.plot.xaxis.min = parseInt(vars[key]); + break; + + case 'to': + vz.options.plot.xaxis.max = parseInt(vars[key]); + break; + + case 'debug': + $.getScript('javascripts/firebug-lite.js'); + break; + } + } + } +}; + +/** + * Load capabilities from backend + */ +vz.capabilities.load = function() { + vz.load({ + context: 'capabilities', + identifier: 'definitions', + success: function(json) { + $.extend(true, vz.capabilities, json.capabilities); + + // load entity details + vz.entities.loadDetails(); + } + }); +}; + +/** + * Lookup definition + */ +vz.capabilities.definitions.get = function(section, name) { + for (var i in this[section]) { + if (this[section][i].name == name) { + return this[section][i]; + } + } +} diff --git a/htdocs/frontend/javascripts/helper.js b/htdocs/frontend/javascripts/helper.js index b60a739..888c3c5 100644 --- a/htdocs/frontend/javascripts/helper.js +++ b/htdocs/frontend/javascripts/helper.js @@ -1,6 +1,8 @@ /** * Some functions and prototypes which make our life easier * + * not volkszaehler.org related + * * @author Florian Ziegler * @author Justin Otherguy * @author Steffen Vogel @@ -24,32 +26,6 @@ * volkszaehler.org. If not, see . */ -/** - * Helper function to wait for multiple ajax requests to complete - */ -function waitAsync(callback, finished, identifier) { - if (!waitAsync.counter) { waitAsync.counter = new Array(); } - if (!waitAsync.counter[identifier]) { waitAsync.counter[identifier] = 0; } - - waitAsync.counter[identifier]++; - - return function (data, textStatus) { - callback(data, textStatus); - - if (!--waitAsync.counter[identifier]) { - finished(); - } - }; -} - -var Exception = function(type, message, code) { - return { - type: type, - message: message, - code: code - }; -} - /* * Array extensions * according to js language specification ECMA 1.6 diff --git a/htdocs/frontend/javascripts/init.js b/htdocs/frontend/javascripts/init.js index d7b062b..39a074c 100644 --- a/htdocs/frontend/javascripts/init.js +++ b/htdocs/frontend/javascripts/init.js @@ -25,11 +25,15 @@ * along with volkszaehler.org. If not, see . */ -// volkszaehler.org namespace (holds all data, options and functions for the frontend) -// we dont want to pollute the global namespace +/** + * volkszaehler.org namespace + * + * holds all data, options and functions for the frontend + * we dont want to pollute the global namespace + */ var vz = { // entity properties + data - entities: new Array, + entities: new Array, // TODO new Entity? // web user interface wui: { @@ -51,14 +55,21 @@ var vz = { options: { } }; -// executed on document loaded complete -// this is where it all starts... +/** + * Executed on document loaded complete + * this is where it all starts... + */ $(document).ready(function() { + // late binding $(window).resize(function() { vz.options.tuples = Math.round($('#flot').width() / 3); $('#tuples').val(vz.options.tuples); - vz.drawPlot(); + vz.wui.drawPlot(); }); + + window.onerror = function(errorMsg, url, lineNumber) { + vz.wui.dialogs.error('Javascript Runtime Error', errorMsg); + }; vz.uuids.load(); // load uuids from cookie vz.options.load(); // load options from cookie @@ -67,14 +78,12 @@ $(document).ready(function() { // initialize user interface vz.wui.init(); vz.wui.initEvents(); - vz.wui.dialogs.init(); if (vz.uuids.length == 0) { $('#entity-add').dialog('open'); } - // starting with request to backend: + // starting with request to backend; try to follow the callbacks ;) // capabiltities -> entities -> data - // try to follow the callbacks ;) vz.capabilities.load(); // load properties, entity types and other capabilities from backend }); diff --git a/htdocs/frontend/javascripts/jquery/jquery-treeTable.js b/htdocs/frontend/javascripts/jquery/jquery-treeTable.js new file mode 100644 index 0000000..70c52c4 --- /dev/null +++ b/htdocs/frontend/javascripts/jquery/jquery-treeTable.js @@ -0,0 +1,219 @@ +/* + * jQuery treeTable Plugin 2.3.0 + * http://ludo.cubicphuse.nl/jquery-plugins/treeTable/ + * + * Copyright 2010, Ludo van den Boom + * Dual licensed under the MIT or GPL Version 2 licenses. + */ +(function($) { + // Helps to make options available to all functions + // TODO: This gives problems when there are both expandable and non-expandable + // trees on a page. The options shouldn't be global to all these instances! + var options; + var defaultPaddingLeft; + + $.fn.treeTable = function(opts) { + options = $.extend({}, $.fn.treeTable.defaults, opts); + + return this.each(function() { + $(this).addClass("treeTable").find("tbody tr").each(function() { + // Initialize root nodes only if possible + if(!options.expandable || $(this)[0].className.search(options.childPrefix) == -1) { + // To optimize performance of indentation, I retrieve the padding-left + // value of the first root node. This way I only have to call +css+ + // once. + if (isNaN(defaultPaddingLeft)) { + defaultPaddingLeft = parseInt($($(this).children("td")[options.treeColumn]).css('padding-left'), 10); + } + + initialize($(this)); + } else if(options.initialState == "collapsed") { + this.style.display = "none"; // Performance! $(this).hide() is slow... + } + }); + }); + }; + + $.fn.treeTable.defaults = { + childPrefix: "child-of-", + clickableNodeNames: false, + expandable: true, + indent: 19, + initialState: "collapsed", + treeColumn: 0 + }; + + // Recursively hide all node's children in a tree + $.fn.collapse = function() { + $(this).addClass("collapsed"); + + childrenOf($(this)).each(function() { + if(!$(this).hasClass("collapsed")) { + $(this).collapse(); + } + + this.style.display = "none"; // Performance! $(this).hide() is slow... + }); + + return this; + }; + + // Recursively show all node's children in a tree + $.fn.expand = function() { + $(this).removeClass("collapsed").addClass("expanded"); + + childrenOf($(this)).each(function() { + initialize($(this)); + + if($(this).is(".expanded.parent")) { + $(this).expand(); + } + + // this.style.display = "table-row"; // Unfortunately this is not possible with IE :-( + $(this).show(); + }); + + return this; + }; + + // Reveal a node by expanding all ancestors + $.fn.reveal = function() { + $(ancestorsOf($(this)).reverse()).each(function() { + initialize($(this)); + $(this).expand().show(); + }); + + return this; + }; + + // Add an entire branch to +destination+ + $.fn.appendBranchTo = function(destination) { + var node = $(this); + var parent = parentOf(node); + + var ancestorNames = $.map(ancestorsOf($(destination)), function(a) { return a.id; }); + + // Conditions: + // 1: +node+ should not be inserted in a location in a branch if this would + // result in +node+ being an ancestor of itself. + // 2: +node+ should not have a parent OR the destination should not be the + // same as +node+'s current parent (this last condition prevents +node+ + // from being moved to the same location where it already is). + // 3: +node+ should not be inserted as a child of +node+ itself. + if($.inArray(node[0].id, ancestorNames) == -1 && (!parent || (destination.id != parent[0].id)) && destination.id != node[0].id) { + indent(node, ancestorsOf(node).length * options.indent * -1); // Remove indentation + + if(parent) { node.removeClass(options.childPrefix + parent[0].id); } + + node.addClass(options.childPrefix + destination.id); + move(node, destination); // Recursively move nodes to new location + indent(node, ancestorsOf(node).length * options.indent); + } + + return this; + }; + + // Add reverse() function from JS Arrays + $.fn.reverse = function() { + return this.pushStack(this.get().reverse(), arguments); + }; + + // Toggle an entire branch + $.fn.toggleBranch = function() { + if($(this).hasClass("collapsed")) { + $(this).expand(); + } else { + $(this).removeClass("expanded").collapse(); + } + + return this; + }; + + // === Private functions + + function ancestorsOf(node) { + var ancestors = []; + while(node = parentOf(node)) { + ancestors[ancestors.length] = node[0]; + } + return ancestors; + }; + + function childrenOf(node) { + return $("table.treeTable tbody tr." + options.childPrefix + node[0].id); + }; + + function getPaddingLeft(node) { + var paddingLeft = parseInt(node[0].style.paddingLeft, 10); + return (isNaN(paddingLeft)) ? defaultPaddingLeft : paddingLeft; + } + + function indent(node, value) { + var cell = $(node.children("td")[options.treeColumn]); + cell[0].style.paddingLeft = getPaddingLeft(cell) + value + "px"; + + childrenOf(node).each(function() { + indent($(this), value); + }); + }; + + function initialize(node) { + if(!node.hasClass("initialized")) { + node.addClass("initialized"); + + var childNodes = childrenOf(node); + + if(!node.hasClass("parent") && childNodes.length > 0) { + node.addClass("parent"); + } + + if(node.hasClass("parent")) { + var cell = $(node.children("td")[options.treeColumn]); + var padding = getPaddingLeft(cell) + options.indent; + + childNodes.each(function() { + $(this).children("td")[options.treeColumn].style.paddingLeft = padding + "px"; + }); + + if(options.expandable) { + cell.prepend(''); + $(cell[0].firstChild).click(function() { node.toggleBranch(); }); + + if(options.clickableNodeNames) { + cell[0].style.cursor = "pointer"; + $(cell).click(function(e) { + // Don't double-toggle if the click is on the existing expander icon + if (e.target.className != 'expander') { + node.toggleBranch(); + } + }); + } + + // Check for a class set explicitly by the user, otherwise set the default class + if(!(node.hasClass("expanded") || node.hasClass("collapsed"))) { + node.addClass(options.initialState); + } + + if(node.hasClass("expanded")) { + node.expand(); + } + } + } + } + }; + + function move(node, destination) { + node.insertAfter(destination); + childrenOf(node).reverse().each(function() { move($(this), node[0]); }); + }; + + function parentOf(node) { + var classNames = node[0].className.split(' '); + + for(key in classNames) { + if(classNames[key].match(options.childPrefix)) { + return $("#" + classNames[key].substring(9)); + } + } + }; +})(jQuery); diff --git a/htdocs/frontend/javascripts/property.js b/htdocs/frontend/javascripts/property.js index c3474de..940155a 100644 --- a/htdocs/frontend/javascripts/property.js +++ b/htdocs/frontend/javascripts/property.js @@ -27,8 +27,8 @@ /** * Property constructor */ -var Property = function(key, value) { - +var Property = function(json) { + $.extend(this, json); }; /** @@ -38,7 +38,7 @@ var Property = function(key, value) { * @todo implement/test */ Property.prototype.validate = function(value) { - switch (property.type) { + switch (this.type) { case 'string': case 'text': // TODO check pattern @@ -59,10 +59,10 @@ Property.prototype.validate = function(value) { return value == '1' || value == ''; case 'multiple': - return $.inArray(value, property.options); + return this.options.contains(value); default: - alert('Error: unknown property!'); + throw new Exception('EntityException', 'Unknown property'); } }; @@ -70,25 +70,25 @@ Property.prototype.validate = function(value) { * * @todo implement/test */ -Property.prototype.getDOM = function() { - switch (property.type) { +Property.prototype.getInput = function(value) { + switch (this.type) { case 'string': case 'float': case 'integer': return $('') .attr('type', 'text') - .attr('name=', property.name) - .attr('maxlength', (property.type == 'string') ? property.max : 0); + .attr('name=', this.name) + .attr('maxlength', (property.type == 'string') ? this.max : 0); case 'text': return $('