Saturday, July 30, 2005

Some screenshots of the Haloscan Ping Firefox Extension

The configuration screen...


And the Ping Screen...


Technorati Tags: , , , , , , , , , , , ,

Trackbacks are getting easier

Ever since I was introduced to trackbacks a couple of months ago I've had a worry about them... they're difficult to setup. So I thought I'd do something about it.

After a bit of help from my good friend Andrew Beacock, I got my head around trackbacks and set up a Haloscan account so I could get them installed onto my own blog. Getting it up and running was pretty straight forward, but then the tricky bit arrived... actually performing a trackback ping request.

In order to make a request you need to go to the Haloscan account and enter the trackback ping URL for the post that you want to ping. You then need to enter the details of your own blog... the blog title, post link, title and extract. It all seemed like a lot of hard work to me.

So here's what I did:
I wrote a Firefox extension to send trackback ping requests to Haloscan

In order to use it you need to set it up:

  1. Install the extension

  2. Configure it with the address of your blog feed (rss and atom are supported). You'll find it under Tools -> Haloscan Ping
  3. Log into Haloscan


In order to send a ping request:

  1. Write your blog entry and publish it

  2. Go to the blog you want to ping, and select the trackback URL

  3. Right-click and pick Ping Haloscan

  4. Pick the entry from your blog to ping with and click OK



Simple as that!

At the moment I'd regard the extension as being at alpha stage, so for now I'm sending it out on request. If you'd like to try it out then mail me and I'll send you a copy.
Once I've got some positive feedback for it I'll try to publish it on Mozilla Update and Mozdev.

Update:You can mail me here...

Technorati Tags: , , , , , , , , , , , ,

Friday, July 29, 2005

Index Cards

In our version of XP, Stories get written on index cards.

Text on the front, in big marker pen describes the user action in a single sentence.
The aim is to get the people involved to think in small chunks, and to think about the fundamentals of each piece of work.

Text on the back, in biro describes any particular behaviour required, we call them the acceptance criteria. The aim is to get people to think about the exceptional requirements of a story, the bits that the simple view of the action doesn't cover, to think of the little gotchas that'll appear.

They work extremely well, focussing the mind on the job in hand.

But there's something I think we've missed the point of: For each story there is only one card.

Here are my thoughts...

Since there's only one card, the card needs to be with the people that are currently working on it. If the story's still being worked out, it's with the customer, if it's being developed then it's with the pair working on it.

That means that if the customer wants to change their mind after the story's been started by the development team then they need to find the pair that's currently working on it and speak to them.
Fair enough, we could have the stories stored electronically and then put a system in place to ensure that the pair are notified of any change as soon as its put onto the system. It wouldn't be difficult, the customer enters each story on the system, revising it until they think it's ready. The story gets marked as ready to start when they've had a discussion with the development team. Each pair would register which story they're working on, marking the story as in progress. And so, and so on.

Or, we could do the simple thing and use natural capabilities of the card; do the simplest thing that will work.

Simplicity is only part of the issue. The big advantage of actually tracking down the pair and speaking to them is that you then get into a conversation. A notification is one way, a conversation is two way. And things that are written down are open to interpretation.

Conversation is crucial to the success of XP, and keeping the story on the card just seems to me to be a great way of keeping that conversation going.


Technorati Tags: , ,

Wednesday, July 27, 2005

The second saddest post ever

The other thing I love about regular expressions is that you can NEVER test them enough. You always find yet another bit of text that doesn't behave itself. There's always better ways of doing what you're doing. And if you ever post a blog entry about one, you always have to follow it up with another that corrects it...


[\w\W]{200,}?[\.!\?\)"\n\r]+


Damn!

RTFM (again)

When I first started pair programming it took me about 2 hours to come to terms with the fact that sometimes you need to ask for help, and that it's OK to look in a manual. You won't lose face with your pair for doing that.

It took our boss about 2 weeks to notice the increase in traffic on uk.php.net caused by my arrival.

So why is it that whenever I'm coding on my own I will tend to try to hack through the undergrowth with a pen knife to get to the solution rather than ask someone if I can borrow their chainsaw?

The latest example is a problem I was having with a firefox extension.

I wanted the user to be able to select a piece of text, right click, pick my extension and have a dialog box where one of the fields contains the selected text.

Since I already knew how to do this I flew straight in and spent ages fiddling round in the dialog box's code with parent.getSelection(), parent.window.getSelection, parent.window.context.getSelection() and every other combination including parents, windows and selections that I could think of. Not a chance.

For some reason I couldn't get the text from the parent window. I suspect that firefox does not regard the window the user sees as the parent window, but I've not bothered looking to find out...

Anyway, quite early on in I thought to mayself:

'This would be easy if you could just pass arguments into the dialog box. I could just get the selection in the code that kicks off the dialog box and there we go'.

But since I knew window.open I knew this wasn't possible.

An hour later I decided to take a look at openDialog, which is the call I was actually making to open the window. It turned out that yep, you can pass arguments into the child dialog box.

If only I'd just accepted that I don't know everything and looked at it an hour earlier I'd have saved myself a lot of stress.

Note to self... RTFM!

Tuesday, July 26, 2005

CSS Rules

We love CSS here, we try to use it as much as possible to ensure that the look and feel of our application is easily skinnable. We're pretty good at it, and we get by.

This guy though... now HE knows what he's doing :-)

UK Flag in CSS only

Hardware Tokens

In our office, we all sit around a couple of sets of desks. You may say that we're definitely not 'geographically challenged'. Each pair can pretty clearly see every other pair.

We don't store any source code on our local machines. Since we're developing a PHP application we need to deliver it though a web server and it access a nice big Oracle database, and guess what... we've got a whole department geared up to supporting web servers and databases. So we don't have local installs of databases and webservers, we don't administrate them, and none of us want to.

So our workspaces live on a network drive. Yep, Win CVS is a bit of a pain over a network, but for now we're living with it. Our PHP files live on a web server so we can access them without running any uploads or anything.

We've given our workspaces numbers, prefixed with the name of the project. The source code lives in a folder with the name of the workspace and the database lives in a schema with the name of the workspace. Your pair is working in workspace 'Laurel1', you get set of source code 'Laurel1' and database schema 'Laurel1'. Nice and simple.

The only problem is, if no one individual owns a particular workspace, then how do we know which pair is allowed to work in which workspace?

We have mutually exclusive hardware tokens. Or, to put it another way... we have paper flags that we put into holsters taped onto the back of our flat screen monitors.
If you've got the Laurel1 flag then you've got the Laurel1 workspace, and only your pair is allowed to change the workspace.
If you 've not got the Laurel1 flag then you can't change the Laurel1 workspace.

Since everyone's living so close, it's easy to see the flags. In the very rare situation where a pair is working remotely, they phone up a proxy to get their flag, a humorous effigy is made of one of the pair, and the flag is given to the effigy.

We have a spare workspace too...

We call it integrate*. It's the workspace where the integration takes place. In order to do a large scale commit you must have the integrate workspace token. If you don't then you don't commit. The pair with the token is the pair that's currently doing an integration. It has the advantage of a separate build machine since you're forced to check in from your dev space and check out to the integrate space and everyone can see you're doing it.

Our hardware tokens make it easy to see who's working where.

And they were fun to make.

Technorati Tags: , , , ,


* actually, we call it autotest, but if we got a new one we'd call it integrate... honest.

Monday, July 25, 2005

The saddest post ever

One of the things that I had to come to terms with about 18 months ago was the fact that I needed to understand regular expressions.
The Oracle world has yet to really embrace these strange beasts, and so I was completely hidden from their glory.

Now, I've reached the point where I'm going to do possibly the saddest thing in the whole world. I'm going to post a recent regular expression onto this blog... here... now.

For a soon to arrive firefox extension, I needed to extract a number of complete sentences from a piece of text that numbered at least 100 characters in length. Here's how I did it:

(.|\n){200,}?[\.!\?\)"\n\r]+


OK, if you use apostrophes as quotation marks it falls down a little, but then you deserve a slap anyway ;-)

For being so sad, I must now go and whip myself with a birch branch.

Updated: OK, so I'm hoping that it now deals with newlines properly as well ;-) Serves me right!

Tuesday, July 19, 2005

Extending in batch

Inspired by Andrew Beacock, with thanks to Brian Duff's post here and the cracking resource at XUL Planet, I've started to look at doing some Firefox extension development.

It all looks pretty straightforward. Produce a bit of XML in the guise of XUL and slap in some Javascript event handlers and you've got the idea. Early testing is easy since Firefox just lets you open XUL files as if they're HTML. It's all very easy to get going with.

The only issue I had at the start was the structuring of the source code tree and the packaging up of the .XPI file.
An .XPI file is a packages extension that Firefox can install. It's easy to run one, you can simply double click on it and firefox kicks of its extension manager. Packaging one up though, it a little trickier. Without wanting to get into the details of the how any why (again, Brian Duff's got the intro covered), I've found it's easist to develop within the directory structure that's ultimately required by the XPI package...


+YourExtentionName
|
|-- install.rdf
|
|-+ chrome
|
|---+ content
|-- contents.rdf + other .rdf files
|-- .xul files
|-- .js files
|-- .css files


Brian suggests dropping the chrome directory and having the content in the extension directory, but that's much of a muchness if you ask me. He also supplies an ANT task to package up the XPI file for you. I don't have ANT installed. I don't use it, because I've got no need for it at home (it's overkill for this, really). So in true Windows skript kiddi traditions, I've put together a batch file that'll do the same job.

It assumes the directory structure is the same as stated above, and it should be ran from the YourExtensionName directory.

Forgive me if it's not perfect, feel free to use and amend. It'd be nice if you posted a comment back here if you improve it.

Update:Just had my attention drawn here/ Shame I didn't spot that before I wrote my own Batch file based packager! Eerily similar they are too...



BUILD_XPI.BAT


@ECHO OFF

SETLOCAL

IF ""=="%1" GOTO :noparams

SET current_directory=%cd%
SET extension_name=%1

MKDIR build\chrome
DEL %extension_name%.xpi

CD chrome
CALL :createZipFile "%current_directory%\build\chrome\%extension_name%" *.*
MOVE "%current_directory%\build\chrome\%extension_name%.zip" "%current_directory%\build\chrome\%extension_name%.jar"

CD ..\build
COPY ..\install.rdf .
CALL :createZipFile "%current_directory%\%extension_name%" *.*
MOVE "%current_directory%\%extension_name%.zip" "%current_directory%\%extension_name%.xpi"

cd ..
RD build /q /s

GOTO:EOF


:createZipFile
SET zip_file=%~1
SET files=%~2

REM Change the following line to one that runs your particular CMD Zip executable
REM
REM It should create a ZIP file, adding files recursively, keeping
REM their directory structure

pkzipc -add -rec -dir=specify %zip_file% %files%

GOTO :EOF

:noparams
Must specify a name for the extension



Technorati Tags: , , , , , ,

Thursday, July 14, 2005

Trackback a go go

Thanks to a mail from my good mate Andrew Beacock (why research things when you've got a mate like him to do it for you!), I've think I've got this trackback thing sorted.

For all those people looking for a service out there, I've started using Haloscan. It's pretty easy to setup, Andy's got some help here if you need it, but to be honest it's straight forward.

The difficulty I always had was what to actually do with the trackback once you've got it!

Here's the deal:

When you want to link a blog entry with a trackback facility you start off by writing your blog entry.
Then get hold of the permalink for YOUR entry.
Go to THEIR entry, and click on the trackback link.
With Haloscan you'll then get a trackback URL.
Copy this URL so you now have two links... YOUR entry link and THEIR trackback link
Log into Haloscan and 'Send a Trackback ping'
Paste in YOUR permalink into the (der) 'Your Permalink URL' field, and put THEIR trackback url into the URLs to Ping.

And that should be it.

Check out Andy's post here and it should have a trackback to here...

Technorati Tags: , , , ,

Wednesday, July 13, 2005

Sharing

Andrew Beacock has once again found a little beauty... del.icio.us have added the concept of forwarding bookmarks to other users. I can't wait to see my inbox fill up with useful links from mates, and masses of spam bookmarks from god knows who!

Experimentation is needed me thinks...

Technorati Tags: , , ,

Tuesday, July 12, 2005

The IN Thing, a simpler example

Things are a little simpler when you're moving around PL/SQL tables of data rather than ref cursors. At that point the data used in the table case no longer needs to be available outside of the procedure that uses it. The object and table definitions still do however.
Please forgive the scrolling window...




First we create the table from which we select:

CREATE TABLE product
( id NUMBER
, descr VARCHAR2( 100 ) )
/

INSERT INTO product ( id, descr ) VALUES ( 1, 'one' );
INSERT INTO product ( id, descr ) VALUES ( 2, 'two' );
INSERT INTO product ( id, descr ) VALUES ( 3, 'three' );
INSERT INTO product ( id, descr ) VALUES ( 4, 'four' );
INSERT INTO product ( id, descr ) VALUES ( 5, 'five' );
INSERT INTO product ( id, descr ) VALUES ( 6, 'six' );

COMMIT;


Then we need to define the object and table types that will be used for the communication.


CREATE TYPE gt_id_type
AS OBJECT ( id NUMBER )
/

CREATE TYPE gt_id_list_type AS TABLE OF gt_id_type
/

CREATE TYPE gt_product_type
AS OBJECT ( id NUMBER
, descr VARCHAR2(100 ) )
/

CREATE TYPE gt_product_list_type AS TABLE OF gt_product_type
/


Then the procedures that wil do the selection.

  1. GET_PRODUCTS, which will receive a list of IDs in a GT_ID_LIST_TYPE table, returning the list of products in a GT_PRODUCT_LIST_TYPE.

  2. GET_PRODS_BY_NAME, which will recieve a name and using GET_PRODUCTS will return a list of products whose descr contains the text specified.



You may note that the function GET_ID_LIST, used in the previous example, does not appear. This is since the data held in the ID list table isn't needed outside of GET_PRODUCTS if we prepare teh result set and pass it back in a PL/SQL table.


CREATE OR REPLACE PACKAGE product_pkg AS

FUNCTION get_products ( pt_id_list gt_id_list_type )
RETURN gt_product_list_type;
FUNCTION get_prods_by_name ( pc_product_name VARCHAR2 )
RETURN gt_product_list_type;

END;
/

CREATE OR REPLACE PACKAGE BODY product_pkg AS
--
FUNCTION get_products ( pt_id_list gt_id_list_type )
RETURN gt_product_list_type IS
--
vt_product_tab gt_product_list_type;
--
BEGIN
--
SELECT gt_product_type( id, descr )
BULK COLLECT
INTO vt_product_tab
FROM product
WHERE id IN ( SELECT id FROM TABLE ( pt_id_list ) );
--
RETURN vt_product_tab;
--
END;
--
FUNCTION get_prods_by_name ( pc_product_name VARCHAR2 )
RETURN gt_product_list_type IS
--
vt_product_ids gt_id_list_type;
vt_product_tab gt_product_list_type;
--
BEGIN
--
SELECT gt_id_type( id )
BULK COLLECT
INTO vt_product_ids
FROM product
WHERE descr LIKE '%'|| pc_product_name|| '%';
--
RETURN get_products( vt_product_ids );
--
END;
--
END;
/



Finally, a script to produce some output...


SET SERVEROUTPUT ON SIZE 1000000

DECLARE
--
vt_product_tab gt_product_list_type;
--
BEGIN
--
vt_product_tab := product_pkg.get_prods_by_name( 't' );
--
FOR i IN 1..vt_product_tab.LAST LOOP
DBMS_OUTPUT.PUT_LINE( vt_product_tab( i ).descr );
END LOOP;
--
END;
/


The result should be the same as the previous example...


two
three


Technorati Tags: , ,

Monday, July 11, 2005

The IN Thing: The example

OK, so how do we actually implement the Table cast lookup with a ref cursor?

First, we bear in mind that this is the most complex TABLE cast we can perform, since we need the data and definition to be available outside of the function that is performing the table cast.

The example given has a very simple main cursor, which is effectively:

SELECT *
FROM product


This makes the method appear a little overtly complex. In reality, the main cursor would have to be a lot more complex in order to merit this approach. Additionally, it is most useful when the results are pulled into a higher tier that is not Oracle bound. E.G. An object oriented tier in Java / PHP or the like, where you want to ensure that the record you get back is always in the same form, so you can construct a complete object.

The Example:

Please forgive the scrolling window...



In order to have something to access, we need the tables and data:


CREATE TABLE product
( id NUMBER
, descr VARCHAR2( 100 ) )
/

INSERT INTO product ( id, descr ) VALUES ( 1, 'one' );
INSERT INTO product ( id, descr ) VALUES ( 2, 'two' );
INSERT INTO product ( id, descr ) VALUES ( 3, 'three' );
INSERT INTO product ( id, descr ) VALUES ( 4, 'four' );
INSERT INTO product ( id, descr ) VALUES ( 5, 'five' );
INSERT INTO product ( id, descr ) VALUES ( 6, 'six' );

COMMIT;



In order to perform the cast we need to declare a type for the row in the table, and then the table type itself:


CREATE TYPE gt_id_type AS OBJECT ( id NUMBER )
/

CREATE TYPE gt_id_list_type AS TABLE OF gt_id_type
/


Then we define the package that will perform the cast.
We have the following functions

  • GET_PRODUCTS, which is passed a table of IDs, and returns a ref cursor containing the products requested.

  • GET_PRODS_BY_NAME, which is passed a string, and returns all the products that have a description containing that string. This function uses GET_PRODUCTS to return the product details

  • GET_ID_LIST, which is a helper function used by GET_PRODUCTS in order to make the list of product IDs available to the outside world (so the ref cursor doesn't fail when it's fetched from).



CREATE OR REPLACE PACKAGE product_pkg AS
TYPE gt_product_cur IS REF CURSOR;
FUNCTION get_products ( pt_id_list gt_id_list_type )
RETURN gt_product_cur;
FUNCTION get_prods_by_name ( pc_product_name VARCHAR2 )
RETURN gt_product_cur;
FUNCTION get_id_list RETURN gt_id_list_type;
END;
/

CREATE OR REPLACE PACKAGE BODY product_pkg AS
--
gt_id_list gt_id_list_type;
--
FUNCTION get_id_list RETURN gt_id_list_type IS
BEGIN
RETURN gt_id_list;
END;
--
FUNCTION get_products
( pt_id_list gt_id_list_type )
RETURN gt_product_cur IS
--
vt_product_cur gt_product_cur;
--
BEGIN
--
gt_id_list := pt_id_list;
--
OPEN vt_product_cur FOR
SELECT *
FROM product
WHERE id IN ( SELECT id
FROM TABLE( product_pkg.get_id_list()));
--
RETURN vt_product_cur;
--
END;
--
FUNCTION get_prods_by_name
( pc_product_name VARCHAR2 )
RETURN gt_product_cur IS
--
vt_product_ids gt_id_list_type;
--
BEGIN
--
SELECT gt_id_type( id )
BULK COLLECT
INTO vt_product_ids
FROM product
WHERE descr LIKE '%'|| pc_product_name|| '%';
--
RETURN get_products( vt_product_ids );
--
END;
--
END;
/



Finally, we have some code to run the GET_PRODS_BY_NAME function and return its set of values.


SET SERVEROUTPUT ON SIZE 1000000

DECLARE
--
vt_cur product_pkg.gt_product_cur;
vr_product_rec product%ROWTYPE;
--
BEGIN
--
vt_cur := product_pkg.get_prods_by_name( 't' );
--
LOOP
--
FETCH vt_cur INTO vr_product_rec;
EXIT WHEN vt_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( vr_product_rec.descr );
--
END LOOP;
--
END;
/


And the output:


two
three


Technorati Tags: , ,

The IN thing

With Oracle 8i and below there was a classic problem that I never really got what I felt was a suitable solution to...

I've always gone for limiting the SQL that exists in both the database and the higher layers, as described (related to PHP)here.

By doing this I find I can very nicely wrap up the database tables with a layer of abstraction that protects the higher layers from changes. So I could have a 'Product' lookup that is based on a reasonably complex query what involves the joining of several tables.
But what if I wanted a list of all the products in the system, or the top 5 priced, or any of those other options that just come up, you've got limited choices.

  1. Produce a function that covers each of the lookups, and include the full SQL statement in each lookup.
  2. Provide a function that covers the return of the list of products ID to return, and pass that list into a function that returns the full details (which can be used for any lookup).
  3. Provide a single function that does the lookup in any case, and has a lot of parameters.
  4. Throw away this whole idea of encapsulation and just put the SQL where you need it.

I've always liked the second option. You can have functions that cover searching for records and then other functions that deal with the returning the details of those records.

One configuration that allows you to do this is to have the function returning the details deal with a single entity. Pass in the ID of the one you're looking for and get the full set of details back. If you need multiple records, then you call the function multiple times. It's a good solution that's clean and simple. It just doesn't perform well when you scale it up. What happens if I want 1,000 records? I call the function 1,000 times?

The alternative would appear to be to pass in a list of the IDs you want and get back a ref cursor or table of records back.
You can reasonably get back a table of results, but it means looping over the incoming IDs and reverting back to your individual lookup.
It's harder to return a ref cursor though: The problem is the inflexibility of the IN statement. It's not possible for you to specify an arbitrary list of values in a non dynamic SQL statement.
I.E. with vc_list_of_ids set to "1, 2, 3, 4", you can't say:

OPEN product_cur FOR
SELECT *
FROM product
WHERE id IN ( vc_list_of_ids );

although you can say:

OPEN product_cur FOR
'SELECT *
FROM product
WHERE id IN ( '|| vc_list_of_ids ||' )';

It's a subtle difference, but the second (the correct one) will require Oracle to reparse the statement every time it is ran. Ask any DBA and they'll say that's not exactly a great idea.

Thankfully, Oracle 9 gave us a much nicer solution. Casting to tables.
We can now use a PL/SQL table of values and treat it as a ful blown SQL table. So, if we use the table vt_list_of_ids, we can now say:

OPEN product_cur FOR
SELECT *
FROM product
WHERE id IN ( SELECT id FROM TABLE( vt_list_of_ids ) );

This has the advantage of not being re-parsed each time.

If we pass this back as an open ref cursor we need to make sure that the table definition and the variable that contains the data are both available to the outside world, but it works a treat.

I'll put a working example up here once I get a chance...

Technorati Tags: , ,

Thursday, July 07, 2005

The BBC don't like parody

I must be missing something, because something pretty minor happened that points to something fairly fundamental. And I just don't understand it.

The BBC shut down IsAGaylord.com .

Now forgive me if I'm going mad here, but a week ago this site was getting 20,000 hits a day from people who got a surprise, had an immature snigger, then noticed that it wasn't really a BBC news site after all. Maybe they forwarded it on, maybe they didn't, but they went away not even considering that it was in any way linked to the BBC and definitely not thinking any the worse of the BBC if they did. Then a couple of people didn't get it, and complained... to the BBC.

And so the lawyers got involved, put some pressure onto the host and the site got pulled. So now, for the next week that site will get its usual 20,000 hits a day, and 140,000 people will read a tirade from a guy who's had to pull a very successful site because the BBC got petty.

So, in order to keep the BBC's reputation with a handful of people, they've managed to tell 140,000 how petty they are.

It takes a lawyer to be THAT clever!

Tuesday, July 05, 2005

Blog stats made easy

Thanks to Andy Beacock's post here I've put some more logging onto the site. At the risk of looking like a right newbie, I've even left the counter on for the world to see... watch that number creep up to 10 over a matter of mere months!

Statcounter will very nicely put together some pretty comprehensive stats on page visits, unique visitors, re-visitors and the like. Split down by day, shown in a nice graph. It's basically got all the little facts about your visitors that let you know if people are actually reading what you've got to say.

As with Andy, I'm hoping that seeing the numbers will push me to get more useful text out there...

The same has, of course, been put onto BobaPhotoBlog

Technorati Tags: , , , ,