Commit 2b28ab2d authored by Timothy Stack's avatar Timothy Stack

Checkpoint a bunch of code that puts an ajaxy tree widget on the

template_show page.  There is also some freely available javascript
libraries (behaviour, cookieLib, prototype) that the tree widget
depends on.
parent f1f5bd85
......@@ -114,6 +114,12 @@ HYFILES += $(wildcard $(SRCDIR)/hyperviewer/*.gif)
SDRFILES = $(wildcard $(SRCDIR)/sdr/*.php3)
SDRFILES += $(wildcard $(SRCDIR)/sdr/*.html)
TTFILES = $(wildcard $(SRCDIR)/timetree/*.png)
TTFILES += $(wildcard $(SRCDIR)/timetree/*.js)
TTFILES += $(wildcard $(SRCDIR)/timetree/*.css)
JSFILES += $(wildcard $(SRCDIR)/js/*.js)
# need to make it *.gz; with simply "*",
# we end up sucking over "CVS"
DOWNLOADFILES = $(wildcard $(SRCDIR)/downloads/*.gz)
......@@ -166,6 +172,8 @@ ALLBUI = $(notdir $(BUIFILES))
ALLNL = $(notdir $(NLFILES))
ALLHY = $(notdir $(HYFILES))
ALLSDR = $(notdir $(SDRFILES))
ALLTT = $(notdir $(TTFILES))
ALLJS = $(notdir $(JSFILES))
ALLROBO = $(notdir $(ROBOTRACKFILES))
ALLWISTATS = $(notdir $(WIRELESSSTATSFILES))
......@@ -181,6 +189,8 @@ INSTALLFILES = $(addprefix $(INSTALL_SBINDIR)/, htmlinstall) \
$(addprefix $(INSTALL_WWWDIR)/netlab/, $(ALLNL)) \
$(addprefix $(INSTALL_WWWDIR)/hyperviewer/, $(ALLHY)) \
$(addprefix $(INSTALL_WWWDIR)/sdr/, $(ALLSDR)) \
$(addprefix $(INSTALL_WWWDIR)/timetree/, $(ALLTT)) \
$(addprefix $(INSTALL_WWWDIR)/js/, $(ALLJS)) \
$(addprefix $(INSTALL_WWWDIR)/robotrack/, $(ALLROBO)) \
$(addprefix $(INSTALL_WWWDIR)/wireless-stats/, $(ALLWISTATS)) \
$(addprefix $(INSTALL_WWWDIR)/autostatus-icons/, $(ALLICONS)) \
......
.dragviewport {
overflow: hidden;
border: solid 1px silver;
display: block;
position: relative;
margin-left: -16px;
margin-right: -16px;
margin-bottom: 16px;
}
.dvzoomout {
height: 125px;
}
.dvzoomin {
height: 650px;
}
.dragsubstrate {
position: absolute;
top: 0px;
left: 0px;
z-index: auto;
}
var Activity = {};
Activity.Indicator = function(element, backgroundImage) {
this.element = element;
this.inProgress = 0;
this.timeout = null;
this.backgroundImage = backgroundImage;
this.timer_cb = function() {
if (this.backgroundImage != null)
this.element.style.backgroundImage = this.backgroundImage;
else
this.element.style.display = "block";
this.timeout = null;
};
this.increment = function() {
if (this.inProgress == 0) {
var outerThis = this;
this.timeout = setTimeout(function() { outerThis.timer_cb(); },
250);
}
this.inProgress += 1;
};
this.decrement = function() {
if (this.timeout != null) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.inProgress -= 1;
if (this.inProgress == 0) {
if (this.backgroundImage != null)
this.element.style.backgroundImage = "";
else
this.element.style.display = "none";
}
};
};
/*
Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
of Simon Willison (see comments by Simon below).
Description:
Uses css selectors to apply javascript behaviours to enable
unobtrusive javascript in html documents.
Usage:
var myrules = {
'b.someclass' : function(element){
element.onclick = function(){
alert(this.innerHTML);
}
},
'#someid u' : function(element){
element.onmouseover = function(){
this.innerHTML = "BLAH!";
}
}
};
Behaviour.register(myrules);
// Call Behaviour.apply() to re-apply the rules (if you
// update the dom, etc).
License:
This file is entirely BSD licensed.
More information:
http://ripcord.co.nz/behaviour/
*/
var Behaviour = {
list : new Array,
register : function(sheet){
Behaviour.list.push(sheet);
},
start : function(){
Behaviour.addLoadEvent(function(){
Behaviour.apply();
});
},
apply : function(){
for (h=0;sheet=Behaviour.list[h];h++){
for (selector in sheet){
list = document.getElementsBySelector(selector);
if (!list){
continue;
}
for (i=0;element=list[i];i++){
sheet[selector](element);
}
}
}
},
addLoadEvent : function(func){
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
oldonload();
func();
}
}
}
}
Behaviour.start();
/*
The following code is Copyright (C) Simon Willison 2004.
document.getElementsBySelector(selector)
- returns an array of element objects from the current document
matching the CSS selector. Selectors can contain element names,
class names and ids and can be nested. For example:
elements = document.getElementsBySelect('div#main p a.external')
Will return an array of all 'a' elements with 'external' in their
class attribute that are contained inside 'p' elements that are
contained inside the 'div' element which has id="main"
New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
See http://www.w3.org/TR/css3-selectors/#attribute-selectors
Version 0.4 - Simon Willison, March 25th 2003
-- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
-- Opera 7 fails
*/
function getAllChildren(e) {
// Returns all children of element. Workaround required for IE5/Windows. Ugh.
return e.all ? e.all : e.getElementsByTagName('*');
}
document.getElementsBySelector = function(selector) {
// Attempt to fail gracefully in lesser browsers
if (!document.getElementsByTagName) {
return new Array();
}
// Split selector in to tokens
var tokens = selector.split(' ');
var currentContext = new Array(document);
for (var i = 0; i < tokens.length; i++) {
token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
if (token.indexOf('#') > -1) {
// Token is an ID selector
var bits = token.split('#');
var tagName = bits[0];
var id = bits[1];
var element = document.getElementById(id);
if (tagName && element.nodeName.toLowerCase() != tagName) {
// tag with that ID not found, return false
return new Array();
}
// Set currentContext to contain just this element
currentContext = new Array(element);
continue; // Skip to next token
}
if (token.indexOf('.') > -1) {
// Token contains a class selector
var bits = token.split('.');
var tagName = bits[0];
var className = bits[1];
if (!tagName) {
tagName = '*';
}
// Get elements matching tag, filter them for class selector
var found = new Array;
var foundCount = 0;
for (var h = 0; h < currentContext.length; h++) {
var elements;
if (tagName == '*') {
elements = getAllChildren(currentContext[h]);
} else {
elements = currentContext[h].getElementsByTagName(tagName);
}
for (var j = 0; j < elements.length; j++) {
found[foundCount++] = elements[j];
}
}
currentContext = new Array;
var currentContextIndex = 0;
for (var k = 0; k < found.length; k++) {
if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
currentContext[currentContextIndex++] = found[k];
}
}
continue; // Skip to next token
}
// Code to deal with attribute selectors
if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
var tagName = RegExp.$1;
var attrName = RegExp.$2;
var attrOperator = RegExp.$3;
var attrValue = RegExp.$4;
if (!tagName) {
tagName = '*';
}
// Grab all of the tagName elements within current context
var found = new Array;
var foundCount = 0;
for (var h = 0; h < currentContext.length; h++) {
var elements;
if (tagName == '*') {
elements = getAllChildren(currentContext[h]);
} else {
elements = currentContext[h].getElementsByTagName(tagName);
}
for (var j = 0; j < elements.length; j++) {
found[foundCount++] = elements[j];
}
}
currentContext = new Array;
var currentContextIndex = 0;
var checkFunction; // This function will be used to filter the elements
switch (attrOperator) {
case '=': // Equality
checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
break;
case '~': // Match one of space seperated words
checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
break;
case '|': // Match start with value followed by optional hyphen
checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
break;
case '^': // Match starts with value
checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
break;
case '$': // Match ends with value - fails with "Warning" in Opera 7
checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
break;
case '*': // Match ends with value
checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
break;
default :
// Just test for existence of attribute
checkFunction = function(e) { return e.getAttribute(attrName); };
}
currentContext = new Array;
var currentContextIndex = 0;
for (var k = 0; k < found.length; k++) {
if (checkFunction(found[k])) {
currentContext[currentContextIndex++] = found[k];
}
}
// alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
continue; // Skip to next token
}
if (!currentContext[0]){
return;
}
// If we get here, token is JUST an element (not a class or ID selector)
tagName = token;
var found = new Array;
var foundCount = 0;
for (var h = 0; h < currentContext.length; h++) {
var elements = currentContext[h].getElementsByTagName(tagName);
for (var j = 0; j < elements.length; j++) {
found[foundCount++] = elements[j];
}
}
currentContext = found;
}
return currentContext;
}
/* That revolting regular expression explained
/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
\---/ \---/\-------------/ \-------/
| | | |
| | | The value
| | ~,|,^,$,* or =
| Attribute
Tag
*/
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2006 University of Utah and the Flux Group.
* All rights reserved.
*/
/**
* Hash used to store client side state that should be available to a group of
* pages. So, if you want to save some state on the client side, add your
* object(s) to this hash and then read/write to them as usual.
*
* XXX This state will end up getting transferred to the server on requests,
* which kinda sucks...
*/
ClientState = {};
ClientStateFunctions = {
/**
* Save the client-side state stored in the ClientState object into a
* cookie.
*
* @todo Add a parameter so state can be separated into different groups so
* they don't intermingle.
*/
saveState: function() {
cookieLib.setCookie("cs", ClientState, 1);
},
/**
* Load the client-side state from the cookie into ClientState.
*/
loadState: function() {
var tmp = cookieLib.getCookie("cs");
if (tmp != null)
ClientState = tmp;
}
};
/*
The contents of this file including all code, commments, etc. within is
Copyright Jim Auldridge 2005.
DISCLAIMER: THIS CODE SUPPLIED 'AS IS', WITH NO WARRANTY EXPRESSED OR IMPLIED.
YOU USE AT YOUR OWN RISK. JIM AULDRIDGE DOES NOT ACCEPT ANY LIABILITY FOR
ANY LOSS OR DAMAGE RESULTING FROM USE, WHETHER CAUSED BY CODING MISTAKES (MINE
OR YOURS), 'HACKERS' FINDING HOLES, OR ANY OTHER MEANS.
*/
var cookieLib = {
getCookie: function(cookieName) {
var cookieNameStart,valueStart,valueEnd,value;
cookieNameStart = document.cookie.indexOf(cookieName+'=');
if (cookieNameStart < 0) {return null;}
valueStart = document.cookie.indexOf(cookieName+'=') + cookieName.length + 1;
valueEnd = document.cookie.indexOf(";",valueStart);
if (valueEnd == -1){valueEnd = document.cookie.length;}
value = document.cookie.substring(valueStart,valueEnd );
value = JSON.parse(unescape(value));
if (value == "") {return null;}
return value;
},
setCookie: function(cookieName,value,hoursToLive,path,domain,secure) {
var expireString,timerObj,expireAt,pathString,domainString,secureString,setCookieString;
if (!hoursToLive || parseInt(hoursToLive)=='NaN'){expireString = "";}
else {
timerObj = new Date();
timerObj.setTime(timerObj.getTime()+(parseInt(hoursToLive)*60*60*1000));
expireAt = timerObj.toGMTString();
expireString = "; expire="+expireAt;
}
pathString = "; path=";
(!path || path=="") ? pathString += "/" : pathString += path;
domainString = "; domain=";
(!domain || domain=="") ? domainString += window.location.hostname : domainString += domain;
(secure === true) ? secureString = "; secure" : secureString = "";
value = escape(JSON.stringify(value));
setCookieString = cookieName+"="+value+expireString+pathString+domainString;
document.cookie = setCookieString;
},
delCookie: function(cookieName,path,domain){
cookieLib.setCookie(cookieName,"",-8760);
}
};
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2006 University of Utah and the Flux Group.
* All rights reserved.
*/
/**
* A "time tree" is a widget that displays a timeline that starts at the left
* side of the screen and grows to the right. Along this timeline there can be
* branches where new timelines are started.
*
* @todo Get rid of any hardcoded constants for the size/look of the tree
* icons.
*/
DelayBox = {};
DelayBox.Controller = function() {
var self = this;
this.timeout = null;
this.sched = function() {
if (self.timeout != null)
clearTimeout(self.timeout);
self.timeout = setTimeout(self.timeoutHandler, 500);
};
this.timeoutHandler = function() {
self.timeout = null;
self.handler();
};
this.behaviour = function(element) {
element.onkeyup = self.sched;
};
};
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2006 University of Utah and the Flux Group.
* All rights reserved.
*/
/**
* Provides a controller for a Google maps like view that can be dragged around
* by the mouse. The code is mostly taken from:
*
* http://markpasc.org/weblog/2005/05/28/landmarker_or_so_youd_like_to_make_an_ajax_map
*
* It works by moving an absolutely positioned inner div (the "substrate")
* around in an outer div (the "viewport") as the user drags the mouse around.
*
* @todo This might actually work better by using just the substrate with a
* "clip" style.
*
* @see The template_show.php for an example usage.
*
* Google maps is the best...
* True dat!
* Double True!
*/
DragView = {};
/**
* Construct a drag view controller.
*
* @param viewport The element that acts as the viewport.
* @param substrate The element that will be moved around inside the viewport.
* Must be a direct descendent of the viewport.
* @param bounds Optional parameter that specifies the width and height for the
* content of the substrate. If this is given, the viewport will always keep
* the content atleast partially in view.
*/
DragView.Controller = function(viewport, substrate, bounds) {
var self = this; // Reference to _this_ object from the event handlers.
/* Add client side state for all drag views. */
if (!("DragView" in ClientState)) {
ClientState.DragView = {};
}
/* Initialize the state for this view. */
if (!(substrate.id in ClientState.DragView)) {
ClientState.DragView[substrate.id] = {};
ClientState.DragView[substrate.id].left = "0px";
ClientState.DragView[substrate.id].top = "0px";
}
/* Retrieve the saved state for this view. */
substrate.style.left = ClientState.DragView[substrate.id].left;
substrate.style.top = ClientState.DragView[substrate.id].top;
this.minViewable = 50; // Number of pixels of content that must be viewable
this.downLeft = 0;
this.downTop = 0;
this.downX = 0;
this.downY = 0;
this.isDragging = false;
this.move = function(evt) {
if (!evt) var evt = window.event;
if (this != viewport && this != substrate) return true;
if (!self.isDragging) {
/*
* Start the drag when the cursor is moved. We change to the
* "dragging" class from "nodrag" so any child elements can adapt.
*/
substrate.className = "dragsubstrate dragging";
/* Switch to a move cursor. */
this.style.cursor = "move";
self.isDragging = true;
}
var newLeft, newTop;
/* Compute the new left and top coordinates, then */
newLeft = self.downLeft + evt.screenX - self.downX;
newTop = self.downTop + evt.screenY - self.downY;
/* ... check them against the bounds, if there are any. */
if (bounds) {
if (newLeft < (self.minViewable - bounds.width))
newLeft = (self.minViewable - bounds.width);
if (newLeft > (viewport.clientWidth - self.minViewable))
newLeft = (viewport.clientWidth - self.minViewable);
if (newTop < (self.minViewable - bounds.height))
newTop = (self.minViewable - bounds.height);
if (newTop > (viewport.clientHeight - self.minViewable))
newTop = (viewport.clientHeight - self.minViewable);
}
/*
* Update the substrate with the new coordinates. NB: left/top in the
* style structure are strings with units, not just numbers.
*/
substrate.style.left = newLeft + "px";
substrate.style.top = newTop + "px";
};
/**
* Switch to/from "zoom" mode. Currently, this is done by switch between
* the "dvzoomin" and "dvzoomout" classes.
*
* @param doZoom True if the view should be in zoomed mode, false otherwise
*/
this.zoom = function(doZoom) {
if (doZoom)
viewport.className = "dragviewport dvzoomin";
else
viewport.className = "dragviewport dvzoomout";
};
/**
* Apply event handlers to the given element.
*
* @param element The element to apply the behaviours to.
*/
this.behaviour = function(element) {
element.ondragstart = function(evt) { return false; }; // XXX IE
element.onmousedown = function(evt) {
if (!evt) var evt = window.event;
if ((evt.which && evt.which == 3) ||
(evt.button && evt.button == 2)) {
return true;
}
self.downLeft = parseInt(substrate.style.left);
self.downTop = parseInt(substrate.style.top);
/* Record where the mouse was clicked. */
self.downX = evt.screenX;
self.downY = evt.screenY;
this.onmousemove = self.move;
return true;
};
element.onmouseup = function(evt) {
this.style.cursor = "auto";
this.onmousemove = null;
substrate.className = "dragsubstrate nodrag";
/* Save state for future loads. */
ClientState.DragView[substrate.id].left = substrate.style.left;
ClientState.DragView[substrate.id].top = substrate.style.top;
self.isDragging = false;
return true;
};
element.onmouseout = function(evt) {
return element.onmouseup(evt);
};
};
/* Apply our behaviours to the viewport. */
this.behaviour(viewport);
};
This diff is collapsed.
Template.GUID = null;
Template.vers = null;
Template.vers2node = {};
if (!("Template" in ClientState)) {
debug("no template state!!!");
ClientState.Template = {
SHOW_ALL: false
};
}
Template.Node = function(dbs) {
Template.vers2node[dbs.vers] = this;
this.vers = dbs.vers;
this.parent_vers = dbs.parent_vers;
this.hidden = dbs.hidden;
this.parent = null;
this.model = dbs;
this.next = null;
this.searchHighlight = false;
this.paths = [];
this.children = [];
};
Template.DataSource = function() {
this.rootForTree = function() {
var retval;
if (1 in Template.vers2node)
retval = Template.vers2node[1];
else
retval = null;
return retval;
};
this.bodyForHead = function(th) {
return "";
};
this.initNode = function(node) {
if (node.model.searchHighlight) {
node.userdata.childNodes[0].style.borderLeftColor = "orange";
}
};
this.pathsForTag = function(tag) {
return tag.paths;
};
this.bodyForTag = function(tag) {
return "<div class='tname'>" + tag.model.tid + "</div>";
};
this.bodyForPopup = function(tag) {
var retval = "Version: " + tag.vers;
retval += "<p class='popupcontent'>";
if ("metadata" in tag.model) {
var keys = $H(tag.model.metadata).keys();
for (var lpc = 0; lpc < keys.length; lpc++) {
var name = keys[lpc];
value = tag.model.metadata[name];
retval += "<span class='metadata'>"