Updated cross site ajax plugin for Prototype

The first post on my cross site ajax plugin for Prototype (1.5.0) was received with great enthusiasm. It was very nice to see my own work on the great Ajaxian website and the delicious front page. Since that post the quality of the plug-in has improved quite a bit. Especially cross browser compatibility improved. I made a test page to evaluate this; have a look to test your own browser. Furthermore some 14 screen shots show that the compatibility is good.

Here the new version: Download the cross site ajax plugin.

The syntax remains exactly the same:

new Ajax.Request(url, {
method: 'GET',
crossSite: true,
onLoading: function() {
//things to do at the start
},
onSuccess: function(transport) {
//things to do when everything goes well
},
onFailure: function(transport) {
//things to do when we encounter a failure
}
});

Cross Browser Compatibility

First of all thanks to Kris Kowal and Gary Gurevich for spotting the problems with Safari. Prior to the changes the plugin used three different methods to detect the loading of the script element. For Safari and Konqueror a sequential script technique was used. This technique has now been replaced with the polling technique, like in COWS. So the following three solutions are used.

1.) For IE it used its proprietary onreadystatechange event
2.) For Safari and Konqueror it uses the polling technique
3.) For Firefox and Opera it uses the standard onload event

Timing problems

In addition to the cross browser compatibility problems I noticed another complication. In Prototype 1.5.0 the onLoading, onSuccess etc., are generally fired by running the onreadystatechange function. However this is not the case for a transport status below 2. Actually the onLoading event is triggered by a delayed function after the open command. I didn’t realize this initially. However if you have a script which loads rather fast it will result in onSuccess executing before onLoading. This issue was fixed by calling respondToReadyState directly.

Points of Improvement

Firstly the current implementation detects browsers, not capabilities. This might create problems with future or buggy versions of browsers.
Secondly the usage of a global variable to indicate transport status makes it impossible to handle simultaneous requests nicely.
Thirdly the script currently does not clean up the script nodes.

If these points turn out to be troublesome, I will use a modified version of COWS for the transport aspects.

Conclusion

The new version of this plug-in is widely cross browser compatible. Currently it is suitable for a large variety of applications. It is however not yet ready to deal with simultaneous requests.

Javascript & Prototype & Web Development tschellenbach 07 Nov 2007 1 Comment

Google at the Campus

GoogleI was so surprised to see a stand from Google at the Erasmus campus today. Usually we don’t see too many hi-tech companies visiting a business university. Google has an amazing brand name amongst students, this was very clear from the crazy amount of attention they got. The staff from Google was representing their Dublin headquarters. Working benefits seem pretty amazing over there.

Good job Google! (why don’t other tech companies do this?)

The movie they recommended us to watch:

Events tschellenbach 07 Nov 2007 3 Comments

Using php to dynamically generate conflict free css

This little blog has been getting a lot of coverage lately thanks to a write up by Ajaxian. Developing with Symfony is great and always gives you a lot to think and write about.

For my new product I was having a CSS conflict. This tends to happen when you include your own html and css into someone else’s website. For instance if you have a widget as such:

html

<div id="mywidget">
<h1>My hello world widget</h1>
</div>

css

H1 {
color:green;
font-size:20px;
}

The solution to this problem is quite straightforward, you simply specify your css selector as div#mywidget H1. However, what if you want to allow people to customize the looks of your widget. Now you could off course ask them to include the div#mywidget part, but chances are this will give problems.

Since I was already using the great sfCombineFilterPlugin an easy solution was available. (If you didn’t use the sfCombineFilterPlugin yet, go check it out immediately. Also have a look at Yahoo’s Yslow)

The sfCombineFilterPlugin uses php to gzip, minify and cache your css and javascript. Here is how to extend that behavior to include the #mywidget specification. (Assuming you have sfCombineFilter installed)

Step 1: open your .htaccess

Just below the RewriteBase instruction add:
# if we are retrieving javascript or css
RewriteRule ^css/packed/prepend/(.*\.css) /sfCombineFilterPlugin/combine.php?type=css&prepend=1&files=$1
RewriteRule ^css/packed/(.*\.css) /sfCombineFilterPlugin/combine.php?type=css&files=$1
RewriteRule ^js/packed/(.*\.js) /sfCombineFilterPlugin/combine.php?type=javascript&files=$1

Step 2: add this class to the top of web/sfCombineFilter/combine.php

Partly based on CSS parser class.

class prependCss
{

    public static function prependCssString($str) {
        // Remove comments
        $str = preg_replace("//*(.*)?*//Usi", "", $str);

        $parts = explode("}",$str);
        if(count($parts) > 0) {
            foreach($parts as $part) {
                list($keystr,$codestr) = explode("{",$part);
                $keys = explode(",",trim($keystr));
                $newkeys = array();
                if(count($keys) > 0) {
                    foreach($keys as $key) {
                        if(strlen($key) > 0) {
                            $key = (!strstr($key, '#mywidget')) ? '#mywidget'.$key : $key;
                            $newkeys[] = $key;
                        }
                    }
                    $keystr = implode(’, ‘,$newkeys);
                }
                if(!empty($keystr)) //needed for spaces behind last }
                $rules[] = $keystr . ” {” . $codestr . “}”;
            }
            $prependedCss = implode(”n”, $rules);
        }
        //
        return $prependedCss;
    }

    public static function prependCssFile($filename) {
        if(file_exists($filename)) {
            return self::prependCssString(file_get_contents($filename));
        } else {
            return false;
        }
    }
}

Step 3: hack around in combine.php

below $minify_js add:
if($_GET['prepend']==1)
$prepend = true;

change the stuff below this comment to:
// Get contents of the files
$contents = '';
reset($elements);
foreach ($files as $path) {
if($prepend && $_GET['type'] == ‘css’) {
$contents .= “\n\n” . prependCss::prependCssFile($path);
} else {
$contents .= “\n\n” . file_get_contents($path);
}
}

And finally just change your urls to css/packed/prepend/yourcss.css (if you are using relative paths in your css you might need to add an ../)

Conclusion

Using this technique your css will load without any problems in third party sites. This comes in very useful when creating widgets or greasemonkey scripts.

Css & PHP & Symfony & Web Development tschellenbach 29 Oct 2007 2 Comments

Introducing a cross site ajax plugin for Prototype

Update: there have been some improvements to this plugin. Have a look at this post regarding the update. Thanks for the feedback!

After some days of hard labor, I finished my cross site Ajax plugin for the prototype framework 1.5.0. (Download Plugin Here) While working on a new product of mine I realized I needed cross site Ajax, which is not supported in the Prototype framework.

During cross site Ajax requests the standard XmlHttpRequest approach breaks down. The problem is that XmlHttpRequest is bounded by the same site policy. Fortunately the script tag has the freedom to do as it pleases.

Some other libraries such as dojo and jquery do support the script method for doing Ajax. There is even a project on source-forge called COWS, which is dedicated to this purpose. This plugin is an adaptation of the jquery plugin, but modeled to look like an XmlHttpRequest. The credits of the original code go to Ralf S. Engelschall , which amazingly achieved to make it nicely cross browser compatible. This plugin supports FF, IE, Safari, Opera and Konqueror, but has only been properly tested in FF and IE.

Prototype’s structured way of doing Ajax was my main reason to choose the prototype framework. Furthermore it is also included in the great Symfony framework. In Prototype Ajax requests are written like this:

new Ajax.Request('myurl', {
method: 'GET',
crossSite: true,
parameters: Form.serialize(obj),
onLoading: function() {
//things to do at the start
},
onSuccess: function(transport) {
//things to do when everything goes well
},
onFailure: function(transport) {
//things to do when we encounter a failure
}
});

The cross site plugin simply allows you to do Ajax cross site, by specifying crossSite: true (line 3 of the above example). I will now cover some technical aspects of the plugin, but if you just want to start using it simply skip to the plug and play instructions below.

How it works - Technical Aspects

This plugin uses the dynamic script tag technique. This basically means that we insert new <script> tags into the Dom. Since this script tag is not bound to the same site you can send and receive data in the Ajax way. In its most basic form the javascript would be like this:

this.node = document.createElement('SCRIPT');
this.node.type = 'text/javascript';
this.node.src = 'http://www.serversite.com';
var head = document.getElementsByTagName('HEAD')[0];
head.appendChild(this.node);

In order to make it very easy to use with Prototype, or any other library for that matter, I decided to mimic the functions of the XmlHttpRequest. This is easily achieved by implementing the functions open, send and onreadystatechange. Furthermore I needed to specify the variables readyState and status in order to support prototype’s onLoad, onSucces and onFailure.

Detecting the loading of a script element is not that easy. Browsers such as Safari and Konqueror simply give no indication of this at all. One common solution to dealing with this is to use an interval and perform a check. The work at TrainOfThoughts however takes the beautiful approach of inserting a helper script. This exploits the fact that the dynamically added scripts are executed in sequence. This approach makes the plugin nicely cross browser compatible.

Detecting failure is rather cumbersome for the script technique. As far as I know there is no way to read the headers on the incoming file, or to inspect its contents through javascript. This leaves us with the rather blunt approach of setting a global variable using the server output. It works, but it could be prettier.

Plug and Play implementation instructions

Firstly you need to load the plugin javascript file: download cross site ajax plugin for the prototype framework 1.5.0.

Secondly you need to change your regular prototype Ajax request, by ensuring that you instruct it to use the crossSite and GET methods, as such (observe line 2 and 3):

new Ajax.Request(baseurl+'/comment/giveratingjs', {
method: 'GET',
crossSite: true,
parameters: Form.serialize(obj),
onLoading: function() {
//things to do at the start
},
onSuccess: function(transport) {
//things to do when everything goes well
},
onFailure: function(transport) {
//things to do when we encounter a failure
}
});

Thirdly you might need to rewrite some of your javascript code to accommodate the instant execution of the scripts.

Fourthly, if you want to use onFailure for any of your scripts you need to send some javascript instructions back from the server. You need to do this both on success and on failure (since a global variable is used). This is the javascript variable you need to set:

'var _xsajax$transport_status = 200;' Or
'var _xsajax$transport_status = 404;'

Symfony specific tips

Symfony detects if it receives a XmlHttpRequest and automatically turns off your debug bar and layout. Unfortunately it is not so kind to the script technique. So in your action you need to do this manually:

sfConfig::set('sf_web_debug', false);
$this->setLayout(false);

Furthermore your validation files by default only look at POST variables (this one tricked me). To instruct them to look at both, simply mention

methods: [post, get]

at the top of your validation.yml

Since you will probably want to send html to the browser, I would suggest you put this little function (found in the symfony escape helpers) in your toolbox.

public static function esc_js($value) {
return addcslashes($value, "\0..\37\\'\"\177..\377\/");
}

Conclusion

The dynamic script tag technique opens up a wide range of possibilities. Personally I am very glad with the results and would like to thank Ralf S. Engelschall for his superb cbc work. Unfortunately I didn’t include an example this time. You will have to wait for the products’ launch:). Comments and improvements are always appreciated. Enjoy your cross site scripting!

Javascript & PHP & Prototype & Symfony & Web Development tschellenbach 25 Oct 2007 31 Comments

Symfony & Gravatars - easy implementation

Lets start with a small explanation. Gravatars are so called ‘globally recognized avatars’. Basically it is an open directory for avatars. If you didn’t get one yet, feel free to head over to www.gravatar.com.

The implementation of gravatars for your site is already extremely easy. However if you are fortunate enough to be using Symfony, it becomes a real piece of cake. Quite a few people already use gravatars, including the Symfony blog. This number will probably increase quite a bit, given the recent purchase of the company by Automattic.

Gravatars are attached to an email address. Lets assume your program is already setting and getting the email addresses. All you need to get up and running with Gravatars is these simple 3 steps.

1. Extend your setEmail to do setGravatar as well

(somewhere in lib/Comment.php)

function setEmail($input) {

$this->setGravatar(md5($input));

parent::setEmail($input);

}

2. When getting the Gravatar, retrieve the full image code

(somewhere in lib/Comment.php)

function getGravatar() {
$md5email = parent::getGravatar();
$size = 45;
$rating = 'R'; // possible values [ G | PG | R | X ]
$url = ‘<img width=’.$size.’px height=’.$size.’px class=”gravatar” src=”http://www.gravatar.com/avatar.php?gravatar_id=’.$md5email.’&rating=’.$rating.’&size=35″ alt=”gravatar” />’;
return $url;
}

3. In your view template

Simply do: $comment->getGravatar();

DONE!

Have a look at the result:
Gravatar implementation

PHP & Symfony & Web Development tschellenbach 19 Oct 2007 5 Comments

We love FireFox, 76%

A few days ago I noticed a bug in the Digg-this plugin for my blog. The javascript with this plugin was causing errors with Internet Explorer. The problem must have been around for a week or so, before I noticed it. Now my site doesn’t get too many visitors, but I would have expected someone to complain about it.

Looking in my stats it becomes clear why no-one has. Though only a small and insignificant sample, the traffic at my blog is strongly IE averse. Just have a look at the stats:

Overview of important browsers

Pie Chart of Overview

PHP & Symfony & Web Development tschellenbach 11 Oct 2007 5 Comments

How the Rubicon Project is innovating Google’s online advertising business

Update: In the comments the founder of the Rubicon Project indicated that they will not be working directly with advertisers. My misperception was based on the TechCrunch write-up. The article below is based on the assumption that they would approach both sides of the problem. Since this is not the case, take this into account when reading the post.

Rubicon

The Rubicon Project is by far the most promising, exciting and revolutionizing startup of the moment. Their business has the potential to completely change the entire web-advertising industry.
They are trying to become an intermediary (of intermediaries) by offering:

  • Publishers: automated ad revenue optimization between networks
  • Advertisers: a central point to setup online advertising (wrong)

They display it in a graphical way in their beta overview video.

Rubicon Industry Value Chain Position

The Changing Industry

An intermediary like Rubicon profoundly changes the competitive landscape for ad networks such as Google Adsense.

Currently startups in the ad network business face a so called chicken and egg problem. Even if you have a technically great product, you will still need advertisers to get publishers and vice-versa. The advertising and sales efforts required in this industry are substantial and present a major obstacle for new companies to enter the market.

Now imagine a new market where Rubicon is the intermediary. Any new advertising network could instantly get its product of the ground by joining Rubicon. No longer are millions in capital needed for sales and promotion. When a startup is able to outperform the market incumbents, in terms of matching advertisements with visitors, it has its road to success paved. Not only does this benefit those startups, it also gives Rubicon’s clients access to the best performing advertising solutions.

Google was the first to get the match between ads and viewers somewhat right. Eager to be next in line seems to be the currently hyped Facebook, with its personalized advertising. My impression is that the current solutions are tremendously under-performing. When an intermediary, be it Rubicon, establishes a position for themselves, innovation in online advertising will boom.

An intermediary like Rubicon changes the balance between required core competences for ad networks. The focus moves from sales&marketing to clever algorithms, extensive data and intelligent models to match the advertisements with the viewers. The market’s changes effectively open up this 27 billion industry to a far larger array of entrepreneurs.

Google’s Perspective

Rubicon effectively lowers the barriers to entry into this Google Adsense dominated market. In the new market situation it will be harder for Google to stay ahead of its competition. This fact makes you wonder, will Google move to the Rubicon position or remain where it is. Or in other words, will it defend its sales competence or its ability to match visitors with advertisements?

If Rubicon succeeds we will soon have some very interesting entrepreneurial possibilities ahead. I certainly wish them the best of luck. Given the experience and progress of the founding team, I think they have a good shot.

@Frank, Good luck starting your sixth company!

Recommended Readings:

Techcrunch coverage of Rubicon

Rubicon about page

Blog of Frank Addante (I subscribed to it, great readings)

http://www.founderblog.com/2007/10/what-is-rubicon-project-part-ii-solving.html

http://www.founderblog.com/2007/10/what-is-rubicon-project.html

Rubicon beta overview video

Business & Web Development tschellenbach 08 Oct 2007 4 Comments

« Previous PageNext Page »