CodeIgniter is a popular, lightweight, open source framework written in PHP and based on the Model-View-Controller (MVC) architectural pattern. jQuery is a fast, lightweight, open source JavaScript library designed to simplify manipulating HTML pages and Ajax interactions. When used together, they form a powerful foundation for rapidly developing usable Web sites and applications.
This article shows how to integrate these two systems into a single framework and how to use jQuery to improve the UI of an existing Web application. It assumes that you have both CodeIgniter version 1.7.2 or later and MySQL version 4.1 or later installed, and that you have a good working knowledge of both. In addition, you need the jQuery library version 1.4.2 or later. If you're new to CodeIgniter or need a quick refresher, see Resources for links to more information.
One of the most important aspects of Web 2.0 is that a user's experience is more like that of a desktop application than a Web site. Specifically, interaction with the Web page doesn't require "visual communication" with the Web server—for example, click Submit, wait for the Web server to process the submitted information, then the entire Web page refreshes with updated content. Instead, only the content that needs to change is updated, while the rest of the page remains in place.
This "submission-less" process works through the use of Ajax, which lets Web developers transmit information between a Web client (browser) and a Web server without requiring page refreshes. Better yet, this information transfer can be triggered without any direct user intervention.
When a Web page uses Ajax, it is asynchronously sending data to and receiving data from a Web server. This transmitted data is plain text and, therefore, can be in a number of different formats, such as XML, HTML, JSON, or just plain text.
The actual transmission of the data is executed using JavaScript and an API called
XMLHttpRequest, and that's where jQuery enters
the picture. The jQuery library has greatly simplified the process of using Ajax.
And not only is using Ajax easier, but displaying the updated data is easier, too.
(If you've ever tried traversing the HTML DOM with JavaScript, you'll really
appreciate just how much easier it is!)
The existing CodeIgniter application
To demonstrate the power and simplicity of using jQuery with CodeIgniter, this article guides you through improving the UI of an existing Web application. This application was designed to help a teacher manage class activities and each parent's participation therein. The teacher first selects class activities from a list of board-approved options and schedules a date for each. Then, parents register on the site and enter the contact information for their child (or children). They can then view the list of class activities and select their level of participation (purchase supplies, help prepare, assist, or serve as an activity leader).
Note: This system could easily be applied to support a kids' sports team, YMCA group, and the like.
For this article, the application's database has been loaded with about 100 events, 5 parents, and 1 teacher. The parents' user names are parent1, parent2, . . . parent5. The teacher's user name is teacher, and all the passwords are ibm. You'll want to download the Web application and database and set them up on a Web server to follow along. The application requires that the CodeIgniter framework be at the root of the server.
To start using jQuery, first download the library (see Resources for a link). Two files are offered for each version release: an uncompressed file and a "minified" file (this highly compressed version is faster to load but impossible to trace if you're so inclined). I recommend using the uncompressed version for development and the minified version for production.
Next, place the jQuery library file in the .assets/js folder at the root of your Web server. Then, create a new file in the same folder called global.js, as shown in Listing 1. This is where you'll place the JavaScript code for the entire application.
Listing 1. "Hello World" using jQuery
/* Global JavaScript File for working with jQuery library */
// execute when the HTML file's (document object model: DOM) has loaded
$(document).ready(function() {
alert ('Hello World');
});
|
For this article—and most applications—much of the jQuery code
will reside inside the $(document).ready() function.
This function is automatically triggered only after the HTML file's DOM has
finished loading.
For the application to load both of these files, edit the ./system/application/views/template.php file, as seen in Listing 2.
Listing 2. Loading jQuery and the global JavaScript file
<head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <link href="/assets/css/screen.css" rel="stylesheet" type="text/css" /> <!-- the following two lines load the jQuery library and JavaScript files --> <script src="/assets/js/jquery-1.4.2.js" type="text/javascript"></script> <script src="/assets/js/global.js" type="text/javascript"></script> <title><?php echo $title;?></title> </head> |
Now, navigate to the index page of the Web site. When the page loads, a JavaScript alert will say "Hello World."
Using jQuery and Ajax with CodeIgniter
With the jQuery library and global.js file in place, you're ready to start improving the application's interface. If you haven't already, take a few minutes to log in as a parent and a teacher to become familiar with how activities work in the system.
The first place you'll improve the UI is on the Registration page. Currently, the validation for whether a username has been taken is done after the user submits the page. However, with Ajax, you can run the server-side validation and return the results without requiring any page submission.
To do so, bind code to the username field's onblur()
event, which is triggered when the user's cursor leaves the field. Listing 3 shows the
updated global.js file.
Listing 3. Checking whether a username is already registered
/* Global JavaScript File for working with jQuery library */
// execute when the HTML file's (document object model: DOM) has loaded
$(document).ready(function() {
/* USERNAME VALIDATION */
// use element id=username
// bind our function to the element's onblur event
$('#username').blur(function() {
// get the value from the username field
var username = $('#username').val();
// Ajax request sent to the CodeIgniter controller "ajax" method "username_taken"
// post the username field's value
$.post('/index.php/ajax/username_taken',
{ 'username':username },
// when the Web server responds to the request
function(result) {
// clear any message that may have already been written
$('#bad_username').replaceWith('');
// if the result is TRUE write a message to the page
if (result) {
$('#username').after('<div id="bad_username" style="color:red;">' +
'<p>(That Username is already taken. Please choose another.)</p></div>');
}
}
);
});
});
|
This code creates a jQuery object using the DOM element with an ID of
username. It then calls the jQuery
blur() method, which binds a function to the
onblur() event for the username field. This
function posts the value in the username field, using Ajax, to the
CodeIgniter controller named ajax and its method,
username_taken. Next, it clears any existing error
message and, based on the result of the Ajax post, displays or does not display
an error message.
Next, in ./system/application/controllers, create a file called ajax.php,
which is referred to by the jQuery Ajax .post()
method. Listing 4 shows the source code for this controller.
(Note that there is nothing special about naming the controller
ajax. It could be called anything as long as the URL
in the .post() method refers to the correct
controller.)
Listing 4. The CodeIgniter controller that processes Ajax requests
<?php
class Ajax extends Controller {
public function Ajax() {
parent::Controller();
}
public function username_taken()
{
$this->load->model('MUser', '', TRUE);
$username = trim($_POST['username']);
// if the username exists return a 1 indicating true
if ($this->MUser->username_exists($username)) {
echo '1';
}
}
}
/* End of file ajax.php */
/* Location: ./system/application/controllers/ajax.php */
|
Notice that the username_taken() method does not
return a value. Rather, it echoes its response, and this is an important point.
The jQuery Ajax request posts data to a Web page and uses the resulting page
data; it's not programmatically touching the method itself.
Now, your first Ajax function is complete. Navigate to the Registration page and register with any user name that is already taken to see the appropriate error message.
Interestingly enough, there's an unintentional result of the jQuery code as written.
The Ajax function is bound to fields with an ID of username,
which includes the username field on the Login page.
Listing 5 shows how to modify the jQuery object so that it
only binds to the username field within the registration_form
form.
Listing 5. Qualifying the correct username field
/* USERNAME VALIDATION */
// use element id=username within the element id=registration_form
// bind our function to the element's onblur event
$('#registration_form').find('#username').blur(function() {
|
Seamless status update and storage
The next UI improvement is on the Class Activity Listing page. For a parent to indicate his or her participation for a particular activity, the parent clicks the appropriate radio button, then clicks the save link to submit the page. To improve the UI, remove the requirement to click the save link and, therefore, the need to submit the page at all.
First, add the following code to the global.js file. It's split across Listing 6, Listing 7, and Listing 8 to more easily describe the process.
Listing 6. Using jQuery to get related element values
/* AUTOSAVE PARTICIPATION */
// use input element name=participation_type_id and type=radio
// bind our function to the element's onclick event
$('input[name=participation_type_id]:radio').click(function() {
var participation_type_id = this.value;
// create global variables for use below
var class_activity_id, user_id;
// get the form's two hidden input elements
// each is a sibling of the parent of the clicked radio button
// store their values in the global variables
var hidden_elements = $(this).parent().siblings('input:hidden');
$(hidden_elements).map(function() {
if (this.name == 'class_activity_id') {
class_activity_id = this.value;
}
if (this.name == 'user_id') {
user_id = this.value;
}
});
|
This function is bound to the onclick() event of
any radio button named participation_type_id. It gets the value of
the clicked radio button. Then, it uses a set of chained methods to return the
hidden form elements. The map() method passes
each element through its function, retrieving the class_activity_id
and user_id values.
Having identified the values required to set a parent's participation, the code executes an Ajax request to save this information, as seen in Listing 7. The server's response to this request does not echo any data, so the jQuery response function is empty (and could actually have been removed).
Listing 7. Posting an Ajax request to CodeIgniter
// Ajax request to CodeIgniter controller "ajax" method "update_user_participation"
// post the user_id, class_activity_id and participation_type_id fields' values
$.post('/index.php/ajax/update_user_participation',
{ 'user_id':user_id,
'class_activity_id':class_activity_id,
'participation_type_id':participation_type_id },
// when the Web server responds to the request
function(result) { }
);
|
Finally, the text next to the radio buttons is changed to the appropriate color
using the jQuery next() method to target each string.
This code is shown in Listing 8.
Listing 8. Dynamically changing the color of the radio button text
// set the text next to the clicked radio button to red
$(this).next().css("color", "red");
// set the text next to the remaining radio buttons to black
var other_r_buttons = $(this).siblings('input[name=participation_type_id]:radio');
$(other_r_buttons).map(function() {
$(this).next().css("color", "black");
});
});
|
Now that the jQuery code has been written, you need to create the
update_user_participation() method in the
ajax controller, as seen in Listing 9.
Listing 9. Processing user participation in CodeIgniter
public function update_user_participation()
{
$this->load->model('MActivity', '', TRUE);
$this->MActivity->set_user_participation($_POST);
}
|
This method uses the set_user_participation() method
already found in the MActivity model, which takes variables posted by the
Ajax request.
Finally, comment out the save link in ./system/application/controller/activity.php, as shown in Listing 10.
Listing 10. Removing the unnecessary save link
'<span style="style="white-space: nowrap;">'.
$participation_type_buttons.' '.
/* the save link is no longer needed
'<a href="" onclick="document.forms[\'form_'.$activity->id.'\'].submit();
return false;">save</a>'. */
'</span>'.
'</form>';
|
You can now change a parent's participation, and it will be automatically saved without a page refresh.
One of the most effective and widespread uses of Ajax is the autosuggest or
autocompletion functionality. Log in as a teacher, and click
Manage Class Activities. To add an activity, users must scroll down the
long list of possible activities. To improve the UI, add an input field bound to
an autosuggest function at the top of the
./system/application/views/activity_master_listing.php file, as seen in
Listing 11. Doing so allows the teacher to more easily
select from all the unscheduled activities.
Listing 11. Adding an autosuggest input field
<div id="select_anchor">
<a href="" onclick="$('#select_anchor').hide(100);
$('#select_activity').show(100);
return false;">
Select an unscheduled Activity to add >></a>
<br /><br />
</div>
<div id="select_activity" class="requested_activity" style="display:none;">
<table>
<caption> Select an unscheduled Activity</caption>
<tr class="odd_row_add">
<td>
Begin by typing a few letters of an activity name<br />
then select from the resulting list<br />
<br />
<input type="text" value="" id="class_activity"
onkeyup="autosuggest(this.value);" class="autosuggest_input" />
<div class="autosuggest" id="autosuggest_list"></div>
</td>
</tr>
</table>
</div>
|
Take note of the two jQuery objects and methods bound to the JavaScript
onclick() event. Remember that jQuery is simply
a JavaScript library and can seamlessly interact with JavaScript throughout
an application, not just within the $(document).ready()
function.
Next, outside the $(document).ready() function in
the global.js file, implement the following JavaScript function. It is bound to
the onkeyup() event of the class_activity
input field. The source code is shown in Listing 12.
Listing 12. Implementing autosuggest with jQuery
/* AUTOSUGGEST SEARCH */
// triggered by input field onkeyup
function autosuggest(str){
// if there's no text to search, hide the list div
if (str.length == 0) {
$('#autosuggest_list').fadeOut(500);
} else {
// first show the loading animation
$('#class_activity').addClass('loading');
// Ajax request to CodeIgniter controller "ajax" method "autosuggest"
// post the str parameter value
$.post('/index.php/ajax/autosuggest',
{ 'str':str },
function(result) {
// if there is a result, fill the list div, fade it in
// then remove the loading animation
if(result) {
$('#autosuggest_list').html(result);
$('#autosuggest_list').fadeIn(500);
$('#class_activity').removeClass('loading');
}
});
}
}
|
Notice that although this function is not within the
$(document).ready() function, it still uses jQuery
objects and methods. Its jQuery .post() method
is calling the Ajax controller's autosuggest() method,
which echoes an unordered list of autosuggest results.
Listing 13 shows this code.
Listing 13. Retrieving and echoing autosuggest results
public function autosuggest()
{
// escapes single and double quotes
$str = addslashes($_POST['str']);
$this->load->model('MActivity', '', TRUE);
$unscheduled_activities_qry = $this->MActivity->find_unscheduled_activities($str);
// echo a list where each li has a set_activity function bound to its onclick() event
echo '<ul>';
foreach ($unscheduled_activities_qry->result() as $activity) {
echo '<li onclick="set_activity(\''.addslashes($activity->name).'\'';
echo ', '.$activity->id.');">'.$activity->name.'</li>';
}
echo '</ul>';
}
|
Next, add the find_unscheduled_activities() method
to return the autosuggest results from the database.
Listing 14 contains the code from
./system/application/models/mactivity.php.
Listing 14. Querying the database for unscheduled activities
// Finds all unscheduled activities that match the passed string
function find_unscheduled_activities($str)
{
$this->db->select('id, name
FROM master_activity
WHERE name LIKE \''.$str.'%\'
AND id NOT IN
(SELECT master_activity_id FROM class_activity)
ORDER BY name', FALSE);
return $this->db->get();
}
|
You'll also want to style the autosuggest <div>
and list so that the UI is clearer and the list elements are clickable. The styles
added to ./assets/css/screen.css are shown in Listing 15.
Listing 15. Making the autosuggest list clear and clickable
/***************/
/* Autosuggest */
.autosuggest {
border:1px solid #000000;
display:none;
overflow:hidden;
padding:0px;
position:absolute;
width:200px;
z-index:1;
}
.autosuggest ul li {
background-color:#FFFFFF;
cursor:pointer;
display:block;
list-style:none;
padding:5px;
white-space:nowrap;
}
.autosuggest ul li:hover {
background-color:#316AC5;
color:#FFFFFF;
}
.loading{
background-image:url('../img/indicator.gif');
background-position:right;
background-repeat:no-repeat;
}
.autosuggest_input {
width:200px;
}
.table_header_add {
background-color:#FFCC66;
}
.odd_row_add {
background-color:#FFCC66;
}
|
You're about halfway done. You can type characters into the field to retrieve a set of matching activities. Now, implement the code to select, display, and save an activity for the class. Begin by adding two functions to the global.js file to set the activity name field and display a row with the activity's information. Listing 16 shows the source code for both.
Listing 16. Getting and displaying the selected activity
/* AUTOSUGGEST SET ACTIVITY */
// triggered by an onclick from any of the li's in the autosuggest list
// set the class_acitity field, wait and fade the autosuggest list
// then display the activity details
function set_activity(activity_name, master_activity_id) {
$('#class_activity').val(activity_name);
setTimeout("$('#autosuggest_list').fadeOut(500);", 250);
display_activity_details(master_activity_id);
}
/* AUTOSUGGEST DISPLAY ACTIVITY DETAILS */
// called by set_activity()
// get the HTML to display and display it
function display_activity_details(master_activity_id) {
// Ajax request to CodeIgniter controller "ajax" method "get_activity_html"
// post the master_class_activity parameter values
$.post('/index.php/ajax/get_activity_html',
{ 'master_activity_id':master_activity_id },
// when the Web server responds to the request
// replace the innerHTML of the select_activity element
function(result) {
$('#select_activity').html(result);
}
);
}
|
This code also uses Ajax to get the table row that displays the selected activity. Its HTML is generated by the code in Listing 17.
Listing 17. Echoing the HTML table displaying an activity
public function get_activity_html()
{
$this->load->model('MActivity', '', TRUE);
$this->load->library('table');
$requested_activity_id = $_POST['master_activity_id'];
$requested_activity_qry =
$this->MActivity->get_requested_master_activity($requested_activity_id);
// code leveraged from /controllers/activity.php manage_class_listing() method
// generate HTML table from query results
$tmpl = array (
'table_open' => '<table>',
'heading_row_start' => '<tr class="table_header_add">',
'row_start' => '<tr class="odd_row_add">'
);
$this->table->set_template($tmpl);
$this->table->set_caption(' Add this Activity');
$this->table->set_empty(" ");
$this->table->set_heading('<span class="date_column">Date</span>',
'<span class="activity_name_column">Activity Name</span>',
'<span class="address_column">Address</span>',
'City', 'Details');
$table_row = array();
foreach ($requested_activity_qry->result() as $activity)
{
$m_id = $activity->master_activity_id;
$table_row = NULL;
$table_row[] = ''.
'<form action="" name="form_'.$m_id.'" method="post">'.
'<input type="hidden" name="master_activity_id" value="'.$m_id.'"/> '.
'<input type="text" name="activity_date" size="12" /> '.
'<input type="hidden" name="action" value="save" /> '.
'</form>'.
'<span class="help-text">format: MM-DD-YYYY</span><br/>'.
'<a href="" onclick="document.forms[\'form_'.$m_id.'\'].submit();'.
'return false;">save</a>';
$table_row[] = '<input type="text" value="'.$activity->name.
'" id="class_activity" onkeyup="autosuggest(this.value);"'.
'class="autosuggest_input" />'.
'<div class="autosuggest" id="autosuggest_list"></div>';
$table_row[] = htmlspecialchars($activity->address);
$table_row[] = htmlspecialchars($activity->city);
$table_row[] = htmlspecialchars($activity->details);
$this->table->add_row($table_row);
}
$requested_activities_table = $this->table->generate();
echo $requested_activities_table;
}
}
|
This code was leveraged from the activity controller with only a few modifications, including echoing the final table, not returning it. It requires the SQL query shown in Listing 18 from ./system/application/models/mactivity.php.
Listing 18. Returning the requested master activity
// Returns a single master activity record
public function get_requested_master_activity($id)
{
$this->db->select('id as master_activity_id, name, address, city, details');
$this->db->where('id', $id);
return $this->db->get('master_activity');
}
|
Finally, although you can already add a new activity, you want to remove the unscheduled activities and re-sort the list in ascending order. The updated SQL code from the MActivity model is shown in Listing 19.
Listing 19. Only returning unscheduled activities
// Retrieve all master activity records
function list_class_activities($activity_id)
{
// get all records
if (!$activity_id) {
$this->db->select('ma.id as master_activity_id, ma.name,
ma.address, ma.city,
ma.details, ca.id as class_activity_id, ca.date
FROM master_activity ma
/*LEFT*/ JOIN class_activity ca ON ca.master_activity_id = ma.id
ORDER BY ca.date /*DESC*/, ma.name', FALSE);
return $this->db->get();
// get all records except the one being requested
} else {
$this->db->select('ma.id as master_activity_id, ma.name,
ma.address, ma.city,
ma.details, ca.id as class_activity_id, ca.date
FROM (SELECT * FROM master_activity
WHERE master_activity.id != '.$activity_id.') ma
/*LEFT*/ JOIN class_activity ca ON ca.master_activity_id = ma.id
ORDER BY ca.date /*DESC*/, ma.name', FALSE);
return $this->db->get();
}
}
|
Visual calendar for date selection
The final UI improvement you'll make is to replace the text input fields for entering dates with a visual calendar. To do so, use the jQuery UI Datepicker, which is a popular plug-in from the jQuery UI library (an open source extension of the jQuery library). Update the ./system/application/views/template.php file as shown in Listing 20.
Listing 20. Including the jQuery UI Datepicker
<!-- including the jQuery UI Datepicker and styles -->
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js"
type="text/javascript"></script>
<link href="/assets/css/jquery-ui-1.7.2.custom.css" rel="stylesheet" type="text/css" />
<style type="text/css">
/* jQuery UI sizing override */
.ui-widget {font-size:1em;}
</style>
|
Notice that instead of having downloaded the library, the
<script> tag references http://ajax.googleapis.com,
which is a free repository for many libraries, such as the jQuery UI. The
CSS file and images were configured to contain only the
necessary styles (http://jqueryui.com/download). Finally, the jQuery UI library
is built assuming a body font size of 62.5%, so you override its default to size it
appropriately.
The Datepicker works by being bound to a CSS class or ID within your HTML. For the
edit activity function, assign a class called date-picker
to the date input field in ./system/application/controllers/activity.php around line
210. The code is shown in Listing 21.
Listing 21. Assigning the date-picker class for edits
// add the date-picker class to the date input field
'<input type="text" name="activity_date" size="12" value="'.
date('m/d/Y', strtotime($activity->date)).'" class="date-picker" /> '.
'<input type="hidden" name="action" value="update" /> '.
'</form>'.
'<span class="help-text">format: MM/DD/YYYY</span><br/>'.
|
For the add activity function, assign the same class to the date input field in ./system/application/controllers/ajax.php around line 83, as seen in Listing 22.
Listing 22. Assigning the date-picker class for adds
// add the date-picker class to the date input field
'<input type="text" name="activity_date" size="12" class="date-picker" /> '.
'<input type="hidden" name="action" value="save" /> '.
'</form>'.
'<span class="help-text">format: MM/DD/YYYY</span><br/>'.
|
Finally, bind the Datepicker so that it shows up when editing or adding a class
activity. The code to place inside the $(document).ready()
function in the global.js file is shown in Listing 23.
Listing 23. Binding Datepicker to a class
/* jQUERY UI CALENDAR PLUGIN */
// bind the Datepicker to the date-picker class
$(".date-picker").datepicker();
|
Now try to edit a class activity. When you place your cursor in the date input
field, a calendar appears, allowing you to select a date for the activity. However,
notice that this doesn't work when trying to add an activity. That's because the
add activity HTML is written after the document has already been loaded (using
Ajax), so the Datepicker is not bound to its date input field. To fix this,
you bind the Datepicker to the date input field after the Ajax function
writes the HTML. This change to the display_activity_details()
function in the global.js file is shown in Listing 24.
Listing 24. Binding Datepicker to the add date field
function(result) {
$('#select_activity').html(result);
// because the add datepicker is not loaded with the DOM
// manually add it after the date input field is written
$(".date-picker").datepicker();
}
|
Now, you can add a class activity using the Datepicker calendar.
Kudos on a job well done! You have learned to combine DOM manipulation through jQuery objects and achieve seamless communication through Ajax to significantly improve the UI of this application. And in many cases, the code to do so was quite small. Now, you might enjoy the challenge of further enhancing the application by having the class activity edit happen inline using Ajax or implementing any other UI ideas that you may have had while reading this article.
| Description | Name | Size | Download method |
|---|---|---|---|
| SQL file to create classroom database | classroom_database.zip | 15KB | HTTP |
| Full initial code base including CodeIgniter files | source_w_codeigniter.zip | 411KB | HTTP |
| The initial MVC files for this article | source_only.zip | 23KB | HTTP |
| The jQuery library v1.4.2 | jquery_library.zip | 45KB | HTTP |
| CSS files needed for jQuery UI Datepicker | jquery_datepicker.zip | 53KB | HTTP |
| Full final code base including CodeIgniter files | final_source_w_codeigniter.zip | 515KB | HTTP |
| The final MVC files from this article | final_source_only.zip | 124KB | HTTP |
Information about download methods
Learn
-
"Getting started
with CodeIgniter" (developerWorks, August 2008): Read a good
introduction to the CodeIgniter framework.
-
"Developing
dynamic Web sites with CodeIgniter" (developerWorks,
January 2010): Get a deeper look at CodeIgniter and the MVC architecture.
-
CodeIgniter User Guide: Read
CodeIgniter's online documentation.
-
developerWorks Web development
zone: The Web development zone is packed with tools and information for
Web 2.0 development.
-
IBM
technical events and webcasts: Stay current with developerWorks' Technical
events and webcasts.
Get products and technologies
-
CodeIgniter: Download the latest
version of CodeIgniter.
-
MySQL: Download the latest version
of MySQL.
-
jQuery: Learn more and download the latest version
of the jQuery library.
-
jQuery UI: Learn more and download additional
widgets from the jQuery UI Web site.
-
IBM
product evaluation versions: Download these versions today and get your hands
on application development tools and middleware products from DB2®,
Lotus®, Rational®, Tivoli®, and WebSphere®.
Discuss
-
developerWorks blogs: Check out
developerWorks blogs and get involved in the
developerWorks community.

With 20 years of experience in the high-tech industry, Kevin Howard Goldberg is a technology executive, author, and consultant residing in Westlake Village, Calif. He currently serves as CTO at imagistic, an award-winning digital marketing and technology company, which he co-founded in 1997. An expert on Web development and technology, he serves on the Santa Monica College Computer Science Advisory Board and held senior positions at Film Roman, Lionsgate, and Philips Interactive Media. He is the author of XML: Visual QuickStart Guide (2nd Edition), (PeachPit Press, 2008). You can reach him at http://kehogo.com/contact.




