Web Design Forum: TUTORIAL: Building an AJAX paste-board - Web Design Forum

Jump to content

WDF
WDF Premium Memberships Reseller Hosting
Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

TUTORIAL: Building an AJAX paste-board Rate Topic: -----

#1 User is offline   php_penguin 

  • richthegeek
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,471
  • Joined: 06-August 07
  • Reputation: 7
  • Gender:Male
  • Location:Liverpool
  • Experience:Web Guru
  • Area of Expertise:Coder

  Posted 19 August 2007 - 02:41 AM

This topic is to discuss the tutorial:
Building an AJAX paste-board STAGE 1, the setup

This might make it into the book in a more expanded form, but i was just looking for something to do.

A pasteboard is somewhere a user can paste a block of text and save it to a specific URL. Simple eh? Well lets see how to...

The plan
First thing is to define a couple of targets for what the finished product should do:
- allow a user to click new, paste, save, and view a post
- show a user links to all previous posts from that machine

And for the second level of complexity:
- syntax highlighting + line numbering
- bookmarking
- recently viewed

The designs + layouts
mysql tables
Just one table is needed here, the posts:
ID integer length 5, primary key, auto increment
userip varchar length 12
posttime integer length 12
post mediumtext

that should do it.

HTML layout
see attached index.html and style.css.

You will need to download jquery yourself, as i can't attach it here. There is a link here: http://www.sportsave...neral/jquery.js

As you can see in the html files provided, there are areas defined for both the users own posts and recently viewed posts.
You might also notice the save screen... this will display when the form is saving (post click, pre return)

there is also an area for your own links, if you feel the need to use this further...

part 2 is saved seperately for ease of reading.

Attached File(s)


0

#2 User is offline   php_penguin 

  • richthegeek
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,471
  • Joined: 06-August 07
  • Reputation: 7
  • Gender:Male
  • Location:Liverpool
  • Experience:Web Guru
  • Area of Expertise:Coder

  Posted 19 August 2007 - 03:49 AM

This topic is to discuss the tutorial:
Building an AJAX paste-board STAGE 2, making it function

So now we need to make the script function... the various stages are as follows:
* on page load, get recently viewed and users own posts, and if required the currently viewing paste
* on save, update users own, and submit post to database

So, lets start at the beginning - sending a request onload to the backend system (more on that later)
in your SCRIPT area:

$(document).ready( function() {


});

this code is the jQuery code for "do this as soon as the document is loaded". This is what we need for the first bullet point...

Now we need to send a GET request, telling the backend to return the initial data:
$(document).ready( function() {

	$.get("backend.php", {info: 'init'}, ajaxCallback );

});

so lets look at this statement a wee bit.... the "onload" section contains a $.get - this is the jquery way of sending a GET request via the XMLHTTPRequest Method. This has three arguments inside it:
* the url (in this case "backend.php");
* the argument(s), in the case info=init ... to add more, simple place a comma, eg info: "init", name: "joe"
* the callback function, in this case "ajaxCallback"

so now we need to handle that callback, so write down this:
function ajaxCallback( data ) {
  alert(data);
}


So we have now defined ajaxCallback... and it has a data input. this happens automatically, and the data is what is returned from the url you GETted... (got surely?)

so lets make it actually do something!
Lets start simple for now, just create backend.php and chuck this into it:
<?php
print "my callback data";
?>


save it, execute the script, and you should get an alert box saying "my callback data". if not, re-read the article so far....

Okay so this is all well and good, but about as much use as a chocolate kettle. We aren't using AJAX (well the X at least) and we aren't updating the recently viewed (RV from now on) or the users own (UO). We need to make our PHP create a wellformed XML output, so lets start by changing your PHP to this:
<pastie>
<?php
$info = $_GET['info'];

if(	$info == "init"	) 
{
	$recent = $_COOKIE['recent'];
	if(	is_array($recent)	) {
		$output .= "\t<recent>";

		foreach(	$recent as $pasteid => $pastetime	) {
				$pastetime = date("g:ia", $pastetime);
				$output .= "
		<paste".$pasteid."><id>".$pasteid."</id><time>".$pastetime."</time></paste>";	
		}
		$output .= "
	</recent>";

	}
	
	$own	= $_COOKIE['own'];
	if(	count($own) > 0 ) {
		$output .= "
	<own>";
		foreach(	$own as $pasteid => $pastetime	) {
			$pastetime = date("g:ia", $pastetime);
			$output .= "
		<paste".$pasteid."><id>".$pasteid."</id><time>".$pastetime."</time></paste>";
		}
		$output .= "
	</own>";
	
	}
}
print $output;
?>

</pastie>


If you can follow that, I have added a document root, then in the php, checked for the "init" string and read the info from some cookies (that dont yet exist). The reason the <paste> elements have the id appended to the tagname is because jQuery doesn't handle multiple occurences of the same tagname so well - it only displays one of them in the DOM.

So now that the script might return something useful, lets make something useful happen when the script returns.

The ajaxCallback function needs to be extended a bit, so lets do so:
function ajaxCallback( data ) {
		
		var recent	= $(data).find("recent");
		var own		= $(data).find("own");
		
		if( recent.size() == 1 ) {
			$("ul#recent").empty();
			recent.children().each( function() {
				var newitem = "<li><a href='#' onclick='viewPostie(\""+$(this).find("id").html()+"\")'>Postie #"+$(this).find("id").html()+"</a> at "+$(this).find('time').html()+"</li>";
				$("ul#recent").append( newitem );
			} );		
		}
		if( own.size() == 1 ) {
			$("ul#own").empty();
			own.children().each( function() {
				var newitem = "<li><a href='#' onclick='viewPostie(\""+$(this).find("id").html()+"\")'>Postie #"+$(this).find("id").html()+"</a> at "+$(this).find('time').html()+"</li>";
				$("ul#own").append( newitem );
			} );		
		}
	
	}


this uses a couple of jQuery-only functions, which i will run through now:
* $(data) - loads the XMLHTTPRequest return data as a traversable object/element
* find - finds elements of the given name within the current element
* empty - removes all child nodes and text from current element
* append - adds the given text to the end of the current element
* each - loops through each of the currently given elements
* html - sets or gets the html of the object - use text to jsut get text.

If you run the attached cookies.php you will get a better view of what happens.

ALSO updated index.html, and the backend so far.

Stage 3 is on its way - i need sleep ;)

Attached File(s)


0

#3 User is offline   php_penguin 

  • richthegeek
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,471
  • Joined: 06-August 07
  • Reputation: 7
  • Gender:Male
  • Location:Liverpool
  • Experience:Web Guru
  • Area of Expertise:Coder

  Posted 19 August 2007 - 11:41 AM

This topic is to discuss the tutorial:
Building an AJAX paste-board STAGE 3, more functionality

OK so a quick recap, whats left to be done?
* save new postie
* view existing postie

Ok, lets do the first one first: saving the new postie.

First we need to make the "Paste" button do something. It already has the OnClick action "PasteIt()", so now lets define that function in your JavaScript area:
	function PasteIt() {
		var pastetext = $("textarea#pastetext").val();
	$.post("backend.php", {action: 'paste', paste: pastetext}, ajaxCallback );
}

Simple eh? very similiar to the previous GET method, just with "post" this time, and a different set of values (action=paste, paste=).

SO lets see what happens in our PHP script yes? Add this to the start of your script:
print_r( $_POST );

and this to your ajaxCallback function:
alert( data )


save both, and submit a pastie. you should get something like this:
<pastie>
array
(
   [action] => paste
   [paste] => some text
)

</pastie>


So now we have to make the PHP do something itself. This involves connecting to the database, so we need to do a number of things:
REPLACE
print_r($_POST);
WITH
$action = $_POST['action'];


also, this is to make any setcookies work later on:
REPLACE
<pastie>
<?php
WITH
<?php
$output = "<pastie>
";


and add this to a portion of your PHP, before the print $output;
if(	$action == "paste"	&&	strlen(	$_POST['paste']) > 1	)
{

}


Ok so now we have a logical block for when something longer than 1 character is pasted.
Now we need to go through a number of steps:
1 make the string safe to save to the mysql db, including adding slashes and replacing html entities with safe chars
2 open a connection the database
3 INSERT the paste into the database.
4 return the new paste to the page along with a success message

step 1: make string safe
$paste = $_POST['paste'];
$paste = htmlentities( $paste, ENT_QUOTES	);
$paste = addslashes(   $paste	);


So now if we were to submit "here is 'some' <b>bold</b> text" we would find "here is 'some' &lt;b&gt;bold&lt;/b&gt; text" being inserted to the database - completely mysql safe.

step 2: open a connection
$user  = "xxxxxx";
$pass  = "xxxxxx";
$server= "xxxxxx";
$connect	=	mysql_connect( $server, $user, $pass );
	
$output .= "<newpaste>";
	
if(	!$connect	) {
	$output .= "<status>0</status>";
	$output .= "<statusmessage>".mysql_error()."</statusmessage>";
} else {
	
}
$output .= "</newpaste>";


Ok, i might have skipped a stage or two here for the less observant... The connection is the first 4 lines, and after that i have done same status checking + XML building. This makes it give a status 0 (false) to the frontend on failure, including the mysql_error string for debugging.

Also, remember to change the mysql info for your own.

See that "} else {" ? This is where we go to:
step 3: insert the new paste
		mysql_select_db( "pastie" );
		$userip  = $_SERVER['REMOTE_ADDR'];
		$insert  = "INSERT INTO `pastes` VALUES('', '".$userip."', '".time()."', '".$paste."')";
		$query = mysql_query($insert);

		$newid   = mysql_insert_id();
		setcookie("own[".$newid."]", time(), time()+(3600*24*365));

Ok... even i realise this is a bit too skippy... Firstly, where has this database come from?
Well remember at the start i posted that database layout? Well we need to create it... heres some nice code for you to save and run:
<?php
$user = "xxxxxx";
$pass = "xxxxxx";
$server	= "xxxxxx";

mysql_connect(	$server, $user, $pass ) or die(mysql_error());

$query = "CREATE DATABASE IF NOT EXISTS `pastie`;";
$query .="USE `pastie`;";
$query .="create table if not exists pastes ( 
id int(6) primary key auto_increment, 
userip varchar(12), 
posttime integer(12), 
post mediumtext);";

mysql_query($query) or die(mysql_error());
?>

Save this, run it, and you should get a blank page - which indicates success, and you now have a database


Ok so back to the backend.PHP, remember the code we just added? well the INSERT string is pretty basic, so i won't explain it. We have also set a cookie to add to our users "own" list.

Now we need to check if the QUERY was succesful, and return to the relevant info to the front end:
		mysql_select_db( "pastie" );
		$userip  = $_SERVER['REMOTE_ADDR'];
		$insert  = "INSERT INTO `pastes` VALUES('', '".$userip."', '".time()."', '".$paste."')";
		$query   = mysql_query( $insert );
		
		if($query) {
			$newid   = mysql_insert_id();
			setcookie("own[".$newid."]", time(), time()+(3600*24*365));
			$output .= "<status>1</status>";
		} else {
			$output .= "<status>0</status>";
			$output .= "<statusmessage>".mysql_error()."</statusmessage>";
		}


See how the code has changed? So now we have all the relevant info in our backend. If it all goes well, it should insert + return status=1.

So back to the frontend:
in ajaxCallback:
		var newpost	= $(data).find("newpaste");
		var npstatus	= $.trim(	$(data).find("newpaste status").text() );
		var recent	= $(data).find("recent");
		var own		= $(data).find("own");

		if( npstatus == 1 ) {
			$("div.viewpane").html( "<h1>Your new paste:</h1>"+newpost.find("text").html() );
			$("div.pasteform").hide().find("textarea").empty();
			$("div.viewpane").show();
			$(".savescreen").fadeOut("fast").hide();
			var newitem = "<li><a href='#' onclick='viewPostie(\""+newpost.find("id").html()+"\")'>Postie #"+newpost.find("id").html()+"</a> just now</li>";
			$("ul#own, ul#recent").append( newitem );			
		} else {
			$(".savescreen").fadeOut("fast").hide();
		}


Thats what i have now.... lots of code which does not an awful lot... just check the status, show the viewpane, add items to the UO and RV lists....

However, if you can see that else at the end, you will notice that handles for when the status ISNT 1, ie when there is an error. We could handle that in a number of ways, for example:
* show something similiar to the savescreen, but with a red "error" in it.
* show an alert
* show the viewpane with the error in it.

the first one is best IMO, so add this to your html:
<div class="errorscreen"><h2>Error</h2><h3></h3><a onclick='$(".errorscreen").fadeOut("fast");'>close this message</a></div>

and download the update CSS at the end of the post


[b]okay, thats everything for the save function, heres a review of the code for various bits:
[indent]
*******
The javascript, ajaxCallback function:
	function ajaxCallback( data ) {
		//	alert(data);	
		var newpost	= $(data).find("newpaste");
		var npstatus	= $.trim(	$(data).find("newpaste status").text() );
		var recent	= $(data).find("recent");
		var own		= $(data).find("own");
		

		
		if( newpost.size() == 1) {
		if( npstatus == 1 ) {
			$("div.viewpane").html( "<h1>Your new paste:</h1>"+newpost.find("text").html() );
			$("div.pasteform").hide().find("textarea").empty();
			$("div.viewpane").show();
			$(".savescreen").fadeOut("fast").hide();
			var newitem = "<li><a href='#' onclick='viewPostie(\""+newpost.find("id").html()+"\")'>Postie #"+newpost.find("id").html()+"</a> just now</li>";
			$("ul#own, ul#recent").append( newitem );			
		} else if( npstatus == 0 ) {
			$(".savescreen").fadeOut("fast").hide();
			$(".errorscreen").css("opacity",0).css("display","block").fadeTo("slow", 0.5).find("h3").html( newpost.find("statusmessage") );
		}
		}
				
		if( recent.size() == 1 ) {
			$("ul#recent").empty();
			recent.children().each( function() {
				var newitem = "<li><a href='#' onclick='viewPostie(\""+$(this).find("id").html()+"\")'>Postie #"+$(this).find("id").html()+"</a> at "+$(this).find('time').html()+"</li>";
				$("ul#recent").append( newitem );
			} );		
		}
		if( own.size() == 1 ) {
			$("ul#own").empty();
			own.children().each( function() {
				var newitem = "<li><a href='#' onclick='viewPostie(\""+$(this).find("id").html()+"\")'>Postie #"+$(this).find("id").html()+"</a> on "+$(this).find('time').html()+"</li>";
				$("ul#own").append( newitem );
			} );		
		}
	
	}

*******
The php add paste section
*******
if(	$action == "paste"	)
{
	$paste = $_POST['paste'];
	$paste = htmlentities( $paste, ENT_QUOTES	);
	
	$user  = "root";
	$pass  = "";
	$server= "localhost";
	$connect	=	mysql_connect( $server, $user, $pass );
	
	$output .= "<newpaste>";
	
	if( strlen( $paste ) < 1) {
		$output .= "<status>0</status>";
		$output .= "<statusmessage>Please paste something longer... we don't like single letters;)</statusmessage>";
	} else
	if(	!$connect	) {
		$output .= "<status>0</status>";
		$output .= "<statusmessage>".mysql_error()."</statusmessage>";
	} else {
		mysql_select_db( "pastie" );
		$userip  = $_SERVER['REMOTE_ADDR'];
		$insert  = "INSERT INTO `pastes` VALUES('', '".$userip."', '".time()."', '".$paste."')";
		$query   = mysql_query( $insert );
		
		if($query) {
			$newid   = mysql_insert_id();
			setcookie("own[".$newid."]", time(), time()+(3600*24*365));
			$output .= "<status>1</status>";
			$output .= "<text>".stripslashes(str_replace("\n","<BR \>",$paste))."</text>";
			$output .= "<id>".$newid."</id>";
		} else {
			$output .= "<status>0</status>";
			$output .= "<statusmessage>".mysql_error()."</statusmessage>";
		}

	}
	$output .= "</newpaste>";
}


okay, i'm gonna split this into a fourth part for the ease of the read.

Attached File(s)


0

#4 User is offline   php_penguin 

  • richthegeek
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,471
  • Joined: 06-August 07
  • Reputation: 7
  • Gender:Male
  • Location:Liverpool
  • Experience:Web Guru
  • Area of Expertise:Coder

  Posted 19 August 2007 - 05:35 PM

This topic is to discuss the tutorial:
Building an AJAX paste-board STAGE 4, finishing up

Ok, welcome to stage 4... this is turning into a bit of monster tutorial ;)

In this stage we will be:
* making the "view post" bit work.
* sort out a bit of basic line-numbering

Ok, view post
you may have noticed that the code already adds a "viewPostie" function in the javascript, so now we need to define that:
	function viewPostie( id ) {
	$(".loadscreen").css("opacity",0).css("display","block").fadeTo("slow",0.5);
	$.get("backend.php", {info: 'view', paste: id}, ajaxCallback);		
}

nice and simple, but what is this ".loadscreen"? Its exactly the same as the savescreen and errorscreen, so add it to your html:
<div class="loadscreen"><h1>Loading...</h1><img src="http://www.napyfab.com/ajax-indicators/images/loading_animation_liferay.gif" /></div>


and add to your css:
.savescreen, .errorscreen { display: none; opacity: 0.5; width: 100%;  position: absolute; top: 0; left: 0; background: #444; text-align: center; color: #ddd; padding: 25% 0% 75%;}

BECOMES

.savescreen, .errorscreen, .loadscreen { display: none; opacity: 0.5; width: 100%;  position: absolute; top: 0; left: 0; background: #444; text-align: center; color: #ddd; padding: 25% 0% 75%;}


next we need to update the backend.PHP to handle this new request (GET, info=view, paste=#):
if(	$info == "view"	)
{
	$output .= "<pasteview>";
	$id	= $_GET['paste'];
	if( is_numeric($id) ) {
		$user  = "root";
		$pass  = "";
		$server= "localhost";
		$connect	=	mysql_connect( $server, $user, $pass );
		if(	!$connect	) {
			$output .= "<status>0</status>";
			$output .= "<statusmessage>".mysql_error()."</statusmessage>";
		} else {
			mysql_select_db( "pastie" );
			$select = "SELECT * FROM pastes WHERE `id` = '".$id."' LIMIT 1";
			$query = 	mysql_query($select) or die( mysql_error() );
			if($query) {
				$data = mysql_fetch_assoc($query);
				setcookie("recent[".$id."]", time(), time()+(3600*24));
				$paste = $data["post"];
				$output .= "<status>1</status>";
				$output .= "<text>".stripslashes(str_replace("\n","<BR \>",$paste))."</text>";
				$output .= "<id>".$id."</id>";
			} else {
				$output .= "<status>0</status>";
				$output .= "<statusmessage>".mysql_error()."</statusmessage>";
			}
		}
	} else {
			$output .= "<status>0</status>";
			$output .= "<statusmessage>Unknown paste ID</statusmessage>";		
	}
	$output .= "</pasteview>";
}

very similiar to the add section, only a couple of changes.

now to do the javascript side, ajaxCallback:
		var viewpost	= $(data).find("pasteview");
		var vpstatus	= $.trim(	$(data).find("pasteview status").text() );
		if( viewpost.size() == 1 ) {
			if(vpstatus == 1 ) {
				$("div.pasteform").hide().find("textarea").empty();
				$("div.viewpane").html( viewpost.find("text") ).show();
				$(".loadscreen").fadeOut("fast").hide();
				var newitem = "<li><a href='#' onclick='viewPostie(\""+viewpost.find("id").html()+"\")'>Postie #"+viewpost.find("id").html()+"</a> just now</li>";
				$("ul#recent").append( newitem );		
			} else {
				$(".loadscreen").fadeOut("fast").hide();
				$(".errorscreen").css("opacity",0).css("display","block").fadeTo("slow", 0.5).find("h3").html( viewpost.find("statusmessage") );
			}
		}


and thats it, all the basic functionality is there!
users can add and view pastes, and there are links to recently viewed and users own posts.

however, there is still more to be done!
* there is not yet a "create new paste" button or action
* there is no line numbering yet
* there is no way of typing in a link to view a post

So, step by step
create new paste button
add this to your sidebar html:
<li><a onclick="$('.pasteform').show();$('.viewpane').hide();" id="newpaste">new paste</a></li>


that is actually it... click on that and the new paste form will show, but its not very styled yet, so lets do so
add this to your CSS:
a#newpaste	{ display: block; padding: 10px 0px 10px 30px; color: #444; cursor: pointer; background: #444 url(addbg.png) no-repeat 0% 50%; }

the addbg.png is attached.

so thats that one done.... but how about the third one?
creating a link to view a post
say we have this link: http://www.mysite.co...astie/?paste=14
we can tell from that that the paste we want is number 14, but the js doesn't know that (yet).

we go to our document ready bit of our js, and add this:
		var objURL = new Object();
		window.location.search.replace( 
			new RegExp( "([^?=&]+)(=([^&]*))?", "g" ),
			function( $0, $1, $2, $3 ){
				objURL[ $1 ] = $3;
			}
		);
		if( objURL["paste"].length > 0 ){
			viewPostie( objURL["paste"] );
		}


this uses some pretty arcane regex stuff which i didn't write and can't be bothered explaning. suffice to say it works yeah?

so whats left? well line-numbering and especially syntax highlighting are absolute pigs to make happen, and the bookmarking works well enough.

To make the bookmarking work better, we could simply link directly rather than ajaxly to each post (as is in the attached index_alt.html for your pleasure).

so comments people!

Attached File(s)


0

#5 User is offline   sweetlou 

  • Forum Newcomer
  • Pip
  • Group: Members
  • Posts: 14
  • Joined: 20-December 07
  • Reputation: 0
  • Experience:Web Guru
  • Area of Expertise:Designer

Posted 20 December 2007 - 05:49 PM

good tutorial, thank you
0

#6 User is offline   CammyD 

  • Forum Newcomer
  • Pip
  • Group: Members
  • Posts: 1
  • Joined: 11-April 08
  • Reputation: 0
  • Experience:Nothing
  • Area of Expertise:Designer

Posted 11 April 2008 - 02:25 PM

Might come in handy:
 CREATE TABLE `pasties` (
`ID` INT( 5 ) NOT NULL AUTO_INCREMENT ,
`userip` VARCHAR( 12 ) NOT NULL ,
`posttime` INT( 12 ) NOT NULL ,
`post` MEDIUMTEXT NOT NULL ,
PRIMARY KEY ( `ID` )
) ENGINE = InnoDB

0

#7 User is offline   Sam G 

  • Forum Newcomer
  • PipPipPipPipPip
  • Group: Members
  • Posts: 1,860
  • Joined: 06-March 09
  • Reputation: 54
  • Gender:Male
  • Location:Dreamland
  • Experience:Advanced
  • Area of Expertise:Designer/Coder

Posted 19 April 2010 - 11:46 AM

Threads merged.
0

Share this topic:


Page 1 of 1
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users