Monday, April 23, 2012
Thursday, April 5, 2012
This is a cross post from: Rally Engineering Blog
Sometimes I run across a task where I need to attach event listeners to many different dom elements of the same type. I am usually tempted to do something similar to the code below.
Ext.each(Ext.query('a', parentNode), function(el) {
el.on('click', doSomethingAwesome);
});
This is ExtJs's way to loop through each dom element matching the passed css selector and attach an event listener to it. I'm lazy and it's less code than the correct implementation, which would be to add a listener to the parent element and filter for the correct target element inside the handler function. The disadvantages to the lazy method are that multiple listeners are created, all of which have some amount of overhead attached, and that listeners need to be removed and/or reattached whenever the innerHTML of the parent element changes.
An ExtJs specific example of when the 'lazy' method doesn't work is when event listeners need to be attached to elements created by a renderer inside of a GridPanel cell. The GridPanel does not expose any row or cell render events, so there is no reliable way to add event listeners to dom elements located inside cells.
Fortunately ExtJs's Element.on method has a helpful 'delegate' option that does all of this for you automatically. Use ExtJs's Element.on method to attach a listener to the parent dom element and specify a css selector for the 'delegate' option to filter out events whose target node does not match the passed css selector.
var parentEl = Ext.fly(parentNode);
parentEl.on('click', function(event, target, options) {
console.log('the "target" argument is the anchor element that is a child of parentNode');
}, this, {
delegate: 'a'
});
Wednesday, November 23, 2011
Tuesday, July 12, 2011
tirtle: a Spring Web MVC project running on GAE
The most frustrating part of the project was just getting everything configured and getting a working server up and running. I feel there is a lack of documentation aimed at beginners, although part of my problems may have been related to jumping right in with Spring + GAE. I chose Spring Framework version 3.0 (the latest version), but there seemed to be more documentation and blog tutorials available for version 2.5. I found an official Web MVC tutorial on the Spring Source site, but it only covered version 2.5.
I had several problems figuring out the base configuration settings, and getting all the correct .jar files in my classpath. webmvc.jar was especially mysterious, because it is not included in the Spring Framework distribution. I ended up finding it via a Google search, but I have yet to find the official download from Spring Source.
Once I got a basic server running, the available documentation for how to actually use Spring seemed pretty good. The Web MVC framework works similar to every other MVC framework you have used. Classes are annotated to turn them into controllers, and controller methods are annotated to turn them into request handlers. You can use either JSPs (Java Server Pages: Java embedded in HTML similar to PHP) or a templating system for your view layer.
The Web MVC stuff is all built on top of Spring's dependency injection framework, so the simple MVC annotations you use are actually shortcuts to lots of complicated XML configuration. Spring's standard DI tools can be used to inject other non-mvc dependencies into your controllers. I used the DI features to configure a simple authentication object (didn't have time to figure out how to get Spring Security working) and also an ORM interface to Google's DataStore (objectify-appengine with the help of objectify-appengine-spring).
Unfortunately, the simple web app I developed didn't require much logic, so it didn't help too much in terms of learning how to code Java, but it was an excellent exercise in getting up and running with Spring. The code for the project is on github. Other new-to-Spring users may find Spring easier to learn by starting with a working app like this one, and learning by modification. I hope to have time to add additional features to the code as I learn how they are accomplished in the Java/Spring ecosystem (unit tests, Spring Security, Javascript framework integration, REST api, templating instead of JSPs).
Sunday, May 29, 2011
Javascript File Browser for Server-Side Files
Galaxy is a web-based bioinformatics toolkit that allows users to create customized data analysis pipelines. It is becoming an extremely common tool, especially in the sequencing field. The project has one major flaw: it is difficult to get large data files into the system. Uploading 2G files (generated from sequencing runs) through a browser is not a workable solution.
Solution - iRods
iRods is a virtual file system commonly used to transfer and share files in the scientific community. It abstracts the storage details and provides tools for access control, sharing, metadata tracking, file type conversion, and high performance multi-threaded file transfer.
Integration
Myself and co-workers Fred Bevins and Susan Miller were tasked with integrating iRods and Galaxy during the iRods code sprint hosted by iPlant in April. My share of the work was a client-side Javascript file browser. The file browser allows users to browse and select server side files. The version I built talks to the iPlant Foundational API, which exposes iRods directories and files. Other back-ends could easily be added to allow the file browser to talk with a standard file system, or any other file repository. Fred and Susan worked on integrating the file browser into Galaxy, which allows Galaxy users to browse and select files in an iRods repository for use in an analysis. The javascript code is available now, and the Galaxy integration code should be available sometime soon.
Monday, April 4, 2011
Using Protovis to Create Simple Flow Charts
I cooked up a scheme where the nodes are absolute positioned div elements that can be styled with CSS, and the edges are drawn with Protovis. I pass an object that defines the edge properties to a Javascript function that uses JQuery to find the exact positions of the nodes, and then I use Protovis to draw the edges.
Example:
HTML:
<div id="workflowContainer">
<!--
-- Draw Simple divs to represent workflow nodes, and connect them with Protovis.
--
-- Nodes are positioned absolutely.
-- Node positions can be static and manually determined,
-- or dynamic and determined by server-side or client-side
-- code. This example uses hard coded node positions.
-->
<div id="workflowChart" >
<!-- Clickable node -->
<a href=""><div id="startFlow" style="top 0; left: 440px;">Start</div></a>
<!-- Foo branch -->
<!-- Unclickable node -->
<div id="foo1Flow" style="top: 100px; left: 200px;">Foo 1</div>
<a href=""><div id="foo2Flow" style="top: 175px; left: 100px;">Foo 2</div></a>
<div id="fooChoice1Flow" style="top: 300px; left: 0px;">Foo Choice 1</div>
<div id="fooChoice2Flow" class="inactive" style="top: 300px; left: 165px;">Foo Choice 2</div>
<div id="fooChoice3Flow" class="inactive" style="top: 300px; left: 360px;">Foo Choice 3</div>
<div id="fooOptionFlow" style="top: 400px; left: 50px;">Foo Option</div>
<a href=""><div id="fooCombineFlow" style="top: 500px; left: 200px;">Foo Combine</div></a>
<a href=""><div id="fooSplit1Flow" style="top: 575px; left: 25px;">Foo Split 1</div></a>
<a href=""><div id="fooSplit2Flow" style="top: 575px; left: 250px;">Foo Split 2</div></a>
<!-- bar branch -->
<div id="barFlow" style="top: 100px; left: 700px;">Bar</div>
<a href=""><div id="bar1Flow" class="inactive" style="top: 200px; left: 550px;">Bar 1</div></a>
<a href=""><div id="bar2Flow" class="inactive" style="top: 200px; left: 825px;">Bar 2</div></a>
</div>
</div>
CSS:
/* Contains both nodes and edges. */
#workflowChartContainer {
position: relative;
width: 1000px;
}
/* This is where the edges will be drawn by protovis. */
#workflowChartContainer span {
position: absolute;
top: 0;
left: 0;
background: transparent;
z-index: 1000; /* SVG needs to be drawn on top of existing layout. */
}
#workflowChart {
position: relative;
top: 0;
left: 0;
height: 700px;
width: 1000px;
}
#workflowChart div {
border-color: #5b9bea;
background-color: #b9cde5;
position: absolute;
margin: 0;
padding: 4px;
border: 2px solid #5b9bea;
background: #b9cde5;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
color: #000;
z-index: 10000; /* Needs to be drawn on top of SVG to be clickable. */
}
#workflowChart a {
cursor: pointer;
}
#workflowChart a div {
border-color: #f89c51;
background: #fcd5b5;
}
#workflowChart div.inactive {
border-color: #ccc;
background-color: #eee;
color: #ccc;
}
#workflowChart div:hover {
border-color: #700000;
}
Javascript:
/* Initialize workflow screen. */
var initWorkflow = function() {
// List HTML nodes to connect.
//
// The edges are hardcoded in this example,
// but could easily be made dynamic.
var edges = [
{
source: 'startFlow',
target: 'foo1Flow'
},
{
source: 'foo1Flow',
target: 'foo2Flow'
},
{
source: 'foo2Flow',
target: 'fooChoice1Flow'
},
{
source: 'foo2Flow',
target: 'fooChoice2Flow'
},
{
source: 'foo2Flow',
target: 'fooChoice3Flow'
},
{
source: 'fooChoice1Flow',
target: 'fooOptionFlow'
},
{
source: 'fooChoice2Flow',
target: 'fooOptionFlow'
},
{
source: 'fooOptionFlow',
target: 'fooCombineFlow'
},
{
source: 'fooChoice3Flow',
target: 'fooCombineFlow'
},
{
source: 'fooCombineFlow',
target: 'fooSplit1Flow'
},
{
source: 'fooCombineFlow',
target: 'fooSplit2Flow'
},
{
source: 'startFlow',
target: 'barFlow'
},
{
source: 'barFlow',
target: 'bar1Flow'
},
{
source: 'barFlow',
target: 'bar2Flow'
},
];
// Us JQUery to set height and width equal to background div.
var workflow = $('#workflowChart'),
h = workflow.height(),
w = workflow.width();
// Create Protovis Panel used to render SVG.
var vis = new pv.Panel()
.width(w)
.height(h)
.antialias(false);
// Attach Panel to dom
vis.$dom = workflow[0];
// Render connectors
drawEdges(vis, edges);
var test = vis.render();
};
/* Draw edges specified in input array. */
var drawEdges = function(vis, edges) {
// Direction indicators,
var directions = [];
$.each(edges, function(idx, item){
// Color of edges
var color = '#000';
// Arrow radius
var r = 5;
// Use JQuery to get source and destination elements
var source = $('#' + item.source);
var target = $('#' + item.target);
if (!(source.length && target.length)) {
// One of the nodes is not present in the DOM; skip it.
return;
}
var data = edgeCoords(source, target);
if (item.sourceLOffset) {
data[0].left += item.sourceLOffset;
}
if (item.targetLOffset) {
data[1].left += item.targetLOffset;
}
if (source.hasClass('inactive') || target.hasClass('inactive')) {
// If target is disabled, change the edge color.
color = '#ccc';
}
// Use Protovis to draw edge line.
vis.add(pv.Line)
.data(data)
.left(function(d) {return d.left;})
.top(function(d) {
if (d.type === 'target') {
return d.top - (r * 2);
}
return d.top;
})
.interpolate('linear')
.segmented(false)
.strokeStyle(color)
.lineWidth(2);
// Here you may want to calculate an angle
// to twist the direction arrows to make the graph
// prettier. I've left out the code to keep thing simple.
var a = 0;
// Add direction indicators to array.
var d = data[1];
directions.push({
left: d.left,
top: d.top - (r * 2),
angle: a,
color: color
});
});
// Use Protovis to draw all direction indicators
//
// Here you may want to check and make
// sure you're only drawing a single indicator
// at each position, to avoid drawing multiple
// indicators for targets that have multiple sources.
// I've left out the code for simplicity.
vis.add(pv.Dot)
.data(directions)
.left(function (d) {return d.left;})
.top(function (d) {return d.top;})
.radius(r)
.angle(function (d) {return d.angle;})
.shape("triangle")
.strokeStyle(function (d) {return d.color;})
.fillStyle(function (d) {return d.color;});
};
/* Returns the bottom-middle offset for a dom element. */
var bottomMiddle = function(node) {
var coords = node.position();
coords.top += node.outerHeight();
coords.left += node.width() / 2;
return coords;
};
/* Returns the top-middle offset for a dom element. */
var topMiddle = function(node) {
var coords = node.position();
coords.left += node.width() / 2;
return coords;
};
/* Return start/end coordinates for an edge. */
var edgeCoords = function(source, target) {
var coords = [bottomMiddle(source), topMiddle(target)];
coords[0].type = 'source';
coords[1].type = 'target';
return coords;
};
