Friday, December 19, 2008

Extending WebKit Web Inspector with Fun!

After being astonished by the code that won the ActionScript contest of 25lines.com I decided to see if it was possible to port it to Javascript. 

I created an html page to hold the game and then worked on the port. After some time, I had the game working on Firefox and Safari, which was more than enough. Then the crazy idea came into my mind... 

Why not to show how to extend WebKit Web Inspector with this game? -I can do a hello world example if you want.

So, to show you how to add a custom Panel to the Web Inspector, in a similar way in how Firebug allows to be extended, we will add the game. 

Here you can see an screenshot of the final project inside Web Inspector:


WebKit Web Inspector is easy to extend. First we need to find where it's installed. If you don't have it already, you can get it from here. In my Mac it resides in the following folder: 

/Applications/WebKit.app/Contents/Frameworks/10.5/WebCore.framework/Versions/A/Resources/inspector

There you will find the following files: inspector.html and inspector.js

Those files are the ones that we will modify to add our custom Panel with the game, but before we need to grab the code from the google project page -you will need to use svn-.

After you get the code, you will find two folders, inside jsport you can find the working HTML/Javascript example. Inside inspector there are the files for the panel. Copy game.css and  game.js to the folder where you have installed Web Inspector. Then edit inspector.html adding the following lines below the inspector.js include line:

 <script type="text/javascript" src="game.js"></script>
 <link rel="stylesheet" type="text/css" href="game.css">

Then open inspector.js and replace the following code:

    this.panels = {
        elements: new WebInspector.ElementsPanel(),
        resources: new WebInspector.ResourcesPanel(),
        scripts: new WebInspector.ScriptsPanel(),
        profiles: new WebInspector.ProfilesPanel(),
        databases: new WebInspector.DatabasesPanel()
    };

with the following one:

    this.panels = {
        elements: new WebInspector.ElementsPanel(),
        resources: new WebInspector.ResourcesPanel(),
        scripts: new WebInspector.ScriptsPanel(),
        profiles: new WebInspector.ProfilesPanel(),
        databases: new WebInspector.DatabasesPanel(),
        game: new WebInspector.GamePanel()
    };

What we did here was add our GamePanel to the hash of panels that Web Inspector will initialize.

Then If you see inside game.js you will find the following -I omitted some code for brevity-:

WebInspector.GamePanel = function()
{
WebInspector.Panel.call(this);
...
};

WebInspector.GamePanel.prototype = {
    toolbarItemClass: "scripts",

    get toolbarItemLabel()
    {
        ...
    },

    show: function()
    {
        WebInspector.Panel.prototype.show.call(this);
        
        ...
    },

    hide: function()
    {
        WebInspector.Panel.prototype.hide.call(this);
    }
};

WebInspector.GamePanel.prototype.__proto__ = WebInspector.Panel.prototype;

Those are the three basic parts of our Panel. First we declare it. Then we implement the interface of WebInspector.Panel and at the end we declare that our panel extends WebInspector.Panel

In the first part what we do is add a container for the game as a pre tag. This element is attached to this.element which is the base element of a WebPanel. By setting the id gameArea, we style the pre with the styles defined in game.css

Then on the second part I set the default css class for our panel as scripts. In this way, our button on the Web Inspector toolbar will have the same icon as the Scripts panel -if you want you can add a new style and define there a background image as you will do with CSS in a normal web page-.

For get toolbarItemLabel() we return Fun!. This will be the label for our Panel in the toolbar.

Then we implement show() where we draw the game. Here we can add the code that will initialize the contents of our Panel. Inside hide() We should implement all the logic to be run when the user switch to another panel, like removing unused objects, etc.

After we have everything in place. We restart WebKit, open the Web Inspector and enjoy our game.

NOTE 1: AFAIK, this technique should work for Google Chrome Web Inspector. The last time I checked the code, it was using the same code of WebKit.

NOTE 2: The drawback of this kind of extensions is that the next time you upgrade the browser, it will erase your Panel. Maybe in the future WebKit allows a more plug and play way of doing it. In this previous post I show some screenshots of  FireSymfony working inside Web Inspector.

NOTE 3: The original author of the ActionScript game is Marius Heil.

Tuesday, December 16, 2008

Symfony Design Patterns

Much has been said this days about how modern web frameworks interpret the MVC architectural pattern. I'm my case those articles made me rethink how I use a framework, specially Symfony.

This lead me to start a study on which patterns come into play while we develop a Symfony application. So while adding new features or refactoring existing ones I will know which class is in charge off doing the job.

Some of the patterns involved in Symfony are:
  • Front Controller
  • Command
  • Intercepting Filter
  • Context Object
  • Two Step View
  • Helper Object or View Helper
  • Table Data Gateway (i.e.: ArticlePeer.php)
  • Row Data Gateway (i.e.: Article.php)
  • Active Record
  • Single Table Inheritance
How do they interoperate? The Front Controller act as an entry point for our application. After it processes the request it will choose which Command should handle the request, in our case, one of our defined actions, that's why we have the execute prefix as a convention for the action
names. 

While the request is being processed Symfony filter chain will launch modifying the results accordingly. The idea of the Intercepting Filter pattern is to be possible to pre and post modify the request and the response without the need of change existing code. What's is a nice feature in Symfony is that we can add them directly on the filters.yml file making really convenient to use. Also we can specify filters that only apply for certain modules, making the process really flexible.

On the View side Symfony uses the Two Step View, so generally the result of our request will be decorated with a common layout to add consistency to our website. Here we can also use helpers, as they are explained in the Symfony documentation or add Helper classes. The Helpers classes can retrieve data from the Model, format data, or anything our application needs to work on the View side. With them we make our templates clear and free off script code, so it's easy to non developers to work on them.

Also Symfony allows to use specific view classes by module, adding extra flexibility in the way we can handle the response, let's say, to return JSON data.

As a way to keep a reference to the context, Symfony implement the -some guess please- Context Object pattern, which I won't talk about here.

Then we have the DAL, which in my case is powered by Propel. This ORM implements the Table Data Gateway and the Row Data Gateway patterns. As they name implies they act as gateways to our tables and rows, but because generally we add some domain logic inside them they start to act as Active Records.

A cool feature of Propel is that it implements the Single Table Inheritance pattern. This pattern is useful when we have a table for publications where we store Magazines and Books. If our table has a field acting as the type of the row, and in our application we treat them differently, whether if they are books or magazines, then this pattern let us work with Publication, Book and Magazine classes.

After this research I came with the following conclusion:
  • Use the controllers to handle user input data and choose which view to display.
  • If there, remove all business logic from the controllers.
  • Let the Model do it's job with the provided data. 
  • When handling data to the model, do it as an array of normalized data -or proper objects pertinent to our application-, the model shouldn't know about the request.
  • Let helpers or Helper Objects handle the data formating, avoiding script code inside templates.
  • Use Propel as a DAL and refactor towards a Model. This means to remove complex business logic from the Propel classes.
What helped my during this research where the following books, which I highly recommend.