Web International Awards

payday loan

9

SEP 2010 34

Build a POI map using jQuery & Google Maps v3 (revised)

Tutorial preview

In the first revision of this tutorial, you've asked a couple of times if it is possible to display multple POIs at the same time on the map. The answer is yes, you can!

clearOverlays() gone

The clearOverlays() function was used to actually erase all markers on the map (all POIs on the map) when you clicked any link in the sidebar. In order to display multiple POIs on the map, this function must not be used. Therefor, in the revised version of the POI map the function was discarded as well as its unique call in the handle_clicks() function.

addMarker() updated

The original version of the function didn't check before appending a marker to the markersArray if there's already a marker with the same coordinates. That wasn't necessary because such a case wasn't possible because the clearOverlays() function assured there are no markers in the array when the addMarker() function was called. However, discarding the clearOverlays() function results in duplicated markers in the array each time someone clicks multiple times on the same link in the sidebar. To prevent that, we must check before adding a new marker to the array if it doesn't already exist. We do that within the addMarker() function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
function addMarker(m_position,m_title,m_infowindow) {
	var mark;
 
	if (markersArray.length!=0) 
	{
		duplicate = false;
		var markcopy;
		var markersCopy = [];
		while(markcopy=markersArray.pop())
		{
			if((markcopy.position.lat()==m_position.lat())&&(markcopy.position.lng()==m_position.lng())) duplicate = true;
			markersCopy.push(markcopy);
		}
		markersArray = markersCopy;
		if(duplicate==false)
		{
			marker = new google.maps.Marker({
			  	position: m_position,
			  	map: map,
				title: m_title
			});
			markersArray.push(marker);
			mark = markersArray.pop();
			google.maps.event.addListener(mark, 'click', function() {
				infoWindow.open(map,mark);
				var stringContent = m_infowindow;
				infoWindow.setContent("<div id=\"infowin-overlay\""+stringContent+"</div>");
 
				overlayHeight = $('#infowin-overlay').height();
				overlayWidth = $('#infowin-overlay').width();
				$('#infowin-overlay').parent().css('height',overlayHeight);
				$('#infowin-overlay').parent().css('width',overlayWidth);
			});
			markersArray.push(mark);
		}
	}
	else
	{
		marker = new google.maps.Marker({
		  	position: m_position,
		  	map: map,
			title: m_title
		});
		markersArray.push(marker);
		mark = markersArray.pop();
		google.maps.event.addListener(mark, 'click', function() {
			infoWindow.open(map,mark);
			var stringContent = m_infowindow;
			infoWindow.setContent("<div id=\"infowin-overlay\""+stringContent+"</div>");
 
			overlayHeight = $('#infowin-overlay').height();
			overlayWidth = $('#infowin-overlay').width();
			$('#infowin-overlay').parent().css('height',overlayHeight);
			$('#infowin-overlay').parent().css('width',overlayWidth);
		});
		markersArray.push(mark);
	}
}

In the above script we do the following: we first check if there's any markers in the array (line 4), and if the array is empty, we create a new marker, add it to the array and add a listener to it (lines 39 to 56). The listener is required because we need a trigger that will display the overlay window when a marker is clicked.

If the array is not empty, we check if the current marker to be added is already in the array. That's done with the while loop between lines 9 and 13. As you can see, the duplicate variable is initialized to false. If there's a marker in the array with the same coordinates as the marker we're trying to add, the value is changed to true. If duplicate variable remains false after the while loop is completed, we can safely add the marker to the array as described in the previous paragraph.

Because the required changes were minor, I decided not to update the entire tutorial, but to simply release the new source codes and show you the new function that does the trick. Therefor, you can view a new demo of the POI map script over here and download the full source code over here.

If you enjoyed this article you can stay updated to new content via our RSS feed or by email.

1

SEP 2010 0

Stationery design showcase (#2)

Last year we had the first post that rounded up some incredible stationeries. Today I'm going to present new interesting stationeries. Moreover, I am announcing the launch of a new WebRaptor website: Stationery Wall. Stationery Wall is, you guessed it, an online stationeries design showcase you can browse for inspiration. Although we're just launching it, Stationery Wall already contains more than 50 designs. Out of those, 5 are showcased just below. Go ahead and visit Stationery Wall and you might win a month of free advertising if you join the September contest! Details over Stationery Wall.

Stationery showcase

stationery design

H2R stationery design

stationery design

Ecocentric stationery design

stationery design

Pretty Green stationery design

stationery design

Nest stationery design

stationery design

Talent stationery design

25

AUG 2010 0

Building a live news blogging system in JSP [II]

Tutorial app preview

Last week I started building a live news blogging system using JSP. This is a follow up tutorial I've decided to do after seeing the success of the previous series, which described how to build the same web application using php as backend.

Adding news to the database

In today's part I am going to code the JSP files needed to add content to the database. The same functionality has already been developed using PHP in the third part of the original tutorial series located over here. The PHP version contained a function that was used to generate a select field into the add news form which would contain all available news categories. Differently, I've chosen not to include such a feature in a JSP function. Instead, I've added it directly into the HTML template. Not so fancy, but it works.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<form action="addnews.jsp" method="post" accept-charset="utf-8" id="form-overlay">
	<fieldset>
		<label for="title">Title : </label>
		<input type="text" name="title" value="" id="title" class="all-rounded"/>
		<label for="formcategory">Category :</label>
		<select name="formcategory" id="formcategory" length="1">
			<% 						
			st = new StringTokenizer(categories,"-");
			query = "SELECT name FROM Categories WHERE id>0 ORDER BY name ASC";
			rs = statement.executeQuery(query);
			count = 0;
				while (rs.next()) 
				{
					count++;
					String name = rs.getString("name");
					if(count==1) { %> <option selected="selected" value="<%=name%>"><%=name%></option> <% }
					else { %> <option value="<%=name%>"><%=name%></option> <% }
				}
			if(count==0) { %> <option value="none">none</option> <% }
			%>
		</select>
		<label for="body">Body text :</label>
		<textarea name="body" rows="8" cols="40" class="all-rounded"></textarea>
		<input type="submit" name="submit" value="Add news" id="submit" class="submit all-rounded" />
	</fieldset>
</form>

As you've seen in the previous part, the code on lines 8 to 10 is used to create a SQL query and run it. Lines 11/19 check if the SQL query returned an empty result. If yes, a default news category is created as the unique option; current case: none. If there's at least one category in the database, the code between line 12 and 17 gets the names of each category and adds each of them to the select field. The first category that's found is also pre-selected.

Processing the add news form

The form used to insert news in the database needs to be processed. The script is pretty straightforward, especially because I've already shown basic SQL queries in JSP just above ( or in the previous article in the series ). The first line of the script that processes the form is the usual line that imports all required libraries. All the following lines up to line 18 are used to create and instantiate the resources required to perform different operations on the mySQL database. Here's the full code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<%@ page import="java.sql.*" import="java.util.*" session="true" import="Javax.servlet.http.*" import="java.security.*" %>
 
<%
 
	String database = "lab8";
	String user = "root";
	String pass = "thebeastlocal";
	String table = "News";
 
	String connectionURL = "jdbc:mysql://localhost:3306/"+database+"?user="+user+";password="+pass;
	Connection connection = null;
	Statement statement = null;
	Statement update = null;
	ResultSet rs = null;
 
	Class.forName("com.mysql.jdbc.Driver").newInstance();
	connection = DriverManager.getConnection(connectionURL,user,pass);
	statement = connection.createStatement();
 
	if(session.getAttribute("loggedin")!=null)
	{
		String title = "";
		String formcategory = "";
		String body = "";
		int error = 0;
 
		if(request.getParameter("title")!=null) title = request.getParameter("title");
		else error = 1;
		if(request.getParameter("formcategory")!=null) formcategory = request.getParameter("formcategory");
		else error = 1;
		if(request.getParameter("body")!=null) body = request.getParameter("body");
		else error = 1;
 
		if(error==1)
		{
			response.sendRedirect("index.jsp?error=Please input information in all fields");
		}
		else
		{
			String username = (String) session.getAttribute("username");
			try
			{
				String query = "INSERT // INTO News(title,body,owner,publishing_date,publishing_time,category) VALUES('"+title+"','"+body+"','"+username+"',NOW(),NOW(),'"+formcategory+"')";
				// remove // between INSERT and INTO in above line
				statement.executeUpdate(query);				
			}
			catch (Exception e)
			{
				%><%=e%><%
			}
			response.sendRedirect("index.jsp?msg=News added to the system.");
		}
	}
	else response.sendRedirect("index.jsp?error=You must be logged in to access this feature.");
%>

Lines 20 and 54 in the above code check if the user trying to add a news to the database is logged in, as only logged in users are allowed to add items to the database. Lines 22 to 32 are used to initialize the variables which retain information users have inserted into the form fields on the previous page. On lines 34/36 errors are checked, and if any errors are found the users are redirected to the homepage of the web app and error messages are displayed. The code on line 40 gets the username of the person trying to add a news to the database as each news has an author and we need to know the info before inserting new items in the database. The try catch block between lines 41 and 50 is used to add the news to the database, or if any exceptions occur, catch and display them. If everything goes as planned the code on line 51 redirects users to the homepage and triggers a success message.

That's all it takes to nicely add information to a database using Java Server Pages. If you enjoyed the article, you can stay updated to new content via our RSS feed or by email.

18

AUG 2010 1

Building a live news blogging system in JSP

Tutorial app preview

About 5 months ago I started a series of tutorials which described the process of building a live news blogging system in php. When the series ended, I promised that another series will follow and will describe the process of building the same web app using JSP. This article is the first of its series, and contains the introductory part of what's needed in this tutorial series, as well as some basic JSP code.

First of all, please read the first part of the original series here as it describes and shows the source codes for the user interface of the web application, namely HTML5, CSS and some jQuery.

You should also check out the second part of the original tutorial here, as this part contains the database model and some more jQuery. There's also some bits of PHP, but those aren't mandatory for today's article.

login.jsp page

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<%@ page import="java.sql.*" import="java.util.*" session="true" import="Javax.servlet.http.*" import="java.security.*" %>
 
<%
 
	String database = "lab8";
	String user = "user here";
	String pass = "pass here";
	String table = "Users";
 
	String connectionURL = "jdbc:mysql://localhost:3306/"+database+"?user="+user+";password="+pass;
	Connection connection = null;
	Statement statement = null;
	Statement update = null;
	ResultSet rs = null;
 
	Class.forName("com.mysql.jdbc.Driver").newInstance();
	connection = DriverManager.getConnection(connectionURL,user,pass);
	statement = connection.createStatement();
 
	String username = "";
	String password = "";
	int error = 0;
	if(request.getParameter("username")!=null) username = request.getParameter("username");
	else error = 1;
	if(request.getParameter("password")!=null) password = request.getParameter("password");
	else error = 1;
	if(error==1)
	{
		response.sendRedirect("index.jsp?error=Please input username and password");
	}
	else
	{
			MessageDigest mdAlgorithm = MessageDigest.getInstance("MD5");
			mdAlgorithm.update(password.getBytes());
 
			byte[] digest = mdAlgorithm.digest();
			StringBuffer hexString = new StringBuffer();
 
			for (int i = 0; i < digest.length; i++) 
			{
			    password = Integer.toHexString(0xFF & digest[i]);
 
			    if (password.length() < 2) 
				{
			        password = "0" + password;
			    }
 
			    hexString.append(password);
			}
 
			password = hexString.toString();
			String query = "SELECT * FROM Users WHERE username='"+username+"' AND password='"+password+"'";
			rs = statement.executeQuery(query);
			if(rs.next())
			{
				session.setAttribute("loggedin","1");
				session.setAttribute("username",username);
 
				response.sendRedirect("index.jsp?msg=Logged in succesfully.");
			}
			else 
			{
				response.sendRedirect("index.jsp?error=Invalid login data.");
			}
	}
%>

The first line of the code above contains instructions to include different libraries that will be used throughout the entire jsp file. The following four lines of code are in fact the variables that will be used to store database connection settings. In the next set of lines we create the datatypes that we will use to create an sql connection, perform sql queries and handle the results returned by the queries. On lines 16 to 18 we instantiate the mysql driver and connect to the database.

The instructions between lines 20 and 30 test if the visitor has inputed his username and password information in the form that send him to the login page. If the user's hasn't filled in required information, the request is denied. Lines 33 to 50 are used to encrypt the password the user typed in the login form. Passwords in the database are encrypted to ensure that, even if third parties gain access to the database, the passwords aren't exposed.

The following two lines make an SQL query to check if the user credentials are valid. If they are, lines 56 to 59 are used to create required login sessions and redirect the user to the homepage, while the code on line 63 redirects the user to the homepage after an invalid login.

logout.jsp

The logout process is much more simple, and the following code snippet does the trick. This snippet also contains the imports found in the login.jsp file, one line of code that destroys the session and one that redirects the user to the homepage.

1
2
3
4
5
6
<%@ page import="java.sql.*" import="java.util.*" session="true" import="Javax.servlet.http.*" import="java.security.*" %>
 
<%
	session.removeAttribute("loggedin");
	response.sendRedirect("index.jsp?msg=Successfully logged out");
%>

While the php application also contained a functions.php file, this does not. Having that said, this is the end of the first part of the tutorial. You can stay updated to new content via our RSS feed or by email.

11

AUG 2010 17

Build a POI map using jQuery & Google Maps v3

UPDATE 09.09.10: This tutorial doesn't describe how to show in the same time multiple POIs on the map. If you wish a tutorial on how to display multiple POI on the map in the same time, read the revised version of the tutorial.

This is the incipient work from a bigger project that I worked on which uses Google Maps. I decided to share it with you as it can be used in a variety of ways and places. You can use it to locate around a map your company's offices, stores, museums or any other interest points you need to present to your website's visitors.

The tutorial is fairly simple as it doesn't include advanced programming or CSS rules. Having that said, here's a screenshot of the final product.

Tutorial preview

As you can see the application, if I can call it that way, has a sidebar that contains a list of POIs (Point of Interest), a diagnosis header that displays information about what's going on and a map. Let's code the HTML structure:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div id="home-wrapper">
	<div id="logo">
		<a href="http://www.webraptor.eu" target="_self"><img src="logo.jpg" width="245" height="40" alt="WebRaptor" /></a>
	</div>
	<div id="home-messages">
		Trying to determine your location...Please allow browser to share location if prompted
	</div>
	<span class="clear">&nbsp;</span>
	<div id="home-sidebar">
		<h1>Our offices locations</h1>
		<ul>
			<li><a href="#" rel="40.453577,-3.68763, 
			<strong>Venue: </strong>Estadio Santiago Bernabeu<br />
			<strong>Address: </strong>Avenida de Concha Espina; No 1; 28036; Madrid<br />
			<strong>Phone: </strong>+34 (91) 398 4300<br />
			<strong>Fax: </strong>+34 (91) 344 0695<br />
			<strong>Email: </strong><a href='mailto:realmadrid@club.realmadrid.com'>realmadrid@club.realmadrid.com</a><br />
			" target="_self">Estadio Santiago Bernabeu</a></li>
			<li><a href="#" rel="41.934115,12.45575, 
			<strong>Venue: </strong>Stadio Olimpico<br />
			<strong>Address: </strong><br />
			<strong>Phone: </strong><br />
			<strong>Fax: </strong><br />
			<strong>Email: </strong><a href='mailto:'></a><br />
			" target="_self">Stadio Olimpico - Rome</a></li>
		</ul>
	</div>
 
	<div id="map-wrapper">
		<div id="map">
 
		</div>	
	</div>
</div>

The role of the home-wrapper div is to hold the contents of the page centered in the browser window. The home-messages div is the one that holds the diagnosis messages in place, the home-sidebar div holds the POIs list while map-wrapper and map serve as a map placeholder.

As you can see, the li items inside the unordered list located in the sidebar contain all information that's provided for each point of interest. No external files or database queries are used. The information is contained within a rel attribute of the anchor tag that's inserted in each li item.

The above HTML code is to be placed inside the body of the page, while the following into the header, as it contains links to CSS files and javascript pointers.

1
2
3
4
5
6
7
<link rel="stylesheet" href="css.css" type="text/css" media="screen" title="no title" charset="utf-8" />
<script type="text/javascript" src="http://www.google.com/jsapi?key={APIKEY}"></script>
<script type="text/javascript">
	google.load("jquery", "1.4.2");	  
</script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script src="js/OfficeLocation.js" type="text/javascript" charset="utf-8"></script>

The CSS file isn't complicated at all.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
*		{ margin:0; padding:0; border:0; outline:0; font-weight:inherit; font-size:inherit; }
body		{ font-family:Geneva, “Lucida Sans”, “Lucida Grande”, “Lucida Sans Unicode”, Verdana, sans-serif; font-size:62.5%; }
.clear 	{ display:block; clear:both; }
 
#logo	{ display:block; width:245px; height:40px; line-height:40px; float:left; text-align:center; }
#home-wrapper	{ display:block; width:930px; margin:0px auto; padding:25px 10px; font-size:12px; }
#home-messages	{ display:block; width:645px; height:40px; line-height:40px; float:right; padding:0 10px; margin:0 0 25px 10px; background-color:#FFFFCC; border:1px solid #EEEEBB; }
#home-sidebar		{ display:block; width:243px; height:400px; float:left; padding:0 0 20px 0; margin:0 10px 25px 0; background-color:#FFFFCC; border:1px solid #EEEEBB; }
#map-wrapper		{ display:block; width:645px; height:400px; float:right; padding:10px; background-color:#FFFFCC; border:1px solid #EEEEBB; }
#map			{ display:block; width:645px; height:400px; padding:0px; }	
#infowin-overlay	{ display:block; width:400px; height:80px; }
#infowin-overlay strong	{ display:block; float:left; clear:left; font-weight:bold; width:100px; }
 
h1	{ font-size:15px; padding:10px 0; text-align:center; background:#EEEEBB; color:#339933; }
ul 	{ list-style:none; }
li a	{ display:block; padding:5px 10px; color:#999; }

What's new in the CSS file is the infowin-overlay identifier used, which is nowhere to be found in our HTML file. That's due to the fact that it is the identifier of the overlay window that is displayed once a visitor clicks a POIs marker on the map. The overlay is added to the DOM by the javascript code that follows, which contains all that's required for the tutorial to be complete and working at warp speed.

Map overlay preview

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
var map; // global var to store the google map
var centerCoord = new google.maps.LatLng(46.782101,23.643855); // tulghesului street, Cluj-Napoca
var centerCoord = new google.maps.LatLng(40.453577,-3.68763); // Estadio Santiago Bernabeu, Madrid
var browserDetectedLocation = null;
 
// global variables used throughout the js functionality
var markersArray = [];
var infoWindow = new google.maps.InfoWindow({});
 
 
function setLocation()
{
	// try to get user location via W3C standard Geolocation in browsers or via Google Gears
	if(navigator.geolocation) 
	{
	    navigator.geolocation.getCurrentPosition(function(position) 
		{
			blueIcon = "http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png";
			browserDetectedLocation = new google.maps.LatLng(position.coords.latitude,position.coords.longitude);
			map.setCenter(browserDetectedLocation);
			var marker = new google.maps.Marker({
			      position: browserDetectedLocation, 
			      map: map, 
			      title: 'You are here',
			 	  icon: blueIcon
			});
			$('#home-messages').text("Location detected. Please wait...");
	    }, function() {
	     // error getting location, though supported
			$('#home-messages').text("Your location cannot be detected.");
	    });
	} else if (google.gears) 
	// if location not found using W3C standard try with Google Gears if browser supports it
	{
	    var geo = google.gears.factory.create('beta.geolocation');
	    geo.getCurrentPosition(function(position) 
		{
			blueIcon = "http://www.google.com/intl/en_us/mapfiles/ms/micons/blue-dot.png";
			browserDetectedLocation = new google.maps.LatLng(position.latitude,position.longitude);
			map.setCenter(browserDetectedLocation);
			var marker = new google.maps.Marker({
			      position: browserDetectedLocation, 
			      map: map, 
			      title: 'You are here',
				  icon: blueIcon
			});
 
			$('#home-messages').text("Location detected. Please wait...");
	    }, function() {
			// error getting location, though supported
			$('#home-messages').text("Your location cannot be found.");
	    });
	} 
	else	
	{
		// Browser doesn't support Geolocation
		$('#home-messages').text("Your location cannot be found.");
	}	
}
 
function InitMap(options, mapIdentifier, defaultLocation, detectLocation)
{ // function to initialize map
	var settings = // json variable for default settings
	{
		zoom: 15,
		center: defaultLocation,
		mapTypeId: google.maps.MapTypeId.SATELLITE
	};
	if (options!=null) settings = options; // if no options provided, start the map with default settings
	map = new google.maps.Map(document.getElementById(mapIdentifier), settings);
 
	map.setCenter(defaultLocation);
	if(detectLocation==true) setLocation(); // try to get user location via W3C standard Geolocation in browsers or via Google Gears
	else
	{
		$('#home-messages').text("Click an office on the left to view its location.");
	}
 
	return map;
}
 
function handle_clicks()
{
	$('#home-sidebar ul li a').live('click',function(){
		clearOverlays();
		var coordString = $(this).attr('rel');
		var coordTitle = $(this).text();
		var coordArray = coordString.split(',');
		var update2Location = new google.maps.LatLng(coordArray[0],coordArray[1]);
		map.setCenter(update2Location);
		addMarker(update2Location,coordTitle,coordArray[2]);
 
  		$('#home-messages').text("Viewing: "+coordTitle);
	});
}
 
function addMarker(m_position,m_title,m_infowindow) {
	marker = new google.maps.Marker({
	  	position: m_position,
	  	map: map,
		title: m_title
	});
	markersArray.push(marker);
  	var mark = markersArray.pop();
	google.maps.event.addListener(mark, 'click', function() {
		infoWindow.open(map,mark);
		var stringContent = m_infowindow;
		infoWindow.setContent("<div id=\"infowin-overlay\""+stringContent+"</div>");
 
		overlayHeight = $('#infowin-overlay').height();
		overlayWidth = $('#infowin-overlay').width();
		$('#infowin-overlay').parent().css('height',overlayHeight);
		$('#infowin-overlay').parent().css('width',overlayWidth);
	});
 
	markersArray.push(mark);
}
 
function clearOverlays() {
  if (markersArray) {
    for (i in markersArray) {
      markersArray[i].setMap(null);
    }
  }
}
/* end of functions */
 
$(document).ready(function(){
 
	if($('#map').get(0))
	{ // only initialize the map if map is located inside the page
		map = InitMap(null,'map', centerCoord, true); // initialize the map on default location
		handle_clicks(); // click events handling by jQuery	
	}
 
});

I've added comments to the most important lines of jQuery above. The setLocation() function tries to detect user location and center the map around his location. The InitMap() function is used to get the entire thing rolling, and takes a couple of arguments that allows you to set the default location displayed when the map loads, as well as to enable or disable user location tracking.

The handle_clicks() function is used to track clicks on the items contained in the sidebar list and move the map around on top of the clicked point of interest. addMarker() is used to add a marker to the map, marker which represents the location of a POI and which users can click to have a detailed overlay displayed. Finally, clear_overlays() clears existing overlays to avoid duplicated content.

Demo

I've prepared a demo of the tutorial over here.

If you enjoyed this article you can stay updated to new content via our RSS feed or by email.

13

JUL 2010 7

Building a live news blogging system in php. Spiced with HTML5, CSS3 and jQuery [sources]

Tutorial app preview

About a month ago the huge tutorial on building a live news blogging system was completed. Today I am releasing the source codes of the tutorial so that you can finally play with the web application however you like.

Demo

You can check out the demo part of the tutorial before you download the source code. Furthermore, a demo of the application is available over here.

Source code

The full source code of the tutorial is available for download over here.

If you liked this tutorial series, stay updated to new content via our RSS feed or by email, because a new series will start soon. This new series will be around coding the same webapp using JSP on Tomcat.

8

JUN 2010 8

Building a live news blogging system in php. Spiced with HTML5, CSS3 and jQuery [end]

Tutorial app preview

Finally, the last part of the tutorial you have all been waiting for. Until now I've covered everything that deals with databases and the CRUD functionalities the system has. However, I haven't tackled the primary view of the app, the homepage, which automatically updates news as they are added in the database without requiring anything from the end user. To bad I didn't finish the tutorial earlier; maybe some great online newspapers would have used it during yesterday's keynote address at WWDC.

HTML5 modifications, integration with php

In case you read all the previous parts of the tutorial, you know I used HTML5 to structure the layout of the app. While implementing some of the functionalities, small upgrades were required. I am going to start with the head of the document. There are two stylesheets added. One is for general styles, while the second one is used to style the datepicker used in the app and is the style that comes with jQuery's datepicker plugin. Having that said, you can see there's also the .js script for it and for jQuery as well. date.js is Jörn Zaefferer's and Brandon Aaron's date prototype extensions I've used in the app. The sprinkle.js file is where all the gizmoz performed by the app are stored.

1
2
3
4
5
6
7
8
9
10
<head>
	<title>Live News System</title>
	<link rel="stylesheet" href="css/style.css" type="text/css" media="screen" title="no title" charset="utf-8">
	<link rel="stylesheet" href="css/datepicker.css" type="text/css" media="screen" title="no title" charset="utf-8">
 
	<script src="js/jquery.js" type="text/javascript" charset="utf-8"></script>
	<script src="js/date.js" type="text/javascript" charset="utf-8"></script>
	<script src="js/datepicker.js" type="text/javascript" charset="utf-8"></script>
	<script src="js/sprinkle.js" type="text/javascript" charset="utf-8"></script>	
</head>

Moving on to the body of the site the first items contained in the body are hidden to public view. It's a small php script that includes the functions file and calls for the initiateLastID() function. This function is newly added, and its code is provided just below.

1
2
3
4
5
6
7
8
9
<body>
 
	<? include("includes/functions.php");
	   initiateLastID(); 
	?>
 
	<header class="top-rounded">
		Live News System <!-- The title of our app -->         
	</header>
1
2
3
4
5
6
7
8
9
10
11
12
function initiateLastID()
{
	if(!session_is_registered('lastID'))
	{ 
		// no previous call
		session_register('lastID');
		$query = "SELECT id FROM News WHERE id>0 ORDER BY id DESC LIMIT 1";
		$data = mysql_query($query) or die(mysql_error());
		$_SESSION['lastID'] = $data['id'];
		$lastID = $data['id'];
	}
}

What this function does is it creates a php session that contains the ID of the last news item added to the database. This session will be used to track new news added in the database while the user is on the homepage. This is a key variable in achieving a functionality similar to Twitter's new X items in search page.

Next, just after the details tag in the header of the page, the one which stores and displays information regarding selected filters, we add some more php code. The code is shown below and its purpose is to catch error and notification messages directly from the URL and display them nicely inside the webapp's UI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?
$msg = $error = "";
$msg=$_REQUEST['msg'];
$error=$_REQUEST['error'];
if($msg!="")
{
	?>
	<article class="no-hover message display">
		<h1><?=$msg?></h1>
	</article>
	<?
}
if($error!="")
{
	?>
	<article class="no-hover error display">                      
		<h1><?=$error?></h1>
	</article>
	<?
}
list_news();
?>

list_news() is a bit complex, and will be presented in just a few minutes.

In the right sidebar we have the start date and end date input fields. We need to store the information within these fields as users could accidentally click the refresh button of the web browser and loose data in those input fields. We protect the information in the fields by storing it using php sessions. The modified code for the two inputs is the following one:

1
2
3
4
5
6
<fieldset>
	<label for="start_date">Start date: </label>
	<input type="text" name="start_date" value="<?=$_SESSION['start']?>" id="start_date" class="date-pick">
	<label for="end_date">End date: </label>
	<input type="text" name="end_date" value="<?=$_SESSION['end']?>" id="end_date" class="date-pick">
</fieldset>

Just below the two date inputs the categories of the webapp are listed. This is achieved by using a php function: list_categories() which was presented in the third part of the tutorial. However, during that part the function returned option values for select tags. The updated function is shown below.

1
2
3
4
<ul><!-- This UL will hold our categories -->
	<li class="title">Browse by category</li>                     
	<? list_categories("") ?>
</ul>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function list_categories($mode)
{
	if(session_is_registered('categories')) $categories = $_SESSION['categories'];
 
	$rows = mysql_query("SELECT name FROM Categories WHERE id>0 ORDER BY name ASC") or die(mysql_error());
	if($mode=="")
	{
		if(mysql_num_rows($rows))
		{
			while($row = mysql_fetch_row($rows))
			{
				if(session_is_registered('categories')) 
					{
						if(sizeof($categories)>0)
						{
							if(array_key_exists($row[0],$categories)) echo "<li><a class=\"selected-aside\">".$row[0]."</a></li>";
								else echo "<li><a>".$row[0]."</a></li>";
						}
						else echo "<li><a>".$row[0]."</a></li>";
					}
				else echo "<li><a>".$row[0]."</a></li>";
			}
		}
		else echo "<li>no categories</li>";
	}
	else if($mode=="select-options")
	{
	// code in part three of the tutorial
	}
}

What the function does is find out which categories are already selected by the user, then display them as list items. If some of them are selected, the selected-aside class is appended to the li tag. Moreover, the $mode param used in this function determines if the function should return li tags or option tags.

The footer has also been updated and now tracks user login and displays information accordingly.

1
2
3
<footer>
	<? if(!session_is_registered('loggedin')) { ?><a rel="login" name="modal">Admin login</a> <? } else { ?>You are logged in. You can <A  href="#addnews" rel="addnews" name="modal">add news</a> or <a href="logout.php" target="_self">logout</a><? } ?>
</footer>

With this we've finished the HTML structure and content of the index page. Before moving on to the jQuery code which is used to add all the awesome functionalities let me remind you what the structure of an article is (referring to HTML article tag). Just below are presented two different structures, one used for showcasing news to end users while the second is used to display the news to admins that are logged in.

1
2
3
4
5
<article>	
	<h1>news title</h1>
	<details>Posted by <span>user</span> in <span class="cat">category</span> on <span>date</span> at <span>time</span>.</details>
	<p>content of news</p>
</article>
1
2
3
4
5
6
<article>
	<span class="controls"><a href="deletenews.php?id=ID" target="_self" class="delete"></a><a href="" name="modal" rel="editform" title="ID" target="_self" class="edit"></a></span>	
	<h1>news title</h1>
	<details>Posted by <span>user</span> in <span class="cat">category</span> on <span>date</span> at <span>time</span>.</details>
	<p>content of news</p>
</article>

As you can see the code used for admins contains an extra span with links to the edit and delete buttons for the news item.

list_news()

Based on the average complexity of the functions presented up to this point, this function that spans across 100 lines of code is huge. Check out the code, try to understand it. Explanations will follow just after it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
function list_news()
{
	$categories_query = "";
	if(session_is_registered('categories'))
		{ // we have some registered categories, so our query will not select all news
			if(sizeof($_SESSION['categories'])>0)
			{
				$categorieskeys = array_keys($_SESSION['categories']);
				$count = 0;
				if(sizeof($categorieskeys)>0)
				{
					$categories_query = "AND (";
					while($categorieskeys[$count]!=null)
					{
						if($count>0) $categories_query = $categories_query." OR category = '".$categorieskeys[$count]."' ";
						else $categories_query = $categories_query." category = '".$categorieskeys[$count]."' ";
						$count++;
					}
					$categories_query = $categories_query . " ) ";				
				}
			}
		}
 
	$start = $_SESSION['start'];
	$end = $_SESSION['end'];	
 
	if($categories_query!='')
	{
		if((session_is_registered('start'))&&(session_is_registered('end'))&&(strlen($start)!=0)&&(strlen($end)!=0))
		{
			$query = "SELECT * FROM News WHERE id>0 ".$categories_query." AND  publishing_date >= '$start' AND publishing_date <='$end' ORDER BY publishing_date DESC, publishing_time DESC";
		}
		elseif (session_is_registered('start')&&(strlen($start)!=0))
		{
			$query = "SELECT * FROM News WHERE id>0 ".$categories_query." AND publishing_date >= '$start' ORDER BY publishing_date DESC, publishing_time DESC";
		}
		elseif (session_is_registered('end')&&(strlen($end)!=0))
		{
			$query = "SELECT * FROM News WHERE id>0 ".$categories_query." AND publishing_date <= '$end' ORDER BY publishing_date DESC, publishing_time DESC";
		}
		else $query = "SELECT * FROM News WHERE id>0 ".$categories_query." ORDER BY publishing_date DESC, publishing_time DESC";
	} 
	else 
	{
		if((session_is_registered('start'))&&(session_is_registered('end'))&&(strlen($start)!=0)&&(strlen($end)!=0))
		{
			$query = "SELECT * FROM News WHERE id>0 AND  publishing_date >= '$start' AND publishing_date <= '$end' ORDER BY publishing_date DESC, publishing_time DESC";
		}
		elseif (session_is_registered('start')&&(strlen($start)!=0))
		{
			$query = "SELECT * FROM News WHERE id>0 AND publishing_date >= '$start' ORDER BY publishing_date DESC, publishing_time DESC";
		}
		elseif (session_is_registered('end')&&(strlen($end)!=0))
		{
			$query = "SELECT * FROM News WHERE id>0 AND publishing_date <= '$end' ORDER BY publishing_date DESC, publishing_time DESC";
		}
		else $query = $query = "SELECT * FROM News WHERE id>0 ORDER BY publishing_date DESC, publishing_time DESC";
	}
 
	$newlastID = 0;
 
	$rows = mysql_query($query) or die(mysql_error());
	// row contains ID title body owner publishing_date publishing_time category
	if(mysql_num_rows($rows))
	{
		while($row = mysql_fetch_row($rows))
		{
			if($newlastID==0) 
			{
				$newlastID = $row[0];
				if(session_is_registered('lastID'))
				{ 
					$_SESSION['lastID'] = $newlastID;
					$lastID = $newlastID;
				}
				else
				{ 
					// no previous call
					session_register('lastID');
					$_SESSION['lastID'] = $lastID;
				}				
			}
			if(session_is_registered('loggedin')) $admincontrols = "<span class=\"controls\"><a href=\"deletenews.php?id=".$row[0]."\" target=\"_self\" class=\"delete\"></a><a href=\"\" name=\"modal\" rel=\"editform\" title=\"".$row[0]."\" target=\"_self\" class=\"edit\"></a></span>";
			echo "
				<article>
					".$admincontrols."
					<h1>".$row[1]."</h1>
					<details>Posted by <span>".$row[3]."</span> in <span class=\"cat\">".$row[6]."</span> on <span>".$row[4]."</span> at <span>".$row[5]."</span>.</details>
					<p>".$row[2]."</p>
				</article>
			";
		}
	}
	else echo "<article class=\"no-hover\"><h1>No news into the system</h1></article>";
}

The first section of the function starting on the third line and going up to line 22 checks if there are any categories selected. If at least one is selected the code builds a SQL statement which will be used in a SELECT / WHERE clause.

The code on lines 24 and 25 is used to check whether the start and end dates used for news filtering are enabled. If they are enabled they must to be used in the SELECT statement against the database.

First batch of SELECT statements, starting on line 27 up to 42. This batch is used if the users have selected any categories to filter the results. There are multiple statements because each one of them is used based on start and end date filters contents. The second batch, line 45 to 57, does the same thing as the above batch, but doesn't limit the results based on categories because in this case none of them is selected.

The code between lines 60 to 94 lists the results of the previous SQL queries. $newlastID variable is used to update the $lastID session described above. This is necessary because the ID of the last news added to the system changes as soon as applied filters change. The last news in one category doesn't have the same ID as the last news inside another category, or throughout the entire system. These checks and session updates are done between lines 68 and 82 and are done once per each query.

Finally, the code from line 83 to 90 is used to create the structure and content of each news item and display it.

Further more, there's another similar function as complex as this one. Its name is list_new_news() and the difference between this one and the one presented above is that it uses the lastID session in its SELECT statements, returning items which have an ID bigger than what lastID stores. This function is used to add to the DOM news items that were added to the system after the user has loaded the page.

Basically, this function is used to achieve the functionality of the Twitter new items feature available in Twitter's search page. However, simply coding it won't do any good. This function has to be combined with some ajax requests, which are coded in the sprinkle.js jQuery file.

sprinkle.js

Thank God I've shown parts of this file in the previous five parts of the tutorial. Otherwise, the task for today would have been explaining 400 or more lines of code. Fortunately, some of them are already known so I am going to stick to showing the newly added lines only.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function updateNews(){
	$.get('includes/check_new_items.php',function(newdata){
		if(newdata.length==3) 
		{
		$('#newdata').insertAfter('section > details');	
		// find out each new article, compute height, and set new heights for section and aside
		var sectionHeight = 75; // padding, margin, included elements
		$('section article').each(function(){
			sectionHeight = sectionHeight + $(this).height() + 10 + 25; // 10 comes from padding t b, 25 from margin top
		});
		asideHeight = 0;
		asideHeight = asideHeight + $('aside div').height();
		$('aside li').each(function(){
			asideHeight = asideHeight + $(this).height();
			if($(this).attr('class')=='title') asideHeight = asideHeight + 15;
		});
		if(asideHeight < sectionHeight) 
		{
		//	alert (sectionHeight);
			$('aside, section').animate({ height: sectionHeight },{queue:false}); // update height	
		}
		else
		{
		//	alert (asideHeight);
			$('aside, section').animate({ height: asideHeight },{queue:false}); // update height	
		}
		// done with column heights
		$('section article').fadeIn('slow');
		}
	});
}

The above function checks for new news added into the database. If new items are available, a message stating so is appended to the DOM. If the message box is clicked new news items are appended to the DOM and then faded in so that users can see them. This functions's corresponding php file is similar to the list_news() function. What is changed is the last part of the function which displays the news items, part that has been replaced with the code shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$newLastID = 0;
$nothere = 0;
$rows = mysql_query($query) or die(mysql_error());
// row contains ID title body owner publishing_date publishing_time category
if(mysql_num_rows($rows))
{
	while($row = mysql_fetch_row($rows))
	{
		if($newLastID==0)
		{
			$newLastID = $row[0];
		}
		if(($oldLastID<$newLastID)&&($nothere==0))
		{
			echo "new";
			$nothere = 1;
		}
	}
}

The following code is used to handle clicks performed on the categories listed in the sidebar. What the code does is it checks whether the clicked category is selected. If it is, the category is removed from the php categories session and its visual style changed. If the category is not selected it is appended to the php categories session and its visual appearance changed. The php file that's used to update the session is also shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// change style for aside category selector and filters above content + ajax requests for updating selected categories
$("aside ul li a").click(function (){
	var classAttr = $(this).attr('class');
	if (classAttr != 'selected-aside')
	{
		// update style class for category just clicked
		$(this).attr('class','selected-aside');
		var category = $(this).text();
		$(this).append('<span>X</span>');
		 // check filter legend status and update it if required
		var classAttr = $('.categories').attr('class');
		if(classAttr == 'categories not-selected') $('.categories').attr('class','categories selected');
		// now we need to make an ajax call to add our category to our selected categories session
		var jdata = "function=add&category="+category; 
		$.ajax({
			type: "POST",
			url: "includes/categories_sessions.php",
 				data: jdata 
		});
	}
	else
	{
		// update style class for category just clicked
		$(this).attr('class','');
		var span = $(this).children();
		$(span).remove();
		var category = $(this).text();
		// now we need to make an ajax call to remove our category to our selected categories session
		var jdata = "function=remove&category="+category; 
		$.ajax({
			type: "POST",
			url: "includes/categories_sessions.php",
 				data: jdata 
		});
		// check if there is any category selected
		// if not, update filter legend class
		var anyCatSelected = $('.selected-aside').size();
		if(anyCatSelected==0) 
		{	
			$('.categories').attr('class','categories not-selected');
		}
	}// from else
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?
	session_start(); 
	$err = 0;
	if(isset($_REQUEST['function'])) $function = $_REQUEST['function'];
	else $err = 1;
	if(isset($_REQUEST['category'])) $category = $_REQUEST['category'];
	else $err = 1;
 
	if(!$err)
	{ 
		if($function == 'add')
		{ 
			if(session_is_registered('categories'))
			{ 
				$categories = $_SESSION['categories'];
				$categories[$category] = 1;
				$_SESSION['categories'] = $categories;
			}
			else
			{ 
				// no previous call
				session_register('categories');
				$categories = array();
				$categories[$category] = 1;
				$_SESSION['categories'] = $categories;
			}
 
		}
		elseif($function  == 'remove')
		{
			$categories = $_SESSION['categories'];
			unset($categories[$category]);
			$_SESSION['categories'] = $categories;
		}
	}
 
?>

However, we're not done yet with the functionality. If someone changes the categories which are selected the news must be updated and the following code does that:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ajax requests to reload content and resize section + aside
$('section article').fadeOut('slow', function(){ 
		// completion of fading current content out
		//$('section').animate({ height: sectionHeight });  // reset height of section to original value
		if($('section article').length>0) $('section article').remove();
		var oldcontent = $('section').html();
		$.get('includes/load_news.php', function(newdata){
			$('section').html(oldcontent+newdata);
			// find out each new article, compute height, and set new heights for section and aside
			var sectionHeight = 50; // padding, margin, included elements
			$('section article').each(function(){
				sectionHeight = sectionHeight + $(this).height() + 10 + 25; // 10 comes from padding t b, 25 from margin top
			});
			asideHeight = 0;
			asideHeight = asideHeight + $('aside div').height();
			$('aside li').each(function(){
				asideHeight = asideHeight + $(this).height();
				if($(this).attr('class')=='title') asideHeight = asideHeight + 15;
			});
			if(asideHeight < sectionHeight) 
			{
			//	alert (sectionHeight);
				$('aside, section').animate({ height: sectionHeight },{queue:false}); // update height	
			}
			else
			{
			//	alert (asideHeight);
				$('aside, section').animate({ height: asideHeight },{queue:false}); // update height	
			}
			// done with column heights
			$('section article').fadeIn('slow');
			overlay();
		});
	});
});

Based on the request processed by the load_news.php file we'll probably have to change the content that's visible on the homepage. Therefore, current content is faded out, new content inserted and main content area and sidebar heights updated. Only after these processes are completed the new content is faded in and displayed in the page. The load_news.php file simply calls the load_news() function.

Same content reloading has to take place if users change the date range they want via the two datepickers located in the top of the right sidebar. The process is the same as the one described above in the case of categories being changed with a simple addition to the code. The new addition is the request processed by the datepicker.php file which does some session updating and nothing more. The differences are shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
$('#start_date').change(function(){
	// the start date of date picker has been changed
	// ajax call to handle it
	var startdate = $(this).val();
	if(startdate!=null)
	{
		var classAttr = $('.daterange').attr('class');
		if(classAttr == 'daterange not-selected') $('.daterange').attr('class','daterange selected');	
 
			var jdata = "function=start&date="+startdate; 
			$.ajax({
				type: "POST",
				url: "includes/datepicker.php",
				data: jdata 
			});
 
			// load_news.php request as presented above in the tutorial
	}
 
});
 
$('#end_date').change(function(){
	// the start date of date picker has been changed
	// ajax call to handle it
	var enddate = $(this).val();
	if(enddate!=null)
	{
		var classAttr = $('.daterange').attr('class');
		if(classAttr == 'daterange not-selected') $('.daterange').attr('class','daterange selected');	
 
			var jdata = "function=end&date="+enddate; 
			$.ajax({
				type: "POST",
				url: "includes/datepicker.php",
				data: jdata 
			});
 
			// load_news.php request as presented above in the tutorial
	}	
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?
	session_start(); 
	$err = 0;
	if(isset($_REQUEST['function'])) $function = $_REQUEST['function'];
	else $err = 1;
	if(isset($_REQUEST['date'])) $date = $_REQUEST['date'];
	else $err = 1;
 
	if(!$err)
	{ 
		if($function == 'start')
		{ 
			if(session_is_registered('start'))
			{ 
				$start = $_SESSION['start'];
				$start = $date;
				$_SESSION['start'] = $start;
			}
			else
			{ 
				// no previous call
				session_register('start');
				$start = $_SESSION['start'];
				$start = $date;
				$_SESSION['start'] = $start;
			}
		}
		elseif($function  == 'end')
		{
			if(session_is_registered('end'))
			{ 
				$_SESSION['end'] = $date;
			}
			else
			{ 
				// no previous call
				session_register('end');
				$_SESSION['end'] = $date;
			}
		}
		elseif($function == 'clear')
		{
			$start = $_SESSION['start'];
			$start = "";
			$_SESSION['start'] = $start;
 
			$end = $_SESSION['end'];
			$end = "";
			$_SESSION['end'] = $end;
		}
	}	
?>

How do we actually handle new news items. During the first part of the series a hidden div was added to the page structure, which was supposed to be used on a later time. That time has come; let me remind you that small piece of HTML:

1
2
3
<article class="no-hover hidden" id="newdata">
	<h1>New articles in the system. Click here to view them</h1>  
</article>

The above div is hidden but it fades in on top of all the articles if ajax requests show that new items are available in the database and they correspond with user's filters. Checking is the task of the updateNews() function which uses the check_new_items.php file. If new items are available the #newdata div is added on top of the articles displayed in the page, just as described above when the updateNews() function was shown. Here's the code that handles the clicks performed on this div and that fades out current content and displays new news items:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$('#newdata').click(function(){
 
	$('#newdata').insertAfter('footer');	
	$('#newdata').removeAttr('style');
 
	// ajax requests to reload content and resize section + aside
		$('section article').fadeOut('slow', function(){ 
				// completion of fading current content out
				//$('section').animate({ height: sectionHeight });  // reset height of section to original value
				if($('section article').length>0) $('section article').remove();
				var oldcontent = $('section').html();
 
				$.get('includes/load_new_news.php', function(newdata){
					$('section').html(oldcontent+newdata);
 
					// find out each new article, compute height, and set new heights for section and aside
					var sectionHeight = 75; // padding, margin, included elements
					$('section article').each(function(){
						sectionHeight = sectionHeight + $(this).height() + 10 + 25; // 10 comes from padding t b, 25 from margin top
					});
					asideHeight = 0;
					asideHeight = asideHeight + $('aside div').height();
					$('aside li').each(function(){
						asideHeight = asideHeight + $(this).height();
						if($(this).attr('class')=='title') asideHeight = asideHeight + 15;
					});
 
					if(asideHeight < sectionHeight) 
					{
					//	alert (sectionHeight);
						$('aside, section').animate({ height: sectionHeight },{queue:false}); // update height	
					}
					else
					{
					//	alert (asideHeight);
						$('aside, section').animate({ height: asideHeight },{queue:false}); // update height	
					}
					// done with column heights
					$('section article').fadeIn('slow');
					overlay();
				});
			});
});

You'd think that this is all the code we need. Unfortunately, it isn't. Just placing the updateNews() function won't make wonders. We need to call this function on a set interval, 5 or 10 seconds for example. This way the users do not have to reload the page to check for new news as the app periodically checks that for them and displays a notification message if new items are added. The users only have to sit back and enjoy the feed. The code that creates the 10 second loop call of the function is shown below:

1
setInterval( "updateNews()", 10000 );

Conclusion

The webapp could be used to live blog different important events, such as WWDC, Oscars and so on. Moreover, due to the categories filters one could live blog different events simultaneously on the same app.

Source code and demo

As you know from the demo part of the tutorial a demo of the application is available over here. I am working on packing the source codes and they should be available to download, use and extend soon!

If you liked this tutorial series, stay updated to new content via our RSS feed or by email, because a new series will start soon. This new series will be around coding the same webapp using JSP on Tomcat.

1

JUN 2010 0

Keep it simple

Whether you're developing a webapp, a website, some native software or a simple installer, keep it simple and straightforward. We barely have time for anything these days, and effective solutions that help us doing what we need to do fast, without error will win over pretty much anything else.

This concept applies to web forms, navigation, software options, and more. The faster the users get things done, the more satisfied they will feel, they'll use your solution even more and refer others to it. This post will be best suited in a thoughts / tumblr blog, but I figured it may get in here as well.

Inspiration

This post was inspired from the recent Windows encounters I've had. Remember the post on web forms usability published just a while ago? Well, while installing the Microsoft Office Suite downloaded after using that faulty web form, I got a new installation wizard out of the blue, which I didn't ask for.

Microsoft installer asking to select unique component to be installed

For some reason, I was asked to install Business contact Manager for Outlook Express 2007. I have no idea what that is, but I assume what it's used for. Bottom line is I don't need it. On top of that is the really bad installer.

Let's assume someone wants to actually install it. The installer starts and shows one component that can be installed. The one I want. Why would you not simply let me get through with the process and require me to select the component to be installed. After all, moments ago I double clicked to install it? Why confirm that again?

Microsoft installer with enabled next button only after unique component is selected

What's even worse is that I cannot click the Next button until I select the unique component. Moreover, there isn't a "finish" installation button. It's a "Next" step button. I don't want to waste any more steps with this issue. Just let me get on with it.

To conclude, whatever you're doing, try to keep it simple and straightforward for your users. Don't frustrate them with never ending jibberish chat.

25

MAY 2010 1

Key usability in web forms

Microsoft's form displaying errors for required / not required fields. Who knows?

Two of the most popular articles published here deal with web forms. The first one is around the theoretical aspects of web forms while the second one resides along the practical issues, coding and validating web forms. This article blends some concepts from both articles and showcases a key error that mustn't be in your web forms.

The issues with required fields

The issue with required fields is that there shouldn't be any required fields on a web form. Don't place asterisks to label required fields. Nobody follows that pattern, though most users expect a field to be required if it has one asterisk appended to its label. If you have a web form that requires user's email, password, first and last names, don't put asterisks or any other messages. Just put the inputs in the form and the user will fill them.

Moreover, if you have those fields, don't place an asterisk only next to email and password and expect users to input their names as well. That will never happen! Following this pattern, after the user hits the submit button, don't display an error message telling them that the name fields are required as well, if you didn't point that out in the first place. This is usually something that will make visitors leave your website and never use your form, which was the primary goal for them upon arriving at that page.

That is total failure! Your mistake, not your user's!

Time to guess who has web forms with such flaws. It's Microsoft! The same company that has .exe executable download managers which users need to run to download any of their software. Mac users need to run them as well to download their operating system, for example, if they wish to switch to Windows, or use it virtually. But that's another story.

The moral

From time to time, I unfortunately need to use Microsoft Office and other Windows native software. Not my option though. While trying to download a trial version of the Office suite, I was asked to sign up on Microsoft's website. The form asked for my email, my desired password, first and last names, and my country. The first two we're marked as mandatory fields, while the rest weren't. The country field was a preselected select field, which I had nothing else to do but update.

After pressing the submit button, I wasn't taken to a download page. Instead, I was shown a couple of errors I've done. I haven't inputted any information in my name fields. But I didn't know I needed to. Thank god for Microsoft that the bid I was working on required specific documents (.docs) and I really needed to proceed, otherwise that would have been the end of my journey on their website.

Conclusion

Try to strip down the fields of your web forms. Not the required ones, but all fields. Don't ask for unnecessary information. Ask only mandatory information, and don't label it as mandatory. If your form is simple and short, users would be delighted to input what you're asking for.

18

MAY 2010 0

Best of the best!

We've hit a really busy, rough couple of weeks at our company. Therefor, the final post of our blogging system tutorial will be published only next week as opposed to this one. However, this week we're going to remind you of our best articles which generated loads of traffic.

Make your own jQuery slider using XML

Slider screenshot

An awesome tutorial on how to build a custom jQuery slider that stores data in external XML files, which had a great follow up plugin: Awesome Slider

Building a live news blogging system in php. Spiced with HTML5, CSS3 and jQuery

Live news system preview

This is a great tutorial series that tackles many technologies and programming methodologies such as HTML5, CSS, PHP, MYSQL, jQuery, AJAX and more!

Impressive web forms. From coding to validation!

Screenshot of WebRaptor's request quote form

Ever wondered how to design, code, and validate beautiful web forms? This article is a must read!

Forms. Can't live with them. Can't live without them.

Edward Pistachio contact form screenshot

If the article just above tackled the practical part of web forms, this one tackles theoretical stuff and you should really read it because it contains a lot of tips, tricks, pros and cons on different web form styling patterns and more. Such a great article even Caroline Jarrett, author of the great book "Forms that work: Designing web forms for usability", got her own tips in the comments section.

And there's more

The above articles are not everything we've got. Just check out our other articles, or stay updated to new content via our RSS feed or by email.

Page 2 of 1112345...10...Last »