Logic’s Last Stand

June 5, 2010

Accessible Forms with PHP and jQuery

Filed under: Computers, Freeware — Tags: , , , , — Zurahn @ 8:30 pm

A primary challenge of recent web-development is how to make use of the great new dynamic tools provided to us in using libraries such as jQuery, while still providing an accessible website without the use of JavaScript. While there’s more to accessibility than making a site work without JavaScript, it’s a fundamental start. This task looks arduous, but it doesn’t have to be — approach it right from the start and it may actually be trivial.

Let’s start from the no-JavaScript version and work up. Normally your form without JavaScript would look something like this

<form method="post" action="example.php">
    Field: <input type="text" name="field" /><br />
    <input type="submit" value="Submit" />
</form>

To have JavaScript handle the post, we’ll add an onsubmit function to the form.

<form method="post" action="example.php" onsubmit="return formSubmit(this)">
    Field: <input type="text" name="field" /><br />
    <input type="submit" value="Submit" />
</form>

When the onsubmit function returns false, the form does not submit. So by having the formSubmit function return false, we can have the page handle the post via AJAX instead of having to refresh the page. Let’s look at the formSubmit function.

function formSubmit(obj)
{
    var form = $(obj);
    $.post(obj.action, form.serialize());
    return false;
}

The .serialize() function takes the form elements and converts them to query string parameters so they can be passed through post. By using this, we can reuse the same generic formSubmit function regardless of the form — all we have to add is the onsubmit attribute to the form.

Now you may also want to have error and success messages return. A good way to handle this is via JSON objects. JSON is a standard by which objects can be represented in string form, so we can pass a string from PHP to JavaScript, which can then be handled as an object. Let’s update out formSubmit function to handle this behaviour (the script will assume that there are hidden divs with the ID “error” and “success”.

function formSubmit(obj)
{
    var form = $(obj);
    $.post(obj.action, form.serialize(), function(data)
    {
        // Return data is JSON object string, so eval to get object
        var message = eval("("+data+")");
        showErrors(message['errors']);
        showSuccesses(message['successes']);
    });
    return false;
}

function showErrors(messages)
{
    if(typeof messages != "undefined")
    {
        $('#success').css('display', 'none');
        var error = $('#error');
        error.css('display', 'none');
        error.html(getMessageList(messages));
        error.fadeIn();
    }
}

function showSuccesses(messages)
{
    if(typeof messages != "undefined")
    {
        $('#error').css('display', 'none');
        var success = $('#success');
        success.css('display', 'none');
        success.html(getMessageList(messages));
        success.fadeIn();
    }
}

function getMessageList(messages)
{
    var output = '<ul>';
    // iterate through the object properties
    for(i in messages)
        output += '<li>'+messages[i]+'</li>';
    output += '</ul>';
    return output;
}

Now we need to construct the JSON object on the PHP side. The error will be echoed, but remember that we want this to work even if it’s not an AJAX post, so we need different behaviour depending on whether or not it was an AJAX post — echo error/success messages if AJAX, redirect back if it’s not. Let’s go to example.php; the script will assume that the value $_SESSION[‘page’] holds the value of $_SERVER[‘PHP_SELF’] before the post.

<?php
session_start();
$field = $_POST['field'];

if(!isset($field) || $field === "")
    Reporting::setError("Name cannot be blank");

if(!Reporting::hasErrors())
{
    /* Do something with $field */
    Reporting::setSuccess("Operation with <em>$field</em> completed successfully");
}

Reporting::endDo();


class Reporting
{
    public function __construct()
    {

    }

    public static function endDo()
    {
        if(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest')
        {
            if(self::hasErrors())
                echo self::getJsonErrors();
            else if(self::hasSuccesses())
                echo self::getJsonSuccesses();
        }
        else
            header('Location: '.$_SESSION['page']);
    }

    public static function hasErrors()
    {
        return isset($_SESSION['errors'][0]);
    }

    public static function hasSuccesses()
    {
        return isset($_SESSION['successes'][0]);
    }

    public static function setError($message)
    {
        $_SESSION['errors'][] = $message;
    }

    public static function setSuccess($message)
    {
        $_SESSION['successes'][] = $message;
    }
    
    public static function getJsonErrors($clear=true)
    {
        return self::getJsonMessages('errors', $clear);
    }

    public static function getJsonSuccesses($clear=true)
    {
        return self::getJsonMessages('successes', $clear);
    }

    public static function showErrors($clear=true)
    {
        return self::showMessages('errors', $clear);
    }

    public static function showSuccesses($clear=true)
    {
        return self::showMessages('successes', $clear);
    }

    private static function showMessages($type, $clear)
    {
        $output = '<ul>';
        foreach($_SESSION[$type] as $val)
            $output .= "<li>$val</li>";
        $output .= '</ul>';
        if($clear)
            $_SESSION[$type] = array();
        return $output;
    }

    private static function getJsonMessages($type, $clear)
    {
        $output = '{ '.$type.': { ';
        $comma = '';
        foreach($_SESSION[$type] as $key => $val)
        {
            $output .= $comma.$key.': "'.$val.'"';
            $comma = ', ';
        }
        $output .= ' } }';
        if($clear)
            $_SESSION[$type] = array();
        return $output;
    }
}
?>

Looks like a lot of work, but with that, we’re all done. Everything from now on is handled identically between jQuery and non-JavaScript versions of the site, and all you have to do is add onsubmit=”return formSubmit(this)” to each form, and in the processing script, close with Reporting::endDo(). Everything else takes care of itself.

November 26, 2008

Very Simple JavaScript Image Viewer

Filed under: Computers — Tags: , , , — Zurahn @ 2:21 am

Being in need of a relatively simple inline viewer for a series of images, I opted to make my own instead of looking to the many premade options with far more detail than I’d need. While I’ll probably spruce my own option up from the bare-bones, I thought the baseline was a nice example piece of how to keep things simple in a time when there’s a tendency to move towards frameworks such as jQuery, which while excellent in many instances, aren’t always necessary.

I’m not a fan of JavaScript’s implementation of objects, so I generally avoid them. However, in this case it seemed rather suitable. Just pass the element ID of what you want to contain the images, along with an array of images to the Gallery constructor, then run the init function with the IDs of the navigation buttons on page load.

The navigation was the part I was a bit torn on. Optimally it would be part of the actual gallery code and not something that needed to be added separately; the problem is that in order to call the next() and prev() functions for the object, I need the actual Gallery object that is created. The only other option was a “setClassObject” function to provide the actual gallery object, but that would get needlessly obtrusive.

/* 
  File: gallery.js  
*/

function Gallery(id, imgs)
{
  this.id = id;
  this.imgs = imgs;
}

function init(prevID, nextID)
{
  if(nextID != null)
    this.nextObj = document.getElementById(nextID);
  if(prevID != null)
    this.prevObj = document.getElementById(prevID);
  this.curFrame = 0;
  var output = '';
  output += '<div id="'+this.id+'_images"><img src="'+this.imgs[this.curFrame]+'" id="'+this.id+'_image_'+this.curFrame+'" alt="" /></div>';
  document.getElementById(this.id).innerHTML = output;
  this.updateImage();
  this.updateButtons();
}

function next()
{
  this.curFrame++;
  this.updateImage();
  this.updateButtons();
}

function prev()
{
  this.curFrame--;
  this.updateImage();
  this.updateButtons();
}

function updateImage()
{
  document.getElementById(this.id+'_images').childNodes[0].src = imgs[this.curFrame];
}

function updateButtons()
{
  var len = this.imgs.length;
  if(typeof this.nextObj != 'undefined')
  {
    if(this.curFrame == len-1)
	  this.nextObj.disabled=true;
	else
	  this.nextObj.disabled=false;
  }
  if(typeof this.prevObj != 'undefined')
  {
    if(this.curFrame == 0)
	  this.prevObj.disabled=true;
	else
	  this.prevObj.disabled=false;
  }
}

Gallery.prototype.init=init;
Gallery.prototype.updateImage=updateImage;
Gallery.prototype.updateButtons=updateButtons;
Gallery.prototype.next=next;
Gallery.prototype.prev=prev;

And an appropriately simple implementation of that viewer is seen here,

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript" src="gallery.js"></script>
<script type="text/javascript">
var imgs = ['image_1.jpg','image_2.jpg', 'image_3.jpg', 'image_4.jpg'];
var gallery = new Gallery('galleryImages', imgs);
function init()
{
  gallery.init('prev', 'next');
}
window.onload=init;
</script>

<title>JavaScript Image Viewer</title>
</head>

<body>
<div id="gallery" style="text-align: center">
  <div id="galleryImages"></div>
  <div id="navigation"><input type="button" id="prev" value="PREV" onclick="gallery.prev();" />
  <input type="button" id="next" value="NEXT" onclick="gallery.next()" />
  </div>
</div>
</body>
</html>

You can see the viewer in action here

Blog at WordPress.com.