Page 1 of 1

AJAX Photo Gallery Logic

Posted: Tue Jun 13, 2006 11:18 am
by Chris Corbyn
This should be a piece of cake but I've got my head all in a muddle and can't get back on focus.

(I'm sort of writing this in hope I'll re-gain focus like I usually do and answer my own question :P)

Basically, I have a page where it load initially with an empty DIV element of ID "node_holder".

Now, on the server side I've created a Matrix of Image ID's from the database which looks like this:

Code: Select all

array (
    array ( id1, id2 ),
    array ( id1, id2, id3 ),
    array ( id1, id2 ),
    array ( id1, id2 )
)
There will always be two or three ID's on each array, dependant upon if the images were determined to be portrait or landscape. Each array contains the list of images which should be displayed next on the page.

When the page loads it first needs to get the array at index 0 and cache that in JavaScript. Each time the user clicks a button called "next" on the client-side 2 things should happen:

1. The array of images which are currently cached in the JS are displayed on the page
2. an ajax request is sent asking if there are more images to display, and then they are cached in the background

That process happens repeatedly until there are no more images.

I seem to have got my logic all screwed up somewhere after trying to get the first set to display without the user clicking "next". Nothing happens when I click next but there are no errors.

Here's my (messy) code so far, with bits and pieces of attempted debugging.

Server side controller:

Code: Select all

<?php

class controller extends core
{
	private $input;
	
	public function __construct()
	{
		parent::__construct();
	
		$this->input = new inputFilter;
		$this->input->expect(
			array(
				array('time', 'numeric'),
				array('get', 'numeric')
			),
			$_GET
		);
		if (!$this->input->get('time')) $this->redirect('?page=home');
		
		if (!isset($_SESSION['gallery'])) $_SESSION['gallery'] = array();
		if (!isset($_SESSION['gallery'][$this->input->get('time')])) $this->generateMatrix();
		
		if ($this->input->get('get') !== null)
		{
			$this->sendAjaxString();
			exit();
		}
	}
	
	public function getTime()
	{
		return $this->input->get('time');
	}
	
	private function sendAjaxString()
	{
		$sess = $_SESSION['gallery'][$this->input->get('time')];
		if (empty($sess)) exit('NULL');
		if (isset($sess[$this->input->get('get')]))
		{
			/*
			 Send a parseable string with the following:
			 ----
			 Id of Attachment --
			 Id of Image --
			 Num Comments --
			 If Prev exists --
			 If next exists --
			 */
			$ret = '';
			$collection = $sess[$this->input->get('get')];
			$set = $this->input->get('time');
			$next = (int) isset($sess[($this->input->get('get')+1)]);
			$prev = (int) isset($sess[($this->input->get('get')-1)]);
			$loop = false;
			
			foreach ($collection as $id)
			{
				if ($loop) $ret .= ':';
				$loop = true;
				$query = "
				select
					b.id as attid,
					a.id as imgid
				from
					gallery_sets as a,
					attachments as b
				where
					a.image = b.id
					and a.timeline_id = '{$set}'
					and a.id = '{$id}'
				order by
					b.filename asc";
				$result = $this->db->query($query);
				$row = $this->db->fetchRow($result);
				
				//Build string
				$ret .= 'img='.$row->imgid.','.
					'att='.$row->attid.','.
					'next='.$next.','.
					'prev='.$prev.',';
				$query2 = "
				select
					count(*) as tot
				from
					gallery_comments
				where
					imgid = '".$row->imgid."'";
				$result2 = $this->db->query($query2);
				$row2 = $this->db->fetchRow($result2);
				$ret .= 'comments='.$row2->tot;
			}
			exit($ret);
		}
		else exit('NULL');
	}

	//Get the title of the current image set (year range)
	public function getSet()
	{
		$id = $this->input->get('time');
		$query = "
		select
			concat(year_start, ' - ', year_end) as range
		from
			timeline
		where
			id = '$id'";
		$result = $this->db->query($query);
		if ($this->db->numRows($result))
		{
			$row = $this->db->fetchRow($result);
			return $row->range;
		}
		else $this->redirect('?page=home');
	}

	//Build the matrix of images and cache it in the session
	public function generateMatrix()
	{
		$set = $this->input->get('time');
		$query = "
		select
			a.id,
			a.image,
			a.width,
			a.height
		from
			gallery_sets as a,
			attachments as b
		where
			a.image = b.id
			and a.timeline_id = '{$set}'
		order by
			b.filename asc";
		$result = $this->db->query($query);
		$collection = array();
		$last_dim = false;
		$tmp = array();
		$n = $this->db->numRows($result);
		$x = 0;
		while ($row = $this->db->fetchRow($result))
		{
			$x++;
			switch (($row->width > $row->height))
			{
				case true:
				$dim = 'landscape';
				break;
				case false:
				$dim = 'portrait';
				break;
			}
			if ($last_dim && $last_dim != $dim)
			{
				$collection[] = $tmp;
				$tmp = array();
			}
			elseif ($dim == 'portrait' && count($tmp) >= 3)
			{
				$collection[] = $tmp;
				$tmp = array();
			}
			elseif ($dim == 'landscape' && count($tmp) >= 2)
			{
				$collection[] = $tmp;
				$tmp = array();
			}
			$tmp[] = $row->id;
			$last_dim = $dim;
			if ($x == $n) $collection[] = $tmp;
		}
		$_SESSION['gallery'][$this->input->get('time')] = $collection;
	}
}

?>
Client Side (Template)

Code: Select all

<html>
<head>
<title>Gallery</title>
<script type="text/javascript">
<!--

var started = false;
var imgIndex = -1;
var imgIndexTmp = 0;
var myImages = new Array();
var lastResponse;

function getNextSet()
{
	if (!started) return loadFirstSet();
	
	http.open("GET", '?page=gallery&time=<?= $con->getTime(); ?>&get='+(imgIndex+1), true);
	http.onreadystatechange = showImages;
	http.send(false);
	imgIndexTmp = imgIndex+1;
}

function getPrevSet()
{
	alert(imgIndex);
	http.open("GET", '?page=gallery&time=<?= $con->getTime(); ?>&get='+(imgIndex-1), true);
	http.onreadystatechange = showImages;
	http.send(false);
	imgIndexTmp = imgIndex-1;
}

function loadFirstSet()
{
	http.open("GET", '?page=gallery&time=<?= $con->getTime(); ?>&get=0', true);
	http.onreadystatechange = loadImages;
	http.send(false);
	started = true;
}

function loadImages(foo)
{
	if (http.readyState == 4)
	{
		var response = http.responseText;
		if (response == 'NULL') return;
		else
		{
			var imageData = response.split(':');
			myImages = new Array();
			for (var x in imageData)
			{
				var myVars = imageData[x].split(',');
				for (var y in myVars) eval(myVars[y]);
				myImages[x] = new Image();
				myImages[x].src = '?page=getfile&id='+att;
			}
			if (!foo) getNextSet();
			return;
		}
	}
}

function showImages()
{
	var arrSize = arraySize(myImages);
	if (arrSize == 3)
	{
		try {
			document.getElementById('node_holder').removeChild(myNode);
		} catch (e) {
			//
		}
		add3columnTbl();
	}
	else if (arrSize != 0)
	{
		try {
			document.getElementById('node_holder').removeChild(myNode);
		} catch (e) {
			//
		}
		add2columnTbl();
	}
	
	if (arrSize != 0)
	{
		loadImages('yes');
		imgIndex = imgIndexTmp;
	}
}

function arraySize(arr)
{
	if (arr.constructor != Array) return 0;
	
	var itsSize = 0;
	for (var x in arr) itsSize++;
	return itsSize;
}

function add2columnTbl()
{
	myNode = document.createElement('table');
	myNode.style.width = '1024px';
	myNode.style.height = '350px';
	myNode.style.backgroundColor = '#777777';
	myTr = document.createElement('tr');
	myTd1 = document.createElement('td');
	myTd2 = document.createElement('td');
	myTd1.style.width = '512px';
	myTd1.style.textAlign = 'center';
	myTd2.style.textAlign = 'center';
	myTd1.appendChild(myImages[0]);
	myTd2.appendChild(myImages[1]);
	myTr.appendChild(myTd1);
	myTr.appendChild(myTd2);
	myNode.appendChild(myTr);
	document.getElementById('node_holder').appendChild(myNode);
}

function add3columnTbl()
{
	myNode = document.createElement('table');
	myNode.style.width = '1024px';
	myNode.style.height = '350px';
	myNode.style.backgroundColor = '#777777';
	myTr = document.createElement('tr');
	myTd1 = document.createElement('td');
	myTd2 = document.createElement('td');
	myTd3 = document.createElement('td');
	myTd1.style.width = '341px';
	myTd2.style.width = '341px';
	myTd1.style.textAlign = 'center';
	myTd2.style.textAlign = 'center';
	myTd3.style.textAlign = 'center';
	myTd1.appendChild(myImages[0]);
	myTd2.appendChild(myImages[1]);
	myTd3.appendChild(myImages[2]);
	myTr.appendChild(myTd1);
	myTr.appendChild(myTd2);
	myTr.appendChild(myTd3);
	myNode.appendChild(myTr);
	document.getElementById('node_holder').appendChild(myNode);
}

function getHTTPObj()
{
	var XMLHttp;
	if (window.XMLHttpRequest) XMLHttp = new XMLHttpRequest();
	else if (window.ActiveXObject)
	{
		try {
			
			XMLHttp = new window.ActiveXObject("Microsoft.XMLHTTP");
			
		} catch(e) {
			
			XMLHttp = false;
		
		}
	}
	else XMLHttp = false;
	
	return XMLHttp;
}

var http = getHTTPObj();

// -->
</script>
</head>
<body onload="getNextSet();">
<a href="javascript:getPrevSet();">Prev</a>
<a href="javascript:getNextSet();">Next</a>
<table style="width: 100%; height: 100%;">
	<tr>
		<td align="center">
			<div id="node_holder"></div>
		</td>
	</tr>
</table>
</body>
</html>
I'll probably suss it out myself before long cos I doubt anybody's gonna be feeling lively enough to make sense of my code 8O

Posted: Tue Jun 13, 2006 12:44 pm
by pickle

Code: Select all

<a href="javascript:getPrevSet();">Prev</a>
<a href="javascript:getNextSet();">Next</a> 
Are those supposed to be special characters?

Throw alert()s at the head of every function just to see if their getting called like you expect.

Posted: Tue Jun 13, 2006 12:49 pm
by Chris Corbyn
pickle wrote:

Code: Select all

<a href="javascript:getPrevSet();">Prev</a>
<a href="javascript:getNextSet();">Next</a> 
Are those supposed to be special characters?

Throw alert()s at the head of every function just to see if their getting called like you expect.
Geshi is playing tricks with those colons ;)

I've done the throwing alerts thing. That's working. It's something to do with the value of imgIndex and how it changes :(

I'm currently re-wrting from scratch since I've realised my logic is flawed anyway if you need to back there are no cached images to use. I'll post updated code if it works :)

Cheers for the help.

Posted: Tue Jun 13, 2006 3:46 pm
by Chris Corbyn
About flippin' time :)

It was actually only the client side code I changed.... Grr... I need to add transitional effects now but at least the logic works. Took me ages :(

Code: Select all

<html>
<head>
<title>Gallery</title>
<script type="text/javascript">
<!--

var myNode;

var dir = 1;

var prevImg = false;
var thisImg = false;
var nextImg = false;

var imgIndex = 0;

function initialize(n, m)
{
	http.open("GET", '?page=gallery&time=<?= $con->getTime(); ?>&get='+(n), true);
	http.onreadystatechange = cacheImages;
	http.send(false);
	imgIndex = n;
}

function cacheImages()
{
	if (http.readyState == 4)
	{
		var response = http.responseText;
		if (response == 'NULL') return showImages();
		else
		{
			var imageData = response.split(':');
			var myImages = new Array();
			for (var x in imageData)
			{
				var myVars = imageData[x].split(',');
				for (var y in myVars) eval(myVars[y]);
				myImages[x] = new Image();
				myImages[x].src = '?page=getfile&id='+att;
			}
		} 
		if (!thisImg) //This should only ever happen on page load
		{//alert('in thisImg');
			thisImg = myImages;
			if (!nextImg) initialize(1, 1);
		}
		else if (!nextImg)
		{//alert('in nextImg');
			nextImg = myImages;
			showImages();
		}
		else if (!prevImg)
		{//alert('in prevImg');
			prevImg = myImages;
			showImages();
		}
	}
}

function getNextSet()
{
	prevImg = thisImg;
	thisImg = nextImg;
	nextImg = false;
	if (dir == 0) n = imgIndex+3;
	else n = imgIndex+1;
	initialize((n), 0);
	dir = 1;
	//alert(imgIndex);
}

function getPrevSet()
{
	nextImg = thisImg;
	thisImg = prevImg;
	prevImg = false;
	if (dir == 1) n = imgIndex-3;
	else n = imgIndex-1;
	initialize((n), 0);
	dir = 0;
	//alert(imgIndex);
}

function showImages()
{//alert(prevImg.toString()+'::'+thisImg.toString()+'::'+nextImg.toString());
	var arrSize = arraySize(thisImg);
	if (arrSize == 3)
	{
		try {
			document.getElementById('node_holder').removeChild(myNode);
		} catch (e) {
			//
		}
		add3columnTbl();
	}
	else if (arrSize != 0)
	{
		try {
			document.getElementById('node_holder').removeChild(myNode);
		} catch (e) {
			//
		}
		add2columnTbl();
	}
}

function arraySize(arr)
{
	if (arr.constructor != Array) return 0;
	
	var itsSize = 0;
	for (var x in arr) itsSize++;
	return itsSize;
}

function add2columnTbl()
{
	myNode = document.createElement('table');
	myNode.style.width = '1024px';
	myNode.style.height = '350px';
	myNode.style.backgroundColor = '#777777';
	myTr = document.createElement('tr');
	myTd1 = document.createElement('td');
	myTd2 = document.createElement('td');
	myTd1.style.width = '512px';
	myTd1.style.textAlign = 'center';
	myTd2.style.textAlign = 'center';
	myTd1.appendChild(thisImg[0]);
	myTd2.appendChild(thisImg[1]);
	myTr.appendChild(myTd1);
	myTr.appendChild(myTd2);
	myNode.appendChild(myTr);
	document.getElementById('node_holder').appendChild(myNode);
}

function add3columnTbl()
{
	myNode = document.createElement('table');
	myNode.style.width = '1024px';
	myNode.style.height = '350px';
	myNode.style.backgroundColor = '#777777';
	myTr = document.createElement('tr');
	myTd1 = document.createElement('td');
	myTd2 = document.createElement('td');
	myTd3 = document.createElement('td');
	myTd1.style.width = '341px';
	myTd2.style.width = '341px';
	myTd1.style.textAlign = 'center';
	myTd2.style.textAlign = 'center';
	myTd3.style.textAlign = 'center';
	myTd1.appendChild(thisImg[0]);
	myTd2.appendChild(thisImg[1]);
	myTd3.appendChild(thisImg[2]);
	myTr.appendChild(myTd1);
	myTr.appendChild(myTd2);
	myTr.appendChild(myTd3);
	myNode.appendChild(myTr);
	document.getElementById('node_holder').appendChild(myNode);
}

function getHTTPObj()
{
	var XMLHttp;
	if (window.XMLHttpRequest) XMLHttp = new XMLHttpRequest();
	else if (window.ActiveXObject)
	{
		try {
			
			XMLHttp = new window.ActiveXObject("Microsoft.XMLHTTP");
			
		} catch(e) {
			
			XMLHttp = false;
		
		}
	}
	else XMLHttp = false;
	
	return XMLHttp;
}

function isSafe(x)
{
	switch (x)
	{
		case 0:
		if (!nextImg)
		{
			alert('Computer says NO!');
			return false;
		}
		else return true;
		case 1:
		if (!prevImg)
		{
			alert('Computer says NO!');
			return false;
		}
		else return true;
	}
	return true;
}

var http = getHTTPObj();

// -->
</script>
</head>
<body onload="initialize(0, 1);" style="padding: 0; margin: 0;">
<a href="javascript:getPrevSet();" onclick="return isSafe(1);">Prev</a>
<a href="javascript:getNextSet();" onclick="return isSafe(0);">Next</a>
<table style="width: 100%; height: 90%;" cellpadding=0 cellspacing=0>
	<tr>
		<td align="center">
			<div id="node_holder"></div>
		</td>
	</tr>
</table>
</body>
</html>

Posted: Tue Jun 13, 2006 3:51 pm
by pickle
Looking pretty good. Do you have a working example I could fawn over?

Posted: Tue Jun 13, 2006 3:53 pm
by Chris Corbyn
pickle wrote:Looking pretty good. Do you have a working example I could fawn over?
I'll PM you. It's a personal site for a friend so it's login only.

Posted: Wed Jun 14, 2006 1:13 pm
by Chris Corbyn
Gahhhhhh!!! Somebody get me a really sharp stick so I can prod Bill Gates in the eye repeatedly.

Doesn't work at all in IE. Yet there's no errors at all. I'd use Firefox to see what the problem is with the JS but of course, Firefox actually works so I can't.

I'm guessing it's something to do with trying to create a table node and td and tr nodes. Hmm... This is gonna be difficult to do without tables if that is the case :(

Posted: Wed Jun 14, 2006 2:49 pm
by Chris Corbyn
This should really be in the Client Side forum but it's been here long enough now...

Problem fixed. It wasn't obvious neither and I bet this will be useful info to someone else some day!

IE *was* creating the table element and appending it's children. It was simply just not displaying it.

The solution? Create a tbody element and append all rows to that before appending tbody to the table itself. Odd, but at least it works now :)

Posted: Wed Jun 14, 2006 4:25 pm
by pickle
d11wtq wrote:Create a tbody element and append all rows to that before appending tbody to the table itself. Odd, but at least it works now :)
From the documentation I've read - that's the way its supposed to work. You're supposed to create a <tbody> element & not just throw the <tr> into the <table>.
http://www.howtocreate.co.uk/tutorials/ ... /domtables
http://developer.mozilla.org/en/docs/Tr ... Interfaces

I'm not sure if it says on those pages, but I've read that browsers automatically put in the <tbody> element if it's not in the actual source code. You can see this when you view the source in Firefox. I think this may actually be a case where IE follows the standards more strictly than Firefox (which brings the score to: Firefox: , IE: 1)

Posted: Wed Jun 14, 2006 4:45 pm
by bokehman
pickle wrote:I've read that browsers automatically put in the <tbody> element if it's not in the actual source code.
<tbody> is an optional element in html 4 and is only required where <thead> or <tfoot> elements are being implemented. Its purpose is to allow the various parts of a table to be scrolled independantly.