made javascript code object-oriented

This commit is contained in:
Steffen Vogel 2010-10-04 21:10:58 +02:00
parent 60bb758774
commit 767ad23145
9 changed files with 423 additions and 404 deletions

View file

@ -116,26 +116,12 @@ class UUID {
}
/**
* Performant validation of UUID's
*
* Replaces preg_match('/[a-f0-9\-]{36}/', $uuid);
* Validation of UUID's
*
* @param string $uuid
* @param boolen $short whether to allow abbreviated form of UUID's or not
*/
public static function validate($uuid, $short = FALSE) {
$len = strlen($uuid);
for ($i = 0; $i < $len; $i++) {
$char = $uuid[$i];
$ord = ord($char);
if (($ord > 57 || $ord < 48) && ($ord > 70 || $ord < 65) && ($ord > 102 || $ord < 97) && $ord != 45) {
return FALSE; // char not allowed
}
}
return ($short) ? $len <= 36 : $len == 36; // check for strlen
public static function validate($uuid) {
return (boolean) preg_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}$/', $uuid);
}
/**

View file

@ -8,6 +8,9 @@
<script type="text/javascript" src="javascripts/jquery-treeTable.min.js"></script>
<script type="text/javascript" src="javascripts/jquery-extensions.js"></script>
<script type="text/javascript" src="javascripts/json.js"></script>
<script type="text/javascript" src="javascripts/helper.js"></script>
<!--[if IE]><script language="javascript" type="text/javascript" src="javascripts/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.selection.js"></script>
@ -15,12 +18,10 @@
<script type="text/javascript" src="javascripts/flot/jquery.flot.crosshair.js"></script>
<script type="text/javascript" src="javascripts/flot/jquery.flot.symbol.js"></script>
<script type="text/javascript" src="javascripts/json.js"></script>
<script type="text/javascript" src="javascripts/uuid.js"></script>
<script type="text/javascript" src="javascripts/helper.js"></script>
<script type="text/javascript" src="javascripts/backend.js"></script>
<script type="text/javascript" src="javascripts/frontend.js"></script>
<script type="text/javascript" src="javascripts/init.js"></script>
<script type="text/javascript" src="javascripts/uuid.js"></script>
<script type="text/javascript" src="javascripts/entity.js"></script>
<script type="text/javascript" src="javascripts/frontend.js"></script>
<link rel="stylesheet" type="text/css" href="stylesheets/jquery.treeTable.css">
<link rel="stylesheet" type="text/css" href="stylesheets/ui-lightness/jquery-ui-1.8.5.css" />

View file

@ -1,258 +0,0 @@
/**
* Backend related javascript code
*
* @author Florian Ziegler <fz@f10-home.de>
* @author Justin Otherguy <justin@justinotherguy.org>
* @author Steffen Vogel <info@steffenvogel.de>
* @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 <http://www.gnu.org/licenses/>.
*/
/**
* Get all entity information from backend
*/
function loadEntities() {
$.each(vz.uuids, function(index, value) {
$.getJSON(vz.options.backendUrl + '/entity/' + value + '.json', ajaxWait(function(json) {
vz.entities.push(json.entity);
}, showEntities, 'information'));
});
}
/**
* Create nested entity list
* @param data
*/
function showEntities() {
$('#entities tbody').empty();
var i = 0;
eachRecursive(vz.entities, function(entity, parent) {
entity.active = true; // TODO active by default or via backend property?
entity.color = vz.options.plot.colors[i++ % vz.options.plot.colors.length];
var row = $('<tr>')
.addClass((parent) ? 'child-of-entity-' + parent.uuid : '')
.attr('id', 'entity-' + entity.uuid)
.append($('<td>')
.css('background-color', entity.color)
.css('width', 19)
.append($('<input>')
.attr('type', 'checkbox')
.attr('checked', entity.active)
.bind('change', entity, function(event) {
event.data.active = $(this).attr('checked');
loadData();
})
)
)
.append($('<td>')
.css('width', 20)
)
.append($('<td>')
.append($('<span>')
.text(entity.title)
.addClass('indicator')
.addClass((entity.type == 'group') ? 'group' : 'channel')
)
)
.append($('<td>').text(entity.type))
.append($('<td>')) // min
.append($('<td>')) // max
.append($('<td>')) // avg
.append($('<td>') // operations
.addClass('ops')
.append($('<input>')
.attr('type', 'image')
.attr('src', 'images/information.png')
.attr('alt', 'details')
.bind('click', entity, function(event) { showEntityDetails(event.data); })
)
);
if (parent == null) {
$('td.ops', row).prepend($('<input>')
.attr('type', 'image')
.attr('src', 'images/delete.png')
.attr('alt', 'delete')
.bind('click', entity, function(event) {
removeUUID(event.data.uuid);
loadEntities();
})
);
}
$('#entities tbody').append(row);
});
// http://ludo.cubicphuse.nl/jquery-plugins/treeTable/doc/index.html
$('#entities table').treeTable({
treeColumn: 2,
clickableNodeNames: true
});
// load data and show plot
loadData();
}
/**
* Show and edit entity details
* @param entity
*/
function showEntityDetails(entity) {
var properties = $('<table>');
$.each(entity, function(key, value) {
properties.append($('<tr>')
.append($('<td>')
.addClass('key')
.text(key)
)
.append($('<td>')
.addClass('value')
.text(value)
)
);
});
$('<div>')
.addClass('details')
.append(properties)
.dialog({
title: 'Entity Details',
width: 450
});
}
function validateEntity(entity) {
var def = getDefinition(vz.definitions.entities, entity.type);
$.each(def.required, function(index, property) {
var property = getDefinition(vz.definitions.properties, property);
if (!validateProperty(property, form.elements[property.name].value)) {
alert('Error: invalid property: ' + property.name + ' = ' + form.elements[property.name].value);
return false;
}
});
$.each(entity.optional, function(index, property) {
var property = getDefinition(properties, property);
});
return true;
}
function validateProperty(property, value) {
switch (property.type) {
case 'string':
case 'text':
// TODO check pattern
// TODO check string length
return true;
case 'float':
// TODO check format
// TODO check min/max
return true;
case 'integer':
// TODO check format
// TODO check min/max
return true;
case 'boolean':
return value == '1' || value == '';
case 'multiple':
return $.inArray(value, property.options);
default:
alert('Error: unknown property!');
}
}
/**
* Show from for new Channel
*
* @param type
* @return
*/
function getEntityDOM(type) {
$('#properties').empty();
var entity = getDefinition(entities, type);
$.each(entity.required, function(index, property) {
var property = getDefinition(properties, property);
if (property) {
$('#properties')
.append($('<tr>')
.addClass('required')
.append($('<td>')
.append($('<label>')
.attr('for', property.name)
.text(property.translation.de + ':')
)
)
.append($('<td>').append(getPropertyDOM(property)))
.append($('<td>').text('(*)'))
);
}
});
// TODO optional properties
}
function getPropertyDOM(property) {
switch (property.type) {
case 'string':
case 'float':
case 'integer':
return $('<input>')
.attr('type', 'text')
.attr('name=', property.name)
.attr('maxlength', (property.type == 'string') ? property.max : 0);
case 'text':
return $('<textarea>')
.attr('name', property.name);
case 'boolean':
return $('<input>')
.attr('type', 'checkbox')
.attr('name', property.name)
.value(1);
case 'multiple':
var dom = $('<select>').attr('name', property.name)
$.each(property.options, function(index, option) {
dom.append($('<option>')
.value(option)
.text(option)
);
});
return dom;
default:
throw {
type: 'PropertyException',
message: 'Unknown property type'
};
}
}

View file

@ -0,0 +1,134 @@
/**
* 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) 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 <http://www.gnu.org/licenses/>.
*/
/**
* Entity
*/
var Entity = function(json) {
for (var i in json) {
switch(i) {
case 'children':
this.children = new Array;
for (var j = 0; j < json.children.length; j++) {
var child = new Entity(json.children[j]);
this.children.push(child);
}
break;
case 'type':
case 'uuid':
default: // properties
this[i] = json[i];
}
}
};
/**
* Show and edit entity details
* @param entity
*/
Entity.prototype.showDetails = function(entity) {
$('<div>')
.addClass('details')
.append(this.getDOM())
.dialog({
title: 'Entity Details',
width: 450
});
};
/**
* Show from for new Channel
*
* @param type
* @todo
*/
Entity.prototype.getDOM = function(type) {
var properties = $('<table><thead><th>Key</th><th>Value</th></thead></table');
$.each(entity, function(key, value) {
properties.append($('<tr>')
.append($('<td>')
.addClass('key')
.text(key)
)
.append($('<td>')
.addClass('value')
.text(value)
)
);
});
var entity = getDefinition(entities, type);
entity.required.each(function(index, property) {
var property = getDefinition(properties, property);
if (property) {
$('#properties')
.append($('<tr>')
.addClass('required')
.append($('<td>')
.append($('<label>')
.attr('for', property.name)
.text(property.translation.de + ':')
)
)
.append($('<td>').append(getPropertyDOM(property)))
.append($('<td>').text('(*)'))
);
}
});
// TODO optional properties
};
Entity.prototype.validate = function(entity) {
var def = getDefinition(vz.definitions.entities, entity.type);
def.required.each(function(index, property) {
var property = getDefinition(vz.definitions.properties, property);
if (!validateProperty(property, form.elements[property.name].value)) {
throw 'Invalid property: ' + property.name + ' = ' + form.elements[property.name].value;
}
});
entity.optional.each(function(index, property) {
var property = getDefinition(properties, property);
});
return true;
};
Entity.prototype.each = function(cb, parent) {
cb(this, parent);
if (this.children) {
for (var i = 0; i < this.children.length; i++) {
this.children[i].each(cb, this); // call recursive
}
}
};

View file

@ -31,7 +31,7 @@
/**
* Initialize the WUI (Web User Interface)
*/
function initInterface() {
vz.initInterface = function() {
// make the whole frontend resizable
/*$('#content').resizable({
alsoResize: $('#plot'),
@ -67,13 +67,13 @@ function initInterface() {
// add UUID
$('#addUUID input[type=button]').click(function() {
addUUID($('#addUUID input[type=text]').val());
vz.uuids.add($('#addUUID input[type=text]').val());
$('#addUUID').dialog('close');
loadEntities();
vz.entities.load();
})
// bind plot actions
$('#move input').click(handleControls);
$('#move input').click(vz.handleControls);
// options
/*$('input[name=trendline]').attr('checked', vz.options.plot.seriesDefaults.trendline.show).change(function() {
@ -100,24 +100,24 @@ function initInterface() {
max: 60000,
step: 500
});
}
};
/**
* Refresh plot with new data
*/
function refreshWindow() {
vz.refresh = function() {
if ($('input[name=refresh]').attr('checked')) {
var delta = vz.to - vz.from;
vz.to = new Date().getTime(); // move plot
vz.from = vz.to - delta; // move plot
loadData();
}
}
};
/**
* Move & zoom in the plotting area
*/
function handleControls() {
vz.handleControls = function () {
var delta = vz.to - vz.from;
var middle = Math.round(vz.from + delta/2);
@ -155,29 +155,122 @@ function handleControls() {
// do nothing; just loadData()
}
loadData();
}
vz.data.load();
};
/**
* Get all entity information from backend
*/
vz.entities.load = function() {
vz.entities.clear();
vz.uuids.each(function(index, value) {
$.getJSON(vz.options.backendUrl + '/entity/' + value + '.json', ajaxWait(function(json) {
vz.entities.push(new Entity(json.entity));
}, vz.entities.show, 'information'));
});
};
/**
* Create nested entity list
* @param data
*/
vz.entities.show = function() {
$('#entities tbody').empty();
var i = 0;
vz.entities.each(function(index, entity) { // loop through all entities
entity.each(function(entity, parent) { // loop through all children of entities (recursive)
entity.active = true; // TODO active by default or via backend property?
entity.color = vz.options.plot.colors[i++ % vz.options.plot.colors.length];
var row = $('<tr>')
.addClass((parent) ? 'child-of-entity-' + parent.uuid : '')
.attr('id', 'entity-' + entity.uuid)
.append($('<td>')
.css('background-color', entity.color)
.css('width', 19)
.append($('<input>')
.attr('type', 'checkbox')
.attr('checked', entity.active)
.bind('change', entity, function(event) {
event.data.active = $(this).attr('checked');
vz.data.load();
})
)
)
.append($('<td>')
.css('width', 20)
)
.append($('<td>')
.append($('<span>')
.text(entity.title)
.addClass('indicator')
.addClass((entity.type == 'group') ? 'group' : 'channel')
)
)
.append($('<td>').text(entity.type))
.append($('<td>')) // min
.append($('<td>')) // max
.append($('<td>')) // avg
.append($('<td>') // operations
.addClass('ops')
.append($('<input>')
.attr('type', 'image')
.attr('src', 'images/information.png')
.attr('alt', 'details')
.bind('click', entity, function(event) { event.data.showDetails(); })
)
);
if (parent == null) {
$('td.ops', row).prepend($('<input>')
.attr('type', 'image')
.attr('src', 'images/delete.png')
.attr('alt', 'delete')
.bind('click', entity, function(event) {
vz.uuids.remove(event.data.uuid);
vz.entities.load();
})
);
}
$('#entities tbody').append(row);
});
});
// http://ludo.cubicphuse.nl/jquery-plugins/treeTable/doc/index.html
$('#entities table').treeTable({
treeColumn: 2,
clickableNodeNames: true
});
// load data and show plot
vz.data.load();
};
/**
* Load json data with given time window
*/
function loadData() {
eachRecursive(vz.entities, function(entity, parent) {
if (entity.active && entity.type != 'group') {
$.getJSON(vz.options.backendUrl + '/data/' + entity.uuid + '.json', { from: vz.from, to: vz.to, tuples: vz.options.tuples }, ajaxWait(function(json) {
vz.data.push({
data: json.data[0].tuples, // TODO check uuid
color: entity.color
});
}, drawPlot, 'data'));
}
vz.data.load = function() {
vz.data.clear();
vz.entities.each(function(index, entity) {
entity.each(function(entity, parent) {
if (entity.active && entity.type != 'group') {
$.getJSON(vz.options.backendUrl + '/data/' + entity.uuid + '.json', { from: vz.from, to: vz.to, tuples: vz.options.tuples }, ajaxWait(function(json) {
vz.data.push({
data: json.data[0].tuples, // TODO check uuid
color: entity.color
});
}, vz.drawPlot, 'data'));
}
});
});
}
};
function drawPlot() {
vz.drawPlot = function () {
vz.options.plot.xaxis.min = vz.from;
vz.options.plot.xaxis.max = vz.to;
vz.plot = $.plot($('#plot'), vz.data, vz.options.plot);
}
};

View file

@ -39,58 +39,24 @@ function ajaxWait(callback, finished, identifier) {
};
}
function eachRecursive(array, callback, parent) {
$.each(array, function(index, value) {
callback(value, parent);
if (value.children) { // has children?
eachRecursive(value.children, callback, value); // call recursive
}
});
}
/**
* Checks if value of part of the array
*
* @param needle the value to search for
* @return boolean
*/
Array.prototype.contains = function(needle) {
return this.key(needle) ? true : false;
Array.prototype.indexOf = function(n) {
for (var i = 0, l = this.length; i < l; i++)
if (n == this[i]) return i;
};
/**
* Calculates the diffrence between this and another Array
*
* @param compare the Array to compare with
* @return array
*/
Array.prototype.diff = function(compare) {
return this.filter(function(elem) {
return !compare.contains(elem);
});
Array.prototype.remove = function(n) {
this.splice(this.indexOf(n), 1);
};
/**
* Find the key to an given value
*
* @param needle the value to search for
* @return integer
*/
Array.prototype.key = function(needle) {
for (var i=0; i<this.length; i++) {
if (this[i] == needle) {
return i;
}
}
Array.prototype.each = function(cb) {
for (var i = 0, l = this.length; i < l; i++)
cb(i, this[i]);
};
/**
* Remove a value from the array
*/
Array.prototype.remove = function(needle) {
var key = this.key(needle);
if (key) {
this.splice(key, 1);
}
};
Array.prototype.contains = function(n) {
return this.indexOf(n) !== undefined;
};
Array.prototype.clear = function() {
this.length = 0;
}

View file

@ -29,11 +29,24 @@
const defaultInterval = 7*24*60*60*1000; // 1 week
// volkszaehler.org object
// holds all data and options for the frontend
// holds all data, options and functions for the frontend
// acts like a namespace (we dont want to pollute the global one)
var vz = {
// entity information & properties
entities: new Array,
// known UUIDs in the browser
uuids: new Array,
// data for plot
data: new Array,
// definitions of entities & properties
// for validation, translation etc..
definitions: {
properties: {},
entities: {}
},
// timeinterval to request
to: new Date().getTime(),
@ -65,8 +78,8 @@ var vz = {
min: 0,
zoomRange: [1, null] // dont scale yaxis when zooming
},
selection: { mode: "x" },
//crosshair: { mode: "x" },
selection: { mode: 'x' },
//crosshair: { mode: 'x' },
grid: { hoverable: true, autoHighlight: false },
zoom: {
interactive: true,
@ -84,26 +97,25 @@ var vz = {
// this is where it all starts...
$(document).ready(function() {
// initialize user interface
initInterface();
vz.initInterface();
// parse uuids from cookie
vz.uuids = getUUIDs();
console.log(vz.uuids);
vz.uuids.parseCookie();
// add optional uuid from url
if($.getUrlVar('uuid')) {
addUUID($.getUrlVar('uuid'));
vz.uuids.add($.getUrlVar('uuid'));
}
// start auto refresh timer
window.setInterval(refreshWindow, 5000);
window.setInterval(vz.refresh, 5000);
// handle zooming & panning
$('#plot')
.bind("plotselected", function (event, ranges) {
vz.from = Math.floor(ranges.xaxis.from);
vz.to = Math.ceil(ranges.xaxis.to);
loadData();
vz.data.load();
})
/*.bind('plotpan', function (event, plot) {
var axes = vz.plot.getAxes();
@ -120,11 +132,11 @@ $(document).ready(function() {
//vz.options.plot.yaxis.max = axes.yaxis.max;
vz.options.plot.yaxis.min = 0;
vz.options.plot.yaxis.max = null; // autoscaling
loadData();
vz.data.load();
})
.bind('mouseup', function(event) {
//loadData();
});
loadEntities();
vz.entities.load();
});

View file

@ -0,0 +1,96 @@
/**
* Property handling & validation
*
* @author Florian Ziegler <fz@f10-home.de>
* @author Justin Otherguy <justin@justinotherguy.org>
* @author Steffen Vogel <info@steffenvogel.de>
* @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 <http://www.gnu.org/licenses/>.
*/
/**
* Property
*/
var Property = function(key, value) {
};
Property.prototype.validate = function(value) {
switch (property.type) {
case 'string':
case 'text':
// TODO check pattern
// TODO check string length
return true;
case 'float':
// TODO check format
// TODO check min/max
return true;
case 'integer':
// TODO check format
// TODO check min/max
return true;
case 'boolean':
return value == '1' || value == '';
case 'multiple':
return $.inArray(value, property.options);
default:
alert('Error: unknown property!');
}
};
Property.prototype.getDOM = function() {
switch (property.type) {
case 'string':
case 'float':
case 'integer':
return $('<input>')
.attr('type', 'text')
.attr('name=', property.name)
.attr('maxlength', (property.type == 'string') ? property.max : 0);
case 'text':
return $('<textarea>')
.attr('name', property.name);
case 'boolean':
return $('<input>')
.attr('type', 'checkbox')
.attr('name', property.name)
.value(1);
case 'multiple':
var dom = $('<select>').attr('name', property.name)
property.options.each(function(index, option) {
dom.append($('<option>')
.value(option)
.text(option)
);
});
return dom;
default:
throw 'Unknown property type';
}
};

View file

@ -27,50 +27,39 @@
/*
* Cookie & UUID related functions
*/
function getUUIDs() {
vz.uuids.parseCookie = function() {
if ($.getCookie('uuids')) {
return JSON.parse($.getCookie('uuids'));
$.each(JSON.parse($.getCookie('uuids')), function(index, uuid) {
vz.uuids.push(uuid);
});
}
};
vz.uuids.add = function(uuid) {
if (vz.uuids.validate(uuid)) {
if (!vz.uuids.contains(uuid)) {
vz.uuids.push(uuid);
$.setCookie('uuids', JSON.stringify(vz.uuids));
}
else {
throw 'UUID already added';
}
}
else {
return new Array;
throw 'Invalid UUID';
}
}
function addUUID(uuid) {
if (!vz.uuids.contains(uuid)) {
vz.uuids.push(uuid);
$.setCookie('uuids', JSON.stringify(vz.uuids));
}
}
function removeUUID(uuid) {
};
vz.uuids.remove = function(uuid) {
if (vz.uuids.contains(uuid)) {
vz.uuids.remove(uuid);
$.setCookie('uuids', JSON.stringify(vz.uuids));
}
}
/**
* Create and return a "version 4" RFC-4122 UUID string
*
* @todo remove after got backend handling working
*/
function getRandomUUID() {
var s = [], itoh = '0123456789ABCDEF';
// make array of random hex digits. The UUID only has 32 digits in it, but we
// allocate an extra items to make room for the '-'s we'll be inserting.
for (var i = 0; i <36; i++) s[i] = Math.floor(Math.random()*0x10);
// conform to RFC-4122, section 4.4
s[14] = 4; // Set 4 high bits of time_high field to version
s[19] = (s[19] & 0x3) | 0x8; // Specify 2 high bits of clock sequence
// convert to hex chars
for (var i = 0; i <36; i++) s[i] = itoh[s[i]];
// insert '-'s
s[8] = s[13] = s[18] = s[23] = '-';
return s.join('');
}
else {
throw 'UUID unkown: ' + uuid;
}
};
vz.uuids.validate = function(uuid) {
return new Boolean(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}$/));
};