20
Apr

Hiding Tables with Empty Body Elements With jQuery

I needed a quick, client-side way to hide empty tables. I thought that something like this should have done the trick with jQuery:

$(document).ready(function(){
  $('tbody:empty').parents('table').hide();
});

Basically, we’re just finding all tbody elements that are empty using the :empty filter and hiding the parent table element.

But, as it turns out, the tables that were being generated had whitespace inside their tbody elements, so they weren’t technically empty since :empty considers text nodes, even whitespace, as non-empty (as per the jQuery documentation).

One solution is to simply trim the whitespace from inside the tbody elements before checking if they’re empty:

$(document).ready(function(){
  $('tbody').each(function(){
    $(this).html($.trim($(this).html()))
  });
  $('tbody:empty').parents('table').hide();
});
18
Apr

Simple Image Submit Button Rollovers with jQuery

Have you ever wanted a simple rollover technique with a form submission button? Something like this:

rollover up state

rollover over state

… without having to resort to a complicated mess of javascript form submission and cross browser compatibility issues?

With jQuery it’s really easy. All you need to do is include a standard image form submission tag, like so:

<input type="image" name="submit" id="submit" src="enter.gif">

… and add the hover behavior to the submit input tag. Something like this:

<script type="text/javascript" charset="utf-8">
    $(document).ready(function(){
        $('#submit').hover(
            function(){ // Change the input image's source when we "roll on"
                $(this).attr({ src : 'enter_over.gif'});
            },
            function(){ // Change the input image's source back to the default on "roll off"
                $(this).attr({ src : 'enter.gif'});             }
        );
    });
</script>

This is just a simple example, but you could definitely spruce it up if you needed something more elaborate by adding/removing CSS attributes and the like. Check out the jQuery documentation for more on the hover event.

The benefit of using this method is that the form will be submitted using the standard form submission action, rather than by calling submit() directly through javascript like with other techniques I’ve seen. This has the added benefit that standard form handlers like “onSubmit” will still work whereas, using the submit() method directly will bypass these handlers.

05
Apr

MAMP Might Break Your Time Machine Backups

After many frustrating hours of trying to figure out why my Time Machine backups were failing (actually, they’d stall after being really close to finishing… they’d just sit there forever) I finally narrowed it down to one set of files: a local symfony project.

At first, I thought that perhaps something weird was happening with the symlinks that I use to link a Symfony’s “/sf” directory to the Symfony library’s web directory, but upon further investigation it boiled down to the Symfony cache files. I just happened to notice that for some reason the cache files were being generated with the user/group combination “mark/nogroup”… “mark” is my login name, so this seemed fine, but “nogroup” seemed a bit odd. After changing the group associated with these files to “staff” like the other files in the project, the Time Machine backup worked fine. So, apparently Time Machine somehow chokes on the “nogroup” group.

Now, I had just switched to using MAMP Pro instead of rolling my own Apache/PHP/MySQL because I wanted my setup to be completely portable from one machine to the next (with little effort), so I was a little bit disappointed with this, but there’s an easy fix. Open up MAMP Pro and notice the “Run Apache/MySQL server as user” dropdown:

mark/mark

I had left this as the default which uses my username “mark/mark” but since there was no “mark” group, it was using “nogroup” and causing Time Machine to explode. So, I changed this to use “www/mysql” like so:

www/mysql

…and now the cache files are being generated with the “www/www” user/group combo and Time Machine backs up as it should.

Although this fix is not necessarily Symfony specific (it really has to do with any file that’s written by the MAMP apache server process), it may save someone some trouble if they’ve got a similar Symfony set up going and they can’t figure out why their Time Machine backup isn’t working as advertised.

P.s. You can find files with a certain group by running this command in the terminal:

 find . -group nogroup

This will find any files in the current directory that belong to the “nogroup” group.

07
Mar

OmniFocus Coming For The iPhone

Finally:

A few hours ago, Apple announced the iPhone SDK! We’re still trying to download it (Apple’s servers are overloaded), but it looks like it has all the features we were hoping for.

We’re eager to get started on our first iPhone app—and, yes, that first app will be… OmniFocus.

… via the Omni Mouth Blog.

04
Mar

Dynamically Loading Symfony Applications Via Subdomains

The concept of “Symfony applications” is sometimes confusing for those new to Symfony, but they can be a very powerful way to break up the functionality of a project. The classic example for this separation is the “front end” and “back end” or “back office” applications. The idea here being that the front-end will be what the normal user sees and he back-end being what the administrator of the site uses to maintain the site through an admin interface. Since these two applications are in the same Symfony project, they can share certain common elements (such as database configurations and schemas), but they can have their own modules and templates. In essence, each application can provide a different “view” of the same data.

If you only have one application, you’re probably content with the way Symfony automatically sets up your first application to use the default index.php controller, but what happens when we add applications? You’ll notice that Symfony adds controllers for each application in the web directory, so you might end up with a set of files like this:

web/index.php
web/frontend_dev.php
web/backend.php
web/backend_dev.php

… assuming you have a “frontend” and “backend” application. Also, note that since we created the “frontend” application first, Symfony automatically used the index.php filename for what would have been called frontend.php.

So, this means that when we view our site at www.mysite.com/ the frontend application is loaded and when we view www.mysite.com/backend.php we get the backend application. This is fine for most uses, but what if we could streamline this a bit so that applications were loaded based on a sub-domain?

That’s exactly what we’ll do.

Setting It Up

First, we’ll rename our index.php file to frontend.php (this will be useful later on) and create a new, blank index.php controller in its stead. Our controllers should now be as follows:

web/index.php (blank!)
web/frontend.php
web/frontend_dev.php
web/backend.php
web/backend_dev.php

Open up the newly created index.php file. We’re going to add a little bit of our own logic here so that Symfony dynamically loads the correct application based on what’s set in the sub-domain:

<?php
// define the standard symfony environment constants
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_ENVIRONMENT', 'prod');
define('SF_DEBUG',       false);

// get the domain's parts
list($tld, $domain, $subdomain, $subdomain2) = array_reverse(explode('.', $_SERVER['HTTP_HOST']));

// determine which subdomain we're looking at
$app = ($subdomain == 'staging') ? $subdomain2 : $subdomain;
$app = (empty($app) || $app == 'www' ) ? 'frontend' : $app;

// determine which app to load based on subdomain
if (!is_dir(SF_ROOT_DIR.'/apps/'.$app))
{
    define('SF_APP','frontend');
}
else
{
    define('SF_APP',$app);
}

require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.
'config'.DIRECTORY_SEPARATOR.'config.php'); // Should be on one line, with the above text
sfContext::getInstance()->getController()->dispatch();

Note: I generally use a “staging” sub-domain for testing on the production server before pushing to the actual production site, so you’ll notice that I check for this sub-domain separately. This is so I can use something like backend.staging.mysite.com and still load the correct back-end application. Feel free to simply remove that step if you want your staging sub-domain to point to an actual Symfony application for some reason.

This controller file should look familiar to you as it’s just a bit of a reworking of a default controller. We simply added some logic to detect a subdomain, check if it’s a valid application and then, if it is, we have Symfony load it dynamically. You’ll notice that instead of using frontend.mysite.com for the front-end we simply check for www or an empty sub-domain field and set the application to “frontend” by hand. If your default application name is something different, you’ll have to change this.

Some Notes About This Method

Firstly, you should know that this method will only work if you have the correct sub-domain addresses as DNS records pointing to your server (or if you have wildcard DNS set up so that anything.yoursite.com is sent to your server).

Also, for clean URL rewriting on each sub-domain, don’t forget to set no_script_name to “on” in each application’s settings.yml file for the “prod” environment:

prod:
  .settings:
    no_script_name:           on

… this allows our sub-domains to use URLs in the form backend.mysite.com/module/action instead of backend.mysite.com/index.php/module/action. This is usually disabled by default for all but the first application you create (in our case, frontend).

Also, remember that file we renamed to frontend.php? Well, we did that for a reason. In previous articles, I’ve stated why I like setting up local development environments, so I won’t go into detail here, but you’ll notice with this method we can still access the frontend application directly by typing something like mysite.dev/frontend.php which may be useful for testing purposes. You could even create an index_dev.php that loads the dev environment for our custom front controller if you needed to. Note though, that I’m specifically not including support for a “dev” sub-domain (or anything that loads the dev environment) since the dev controllers should not live on your production server!

So What?

You might be wondering what all the fuss is about. Why go through the trouble of setting up these dynamic sub-domains linked to Symfony applications? Well, as with many things, necessity is the mother of invention: for a project I’m working on right now, I needed to add an iPhone/iPod Touch specific site to an existing Symfony project. A separate Symfony application makes perfect sense since it will simply be a different view for the same data. It would have been easy enough to just use the default mysite.com/iphone.php controller, but I wanted it to be a separate sub-domain because it just feels better separating it out that way, and you get nice clean URLs and an easy to remember iPhone-specifc domain name to boot.

In another article, I’ll be publishing some tips for incorporating this method into a site that automatically detects an iPhone or iPod Touch and reacts accordingly.

28
Feb

sfLucene Quick Tip 2: Automatic Re-indexing

Here’s another quick tip when using sfLucene. As you’ll notice when you start using the plugin, in order to index all of your results (after you’ve set up all of your search.yml files) you need to run a pake task to build the index:

symfony lucene-rebuild frontend dev

This will (re)build a search index for the “frontend” application using the “dev” environment. This is great, but this means that the search index is only up-to-date when you run this command. It would be a pain to have to do this manually whenever we wanted our search index updated, so we’ll use a cron job to automate the re-indexing.

Since I’m using a Media Temple Grid Server for this project, I’ll simply use their control panel to enable the cron job. Media Temple actually has a great KnowledgeBase Article on how to setup and configure a cron job through your grid server control panel, so you can see that article for more info on setting this up using a Grid Server.

For those of you using another service or your own brand of linux/unix, there are instructions all over the web that you can use to follow the same methodology, but basically all you have to do is run the same “lucene-rebuild” command on an interval of your choosing. You also have to use the full path to your symfony project’s root folder where the symfony command is located. For a Grid Server, this would be something like this:

php5 /home/XXXXX/domains/example.com/symfony lucene-rebuild frontend prod

…where XXXXX is your Grid Server site number and “example.com” is your domain name. Note the use of “php5” before the path of the actual command. This is required if you’re using a Grid Server as by default it will try to run this script using the php4 CLI. Also, your path my vary depending on how your files are setup in the domain folder itself. I set mine to index once a day, but if your site has a high data turnover rate, you may want to make it index more frequently so that results are fresh. You’ll also notice that I’m using the “prod” environment since that’s what the site is using in production.

Update 2008-03-04: Looks like I had propel.builder.addBehaviors set to false in my propel.ini so the behaviors weren’t being built into the model classes when I was rebuilding them. So new and modified objects are now correctly and automatically re-indexed. I still use this cron job though, albeit less frequently, to update the search index for static files that are being indexed through the Action indexer.

17
Feb

Pake Task Locations and File Naming Conventions

I was writing a custom Pake task for a project and was completely stumped as to why the task wouldn’t show up from the command line. Since I only usually write pake tasks for plugins, I was completely confused as to why a file named tasks.php in myproject/data/tasks wouldn’t register its tasks. It turns out that symfony has very specific file naming conventions for Pake tasks depending on where you’re writing them.

Symfony tasks located in:

sf_symfony_data_dir/tasks (the symfony library’s tasks) must have an sfPake*.php naming scheme

sf_data_dir/tasks (your project’s tasks) must have a myPake*.php naming scheme

… and of course plugin tasks located in sf_root_dir/plugins/*/data/tasks can use *.php (which is probably why I’ve never run into this little conundrum before.

You can check which tasks are registered in your project by running:

symfony -T

… at the command line.

12
Feb

sfLucene Quick Tip: Displaying Categories In Model Results

If any of you are using the great sfLucene Plugin, here’s a quick tip on making your search results a bit more intuitive.

This really only applies to model results (not action results) as there’s an option to add a category to each model. Here’s an example search.yml file that would go in your project’s config folder:

MyIndex:
  models:
    Podcast:
      fields:
        id: unindexed
        title:
          type: text
        description:
          type: text
      title: title
      description: description
      validator: isPublished
      categories: [Podcast]

You can see that I’ve added the category “Podcast” to the model’s definition. Now, you can override the plugin’s default display for model results by creating a module in your project named “sfLucene” and overriding the default _modelResult.php partial (in the sfLucene/templates directory) with something like this:

<?php echo link_to($result->getsfl_category() .' &raquo; '. highlight_keywords($result->getInternalTitle(), $query, sfConfig::get('app_lucene_result_highlighter', '<strong class="highlight">%s</strong>')), add_highlight_qs($result->getInternalUri(), $query)) ?> (<?php echo $result->getScore() ?>%)
<p><?php echo highlight_result_text($result->getInternalDescription(), $query, sfConfig::get('app_lucene_result_size', 200), sfConfig::get('app_lucene_result_highlighter', '<strong class="highlight">%s</strong>')) ?></p>

The only thing we’re really changing from the default template is that we’re adding this:

$result->getsfl_category() .' &raquo; '.

…to the beginning of the call to the link_to() helper. The category is stored automatically by sfLucene in this field in the lucene document, so we have access to it in the template. This new partial effectively inserts the category in front of the “title” field (along with a “»” for separation) while building the link, which produces something like this:

Podcast » My Great Podcast Title

As you can see, if you have several model definitions this comes in handy while looking at a list of results since you’d get a nice listing that shows which category each link belongs to:

Podcast » My Great Podcast Title

Blog » This Is Some Article

Podcast » Another Awesome Podcast

… instead of just a bunch of titles in a list.

29
Jan

sfZendPlugin Alternative to Installing the Zend Framework

I often use the Zend Framework from within my Symfony applications and until recently, I’ve been using the sfZendPlugin to import and autoload the library. You’ll notice that this plugin has recently been axed leaving some people confused as to how to use the library in their projects.

Adding the Library

First, you’ll need to add the Zend Framework library. Since I like to link libraries via subversion, I’ll link the Zend Framework to my project’s lib/vendor directory. Open up a terminal to your symfony project’s root directory and type this:

 svn propedit svn:externals lib/vendor/

Note: You may have to create the vendor directory (and use svn add to add it to your repository) if you don’t already have it.

This will bring up a text editor where you can add a link to an external library via subversion. Type this in:

Zend http://framework.zend.com/svn/framework/tag/release-1.5.0PR/library/Zend

… and save and close the file. This will link lib/vendor/Zend to the 1.5.0 PR release of the Zend Framework. You can, of course, use another version if you prefer. Of course, you don’t have to link this via subversion. You can simply download and copy the Zend Framework into this folder for the same effect, but using subversion is my preferred method.

Linking the Library to Symfony

All you need to do now is activate symfony’s autoloading of the library files. Open up your application’s setting.yml file (mine is at apps/frontend/config/settings.yml) and add the appropriate settings. It should look something like this:

all:
  .settings:
    zend_lib_dir: %SF_ROOT_DIR%/lib/vendor
    autoloading_functions:
     - [sfZendFrameworkBridge, autoload]

Make sure to clear your symfony cache by using:

./symfony cc

That’s it! You should be able to use any of the Zend Framework classes from within symfony without using a require statement.

Edit: As per Gerald’s comment below, the zend_lib_dir path was listed as “zend_lib_dir: %SF_ROOT_DIR%/lib/vendor/Zend” but should not included the “Zend” folder name at the end as sfZendFrameworkBridge.class.php actually adds this automatically.

19
Jan

Quick Tip: Symfony and the iPhone WebClip Bookmark Icon

According to the Apple docs, you need to create a 57x57 PNG, name it “apple-touch-icon.png” and upload it to the root of your web directory in order for the iPhone to find and use your icon.

What I found more useful though, was that this can be overridden using a link attribute, similar to the way favicons are handled. This allows us to use symfony’s standard “images” directory to house the icon. So, the head of our layout.php could be updated with the addition of:

<link rel="apple-touch-icon" href="<?php echo image_path('apple-touch-icon') ?>" />

… which would make it look something like this:

webclip example

Note: as this is a post on symfony, I’m using symfony’s built-in image_path helper, but this can be simply href="/images/apple-touch-icon.png" if you’re not into that.

Taking it a Step Further

The way the real power of symfony gets leveraged is when you want to have different icons for different sections of your site. Let’s say you wanted to have a different touch icon related to every different module in your symfony project. Symfony makes this really simple to do. Replace the existing link tag we made earlier with something like this:

<?php if (file_exists(sfConfig::get('sf_web_dir').'/images/webclip_icons/'.$sf_context->getModuleName().'.png')): ?>
    <link rel="apple-touch-icon" href="<?php echo image_path('webclip_icons/'.$sf_context->getModuleName()) ?>" />
<?php else: ?>
    <link rel="apple-touch-icon" href="<?php echo image_path('webclip_icons/apple-touch-icon') ?>" />
<?php endif ?>

Basically, this code just checks to see if there’s a Web Clip icon with the same name as the current module in /images/webclip_icons and if it finds it, it links to that one instead of the default one.

Of course, this is just an example of how powerful and easy this is. This idea could be extended in many different ways.

More Info

You can read more about iPhone development at Apple’s iPhone Dev Center. One of the more useful pages on that site is the one on Designing Content.

Also, as noted elsewhere, you seem to get a crisper icon if you use a 60x60 image at 72 DPI.