This first real post's already weeks later than i'd planned it to be. So instead of starting right off with something properly technical, i'm rather going to detail a specific character building experience that i undertook a few weeks ago. For a little non-NDA-violating context, i've taken over some code on an ASP.net project, which happens to be using nGallery for a separate communal gallery to accompany the main site. Any references made to 'my site' below refer to this main site, which really isn't my site at all, but brevity beats accuracy every time. While there's no dissatifaction with the nGallery side of things, it was felt that it would be nice if images from the nGallery database could be displayed in a random thumbnail image sort of way in the navigation controls of the main site. Preferrably linked to a larger version of the image and maybe even the image's actual nGallery page.
Being a can-do kind of a guy, i naturally assumed this would be a total breeze. Like a man strutting up the side of Everest armed with a Drimac, a particularly sturdy pair of board shorts and half a litre of Coke Lite, i figured that in a worst case scenario i could just go groping about the nGallery database and get whatever i needed. Sure, it's not the right thing to do, but everyone in the know could just accomodatingly look the other way and whistle Always Look On The Bright Side Of Life or something. Right? Not Quite. I was soon to discover that while nGallery does use a SQL Server DB to store gallery and image data (although it can also use an XML flat file), the images themselves are stored directly to the file structure. This piece of design caught me a little off-guard, but i still wasn't too phased at this point. Even if the images themselves weren't in the DB, the information to allow nGallery itself to find them had to be.
While this assumption obviously had to be correct, i soon found it was correct in a far less straight forward way than i'd expected. Finding the image itself was basically exactly as straight forward as i'd imagined. The thumbnail corresponding to the image turned out to be a bit more effort. Why so? Well, to allow for maximum flexibility, nGallery doesn't store a concrete version of the thumbnails for the images in its galleries. Rather, on each request to nGallery.Lib.PhotoCache.GetThumbnail(), nGallery queries it's config file to find the required thumbnail size at the current time. The thumbnail size is dynamically determined according to how the aspect ratio of the image will best fit into the thumbnail size specified by the config file. nGallery then assembles a path and filename using the end-product thumbnail size, combined with the image's AlbumID and PictureID (e.g.: /1/4/70x70.aspx points to Gallery 1, Image 4, thumbnail size 70x70 px). If the file specified in the path doesn't already exist, nGallery will at that time generate the appropriate file in it's cache directory. Whether or not the thumbnail file needs to be generated, the URL to the image will be passed back to the calling code.
This is, of course, clever and awesome for nGallery admins since any changes to the config file will instantly take effect across the board without any extra work. At the same time, this is quite the opposite of awesome for someone attempting what i was trying to do. Even if i cheated and directly accessed the nGallery DB from my main project, i still wouldn't have access to nGallery's config file to know what the thumbnail size should be. Assuming a thumbnail size would be a cheat. Furthermore, even if i knew or assumed the size, there was always the chance that the file wouldn't actually exist yet. This issue of the file's existence in particular limited my options - it was becoming clear to me that it would be best to have nGallery generate the URL and (if necessary) create the thumbnail image file itself. This, unfortunately, was going to involve making some slightly intrusive additions to the nGallery code. Firstly, one of the more fundamental requirements was a stored procedure to return a random image. No problem, it goes a little something like this:
CREATE PROCEDURE dbo.ngGetRandomPicture
AS
SELECT TOP 1
PictureID,
PictureTitle,
PictureCaption,
PictureFileName,
PictureHighlight,
PictureCreateDate,
PictureViewCount,
PictureAlbumID
FROM Picture
ORDER BY newid()
GO
Simple as this is, though, the intrusions start mounting up. Firstly, this requires being wrapped in nGallery's business layer (nGallery.Lib.BL). All that does is to make a call to a corresponding method in nGallery's data access layer. This is implemented as a base class (nGallery.Lib.DL.DLBase) with an implementation for SQL Server, ANSI SQL and XML databases (nGallery.Lib.DL.SQL, nGallery.Lib.DL.AnsiSql, nGallery.Lib.DL.XML). Being a man of questionable moral fibre, i quite predictably only did the proper call from the SQL Server implementation, and without so much as a second though threw NotImplementedException exceptions from the ANSI SQL and XML implementations of the method. I called all those methods GetRandomPicture(), if you're interested.
Aside from adding all these methods, i needed to make one slightly more intrusive addition to the nGallery code and this one leaves me a little uneasy. nGallery's data access layer returns data using a variety of container objects - with picture information being returned in Picture objects (nGallery.Lib.Picture). Unfortunately, these don't include a property for the picture's Picture.PictureAlbumID data column (nGallery always already has that information to fetch the picture and it's stored proc's for Picture selects don't even return this column). For the purposes of what i was doing, however, i thought it would useful to have this information. If you're going to be showing a random thumbnail image and it's description with a link to the original image, wouldn't it be really cool to also be able to say what gallery it comes from and have a link to the actual gallery as well? Well, i thought it'd be hella cool, so i added a private int member _pictureAlbumID with a corresponding public property PictureAlbumID to the Picture object.
That took care of getting the random image. Now i needed a way for nGallery to present this information to my project. Still being a man of embarrassingly weak moral fibre, i initially opted for the easiest imaginable way of doing this. I added a (quite minimilistic) randomImage.aspx page to nGallery. All this did was to display the random image, hyperlinked to the actual image's page, etc. My intention was to load this page into an iframe in the navigation control of my site. I soon grew to loathe this idea, mainly because it sucked. It meant that my site itself had no real control over how the random image would be presented and if the loaded page got too big you'd having the iframe sprouting icky scroll bars. Which would not do.
So, i thought it would be cooler to rather have randomImage.aspx return a packet of XML containing album and picture details, ids, urls for the particular random image. Actually, in all honesty, that data could have been returned as a set of tilder delimited values for all i cared, but XML is The Right Thing To Do these days after all. So, after making those changes, a call to randomImage.aspx would return something along these lines, kind of like a poor man's web service:
< ?xml version="1.0" ? >
< RandomImage >
< AlbumID >1< /AlbumID >
< PictureID >2< /PictureID >
< AlbumName >Album covers< /AlbumName >
< PictureName >out of exile< /PictureName >
< AlbumURL >http://localhost/nGallery/albums/1.aspx< /AlbumURL >
< ThumbNailURL >/nGallery/photos/1/2/69x70.aspx< /ThumbNailURL >
< PictureURL >http://localhost/nGallery/albums/1/2.aspx< /PictureURL >
< /RandomImage >
That just left the issue of what my site's nav bar was going to do with this piece of XML. I could have made the call to randomImage.aspx from the server side code of my site, but i didn't really like the idea of my site hanging around waiting to get some XML back from an nGallery page, which may take mysteriously long at any given time for any number of reasons. I was having this itch to use AJAX (or, at least, those long existing things that have now been rebranded as AJAX for a brave new web) in a not utterly gratuitous fashion, and this seemed not utterly gratuitous. Basically, if i had some javascipt asynchronously calling the randomImage.aspx page and dynamically generating the div that would be containing the random image, links, etc on my site, all would be peachy. This also meant (ta da!) that i could have a button allowing the user to call further random images, that could be refreshed without reloading the entire page.
The bit of javascript that populates the div with the random image stuff looks like so:
function alertContents()
{
if( http_request.readyState == 4 )
{
if( http_request.status == 200 )
{
var xmlDoc = http_request.responseXML;
var randomImageNodes = xmlDoc.getElementsByTagName( "RandomImage" );
var randomImageNode = randomImageNodes.item( 0 );
var childNodes = randomImageNode.childNodes;
var nodeItem;
var albumID;
var pictureID;
var albumName;
var pictureName;
var albumURL;
var thumbNailURL;
var pictureURL;
for( var i = 0; i < childNodes.length; i++ )
{
nodeItem = childNodes.item( i );
if( nodeItem.nodeName == "AlbumID" )
albumID = nodeItem.text;
else if( nodeItem.nodeName == "PictureID" )
pictureID = nodeItem.text;
else if( nodeItem.nodeName == "AlbumName" )
albumName = nodeItem.text;
else if( nodeItem.nodeName == "PictureName" )
pictureName = nodeItem.text;
else if( nodeItem.nodeName == "AlbumURL" )
albumURL = nodeItem.text;
else if( nodeItem.nodeName == "ThumbNailURL" )
thumbNailURL = nodeItem.text;
else if( nodeItem.nodeName == "PictureURL" )
pictureURL = nodeItem.text;
}
// build HTML
var html = "<div align=center><a href=\"" + pictureURL + "\" target=nG>";
html += "<img src=\"" + thumbNailURL + "\" border=0/></a></div>";
html += "<br />Album: <a href=\"" + albumURL + "\" target=nG>" + albumName + "</a>";
html += "<br />Picture: <a href=\"" + pictureURL + "\" target=nG>" + pictureName + "</a>";
// add HTML to page
var div = document.getElementById( "divRandomImage" );
div.innerHTML = html;
}
else
// an alert is probably the last thing you'd actually want to do here
alert( 'There was a problem with the request.' );
http_request = null;
}
}
And that's that then. I definitely don't think this is necessarily the best solution. A big problem is, of course, the need to go tinkering around in a lot of different places in the nGallery code (and a bit in the database) to make this work. So any upgrade or patching of nGallery code could necessitate redoing these additions. Also, if the clever people at nGallery make big enough changes to the code or DB structure, you could find yourself needing to do some fair rethinking. Not ideal, obviously. The other big problem would be that this exact form of the solution marries the size of the thumbnail displayed for the random image to the thumbnail size for the nGallery site. So, anyone updating the nGallery config has to be wary of unwittingly disfiguring the other site at the same time (you can get around this with only slightly more work though, so i'm not stressing about this too much).
Naturally, there is also the option to add the RandomImage=true tag to the Web.Config file... 5 minutes after the file has been saved with this addition, Patrick Swayze will pull up in front of your house in a 1300 Nissan Champ and personally sit down to write the code for you. Because sometimes you can't make it on your own.
(btw, sorry there's not much here in the way of source code, but i really consider this to be more of a story telling session than a proper technical post)
|