Using JavaScript To Perform A Task Traditionally Solved With Server-Side Scripting

Asked recently on Yahoo! Answers:

Does anyone know how to automatically update a birthday list on a web page?
Our web page has a section that displays all staff birthdays for the week. This section is updated manually every Monday morning. Is there a way to have this update done automatically everyday. Must display all birthdays for that day and all birthdays for the next 6 days. The format required is:
Date (eg: 13 May)
Name Surname, Department (a new line entry for each employee with a birthday on that day)
Above repeated for each of the other 6 days.

This question presents me with an excellent opportunity to discuss a topic I’ve not really had a chance to broach before: namely, the decision process on whether to use JavaScript or a server-side scripting language, such as PHP or ASP.NET, to accomplish a given task.

Using JavaScript to accomplish a traditional server-side scripted, data-driven task is becoming increasingly popular, especially as ECMAScript standards are more widely adopted, libraries such as jQuery gain prevalence and supporting technologies, such as Google Gears, remove many of the traditional roadblocks in writing client-side scripts for stateless Web applications.

In this case, we have a problem that can be solved either client-side (JavaScript) or server-side (in this case, PHP / MySQL). Which to choose is largely a function of scale: If we’re dealing with hundreds or thousands of employee birthdays we want to display, we’re probably best off using a server-side technology; but if we have fewer employees, it might make sense to simply use JavaScript, and push the workload from the server and onto our visitors.

I’ll demonstrate both the traditional PHP / MySQL approach, as well as a JavaScript approach.

The Old-School Approach: PHP And MySQL

Let’s start with the traditional way of selecting and sorting a subset of data from a master data store: PHP and MySQL.

In this case, we simply create a table in MySQL that holds a listing of employee names, birthdays and departments (depending on the size and complexity of the organization, we’d likely use multiple tables to store this data, with foreign keys relating each; for the purposes of this demo, we’ll use one table). Here’s the SQL to create such a table, with 11 demo records:

CREATE TABLE IF NOT EXISTS `php_birthday_list` (
  `person_id` int(10) unsigned NOT NULL auto_increment,
  `person_name` varchar(50) NOT NULL,
  `person_surname` varchar(50) NOT NULL,
  `person_birthdate` date NOT NULL,
  `person_department` varchar(50) NOT NULL,
  PRIMARY KEY  (`person_id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ;

INSERT INTO `php_birthday_list` (`person_id`, `person_name`, `person_surname`, `person_birthdate`, `person_department`) VALUES
(1, 'Tom', 'Jones', '1984-05-10', 'Maintenance'),
(2, 'Richard', 'Gingras', '1946-05-11', 'Administration'),
(3, 'Joan', 'Walsh', '1973-05-11', 'Administration'),
(4, 'Norman', 'Blashka', '1953-05-12', 'Maintenance'),
(5, 'Kerry', 'Lauerman', '1967-05-13', 'Administration'),
(6, 'Jeanne', 'Carstensen', '1990-05-15', 'Accounts Payable'),
(7, 'Ruth', 'Henrich', '1981-05-16', 'Maintenance'),
(8, 'Joy', 'Press', '1969-05-14', 'Accounts Payable'),
(9, 'Page', 'Rockwell', '1951-05-10', 'Warehouse'),
(10, 'Laura', 'Miller', '1982-03-21', 'Accounts Payable'),
(11, 'Wendy', 'Williams', '1992-05-17', 'Accounts Payable');

With data now on hand, we can create a PHP script to query the database for all birthdays this week. We do that in several steps:

First, we determine the timestamp for the Sunday of the current week. The easiest way to do that is via the getdate() function, which will return the day of the week represented by the current date, indexed from 0 (Sunday) to 6 (Saturday). Since we’re interested in Sunday, we take the current timestamp, then subtract from it the number of seconds in a day (86,400) times the day of the week returned by getdate().

So, for example, suppose $today[‘wday’] is 2 (i.e., today is Tuesday). If I multiply 86,400 by 2 (172,800), and subtract that from the current timestamp, I’ll have a timestamp that falls on the previous Sunday.

We next need to find when Saturday falls on the same week. The math this time is to subtract the current weekday value from 6 (which is Saturday), multiply that by the number of seconds in a day, and add that to the current timestamp. So, if today is Tuesday, (6 – 2) * 86,400 = 4 * 86,400 = 345,600 seconds, which when added to the current timestamp will give me a timestamp that falls on Saturday.

Now that we have timestamps for the Sunday and Saturday of the week, we need to get the month and day values from each. We’ll then find all the birthdays in the table that have a month equal to or greater than the Sunday month, and a month equal to or less than the Saturday month, and a day equal to or greater than the Sunday day, and a day equal to or less than the Saturday day.

If we find no matches, we have no birthdays. Otherwise, we will loop through the results set, outputting every person whose birthday falls within this week.

if(!$link = mysql_connect('server', 'host', 'password')) {
	echo "<p><strong>Could not connect to database server.</strong></p>n";
}
//select database
elseif(!mysql_select_db('database')) {
	echo "<p><strong>Could not select database.</strong></p>n";
}
else {
	//get today's date info
	$today = getdate(time());

	/*
	OPTION 1: This week only. Birthdays falling between Sunday and Saturday of this week are shown.
	*/

	//find the sunday of this week
	$sun = time() - (86400 * $today['wday']);
	//find the saturday of this week
	$sat = time() + ((6 - $today['wday']) * 86400);

	//get month and day for each
	$sun_mon = date('m', $sun);
	$sun_day = date('d', $sun);
	$sat_mon = date('m', $sat);
	$sat_day = date('d', $sat);

	//get records
	if(!$rs = mysql_query("SELECT person_name, person_surname, person_birthdate, person_department FROM php_birthday_list WHERE MONTH(person_birthdate) >= $sun_mon AND MONTH(person_birthdate) <= $sat_mon AND DAYOFMONTH(person_birthdate) >= $sun_day AND DAYOFMONTH(person_birthdate) <= $sat_day ORDER BY MONTH(person_birthdate), DAYOFMONTH(person_birthdate), person_surname, person_name, person_department")) {
		echo "<p><strong>There was an error parsing the query.</strong></p>n";
	}
	//no records found
	elseif(mysql_num_rows($rs) == 0) {
		echo "<p><strong>No staff birthdays this week.</strong></p>n";
	}
	else {
		echo "<p>Option 1: This week's birthdays</p>n";
		//output birthdays
		echo "<table class="bordered" cellspacing="0">n";
		echo "<tr><th>Name</th><th>Birthday</th><th>Age</th><th>Department</th></tr>n";
		while($row = mysql_fetch_array($rs)) {
			$age = time() - strtotime($row['person_birthdate']);
			$age = floor($age / 31536000);

			echo "<tr>";
			echo "<td>$row[person_name] $row[person_surname]</td>";
			echo "<td>" . date('F j, Y', strtotime($row['person_birthdate'])) . "</td>";
			echo "<td>$age</td>";
			echo "<td>$row[person_department]</td>";
			echo "</tr>n";
		}
		echo "</table><br />n";
	}
}

The question above wasn’t crystal-clear about what constitutes a week. It may be that the questioner actually means a week to be today, plus the next six days. If so, a little tweaking of our code above accomplishes that task.

Instead of calculating the Sunday and Saturday of the current week, we can simply take the current timestamp, add 518,400 seconds (the number of seconds in six days) and get a end timestamp that’s six days from now.

Otherwise, the script is unchanged.

/*
OPTION 2: Today and six days hence.
*/

//get starting and ending months and days
$end = time() + 518400;
$start_month = date('m', time());
$start_day = date('d', time());
$end_month = date('m', $end);
$end_day = date('d', $end);

//get records
if(!$rs = mysql_query("SELECT person_name, person_surname, person_birthdate, person_department FROM php_birthday_list WHERE MONTH(person_birthdate) >= $start_month AND MONTH(person_birthdate) <= $end_month AND DAYOFMONTH(person_birthdate) >= $start_day AND DAYOFMONTH(person_birthdate) <= $end_day ORDER BY MONTH(person_birthdate), DAYOFMONTH(person_birthdate), person_surname, person_name, person_department")) {
	echo "<p><strong>There was an error parsing the query.</strong></p>n";
}
//no records found
elseif(mysql_num_rows($rs) == 0) {
	echo "<p><strong>No staff birthdays this week.</strong></p>n";
}
else {
	echo "<p>Option 2: Today and six days hence; also shows only birth month and day</p>n";
	//output birthdays
	echo "<table class="bordered" cellspacing="0">n";
	echo "<tr><th>Name</th><th>Birthday</th><th>Department</th></tr>n";
	while($row = mysql_fetch_array($rs)) {
		echo "<tr>";
		echo "<td>$row[person_name] $row[person_surname]</td>";
		echo "<td>" . date('F j', strtotime($row['person_birthdate'])) . "</td>";
		echo "<td>$row[person_department]</td>";
		echo "</tr>n";
	}
	echo "</table><br />n";
}

And that’s all there is to the traditional, server-side approach to this problem. You can see a working demo here:

http://demo.dougv.com/php_birthday_list/index.php

The JavaScript Approach

You might ask yourself, “Why should I waste valuable server CPU cycles and memory to output something so simple? Why not make the client’s Web browser do the work?” So long as you don’t need to make frequent changes to your employee list, and your employee list isn’t hundreds or thousands of entries long (making it a management nightmare), you can — and, probably, should — make the client’s Web browser do the heavy lifting.

Via DOM scripting, we can add rows and cells to an otherwise empty table, for each and every employee with a birthday this week. Not only that, we can even default the table to say there are no birthdays, and remove that default message if we do, indeed, have birthdays to display.

Let’s start with the HTML for the table, which will take the unique ID “birthdays”:

<table id="birthdays" class="bordered" cellspacing="0">
    <tr>
        <th>Name</th><th>Birthday</th><th>Age</th><th>Department</th>
        <tr><td colspan="4">No birthdays this week!</td></tr>
    </tr>
</table>

Nothing all that special there. But there’s some heavy lifting to be done in JavaScript.

The Person Class

First, we need to create a simple Person object which can accept a name, surname, birthdate and department.

Why an object? We could use a two-dimensional array; first dimension being employee number, second dimension being each of those attributes.

An object, however, makes things neater and easier to address. Each named property is easy to call; there’s conformity to the way each Person is created, as well. This is going to make it easier for us to code, and the code more easy to understand for people who are looking at it.

In JavaScript, classes (aka objects) are declared by creating a function with the name of the class you want to create, and by using the reserved word this to assign properties and methods to the class. (Our Person class will only have properties.)

function Person(name, surname, birthday, department) {
	//object representing an employee
	this.name = name;
	this.surname = surname;
	this.birthday = birthday;
	this.department = department;
}

The employee Array

Now that we have a class which will make every employee record we work with nice and neat, we need to add all our employees to a global array. We’re using a global array of employees because it’s convenient, and since the entire script relies on this array to work, it’s appropriate to scope the array globally. (For more of an understanding about variable scope, see my blog entry titled, “Variable Scope Made Simple.”

The way we’ll add employees to that global array is via a function called createEmployeeList(). All it does is push onto the end of the employees array a new Person object, one for each employee on the payroll.

//declare global array of employees
var employees = new Array();

function createEmployeeList() {
	//add employees to global employees array
	employees.push(new Person('Laura', 'Miller', new Date('March 21, 1982'), 'Accounts Payable'));
	employees.push(new Person('Page', 'Rockwell', new Date('May 10, 1951'), 'Warehouse'));
	employees.push(new Person('Tom', 'Jones', new Date('May 10, 1984'), 'Mainenance'));
	employees.push(new Person('Richard', 'Gingras', new Date('May 11, 1946'), 'Administration'));
	employees.push(new Person('Joan', 'Walsh', new Date('May 11, 1973'), 'Administration'));
	employees.push(new Person('Norman', 'Blashka', new Date('May 12, 1953'), 'Mainenance'));
	employees.push(new Person('Kerry', 'Lauerman', new Date('May 13, 1967'), 'Mainenance'));
	employees.push(new Person('Joy', 'Press', new Date('May 14, 1969'), 'Accounts Payable'));
	employees.push(new Person('Jeanne', 'Carstensen', new Date('May 15, 1990'), 'Accounts Payable'));
	employees.push(new Person('Ruth', 'Henrich', new Date('May 16, 1981'), 'Mainenance'));
	employees.push(new Person('Wendy', 'Williams', new Date('May 17, 1992'), 'Accounts Payable'));
}

Note that one compromise we have to make in JavaScript, versus the traditional PHP / MySQL approach, is that records must be listed in the order of birth date; specifically, by month, day and year. In other words, all the Jan. 1 birthdays must be listed first; of all the Jan. 1 birthdays, the one in 1931 must be listed before the one in 1978.

That’s assuming you want to order your results by date; in other words, you want birthdays appearing today to come first, birthdays tomorrow second, birthdays two days from now third, etc.; and you want older people listed before younger people. If you want your list ordered some other way — say, by last name, department, etc. — that’s the order they should appear in the list.

If we were using a supporting technology such as Google Gears Database, we wouldn’t face this problem; we’d simply offload the employee list to the client and sort it as we would with a traditional SQL query (or, more specifically, a SQLite query).

I don’t want to delve into Google Gears too deeply in this entry, but assuming I ever get around to working on the Gulf Of Maine Wrecks Web site again, Gears will feature prominently, and there is where I will blog about using it.

The listBirthdays() Function

With an array of employees at our disposal, we can now get down to the nitty-gritty: Showing employees with a current birthday in our table.

The approach is the same as the one used in our PHP script. We get today’s date; from that, figure out the Sunday and Saturday of the current week; then iterate through the entire array of employees, looking for those who have a birthday with a month and day that fall between Sunday and Saturday.

For each employee that has a birthday this week, we add a new row to the table, then a cell for the name, birth month / day, age and department of that employee. We keep track of which row we’re adding via a counter, named x, which begins at index 2 (that is, row 3).

We start at row 3 because our table has two rows: A header row (1) and a row that says there aren’t any current birthdays (2). If we encounter at least one birthday this week, we set the value of a Boolean named ok to true (its initial value is false).

After we’re all done iterating the records, if the value of ok is true, we tell the table to remove row 2 — that is, the row that says there are no current birthdays.

function listBirthdays() {
	//get birthday list table
	var table = document.getElementById('birthdays');

	//set today's date
	//comment out declaration below and use this for production runs
	//var today = new Date();

	//for demo purposes, I'm using May 12 as the date
	//use the code above instead of this line
	var today = new Date('May 12, 2009');

	//set Sunday
	var sun = new Date();
	sun.setTime(today.getTime() - (86400000 * today.getDay()));

	//set Saturday
	var sat = new Date();
	sat.setTime(today.getTime() + (86400000 * (6 - today.getDay())));

	//declare table row counter; index begins at 2 to accommodate header row and no birthdays message
	var x = 2;

	//boolean to check if any birthdays found for this week
	var ok = false;

	//iterate employee array
	for(var i = 0; i < employees.length; i++) {
		//if birthday exists this week ...
		if(employees[i].birthday.getMonth() >= sun.getMonth() && employees[i].birthday.getMonth() <= sat.getMonth() && employees[i].birthday.getDate() >= sun.getDate() && employees[i].birthday.getDate() <= sat.getDate()) {
			//change birthday found boolean to true if it is false
			if(!ok) {
				ok = true;
			}
			//add row to table for employee
			var row = table.insertRow(x);
			//name column
			var name = row.insertCell(0);
			name.innerHTML = employees[i].name + ' ' + employees[i].surname;
			//birthday column
			var birthday = row.insertCell(1);
			birthday.innerHTML = monthName(employees[i].birthday.getMonth()) + ' ' + employees[i].birthday.getDate();
			//age column
			var age = row.insertCell(2);
			age.innerHTML = Math.floor((today.getTime() - employees[i].birthday.getTime()) / 31536000000);
			//department column
			var dept = row.insertCell(3);
			dept.innerHTML = employees[i].department;
			//iterate row counter
			x++;
		}
	}
	//remove no birthdays message if birthdays found
	if(ok) {
		table.deleteRow(1);
	}
}

Final JavaScript Functions

To complete the JavaScript version, we need two more functions. The first is monthName(), which takes an integer argument and returns a text value corresponding to the numeric value of the month indicated. (In JavaScript, the getMonth() method of the Date object returns an integer from 0-11, where January is 0 and December, 11.)

function monthName(month) {
	//return text name for month numeric value
	switch(month) {
		case 0:
			return 'Jan.';
			break;
		case 1:
			return 'Feb.';
			break;
		case 2:
			return 'March';
			break;
		case 3:
			return 'April';
			break;
		case 4:
			return 'May';
			break;
		case 5:
			return 'June';
			break;
		case 6:
			return 'July';
			break;
		case 7:
			return 'Aug.';
			break;
		case 8:
			return 'Sept.';
			break;
		case 9:
			return 'Oct.';
			break;
		case 10:
			return 'Nov.';
			break;
		case 11:
			return 'Dec.';
			break;
		default:
			return 'Undefined';
	}
}

Finally, we need to create a function we can call with the onload event, because we need to trigger two functions: createEmployeeList() and listBirthdays(). (I could have combined these functions into one, or called createEmployeeList() from listBirthdays(), but the two functions really do two different things, and I believe elegance dictates keeping them separate.)

function pageLoad() {
	//on page load, bind employee list, show birthdays
	createEmployeeList();
	listBirthdays();
}

Invoking The JavaScript

To get the whole thing to work, we simply make pageLoad() the onload event for the body tag:

<body onload="pageLoad()">

(Technically, we could simply use the window.onload event handler in JavaScript, but I find that doesn’t always work well in every browser; I prefer to call onload events via the body tag.)

And that’s all there is to it. You can see a working demo here:

http://demo.dougv.com/php_birthday_list/javascript.html

Code on github: https://github.com/dougvdotcom/php_birthday_list

All links in this post on delicious: http://delicious.com/dhvrm/using-javascript-to-perform-a-task-traditionally-solved-with-server-side-scripting

Special thanks to Salon.com for the staff list, which provided most of the names used in this demo.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  • Check out the Commenting Guidelines before commenting, please!
  • Want to share code? Please put it into a GitHub Gist, CodePen or pastebin and link to that in your comment.
  • Just have a line or two of markup? Wrap them in an appropriate SyntaxHighlighter Evolved shortcode for your programming language, please!