I/O Reader

RSS Feed

Peter Goodman's blog about PHP, Javascript, Functional Programming, Applications, Actionscript,

Archives for 2006

Observers and Dispatchers Revisited

posted on Dec 21, 2006 at 7:26pm with two comments and tagged with PHP

So I've had requests to do some more explaining on the observer and dispatcher patterns, and what better way to do that than to make code for an example!

Let's assume that we have a simple database abstraction layer. For the purpose of debugging, we want to be able to display information about each query at the end of the page. To make an effective debugging system we need to keep track of the query used, how many rows it affected, and how much time it took to execute. So, how could we do this? Well, we could store the information in a singleton (registry) but that is more of a hack than anything. So why not dispatch observable events to some observers?

Before I get into the code, I'll briefly go over how things will work. First, we need an observer to collect observable events. This observer will be instanciated at the start of our program and will be available to our dispatcher. The dispatcher will take in observable events and pass them to our observer. Finally, at the end of the program, we will loop over all of the events collected with our observer (the observable events) and put out a nice debug display that makes use of all of that info.

So to start off, let's just create a dummy database abstraction layer. In the executeQuery function of our dummy database abstraction layer, we will notify observers of database query events. These events don't know if they are going to be picked up by observers or not. If this seems odd to you, think of it as if you are looking for a job and you are sending your resume out. You don't know if anyone will read your resume or not, and if you did, it would probably make the whole task unecessary.

//--------------------------------------------
// Our base class for the database abstraction layer.
// This class deals with setting up and managing the
// dispatcher that we are using.
//--------------------------------------------

class FADatabaseConnection
{
    //--------------------------------------------
    // The dispatcher that the database abstraction layer
    // will use to notify observers about events.
    //--------------------------------------------

    var $_dispatcher;

    //--------------------------------------------
    // Constructor, instanciate a new dispatcher to deal
    // with notifying any observers added to it about events.
    //--------------------------------------------

    function FADatabaseConnection()
    {
        $this->_dispatcher = &new FADispatcher();
    }

    //--------------------------------------------
    // Add observers to this dispatcher so that when this
    // dispatcher is notified about events, it can tell
    // the observers about them.
    //--------------------------------------------

    function addObserver(&$observer)
    {
        $this->_dispatcher->addObserver($observer);
    }

    //--------------------------------------------
    // Notify any observer in the dispatcher about
    // an event.
    //--------------------------------------------

    function notifyAll(&$event)
    {
        $this->_dispatcher->notifyAll($event);
    }

    //--------------------------------------------
    // The execute query method, this method will execute
    // a query on a database.
    //--------------------------------------------

    function executeQuery($sql = '')
    {
        assert(FALSE);
    }
}

So, everything in there should be self-explanatory, but... a dispatcher is used! Wtf is that!? A dispatcher (in this case FADispatcher) is used to collect observers and then notify them about incoming events. Here, I will paste the nicely commented code for the dispatcher and hopefully it will start to make more sense.

//--------------------------------------------
// A dispatcher. This class deals with managing
// observer classes and notifying these observers
// about events.
//--------------------------------------------

class FADispatcher 
{
    //--------------------------------------------
    // An array of the observers that this dispatcher
    // will notify about incoming events.
    //--------------------------------------------

    var $_observers = array();

    //--------------------------------------------
    // Add an observer to the above array so that when
    // the dispatcher is told to notifyAll (notify all
    // observers) then we will have an/some observers to
    // notify!
    //--------------------------------------------

    function addObserver(&$observer) 
    {
        //--------------------------------------------
        // Make sure that it's actually an observer being
        // passed in.
        //--------------------------------------------

        assert(is_a($observer, 'FAObserver'));

        //--------------------------------------------
        // Add an observer to the array of observers.
        //--------------------------------------------

        $this->_observers[] = &$observer;
    }

    //--------------------------------------------
    // Notify all of the observers in the _observers
    // array about $event.
    //--------------------------------------------

    function notifyAll(&$event) 
    {
        //--------------------------------------------
        // Make sure the even being passed is observable.
        //--------------------------------------------

        assert(is_a($event, 'FAObservableEvent'));

        $ret = FALSE;

        for($i = 0; $i < count($this->_observers); $i++)
        {
            //--------------------------------------------
            // Get one of the observers.
            //--------------------------------------------

            $observer = &$this->_observers[$i];

            //--------------------------------------------
            // If an observer has successfully been notified,
            // stop notifying observers.
            //--------------------------------------------

            if($ret = (bool)$observer->notify($event)) 
            {
                break;
            }
        }

        return $ret;
    }
}

The above FADispatcher class collects observers and tells them about events. Here's how it does it:

So, the whole picture should slowly be coming together. I'll show you the last two base classes that we need, describe how everything fits together, then show you the final result in code!

//--------------------------------------------
// The base observer class. This class cannot
// be used directly.
//--------------------------------------------

class FAObserver 
{
    //--------------------------------------------
    // Notify this observer about an incoming event.
    //--------------------------------------------

    function notify(&$event) 
    {
        assert(FALSE);
    }
}
//--------------------------------------------
// The observable event class... Not much to say
// here.
//--------------------------------------------

class FAObservableEvent { }

Isn't this convenient: there's almost no code to look at! Well, here's how things will piece together... The observer that we will use will collect the events passed to it through its "notify" function. Also, we will have a function in that observer, "getEvents", that will return all of the events passed to it through its "notify" function.

Each of the events passed to the dispatcher and consequently the observer will have information about each query. That info will be stored in reference variables and will be accessible through some getter functions (getQuery, getRows, getTime).

Finally, at the end of our program, we will use the observer that we added to the FADatabaseConnection class's dispatcher and use the events passed to it (the info about each database query) to build a nice debug table for the queries executed on each page load. So, time to build some code. First, the observable event that will hold the query information...

//--------------------------------------------
// An observable database query event. This class holds
// information about a specific database query.
//--------------------------------------------

class DatabaseQueryEvent extends FAObservableEvent 
{
    var $_sql;
    var $_rows;
    var $_time;

    //--------------------------------------------
    // Constructor. The variables passed in are:
    // $sql - the SQL query
    // $rows - the number of rows that the sql query affected
    // $time - the number of microseconds that the query took
    //         to execute.
    //--------------------------------------------

    function DatabaseQueryEvent($sql, $rows = 0, $time = 0) 
    {
        $this->_sql = $sql;
        $this->_rows = $rows;
        $this->_time = $time;
    }

    //--------------------------------------------
    // Get the SQL of the query used.
    //--------------------------------------------

    function getQuery() 
    {
        return $this->_sql;
    }

    //--------------------------------------------
    // Get the number of rows that the query affected.
    //--------------------------------------------

    function getRows() 
    {
        return $this->_rows;
    }

    //--------------------------------------------
    // Get the number of microseconds that the query
    // took to execute.
    //--------------------------------------------

    function getTime()
    {
        return $this->_time;
    }
}

That code should be simple enough to understand.. nothing really goes on in it. If you're wondering how it is implemented, don't worry, that will come shortly. Now, on to the observer that we are going to use!

//--------------------------------------------
// The database observer class. This class collects
// database query events.
//--------------------------------------------

class DatabaseObserver extends FAObserver 
{
    //--------------------------------------------
    // An array of database query events.
    //--------------------------------------------

    var $_events = array();

    //--------------------------------------------
    // Notify this observer about an event. In this case
    // it will be a database query event. Push that event
    // onto the array of events.
    //--------------------------------------------

    function notify(&$event) 
    {
        $this->_events[] = $event;
    }

    //--------------------------------------------
    // Return the array of all of the database query
    // events.
    //--------------------------------------------

    function getEvents() 
    {
        return $this->_events;
    }
}

Heh, the database observer class is also simple and self-explanatory! Well, right now all of the core code has been laid out except for the final implementation of the database abstraction layer. In the DBA, everything should come together nicely :D

//--------------------------------------------
// This would normally be an abstraction layer for
// something like MySQL or PostgreSQL... For this example,
// we are dealing with the ever ambiguous SomeSQL!
//--------------------------------------------

class SomeSQL extends FADatabaseConnection
{
    //--------------------------------------------
    // Execute a database query
    //--------------------------------------------

    function executeQuery($sql)
    {
        //--------------------------------------------
        // Now, these two variables would normally take
        // their values from something like mysql_affected_rows
        // and by putting two microtime()'s around the execution
        // of the query and finding the difference. But, we won't
        // do that in this example.. mwahahaha.
        //--------------------------------------------

        $execution_time = microtime();
        $affected_rows = 0;

        //--------------------------------------------
        // Normally one would call the query method for a database here.
        //--------------------------------------------

        //--------------------------------------------
        // Aha! Instanciate a new database query event
        // (an observable event) and pass it to the notifyAll
        // function. The notifyAll function calls FADispatcher::notifyAll
        // which in turn calls FAObserver::notify! What a nice
        // chain of events.
        //--------------------------------------------

        $this->notifyAll(new DatabaseQueryEvent($sql, $affected_rows, $execution_time));
    }
}

Make sure to read the comments in that code, much is explained. By this point, all necessary code to make observers and dispatchers has been laid out. It's about time to finish everything off, but before I do that I will list out the chain of events of everything will happen.

So, let's go finish off our program!

//--------------------------------------------
// Instantiate our observer. By the end of execution,
// this observer will hold and array of information
// for all of the database queries.
//--------------------------------------------

$observer = &new DatabaseObserver;

//--------------------------------------------
// Instanciate our SomeSQL database abstraction layer
// and add the above observer to the dispatcher that
// was implicitly instanciated when SomeSQL was.
//--------------------------------------------

$dba = &new SomeSQL;
$dba->addObserver($observer);

//--------------------------------------------
// Execute a dummy query just for the sake of this
// example.
//--------------------------------------------

$dba->executeQuery("SELECT moo FROM cow WHERE alive=1");

//--------------------------------------------
// Debug time! Let's loop over all of the query
// events stored in the observer and make a table
// with some debug information.
//--------------------------------------------

$html = "";
$html .= "<table border="1">";
$html .= "<tr><th>Query</th><th>Query Time</th><th>Affected Rows</th></tr>";

$i = 0;
foreach($observer->getEvents() as $event) {
    $html .= "\n\t<tr>";
    $html .= "\n\t\t<td>\n\t\t\t" . $event->getQuery() . "\n\t\t</td>";
    $html .= "\n\t\t<td>\n\t\t\t" . $event->getTime() . "\n\t\t</td>";
    $html .= "\n\t\t<td>\n\t\t\t" . $event->getRows() . "\n\t\t</td>";
    $html .= "\n\t</tr>";

    $i++;
}
$html .= "n</table>";

echo $html;

And what we get from running this program is...

QueryQuery TimeAffected Rows
SELECT moo FROM cow WHERE alive=1 0.16262100 1166746068 0
Note: the space in the number in the query time column is just what microtime() spits out because I didn't put any formatting on it.

Hopefully by now, you understand how observers and dispatchers work. As you might have noticed, a lot of code went into such a simple thing as database debugging. Obviously, if you want to do database debugging and only debugging, then working with observers and dispatchers is not the most efficient way to do it. However, if you are working in a coding environment where observers and dispatchers are already supported, usign them for database debugging might just be a nice way to more fully implement the tools available to you. If you want to download a working version of the above observers and dispatchers, click here. If you have any comments/questions, or need anything further clarified, just ask.

Simple CAPTCHA

posted on Dec 16, 2006 at 4:51pm with two comments and tagged with PHP, Applications, OneLobby

So I made this CAPTCHA script a little while ago for OneLobby. I have no idea how strong it is, but I took a different approach to obfuscating the image. Normally one would put lot's of lines and shapes and whatnot in the image as extra noise. I didn't feel like doing this this time around so I messed around with the quality of the image instead... (skip to the end if you want to download the .zip)

You can find an example of the CAPTCHA script if you click here.

<?php

/*********************************************************************************\
 *
 *     (c) Copyright 2006, Peter Goodman, all rights reserved
 *
 *     Permission is hereby granted, free of charge, to any person obtaining 
 *     a copy of this software and associated documentation files (the 
 *     "Software"), to deal in the Software without restriction, including 
 *     without limitation the rights to use, copy, modify, merge, publish, 
 *     distribute, sublicense, and/or sell copies of the Software, and to 
 *     permit persons to whom the Software is furnished to do so, subject to 
 *     the following conditions:
 *
 *     The above copyright notice and this permission notice shall be 
 *     included in all copies or substantial portions of the Software.
 *
 *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 *     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
 *     BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
 *     ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 *     CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 *     SOFTWARE.
\*********************************************************************************/

//--------------------------------------------
// A little class to make and deal with captcha
// images.
//--------------------------------------------

class Captcha
{
    private $font = 'Action Man Bold'; // make sure to exclude .ttf
    private $captcha_length = 6;
    private $reg_id = NULL;
    private $use_captcha = FALSE;
    private $font_dir;

    //--------------------------------------------
    // Constructor.
    //--------------------------------------------

    public function __construct()
    {
        if(!session_id() || session_id() == '')
        {
            session_start();
        }

        $this->reg_id = md5(uniqid(""));
        $this->use_captcha = function_exists('imagecreate');

        // directory where the font is in
        $this->font_dir = dirname(__FILE__) .'/';
    }

    //--------------------------------------------
    // Create the captcha image itself. It is a JPEG.
    //--------------------------------------------

    public function createCaptcha()
    {
        //--------------------------------------------
        // Make sure that something is actually using this
        // captcha.
        //--------------------------------------------

        if(!$this->captchaIsRegistered() || !$this->use_captcha)
        {
            header("HTTP/1.0 404 Not Found");
            exit();
        }

        //--------------------------------------------
        // Unregister this captcha.
        //--------------------------------------------

        $this->unregisterCaptcha();

        //--------------------------------------------
        // Deal with the font path.
        //--------------------------------------------

        putenv('GDFONTPATH='. realpath($this->font_dir));

        //--------------------------------------------
        // Do all of the headers.
        //--------------------------------------------

        header("Content-type: image/jpeg");
        header(base64_decode("WC1DYXB0Y2hhOiBPbmVMb2JieSBodHRwOi8vd3d3Lm9uZWxvYmJ5LmNvbQ=="));
        header("Expires: Mon, 23 Jul 1993 05:00:00 GMT");
        header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
        header("Cache-Control: no-store, no-cache, must-revalidate");
        header("Cache-Control: post-check=0, pre-check=0", false);
        header("Pragma: no-cache");


        //--------------------------------------------
        // Create the base image 95x40 px.
        //--------------------------------------------

        $create_fn = function_exists('imagecreatetruecolor') ? 'imagecreatetruecolor' : 'imagecreate';

        $final_image = $create_fn(95, 40);

        //--------------------------------------------
        // Create a solid white background. If background
        // junk was to be used, it would probably be added
        // in here.
        //--------------------------------------------

        $background = imagecolorallocate($final_image, 255, 255, 255);
        imagefill($final_image, 0, 0, $background);
        imagecolordeallocate($final_image, $background);

        unset($background);

        //--------------------------------------------
        // Figure out the code.
        //--------------------------------------------

        $code = strtoupper(substr(md5(uniqid("")), 0, $this->captcha_length));

        //--------------------------------------------
        // Add the code the session.
        //--------------------------------------------

        $_SESSION['captcha_code'] = $this->hashInput($code);

        //--------------------------------------------
        // Add the code in.
        //--------------------------------------------

        // stuff for the letter itself
        $letter_width = 14;
        $letter_height = 20;

        for($i = 0; $i < $this->captcha_length; $i++) {

            // stuff for the letter itself
            $letter_text = $code{$i};

            // positioning
            $layer_x = ($i * $letter_width);
            $layer_y = 32 + rand(-7, 7);

            $font_color = imagecolorallocate($final_image, rand(0,150), rand(0,150), rand(0,150));
            imagettftext($final_image, 20, rand(-5, 5), $layer_x, $layer_y, $font_color, $this->font, $letter_text);
            imagecolordeallocate($final_image, $font_color);
        }

        unset($font_color);

        //--------------------------------------------
        // Interlace the image for better output in the
        // browser.
        //--------------------------------------------

        imageinterlace($final_image, 1);

        //--------------------------------------------
        // Output the final image in very low quality.
        //--------------------------------------------

        imagejpeg($final_image, FALSE, 15);

        //--------------------------------------------
        // Clean up the memory
        //--------------------------------------------

        imagedestroy($final_image);
        unset($final_image);
    }

    //--------------------------------------------
    // Create all the HTML needed to use this captcha
    // so that we can just plunk in {$captcha_img},
    // etc. where it's stylish and everything
    // will work.
    //--------------------------------------------

    public function drawCaptcha()
    {
        //--------------------------------------------
        // If a captcha has already been passed, we don't
        // need to display any others.
        //--------------------------------------------

        if($this->passedAlready())
        {
            // if you want it to not display the captcha a second time,
            // don't use this.
            //return;
        }

        //--------------------------------------------
        // Register the captcha.
        //--------------------------------------------

        $this->registerCaptcha();

        //--------------------------------------------
        // Path to the image. The 'reg_id' needs to be
        // passed to this!!
        // *** might want to change this ***
        //--------------------------------------------

        $url = 'index.php?act=captcha&reg_id='. $this->reg_id;

        //--------------------------------------------
        // Set the html to the templates.
        //--------------------------------------------

        $html = '';
        $html .= '<div style="width:95px;height:40px;background:url('. $url .') top left no-repeat;"> </div>';
        $html .= '<input type="text" name="captcha_input" id="captcha_input" value="" maxlength="'. $this->captcha_length .'" />';

        if($this->use_captcha)
        {
            echo $html;
        }
    }

    //--------------------------------------------
    // Validate a the captcha image with the session.
    //--------------------------------------------

    public function isValid()
    {       
        //--------------------------------------------
        // If we can't use captcha's then set the valid to
        // default to TRUE.
        //--------------------------------------------

        $valid = !$this->use_captcha ? TRUE : FALSE;

        //--------------------------------------------
        // Validate the request variable against the
        // session variable.
        //--------------------------------------------

        if(!$valid && isset($_SESSION['captcha_code']))
        {
            if($this->hashInput($_POST['captcha_input']) == $_SESSION['captcha_code'])
            {
                $valid = TRUE;

                $_SESSION['captcha_passed'] = TRUE;
            }
        }

        //--------------------------------------------
        // Reset the value to nothing, which means that this
        // function can and should only be used once.
        //--------------------------------------------

        $_SESSION['captcha_code'] = NULL;

        //--------------------------------------------
        // So... is it valid?
        //--------------------------------------------

        return $valid;
    }

    //--------------------------------------------
    // Register a captcha.
    //--------------------------------------------

    private function registerCaptcha()
    {
        $_SESSION['captcha_registered'] = $this->reg_id;
    }

    //--------------------------------------------
    // Unregister a captcha.
    //--------------------------------------------

    private function unregisterCaptcha()
    {
        $_SESSION['captcha_registered'] = FALSE;
    }

    //--------------------------------------------
    // Check to see if the captcha is in use, otherwise
    // we don't want to be able to see the image.
    //--------------------------------------------

    private function captchaIsRegistered()
    {       
        $sess_reg_id = isset($_SESSION['captcha_registered']) ? $_SESSION['captcha_registered'] : FALSE;
        $req_reg_id = isset($_GET['reg_id']) ? $_GET['reg_id'] : FALSE;

        return ($sess_reg_id && $req_reg_id && $sess_reg_id == $req_reg_id);
    }

    //--------------------------------------------
    // Check to see if a captcha has been passed already.
    //--------------------------------------------

    private function passedAlready()
    {
        $passed = FALSE;

        if(isset($_SESSION['captcha_passed']) && $_SESSION['captcha_passed'])
        {
            $passed = TRUE;
        }

        return $passed;
    }

    //--------------------------------------------
    // Hash some input with some salt.
    //--------------------------------------------

    private function hashInput($in)
    {
        return md5('d41d8cd98f00b204e9800998ecf8427e' . $in);
    }
}

And here is an example of it in use...

<?php

session_start();

require_once dirname(__FILE__) .'/captcha.php';

$captcha = new Captcha;

$act = isset($_GET['act']) ? $_GET['act'] : '';

switch($act)
{
    // output the captcha image
    case 'captcha':
    {
        $captcha->createCaptcha();
        break;
    }

    // validate the form input
    case 'check':
    {
        if($captcha->isValid())
        {
            echo 'code is valid';
        }
        else
        {
            echo 'code is invalid';
        }
        break;
    }

    // display a form
    case '':
    default:
    {
        echo '<html><head></head><body>';
        echo '<form action="index.php?act=check" method="post" enctype="multipart/form-data">';
        $captcha->drawCaptcha();
        echo '</form>';
        echo '</body></html>';
    }
}

If you want to download the simple CAPTCHA class and related files, click here.

Quick and Dirty Re-Design

posted on Dec 16, 2006 at 2:25pm with three comments

Okay, I did a quick and dirty re-design. It's not as humorous as the previous look with my distorted green face, oh well. I don't know exactly why I re-designed the blog but I just did.. maybe I'll start trying to write more often too... we'll see. :D

A quick note, one thing I've done in this design is use the Reset CSS that's part of the Yahoo User Interface. It's pretty kickass.

P.S. If you think it sucks, please say so.

k4BB version 1, circa 2004

posted on Dec 15, 2006 at 10:00pm with six comments and tagged with PHP, Applications

A few weeks ago I was approached to sell k4 Bulletin Board (version 2) and although the persons price wasn't what I had in mind, I never thought of offering to sell k4BB v1.

I think the reason why that thought never occured to me is because most people don't know that the k4BB that (if anyone) people know about is actually the second version. The first version of k4BB is actually more modern, with the exception of fancy javascript. It was built with PHP 5 whereas k4BB v2 is PHP 4.

The reason k4BB v1 was PHP 5 was that it was made to compete in the Zend PHP 5 Competition. It tied for 22nd place!

Another reason why people might not care about it is because it only has slightly more features than phpBB 2 and looks a lot like vBulletin 3... haha!

EDIT: I no longer host older versions of k4BB online and thus have removed the link.

Flash Tooltips

posted on Dec 8, 2006 at 6:24pm with no comments and tagged with Javascript, Actionscript

A more recent version, cross-browser compatible version of this code can be found in my flash tooltips using jquery article.

Now I'm 100 per cent sure that this has been done, but I haven't seen it so here goes... The other night I was thinking, "how can I get that cool OSX min/maximizing animation into tooltips?" Well, the obvious answer is with Flash.
Note: this does not work across all browsers and has not been extensively tested.

Before going about making this work, I needed to find out how all sorts of websites I've seen managed to make the background of the flash animations transparent. It turned out to be a very simple task; all that needed to be done was set the wmode parameter to "transparent" in the <param> and <embed> tags. Problem one solved.

Then I needed to make it so that I could have one flash animation deal with all of the tooltips. I would need to be able to send information from javascript to actionscript, and if you remember on one of my previous articles about usable flash, I did this with ExternalInterface. Problem two solved.

Finally, I needed to make some sort of animation. This I'm not so great at so I just did a small stupid thing. Oh well, problem three solved.

So, at the time of writing this the javascript isn't sending the info to actionscript properly, but I assure you is *was* working.. I will probably update once I get everything working nicely. So, onto some code...

//--------------------------
// The javascript
//--------------------------

// set a variable for our flash tooltip object
// and do some IE browser sniffing
var flash_tooltip, tt, ie/*@cc_on =1@*/;

// get the flash tooltip
if(ie)
{
    flash_tooltip = window['flashTooltip'];
    tt = document.all['flash_tooltip'];
}
else
{
    flash_tooltip = document['flashTooltip'];
    tt = document.getElementById('flash_tooltip');
}

// show a tooltip
var fTooltipShow = function(o)
{
    // if we have our flash tooltip
    if(flash_tooltip)
    {
        // show and position the div. This is rudimentary
        // position cause I'm lazy. More elaborate code would
        // need to be used in production code
        tt.style.display = 'block';
        tt.style.top = parseInt(o.offsetTop - 280) + 'px';
        tt.style.left = parseInt(o.offsetLeft + o.offsetWidth) + 'px';

        try
        {
            // toggle the motion tween in flash
            flash_tooltip.showTooltip('Hello this is text is from javascript.');
        }
        catch(e) { }
    }
};

// hide the tooltip
var fTooltipHide = function()
{
    // hide the div
    tt.style.display = 'none';
};
//--------------------------
// Part of the actionscript
//--------------------------

import flash.external.*;

var tt_text = '';

function showTooltip(str) 
{
    trace('Showing tooltip...');

    _root.tt_text = str;

    gotoAndPlay(2);
}

// let javascript call this func
ExternalInterface.addCallback("showTooltip", null, showTooltip);
//--------------------------
// The last part of the actionscript
//--------------------------

with(_root)
{
    if(tt_text && tt_text != '')
    {
        tooltip.tooltip_text.type = 'static';
        tooltip.tooltip_text.text = tt_text;

        trace('Set tooltip text to: '+ tt_text);
    }
}
stop();

So, if you want to go check the flash tooltips in action, click here.

Also, one problem I've been having with doing this is that when the javascript call actually works, it doesn't set the text of the tooltip until the tooltip is opened a second time. Hopefully someone can enlighten me with an answer to this in the comments... That or I will eventually find a hack that will work :P

next