first bunch of changes to subscribe to remote channels

This commit is contained in:
Steffen Vogel 2011-06-26 04:38:13 +02:00
parent 3493bce3bc
commit e4e077f412
11 changed files with 527 additions and 500 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

View file

@ -15,21 +15,18 @@
<script type="text/javascript" src="javascripts/jquery/jquery-treeTable.js"></script>
<script type="text/javascript" src="javascripts/jquery/jquery-extensions.js"></script>
<!-- misc helpers -->
<script type="text/javascript" src="javascripts/helper.js"></script>
<!--[if IE]><script language="javascript" type="text/javascript" src="javascripts/flot/excanvas.min.js"></script><![endif]-->
<script type="text/javascript" src="javascripts/flot/jquery.flot.js"></script>
<script type="text/javascript" src="javascripts/flot/jquery.flot.crosshair.js"></script>
<script type="text/javascript" src="javascripts/flot/jquery.flot.selection.js"></script>
<!-- volkszaehler.org code -->
<script type="text/javascript" src="javascripts/init.js"></script>
<script type="text/javascript" src="javascripts/options.js"></script>
<script type="text/javascript" src="javascripts/functions.js"></script>
<script type="text/javascript" src="javascripts/uuid.js"></script>
<script type="text/javascript" src="javascripts/wui.js"></script>
<script type="text/javascript" src="javascripts/entity.js"></script>
<script type="text/javascript" src="javascripts/entities.js"></script>
<script type="text/javascript" src="javascripts/wui.js"></script>
<script type="text/javascript" src="javascripts/helper.js"></script>
<script type="text/javascript" src="javascripts/functions.js"></script>
<link rel="stylesheet" type="text/css" href="stylesheets/ui-lightness/jquery-ui-1.8.10.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/jquery.treeTable.css">
@ -113,10 +110,6 @@
<td><label for="middleware-url"><img src="images/server.png" alt="" /> Adresse der Middleware</label></td>
<td><input type="text" id="middleware-url" /></td>
</tr>
<!-- <tr>
<td><label for="tuples"><img src="images/shading.png" alt="" /> Tuples (Auflösung)</label></td>
<td><input type="text" id="tuples" size="5" /></td>
</tr> -->
</tbody>
</table>
<div><button name="options-save"><img src="images/save.png" alt="save" /> Einstellungen speichern</button></div>
@ -144,30 +137,35 @@
</ul>
<div id="entity-subscribe">
<p>Hier k&ouml;nnen Sie einen existierenden Kanal über seine UUID hinzuf&uuml;gen</p>
<p><label for="uuid">UUID:</label> <input id="uuid" type="text" size="36" maxlength="36" /></p>
<!-- <p><label for="middleware">Middleware: </label> <input id="middleware" type="text" /></p> -->
<p><label for="cookie">Cookie:</label> <input class="cookie" type="checkbox" /></p>
<input type="button" value="abonnieren" />
<table>
<tr><td><label for="uuid">UUID:</label></td><td><input id="uuid" type="text" size="36" maxlength="36" /></td></tr>
<tr><td><label for="middleware">Middleware URL:</label></td><td><input id="middleware" type="text" /></td></tr>
</table>
<p><input type="button" value="abonnieren" /> <label for="cookie">Cookie:</label> <input class="cookie" type="checkbox" /></p>
</div>
<div id="entity-subscribe-public">
<p>Hier k&ouml;nnen Sie &ouml;ffentliche Kan&auml;le abonnieren.</p>
<p><label for="uuid">Kanal:</label> <select id="public" size="1"></select></p>
<p><label for="cookie">Cookie:</label> </td><td><input class="cookie" type="checkbox" /></p>
<input type="button" value="abonnieren" />
<table>
<tr><td><label for="middleware">Middleware:</label></td><td><select id="middleware"><option value="../middleware.php">default (local)</option></select></td></tr>
<tr><td><label for="uuid">Kanal:</label></td><td><select id="public" size="1"></select></td></tr>
</table>
<p><input type="button" value="abonnieren" /> <label for="cookie">Cookie:</label> <input class="cookie" type="checkbox" /></p>
</div>
<div id="entity-create">
<form method="post" target="_blank">
<form method="get" target="_blank">
<table>
<tr><td>Middleware URL:</td><td><input type="text" id="entity-create-middlware" /><p style="font-size: small">Bsp: http://volkszaehler.org/demo/middleware.php</p></td></tr>
<tr><td>Typ:</td><td><select name="type" size="1"></select></td></tr>
<tr><td>&Ouml;ffentlich:</td><td><input type="radio" name="public" value="1"> ja<br /><input type="radio" name="public" value="0"> nein</td></tr>
<tr><td>Titel:</td><td><input type="text" name="title" value="K&uuml;hlschrank" /></td></tr>
<tr><td>Aufl&ouml;sung:</td><td><input type="text" name="resolution" value="1000" /></td></tr>
<tr><td>Kosten:<br /><p style="font-size: small">pro Wh</p></td><td><input type="text" name="cost" value="0.00025" /></td></tr>
<tr><td>Beschreibung:</td><td><input type="text" name="description" value="Swissnox 1-phasig" /></td></tr>
<tr class="property"><th>Eigenschaft</th><th>Wert</th></tr>
<tr class="property"><td>Middleware URL:</td><td><input type="text" id="entity-create-middlware" value="../middleware.php" /></td></tr>
<tr class="property"><td>Typ:</td><td><select name="type" size="1"></select></td></tr>
<tr class="property"><td>&Ouml;ffentlich:</td><td><input type="radio" name="public" value="1"> ja&nbsp;&nbsp;<input type="radio" name="public" value="0"> nein</td></tr>
<tr class="property"><td>Titel:</td><td><input type="text" name="title" value="K&uuml;hlschrank" /></td></tr>
<tr class="property"><td>Aufl&ouml;sung:</td><td><input type="text" name="resolution" value="1000" /></td></tr>
<tr class="property"><td>Kosten:<br /><p style="font-size: small">pro Wh</p></td><td><input type="text" name="cost" value="0.00025" /></td></tr>
<tr class="property"><td>Beschreibung:</td><td><input type="text" name="description" value="Swissnox 1-phasig" /></td></tr>
</table>
<input type="hidden" name="operation" value="add" />
<p><input type="submit" value="erstellen" /></p>
<p>Es wird ein neuer Kanal erstellt. Die Antwort der Middlewarenthält dann die UUID.<br />
<p>Es wird ein neuer Kanal erstellt. Die Antwort der Middleware enthält dann die UUID.<br />
Diese muss dann im Controller angegeben werden (z.B. AVR Net-IO).</p>
</form>
</div>

View file

@ -0,0 +1,191 @@
/**
*
*
* @author Florian Ziegler <fz@f10-home.de>
* @author Justin Otherguy <justin@justinotherguy.org>
* @author Steffen Vogel <info@steffenvogel.de>
* @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 <http://www.gnu.org/licenses/>.
*/
/**
* 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('<img src="images/loading.gif" alt="loading..." /><p>loading...</p>');
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'
});
};

View file

@ -1,7 +1,6 @@
/**
* Entity handling, parsing & validation
*
* @author Florian Ziegler <fz@f10-home.de>
* @author Justin Otherguy <justin@justinotherguy.org>
* @author Steffen Vogel <info@steffenvogel.de>
* @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() {
$('<div>')
.addClass('details')
.append(this.getDOM())
.append(this.getDOMDetails())
.dialog({
title: 'Details f&uuml;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 = $('<table><thead><tr><th>Eigenschaft</th><th>Wert</th></tr></thead></table>');
var data = $('<tbody>');
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 = '<a href="' + vz.options.middlewareUrl + '/entity/' + this[property] + '.json">' + this[property] + '</a>';
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 = '<span style="background-color: ' + this[property] + '">' + this[property] + '</span>';
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 = '<a href="' + this.middleware + '/capabilities.json">' + this.middleware + '</a>';
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($('<tr>')
.append($('<td>')
.addClass('key')
.text(title)
)
.append($('<td>')
.addClass('value')
.append(value)
)
);
case 'uuid':
var title = 'UUID';
var value = '<a href="' + this.middleware + '/entity/' + this.uuid + '.json">' + this.uuid + '</a>';
break;
case 'color':
var title = 'Farbe';
var value = '<span style="background-color: ' + this.color + '">' + this.color + '</span>';
break;
case 'cookie':
var title = 'Cookie';
value = '<img src="images/' + ((this.cookie) ? 'tick' : 'cross') + '.png" alt="' + ((value) ? 'ja' : 'nein') + '" />';
break;
case 'active':
var title = 'Aktiv';
var value = '<img src="images/' + ((this.active) ? 'tick' : 'cross') + '.png" alt="' + ((this.active) ? 'ja' : 'nein') + '" />';
break;
}
}
data.append($('<tr>')
.addClass('property')
.addClass('general')
.append($('<td>')
.addClass('key')
.text(title)
)
.append($('<td>')
.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 = '<img src="images/' + ((value) ? 'tick' : 'cross') + '.png" alt="' + ((value) ? 'ja' : 'nein') + '" />';
}
if (property == 'cost') {
value = (value * 1000 * 100) + ' ct/k' + this.definition.unit + 'h'; // ct per kWh
}
data.append($('<tr>')
.addClass('property')
.addClass(section)
.append($('<td>')
.addClass('key')
.text(title)
)
.append($('<td>')
.addClass('value')
.append(value)
)
);
}
}, this);
}, this);
return table.append(data);
};
Entity.prototype.getRow = function() {
Entity.prototype.getDOMRow = function() {
var row = $('<tr>')
.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($('<td>')
.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($('<input>')
.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($('<input>')
.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
}
}
}
};

View file

@ -24,13 +24,20 @@
* volkszaehler.org. If not, see <http://www.gnu.org/licenses/>.
*/
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();
}
};
/**

View file

@ -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);
});

View file

@ -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) {

View file

@ -1,81 +0,0 @@
/**
* UUID handling
*
* @author Florian Ziegler <fz@f10-home.de>
* @author Justin Otherguy <justin@justinotherguy.org>
* @author Steffen Vogel <info@steffenvogel.de>
* @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 <http://www.gnu.org/licenses/>.
*/
/**
* 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);
});
}
};

View file

@ -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(
$('<option>').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('<img src="images/loading.gif" alt="loading..." /><p>loading...</p>');
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('<img src="images/empty.png" alt="no data..." /><p>nothing to plot...</p>');
@ -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) {

View file

@ -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;
}