diff --git a/htdocs/frontend/images/cross.png b/htdocs/frontend/images/cross.png
new file mode 100644
index 0000000..1514d51
Binary files /dev/null and b/htdocs/frontend/images/cross.png differ
diff --git a/htdocs/frontend/images/tick.png b/htdocs/frontend/images/tick.png
new file mode 100644
index 0000000..a9925a0
Binary files /dev/null and b/htdocs/frontend/images/tick.png differ
diff --git a/htdocs/frontend/index.html b/htdocs/frontend/index.html
index 7765dc1..87cc549 100644
--- a/htdocs/frontend/index.html
+++ b/htdocs/frontend/index.html
@@ -15,21 +15,18 @@
-
-
-
-
-
-
-
+
+
+
+
@@ -113,10 +110,6 @@
Hier können Sie öffentliche Kanäle abonnieren.
-
Kanal:
-
Cookie:
-
+
+ Middleware: default (local)
+ Kanal:
+
+ Cookie:
diff --git a/htdocs/frontend/javascripts/entities.js b/htdocs/frontend/javascripts/entities.js
new file mode 100644
index 0000000..3146047
--- /dev/null
+++ b/htdocs/frontend/javascripts/entities.js
@@ -0,0 +1,191 @@
+/**
+ *
+ *
+ * @author Florian Ziegler
+ * @author Justin Otherguy
+ * @author Steffen Vogel
+ * @copyright Copyright (c) 2011, 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 .
+ */
+
+/**
+ * Save minimal Entity in JSON cookie
+ */
+vz.entities.saveCookie = function() {
+ var expires = new Date(new Date().getTime() + 3e10); // in about a year
+ var arr = new Array;
+
+ this.each(function(entity) {
+ if (entity.cookie === true) {
+ arr.push(entity.uuid + '@' + entity.middleware);
+ }
+ }, true); // recursive!
+
+ $.setCookie('vz_entities', arr.join('|'), {expires: expires});
+};
+
+/**
+ * Load entities from JSON cookie
+ */
+vz.entities.loadCookie = function() {
+ var cookie = $.getCookie('vz_entities');
+ if (cookie) {
+ var arr = cookie.split('|');
+ arr.each(function(index, entry) {
+ var entity = entry.split('@');
+ vz.entities.push(new Entity({
+ middleware: entity[1],
+ uuid: entity[0],
+ cookie: true
+ }));
+ });
+ }
+};
+
+/**
+ * Load JSON data from the middleware
+ */
+vz.entities.loadData = function() {
+ $('#overlay').html('loading...
');
+
+ var queue = new Array;
+ this.each(function(entity) {
+ if (entity.active && entity.definition.model == 'Volkszaehler\\Model\\Channel') {
+ queue.push(entity.loadData());
+ }
+ }, true); // recursive!
+
+ return $.when.apply($, queue);
+};
+
+/**
+ * Overwritten each iterator to iterate recursively throug all entities
+ */
+vz.entities.each = function(cb, recursive) {
+ for (var i = 0; i < this.length; i++) {
+ cb(this[i]);
+
+ if (recursive) {
+ this[i].each(cb, true);
+ }
+ }
+}
+
+/**
+ * Create nested entity list
+ *
+ * @todo move to Entity class
+ */
+vz.entities.showTable = function() {
+ $('#entity-list tbody').empty();
+
+ vz.entities.sort(Entity.compare);
+
+ var c = 0; // for colors
+ this.each(function(entity) {
+ entity.color = vz.options.plot.colors[c++ % vz.options.plot.colors.length];
+ $('#entity-list tbody').append(entity.getDOMRow());
+ }, true); // recursive!
+
+ /*
+ * Initialize treeTable
+ *
+ * http://ludo.cubicphuse.nl/jquery-plugins/treeTable/doc/index.html
+ * https://github.com/ludo/jquery-plugins/tree/master/treeTable
+ */
+ // configure entities as draggable
+ $('#entity-list tr.channel span.indicator, #entity-list tr.aggregator span.indicator').draggable({
+ helper: 'clone',
+ opacity: 0.75,
+ refreshPositions: true, // Performance?
+ revert: 'invalid',
+ revertDuration: 300,
+ scroll: true
+ });
+
+ // configure aggregators as droppable
+ $('#entity-list tr.aggregator span.indicator').each(function() {
+ $(this).parents('tr').droppable({
+ //accept: 'tr.channel span.indicator, tr.aggregator span.indicator', // TODO
+ drop: function(event, ui) {
+ var child = $(ui.draggable.parents('tr')[0]).data('entity');
+ var from = child.parent;
+ var to = $(this).data('entity');
+
+ $('#entity-move').dialog({ // confirm prompt
+ resizable: false,
+ modal: true,
+ title: 'Verschieben',
+ width: 400,
+ buttons: {
+ 'Verschieben': function() {
+ try {
+ var queue = new Array;
+ queue.push(to.addChild(child)); // add to new aggregator
+
+ if (from !== undefined) {
+ queue.push(from.removeChild(child)); // remove from aggregator
+ }
+ else {
+ vz.uuids.remove(child.uuid); // remove from cookies
+ vz.uuids.save();
+ }
+ } catch (e) {
+ vz.wui.dialogs.exception(e);
+ } finally {
+ $.when(queue).done(function() {
+ // wait for middleware
+ vz.entities.loadDetails().done(vz.entities.showTable);
+ });
+ $(this).dialog('close');
+ }
+ },
+ 'Abbrechen': function() {
+ $(this).dialog('close');
+ }
+ }
+ });
+ },
+ hoverClass: 'accept',
+ over: function(event, ui) {
+ // make the droppable branch expand when a draggable node is moved over it
+ if (this.id != $(ui.draggable.parents('tr')[0]).id && !$(this).hasClass('expanded')) {
+ $(this).expand();
+ }
+ }
+ });
+ });
+
+ // make visible that a row is clicked
+ $('#entity-list table tbody tr').mousedown(function() {
+ $('tr.selected').removeClass('selected'); // deselect currently selected rows
+ $(this).addClass('selected');
+ });
+
+ // make sure row is selected when span is clicked
+ $('#entity-list table tbody tr span').mousedown(function() {
+ $($(this).parents('tr')[0]).trigger('mousedown');
+ });
+
+ $('#entity-list table').treeTable({
+ treeColumn: 2,
+ clickableNodeNames: true,
+ initialState: 'expanded'
+ });
+};
diff --git a/htdocs/frontend/javascripts/entity.js b/htdocs/frontend/javascripts/entity.js
index 4f5e813..7a2ae0f 100644
--- a/htdocs/frontend/javascripts/entity.js
+++ b/htdocs/frontend/javascripts/entity.js
@@ -1,7 +1,6 @@
/**
* Entity handling, parsing & validation
*
- * @author Florian Ziegler
* @author Justin Otherguy
* @author Steffen Vogel
* @copyright Copyright (c) 2011, The volkszaehler.org project
@@ -28,23 +27,70 @@
* Entity constructor
* @todo add validation
*/
-var Entity = function(json, parent) {
- $.extend(true, this, json);
- this.parent = parent;
-
+var Entity = function(json) {
+ this.parseJSON(json);
+
if (this.active === undefined) {
- this.active = true; // active by default
+ this.active = true; // activate by default
}
+
+};
+
+Entity.prototype.parseJSON = function(json) {
+ $.extend(true, this, json);
+
if (this.children) {
for (var i = 0; i < this.children.length; i++) {
- this.children[i] = new Entity(this.children[i], this);
- };
+ this.children[i] = new Entity(this.children[i]);
+ }
this.children.sort(Entity.compare);
}
+
+ if (this.type !== undefined) {
+ this.definition = vz.capabilities.definitions.get('entities', this.type);
+ }
+};
- this.definition = vz.capabilities.definitions.get('entities', this.type);
+/**
+ * Query middleware for details
+ */
+Entity.prototype.loadDetails = function() {
+ return vz.load({
+ url: this.middleware,
+ controller: 'entity',
+ identifier: this.uuid,
+ context: this,
+ success: function(json) {
+ this.parseJSON(json.entity);
+ }
+ });
+};
+
+Entity.prototype.loadData = function() {
+ return vz.load({
+ controller: 'data',
+ url: this.middleware,
+ identifier: this.uuid,
+ context: this,
+ data: {
+ from: Math.floor(vz.options.plot.xaxis.min),
+ to: Math.ceil(vz.options.plot.xaxis.max),
+ tuples: vz.options.tuples
+ },
+ success: function(json) {
+ this.data = json.data;
+
+ if (this.data.count > 0) {
+ if (this.data.min[1] < vz.options.plot.yaxis.min) { // allow negative values for temperature sensors
+ vz.options.plot.yaxis.min = null;
+ }
+ }
+
+ this.updateDOMRow();
+ }
+ });
};
/**
@@ -53,7 +99,7 @@ var Entity = function(json, parent) {
Entity.prototype.showDetails = function() {
$('')
.addClass('details')
- .append(this.getDOM())
+ .append(this.getDOMDetails())
.dialog({
title: 'Details für ' + this.title,
width: 480,
@@ -66,63 +112,98 @@ Entity.prototype.showDetails = function() {
*
* @todo implement/test
*/
-Entity.prototype.getDOM = function(edit) {
+Entity.prototype.getDOMDetails = function(edit) {
var table = $('
');
var data = $('
');
-
- for (var property in this) {
- if (this.hasOwnProperty(property) && !['data', 'definition', 'children', 'parent'].contains(property)) {
- switch(property) {
- case 'type':
- var title = 'Typ';
- var value = this.definition.translation[vz.options.language];
- break;
-
- case 'uuid':
- var title = 'UUID';
- var value = '' + this[property] + ' ';
- break;
+
+ // general properties
+ var general = ['uuid', 'middleware', 'type', 'color', 'cookie'];
+ var sections = ['required', 'optional'];
+
+ general.each(function(index, property) {
+ switch(property) {
+ case 'type':
+ var title = 'Typ';
+ var value = this.definition.translation[vz.options.language];
+ break;
- case 'color':
- var title = 'Farbe';
- var value = '' + this[property] + ' ';
- break;
-
- case 'public':
- var title = vz.capabilities.definitions.get('properties', property).translation[vz.options.language];
- var value = (this[property]) ? 'ja' : 'nein';
- break;
-
+ case 'middleware':
+ var title = 'Middleware';
+ var value = '' + this.middleware + ' ';
+ break;
- case 'active':
- var title = 'Aktiv';
- var value = (this[property]) ? 'ja' : 'nein';
- break;
-
- default:
- var title = vz.capabilities.definitions.get('properties', property).translation[vz.options.language];
- var value = this[property];
- }
-
- data.append($('')
- .append($('')
- .addClass('key')
- .text(title)
- )
- .append($(' ')
- .addClass('value')
- .append(value)
- )
- );
+ case 'uuid':
+ var title = 'UUID';
+ var value = '' + this.uuid + ' ';
+ break;
+
+ case 'color':
+ var title = 'Farbe';
+ var value = '' + this.color + ' ';
+ break;
+
+ case 'cookie':
+ var title = 'Cookie';
+ value = ' ';
+ break;
+ case 'active':
+ var title = 'Aktiv';
+ var value = ' ';
+ break;
}
- }
+
+ data.append($(' ')
+ .addClass('property')
+ .addClass('general')
+ .append($('')
+ .addClass('key')
+ .text(title)
+ )
+ .append($(' ')
+ .addClass('value')
+ .append(value)
+ )
+ );
+ }, this);
+
+ sections.each(function(index, section) {
+ this.definition[section].each(function(index, property) {
+ if (this.hasOwnProperty(property)) {
+ var definition = vz.capabilities.definitions.get('properties', property);
+ var title = definition.translation[vz.options.language];
+ var value = this[property];
+
+ if (definition.type == 'boolean') {
+ value = ' ';
+ }
+
+ if (property == 'cost') {
+ value = (value * 1000 * 100) + ' ct/k' + this.definition.unit + 'h'; // ct per kWh
+ }
+
+ data.append($(' ')
+ .addClass('property')
+ .addClass(section)
+ .append($('')
+ .addClass('key')
+ .text(title)
+ )
+ .append($(' ')
+ .addClass('value')
+ .append(value)
+ )
+ );
+ }
+ }, this);
+ }, this);
return table.append(data);
};
-Entity.prototype.getRow = function() {
+Entity.prototype.getDOMRow = function() {
var row = $(' ')
.addClass((this.parent) ? 'child-of-entity-' + this.parent.uuid : '')
.addClass((this.definition.model == 'Volkszaehler\\Model\\Aggregator') ? 'aggregator' : 'channel')
+ .addClass('entity')
.attr('id', 'entity-' + this.uuid)
.append($('')
.addClass('visibility')
@@ -171,81 +252,59 @@ Entity.prototype.getRow = function() {
)
.data('entity', this);
- if (vz.uuids.contains(this.uuid)) { // removable from cookies?
- $('td.ops', row).prepend($(' ')
- .attr('type', 'image')
- .attr('src', 'images/delete.png')
- .attr('alt', 'delete')
- .bind('click', this, function(event) {
- vz.uuids.remove(event.data.uuid);
- vz.uuids.save();
-
- vz.entities.remove(event.data);
- vz.entities.showTable();
-
- vz.wui.drawPlot();
- })
- );
- }
-
+ $('td.ops', row).prepend($(' ')
+ .attr('type', 'image')
+ .attr('src', 'images/delete.png')
+ .attr('alt', 'delete')
+ .bind('click', this, function(event) {
+ vz.entities.remove(event.data);
+ vz.entities.saveCookie();
+ vz.entities.showTable();
+ vz.wui.drawPlot();
+ })
+ );
+
return row;
};
-Entity.prototype.loadData = function() {
- return vz.load({
- controller: 'data',
- identifier: this.uuid,
- context: this,
- data: {
- from: Math.floor(vz.options.plot.xaxis.min),
- to: Math.ceil(vz.options.plot.xaxis.max),
- tuples: vz.options.tuples
- },
- success: function(json) {
- this.data = json.data;
-
- var year = 60*60*24*365*1000; /* in seconds */
- var delta = this.data.to - this.data.from;
+Entity.prototype.updateDOMRow = function() {
+ var row = $('#entity-' + this.uuid);
+
+ var delta = this.data.to - this.data.from;
+ var year = 365*24*60*60*1000;
+
+ if (this.data.count > 0) { // update statistics if data available
+ $('.min', row)
+ .text(vz.wui.formatNumber(this.data.min[1], true) + this.definition.unit)
+ .attr('title', $.plot.formatDate(new Date(this.data.min[0]), '%d. %b %y %h:%M:%S', vz.options.plot.xaxis.monthNames));
+ $('.max', row)
+ .text(vz.wui.formatNumber(this.data.max[1], true) + this.definition.unit)
+ .attr('title', $.plot.formatDate(new Date(this.data.max[0]), '%d. %b %y %h:%M:%S', vz.options.plot.xaxis.monthNames));
+ $('.average', row)
+ .text(vz.wui.formatNumber(this.data.average, true) + this.definition.unit);
+ $('.last', row)
+ .text(vz.wui.formatNumber(this.data.tuples.last()[1], true) + this.definition.unit);
- if (this.data.count > 0) {
- if (this.data.min[1] < vz.options.plot.yaxis.min) { // allow negative values for temperature sensors
- vz.options.plot.yaxis.min = null;
- }
-
- // update details in table
- $('#entity-' + this.uuid + ' .min')
- .text(vz.wui.formatNumber(this.data.min[1]) + ' ' + this.definition.unit)
- .attr('title', $.plot.formatDate(new Date(this.data.min[0] + vz.options.timezoneOffset), '%d. %b %h:%M:%S', vz.options.plot.xaxis.monthNames));
- $('#entity-' + this.uuid + ' .max')
- .text(vz.wui.formatNumber(this.data.max[1]) + ' ' + this.definition.unit)
- .attr('title', $.plot.formatDate(new Date(this.data.max[0] + vz.options.timezoneOffset), '%d. %b %h:%M:%S', vz.options.plot.xaxis.monthNames));
- $('#entity-' + this.uuid + ' .average')
- .text(vz.wui.formatNumber(this.data.average) + ' ' + this.definition.unit);
- $('#entity-' + this.uuid + ' .last')
- .text(vz.wui.formatNumber(this.data.tuples.last()[1]) + ' ' + this.definition.unit);
- if (this.definition.interpreter == 'Volkszaehler\\Interpreter\\MeterInterpreter') { // sensors have no consumption
- $('#entity-' + this.uuid + ' .consumption')
- .text(vz.wui.formatNumber((this.data.consumption > 1000) ? this.data.consumption / 1000 : this.data.consumption) +
- ((this.data.consumption > 1000) ? ' k' : ' ') + this.definition.unit + 'h')
- .attr('title', vz.wui.formatNumber((this.data.consumption * (year/delta) > 1000) ? (this.data.consumption * (year/delta)) / 1000 : this.data.consumption * (year/delta)) +
- ((this.data.consumption * (year/delta) > 1000) ? ' k' : ' ') + this.definition.unit + 'h' + '/Jahr');
- }
- if (this.cost !== undefined) {
- $('#entity-' + this.uuid + ' .cost')
- .text(vz.wui.formatNumber(this.cost * this.data.consumption) + ' €')
- .attr('title', vz.wui.formatNumber(this.cost * this.data.consumption * (year/delta)) + ' €/Jahr');
- }
- }
- else { // no data available, clear table
- $('#entity-' + this.uuid + ' .min').text('').attr('title', '');
- $('#entity-' + this.uuid + ' .max').text('').attr('title', '');
- $('#entity-' + this.uuid + ' .average').text('');
- $('#entity-' + this.uuid + ' .last').text('');
- $('#entity-' + this.uuid + ' .consumption').text('');
- $('#entity-' + this.uuid + ' .cost').text('');
- }
+ if (this.definition.interpreter == 'Volkszaehler\\Interpreter\\MeterInterpreter') { // sensors have no consumption
+ $('.consumption', row)
+ .text(vz.wui.formatNumber(this.data.consumption, true) + this.definition.unit + 'h')
+ .attr('title', vz.wui.formatNumber(this.data.consumption * (year/delta), true) + this.definition.unit + 'h' + '/Jahr');
}
- });
+
+ if (this.cost) {
+ $('.cost', row)
+ .text(vz.wui.formatNumber(this.cost * this.data.consumption) + ' €')
+ .attr('title', vz.wui.formatNumber(this.cost * this.data.consumption * (year/delta)) + ' €/Jahr');
+ }
+ }
+ else { // no data available, clear table
+ $('.min', row).text('').attr('title', '');
+ $('.max', row).text('').attr('title', '');
+ $('.average', row).text('');
+ $('.last', row).text('');
+ $('.consumption', row).text('');
+ $('.cost', row).text('');
+ }
};
/**
@@ -280,38 +339,19 @@ Entity.prototype.removeChild = function(child) {
});
};
-/**
- * Validate Entity for required and optional properties and their values
- *
- * @return boolean
- * @todo implement/test
- */
-Entity.prototype.validate = function() {
- this.definition.required.each(function(index, property) {
- var propertyDefinition = vz.capabilities.definitions.get('properties', property);
- if (!validateProperty(property, form.elements[property.name].value)) {
- throw new Exception('EntityException', 'Invalid property: ' + property.name + ' = ' + form.elements[property.name].value);
- }
- });
-
- entity.optional.each(function(index, property) {
- var property = getDefinition(properties, property);
- });
-
- return true;
-};
-
/**
* Calls the callback function for the entity and all nested children
*
* @param cb callback function
*/
-Entity.prototype.each = function(cb) {
- cb(this, this.parent);
-
+Entity.prototype.each = function(cb, recursive) {
if (this.children) {
for (var i = 0; i < this.children.length; i++) {
- this.children[i].each(cb, this); // call recursive
+ cb(this.children[i], this);
+
+ if (recursive) {
+ this.children[i].each(cb, true); // call recursive
+ }
}
}
};
diff --git a/htdocs/frontend/javascripts/functions.js b/htdocs/frontend/javascripts/functions.js
index 70b700a..8e9a785 100644
--- a/htdocs/frontend/javascripts/functions.js
+++ b/htdocs/frontend/javascripts/functions.js
@@ -24,13 +24,20 @@
* volkszaehler.org. If not, see .
*/
+var Exception = function(type, message, code) {
+ return {
+ type: type,
+ message: message,
+ code: code
+ };
+}
+
/**
* Universal helper for middleware ajax requests with error handling
*/
vz.load = function(args) {
$.extend(args, {
- url: this.options.middlewareUrl,
- dataType: 'json',
+ accepts: 'application/json',
error: function(xhr) {
try {
if (xhr.getResponseHeader('Content-type') == 'application/json') {
@@ -40,8 +47,9 @@ vz.load = function(args) {
throw new Exception(json.exception.type, json.exception.message, (json.exception.code) ? json.exception.code : xhr.status);
}
}
-
- throw new Exception(xhr.statusText, 'Unknown middleware response', xhr.status)
+ else {
+ throw new Exception(xhr.statusText, 'Unknown middleware response', xhr.status)
+ }
}
catch (e) {
vz.wui.dialogs.exception(e);
@@ -49,14 +57,28 @@ vz.load = function(args) {
}
});
- if (args.controller) {
+ if (args.url === undefined) { // local middleware by default
+ args.url = vz.middleware[0].url;
+ }
+
+ if (args.url == vz.middleware[0].url) { // local request
+ args.dataType = 'json';
+ }
+ else { // remote request
+ args.dataType = 'jsonp';
+ args.jsonp = 'padding';
+ }
+
+ if (args.controller !== undefined) {
args.url += '/' + args.controller;
}
- if (args.identifier) {
+
+ if (args.identifier !== undefined) {
args.url += '/' + args.identifier;
}
+
args.url += '.json';
-
+
return $.ajax(args);
};
@@ -65,14 +87,18 @@ vz.load = function(args) {
*/
vz.parseUrlParams = function() {
var vars = $.getUrlParams();
+ var uuids = new Array;
+ var save = false;
+
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 */ }
- });
+ uuids = (typeof vars[key] == 'string') ? [vars[key]] : vars[key]; // handle multiple uuids
+ break;
+
+ case 'save': // save new uuids in cookie
+ save = true;
break;
case 'from':
@@ -85,6 +111,22 @@ vz.parseUrlParams = function() {
}
}
}
+
+ uuids.each(function(index, uuid) {
+ try {
+ vz.entities.push(new Entity({
+ middleware: vz.middleware[0].url,
+ uuid: uuid,
+ cookie: save
+ }));
+ } catch (exception) {
+ /* ignore exception */
+ }
+ });
+
+ if (save) {
+ vz.entities.saveCookie();
+ }
};
/**
diff --git a/htdocs/frontend/javascripts/init.js b/htdocs/frontend/javascripts/init.js
index fdf8667..608bc44 100644
--- a/htdocs/frontend/javascripts/init.js
+++ b/htdocs/frontend/javascripts/init.js
@@ -32,8 +32,10 @@
* we dont want to pollute the global namespace
*/
var vz = {
- // entity properties + data
- entities: new Array, // TODO new Entity?
+ entities: new Array, // entity properties + data
+ middleware: [{ // default middleware
+ url: '../middleware.php',
+ }],
// web user interface
wui: {
@@ -41,17 +43,14 @@ var vz = {
timeout: null
},
- // known UUIDs in the browser
- uuids: new Array,
-
- // flot instance
- plot: { },
-
// debugging and runtime information from middleware
capabilities: {
definitions: { } // definitions of entities & properties
},
-
+
+ // flot instance
+ plot: { },
+
// options loaded from cookies in options.js
options: { }
};
@@ -72,15 +71,15 @@ $(document).ready(function() {
vz.wui.dialogs.error('Javascript Runtime Error', errorMsg);
};
- vz.uuids.load(); // load uuids from cookie
- vz.options.load(); // load options from cookie
+ vz.entities.loadCookie(); // load uuids from cookie
+ vz.options.loadCookies(); // load options from cookie
vz.parseUrlParams(); // parse additional url params (new uuid etc..)
// initialize user interface
vz.wui.init();
vz.wui.initEvents();
- if (vz.uuids.length == 0) {
+ if (vz.entities.length == 0) {
$('#entity-add').dialog('open');
}
@@ -90,7 +89,12 @@ $(document).ready(function() {
$('#snapshot').show();
}
- vz.entities.loadDetails().done(function(a, b, c, d) {
+ var queue = new Array;
+ vz.entities.each(function(entity) {
+ queue.push(entity.loadDetails());
+ }, true);
+
+ $.when.apply($, queue).done(function() {
vz.entities.showTable();
vz.entities.loadData().done(vz.wui.drawPlot);
});
diff --git a/htdocs/frontend/javascripts/options.js b/htdocs/frontend/javascripts/options.js
index ce1ecbf..e20418c 100644
--- a/htdocs/frontend/javascripts/options.js
+++ b/htdocs/frontend/javascripts/options.js
@@ -27,9 +27,8 @@
// default time interval to show
vz.options = {
language: 'de',
- middlewareUrl: '../middleware.php', // TODO default middleware, store middleware urls in cookies
- tuples: 300,
precision: 2, // TODO update from middleware capabilities?
+ tuples: null, // automatically determined by plot size
render: 'lines',
refresh: false,
minTimeout: 3000, // minimum refresh time in ms
@@ -43,7 +42,6 @@ vz.options.plot = {
shadowSize: 0,
points: {
radius: 1,
- //symbol: 'square'
symbol: function(ctx, x, y, radius, shadow) { // just draw simple pixels
ctx.lineWidth = 1;
ctx.strokeRect(x-1, y-1, 2, 2);
@@ -70,7 +68,7 @@ vz.options.plot = {
}
}
-vz.options.save = function() {
+vz.options.saveCookies = function() {
for (var key in vz.options) {
if (vz.options.hasOwnProperty(key) &&
typeof vz.options[key] != 'function' &&
@@ -82,7 +80,7 @@ vz.options.save = function() {
}
};
-vz.options.load = function() {
+vz.options.loadCookies = function() {
for (var key in this) {
var value = $.getCookie('vz_' + key);
if (value !== undefined) {
diff --git a/htdocs/frontend/javascripts/uuid.js b/htdocs/frontend/javascripts/uuid.js
deleted file mode 100644
index 2ce56e6..0000000
--- a/htdocs/frontend/javascripts/uuid.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * UUID handling
- *
- * @author Florian Ziegler
- * @author Justin Otherguy
- * @author Steffen Vogel
- * @copyright Copyright (c) 2011, 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 .
- */
-
-/**
- * Add given UUID and update cookie
- */
-vz.uuids.add = function(uuid) {
- if (this.validate(uuid)) {
- if (!this.contains(uuid)) {
- this.push(uuid);
- }
- else {
- throw new Exception('UUIDException', 'UUID already added: ' + uuid);
- }
- }
- else {
- throw new Exception('UUIDException', 'Invalid UUID');
- }
-};
-
-/**
- * Remove UUID and update cookie
- */
-vz.uuids.remove = function(uuid) {
- if (this.contains(uuid)) {
- this.splice(this.indexOf(uuid), 1); // remove uuid from array
- }
- else {
- throw new Exception('UUIDException', 'Unknown UUID: ' + uuid);
- }
-};
-
-/**
- * Validate UUID
- */
-vz.uuids.validate = function(uuid) {
- return uuid.match(/^[0-9a-zA-Z]{8}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{4}-[0-9a-zA-Z]{12}$/);
-};
-
-/**
- * Save uuids as cookie
- */
-vz.uuids.save = function() {
- var expires = new Date(new Date().getTime() + 31536e6); // expires in a year
- $.setCookie('vz_uuids', this.join(';'), {expires: expires});
-};
-
-/**
- * Load uuids from cookie
- */
-vz.uuids.load = function() {
- var cookie = $.getCookie('vz_uuids');
- if (cookie) {
- cookie.split(';').each(function(index, uuid) {
- vz.uuids.add(uuid);
- });
- }
-};
diff --git a/htdocs/frontend/javascripts/wui.js b/htdocs/frontend/javascripts/wui.js
index eef7569..c584e4d 100644
--- a/htdocs/frontend/javascripts/wui.js
+++ b/htdocs/frontend/javascripts/wui.js
@@ -28,6 +28,8 @@
* Initialize the WUI (Web User Interface)
*/
vz.wui.init = function() {
+ vz.options.tuples = Math.round($('#flot').width() / 3);
+
// initialize dropdown accordion
$('#accordion h3').click(function() {
$(this).next().toggle('fast');
@@ -37,7 +39,7 @@ vz.wui.init = function() {
// buttons
$('button, input[type=button],[type=image],[type=submit]').button();
- $('button[name=options-save]').click(vz.options.save);
+ $('button[name=options-save]').click(vz.options.saveCookies);
$('button[name=entity-add]').click(this.dialogs.init);
$('#permalink').click(function() { window.location = vz.wui.getPermalink(); });
$('#snapshot').click(function() { window.location = vz.wui.getSnaplink(); }).hide();
@@ -46,20 +48,6 @@ vz.wui.init = function() {
$('#controls button').click(this.handleControls);
$('#controls').buttonset();
- // tuple resolution
- vz.options.tuples = Math.round($('#flot').width() / 4);
- $('#tuples').val(vz.options.tuples).change(function() {
- vz.options.tuples = $(this).val();
- vz.entities.loadData().done(vz.wui.drawPlot);
- });
-
- // middleware address
- $('#middleware-url')
- .val(vz.options.middlewareUrl)
- .change(function() {
- vz.options.middlewareUrl = $(this).val();
- });
-
// auto refresh
if (vz.options.refresh) {
$('#refresh').attr('checked', true);
@@ -102,7 +90,8 @@ vz.wui.dialogs.init = function() {
controller: 'entity',
success: function(json) {
if (json.entities.length > 0) {
- json.entities.each(function(index, entity) {
+ json.entities.each(function(index, json) {
+ var entity = new Entity(json);
$('#entity-subscribe-public select#public').append(
$('').html(entity.title).data('entity', entity)
);
@@ -119,25 +108,20 @@ vz.wui.dialogs.init = function() {
});
$('#entity-create option[value=power]').attr('selected', 'selected');
- /*$('#entity-create select[name=type] option:selected').data('definition').required.each(function(index, property) {
- $('#entity-create #properties').append(
- vz.capabilities.definitions.get('properties', property).getDOM()
- )
- });*/
-
- $('#entity-create-middlware').val(vz.options.middlewareUrl);
+ $('#entity-create-middleware').val(vz.middleware[0].url);
// actions
$('#entity-subscribe input[type=button]').click(function() {
try {
- var uuid = $('#entity-subscribe input#uuid');
- vz.uuids.add(uuid.val());
-
- if ($('#entity-subscribe input.cookie').attr('checked')) {
- vz.uuids.save();
- }
+ var entity = new Entity({
+ uuid: $('#entity-subscribe input#uuid').val(),
+ middleware: $('#entity-subscribe input#middleware').val(),
+ cookie: Boolean($('#entity-subscribe input.cookie').attr('checked'))
+ });
- vz.entities.loadDetails().done(function() {
+ entity.loadDetails().done(function() {
+ vz.entities.push(entity);
+ vz.entities.saveCookie();
vz.entities.showTable();
vz.entities.loadData().done(vz.wui.drawPlot);
}); // reload entity details and load data
@@ -147,8 +131,6 @@ vz.wui.dialogs.init = function() {
}
finally {
$('#entity-add').dialog('close');
- $('#entity-add input[type!=button]').val(''); // reset form
- $('#entity-add input.cookie').attr('checked', false); // reset form
}
});
@@ -156,24 +138,19 @@ vz.wui.dialogs.init = function() {
var entity = $('#entity-subscribe-public select#public option:selected').data('entity');
try {
- vz.uuids.add(entity.uuid);
-
- if ($('#entity-subscribe-public input.cookie').attr('checked')) {
- vz.uuids.save();
- }
+ entity.cookie = Boolean($('#entity-subscribe-public input.cookie').attr('checked'));
+ entity.middleware = vz.middleware[0].url;
- vz.entities.loadDetails().done(function() {
- vz.entities.showTable();
- vz.entities.loadData().done(vz.wui.drawPlot);
- }); // reload entity details and load data
+ vz.entities.push(entity);
+ vz.entities.saveCookie();
+ vz.entities.showTable();
+ vz.entities.loadData().done(vz.wui.drawPlot);
}
catch (e) {
vz.wui.dialogs.exception(e);
}
finally {
$('#entity-add').dialog('close');
- $('#entity-add input[type!=button]').val(''); // reset form
- $('#entity-add input.cookie').attr('checked', false); // reset form
}
});
@@ -200,7 +177,7 @@ vz.wui.getPermalink = function() {
if (entity.active && entity.definition.model == 'Volkszaehler\\Model\\Channel') {
uuids.push(entity.uuid);
}
- });
+ }, true); // recursive!
var params = $.param({
from: Math.floor(vz.options.plot.xaxis.min),
@@ -222,7 +199,7 @@ vz.wui.getSnaplink = function() {
if (entity.active) {
uuids.push(entity.uuid);
}
- });
+ }, true); // recursive!
return vz.options.middlewareUrl + '/data/' + uuids[0] + '.png?' + $.param({
from: Math.floor(vz.options.plot.xaxis.min),
@@ -404,168 +381,30 @@ vz.wui.clearTimeout = function(text) {
* therefore "vz.options.precision" needs
* to be set to 1 (for 1 decimal) in that case
*/
-vz.wui.formatNumber = function(number) {
- return Math.round(number * Math.pow(10, vz.options.precision)) / Math.pow(10, vz.options.precision);
+vz.wui.formatNumber = function(number, prefix) {
+ var siPrefixes = ['k', 'M', 'G', 'T'];
+ var siIndex = 0;
+
+ while (prefix && number > 1000 && siIndex < siPrefixes.length-1) {
+ number /= 1000;
+ siIndex++;
+ }
+
+ number = Math.round(number * Math.pow(10, vz.options.precision)) / Math.pow(10, vz.options.precision); // rounding
+
+ if (prefix) {
+ number += (siIndex > 0) ? ' ' + siPrefixes[siIndex-1] : ' ';
+ }
+
+ return number;
}
vz.wui.updateHeadline = function() {
- var from = $.plot.formatDate(new Date(vz.options.plot.xaxis.min + vz.options.timezoneOffset), '%d. %b %h:%M:%S', vz.options.plot.xaxis.monthNames);
- var to = $.plot.formatDate(new Date(vz.options.plot.xaxis.max + vz.options.timezoneOffset), '%d. %b %h:%M:%S', vz.options.plot.xaxis.monthNames);
+ var from = $.plot.formatDate(new Date(vz.options.plot.xaxis.min), '%d. %b %y %h:%M', vz.options.plot.xaxis.monthNames);
+ var to = $.plot.formatDate(new Date(vz.options.plot.xaxis.max), '%d. %b %y %h:%M', vz.options.plot.xaxis.monthNames);
$('#title').html(from + ' - ' + to);
}
-/**
- * Overwritten each iterator to iterate recursively throug all entities
- */
-vz.entities.each = function(cb) {
- for (var i = 0; i < this.length; i++) {
- this[i].each(cb);
- }
-}
-
-/**
- * Get all entity information from middleware
- */
-vz.entities.loadDetails = function() {
- this.clear();
-
- var queue = new Array;
-
- vz.uuids.each(function(index, uuid) {
- queue.push(vz.load({
- controller: 'entity',
- identifier: uuid,
- success: function(json) {
- vz.entities.push(new Entity(json.entity));
- }
- }));
- });
-
- return $.when.apply($, queue);
-};
-
-/**
- * Create nested entity list
- *
- * @todo move to Entity class
- */
-vz.entities.showTable = function() {
- $('#entity-list tbody').empty();
-
- vz.entities.sort(Entity.compare);
-
- var c = 0; // for colors
- vz.entities.each(function(entity, parent) {
- entity.color = vz.options.plot.colors[c++ % vz.options.plot.colors.length];
-
- $('#entity-list tbody').append(entity.getRow());
- });
-
- /*
- * Initialize treeTable
- *
- * http://ludo.cubicphuse.nl/jquery-plugins/treeTable/doc/index.html
- * https://github.com/ludo/jquery-plugins/tree/master/treeTable
- */
- // configure entities as draggable
- $('#entity-list tr.channel span.indicator, #entity-list tr.aggregator span.indicator').draggable({
- helper: 'clone',
- opacity: .75,
- refreshPositions: true, // Performance?
- revert: 'invalid',
- revertDuration: 300,
- scroll: true
- });
-
- // configure aggregators as droppable
- $('#entity-list tr.aggregator span.indicator').each(function() {
- $(this).parents('tr').droppable({
- //accept: 'tr.channel span.indicator, tr.aggregator span.indicator', // TODO
- drop: function(event, ui) {
- var child = $(ui.draggable.parents('tr')[0]).data('entity');
- var from = child.parent;
- var to = $(this).data('entity');
-
- $('#entity-move').dialog({ // confirm prompt
- resizable: false,
- modal: true,
- title: 'Verschieben',
- width: 400,
- buttons: {
- 'Verschieben': function() {
- try {
- var queue = new Array;
- queue.push(to.addChild(child)); // add to new aggregator
-
- if (from !== undefined) {
- queue.push(from.removeChild(child)); // remove from aggregator
- }
- else {
- vz.uuids.remove(child.uuid); // remove from cookies
- vz.uuids.save();
- }
- } catch (e) {
- vz.wui.dialogs.exception(e);
- } finally {
- $.when(queue).done(function() {
- // wait for middleware
- vz.entities.loadDetails().done(vz.entities.showTable);
- });
- $(this).dialog('close');
- }
- },
- 'Abbrechen': function() {
- $(this).dialog('close');
- }
- }
- });
- },
- hoverClass: 'accept',
- over: function(event, ui) {
- // make the droppable branch expand when a draggable node is moved over it
- if (this.id != $(ui.draggable.parents('tr')[0]).id && !$(this).hasClass('expanded')) {
- $(this).expand();
- }
- }
- });
- });
-
- // make visible that a row is clicked
- $('#entity-list table tbody tr').mousedown(function() {
- $('tr.selected').removeClass('selected'); // deselect currently selected rows
- $(this).addClass('selected');
- });
-
- // make sure row is selected when span is clicked
- $('#entity-list table tbody tr span').mousedown(function() {
- $($(this).parents('tr')[0]).trigger('mousedown');
- });
-
- $('#entity-list table').treeTable({
- treeColumn: 2,
- clickableNodeNames: true,
- initialState: 'expanded'
- });
-};
-
-/**
- * Load json data from the middleware
- *
- * @todo move to Entity class
- */
-vz.entities.loadData = function() {
- $('#overlay').html('loading...
');
-
- var queue = new Array;
-
- vz.entities.each(function(entity) {
- if (entity.active && entity.definition.model == 'Volkszaehler\\Model\\Channel') {
- queue.push(entity.loadData());
- }
- });
-
- return $.when.apply($, queue);
-};
/**
* Draws plot to container
@@ -588,7 +427,7 @@ vz.wui.drawPlot = function () {
series.push(serie);
}
- });
+ }, true); // recursive!
if (series.length == 0) {
$('#overlay').html('nothing to plot...
');
@@ -612,14 +451,6 @@ vz.wui.drawPlot = function () {
/*
* Error & Exception handling
*/
-
-var Exception = function(type, message, code) {
- return {
- type: type,
- message: message,
- code: code
- };
-}
vz.wui.dialogs.error = function(error, description, code) {
if (code !== undefined) {
diff --git a/htdocs/frontend/stylesheets/style.css b/htdocs/frontend/stylesheets/style.css
index 7f22f17..e514940 100644
--- a/htdocs/frontend/stylesheets/style.css
+++ b/htdocs/frontend/stylesheets/style.css
@@ -11,14 +11,18 @@ table {
width: 100%;
}
-thead tr th {
+tr th {
border-bottom: 2px solid grey;
text-align: left;
font-size: 0.9em;
}
-tbody tr td {
+tr.property td {
+ border-top: 1px solid #A7A7A7;
+}
+
+tr.entity td {
border-top: 1px solid #A7A7A7;
}