Things for iPhone 1.1 Update Released

Monday, 18 August 2008

Speaking of Things for iPhone, it looks like the 1.1 update (which introduces syncing with the desktop version) has finally propagated to Apple’s servers and is available in the iTunes store.

At first glance, it looks good. Syncing worked flawlessly and was really easy to set up. The iPhone app itself is lightweight and pretty well-done. It’ll be interesting to see how well my iPhone version stays synced with my desktop version as I work throughout the day.

Update: The Things blog has been updated with an announcement about the Things for iPhone 1.1 release.

Also, after using syncing for the last day or so I can say that it works very well and I’m actually really impressed with the seamlessness of it all.


Parsing jQuery's sortable("serialize") Method with PHP (and symfony)

Monday, 18 August 2008

It looks like as of jQuery version 1.2.6 there’s no built in way to serialize a sortable object into a javascript array for use in ajax calls.

This example uses symfony-specific code, but you should be able to use this method in any type of php scripting. Here’s some example HTML from the template where we’re going to make the ajax call:

<ul id="item_list">
<?php foreach ($items as $item): ?>
  <li id="item_order_<?php echo $item->getId() ?>"><?php echo $item->getName() ?></li>
<?php endforeach ?>
</ul>

And here’s our ajax call from within the template:

$('#save_btn').click(function(){
    $.post("<?php echo url_for('collection/reorder') ?>", { item_order_str: $('#item_list').sortable("serialize") });
});

Unfortunately, this simply sends a string of key/value pairs to the server under the variable name item_order_str. It would be nice if there was a serializeArray method for sortables in jQuery just like there is for inputs. That way php would automatically create an array of values for us but, luckily, PHP has a function for dealing with variables in this format: parse_str

Here’s the example symfony action for dealing with this string:

public function executeReorder()
{
  $item_order_str = $this->getRequestParameter('item_order_str');

  // this will generate $output['item_order'] from the string sent by the ajax call
  parse_str($item_order_str, $output);
  
  // do something with item_order... this is just an example:
  foreach ($collection->getItems() as $item)
  {
    $item->setSortOrder(array_search($item->getId(), $output['item_order']));
    $item->Save();
  }
  
  $this->renderText('Success!');
  return sfView::NONE;
}

The reason the name of the outputted variable from parse_str is item_order is because each of the li tags has an id prefixed with item_order_ and jQuery automatically parses that attribute and creates the key/value pairs accordingly which turns it into something like this:

item_order[]=10&item_order[]=9&item_order[]=4&item_order[]=2&item_order[]=13&item_order[]=3&item_order[]=16

A couple of notes: You can change the way this string is generated by jQuery. See the documentation for more info. Also, the array_search method is handy for returning the key of an associated item. I’m using it above to find out where in the sorted array my item is so that I can update its sort_order field.

Update: I edited the parse_str usage in the above example so that all of the variables expanded from the function are captured in $output.


OmniFocus, It's Not You, It's me... Really

Saturday, 16 August 2008

Via Subtraction:

Not long ago I downloaded a new productivity application that recently emerged from a prolonged beta period. Finally, the 1.0 version had arrived, and I was eager to get my hands on it, play around with its features and see what it had to offer. But, for the life of me, I couldn’t figure out how to use it.

To be fair, this application, which shall remain nameless, had clearly been designed with great attention to detail. Its interface is not unattractive and its fit and finish is commendable; you wouldn’t be remiss in regarding it as a completely professional product.

However. I kept staring at it, and kept clicking on interface widgets and pushing buttons, but the more I explored, the less likely it seemed that I would ever really master it. I’m sure that its workflow makes sense, that with some investment in time, a user could realize some significant benefits from it. I just had a hard time thinking that one of those users would be me.

I have a sneaking suspicion that Khoi Vinh may be writing about OmniFocus, but with the copious amount of Mac GTD apps out there, one can never be sure. Regardless, I’d have to say that this pretty much sums up my experience with OmniFocus. I desperately feel like I should love it and that it could make me more productive, but at the end of the day it feels like I’m doing more trying to keep my organizer organized than I am “Getting Things Done”.

I was excited about the eminent release of OmniFocus for iPhone, but I just kept feeling like there was something very broken in the whole OmniFocus process to begin with. I enjoy the level of granularity that OmniFocus allows, but at some point it feels a bit trivial to have to specify things like, for example, whether or not I’m going to work on my “actions” in my “project” sequentially or in parallel–or maybe this project is simply a “single action list” that “contains loose, unrelated items instead of actions aimed toward the completion of a goal.” I mean, really, does this look like an “‘At-a-glance’ Quick Reference Chart” to you?

When I realized that I was getting in to the habit of writing To-Do items down on 3x5 cards so that I could first “organize” them before going through the mentally tedious act of officially putting them into OmmniFocus, I knew I had to try something else. OmniFocus’ GTD implementation is amazingly near-perfect. And that’s probably why it’s hard for me to use. You see, when it comes to really Getting Things Done, whatever system you’re using needs to completely disappear until you need it, and then it needs to disappear again after you’ve gotten what you need from it and you’ve gone back to work. With OmniFocus, there was just too much to get distracted by. Bells. Whistles. Context Mode. Planning mode. Perspectives. Grouping. Sorting. Filtering. The list goes on and on. I spent a lot of time deliberating what project should contain what and just setting everything up so that I could work that it took a long time to finally just get to work. On top of this, OmniFocus feels clunky and very un-Mac-Like for some reason. Well, mostly for these reasons actually.

I decided to try Cultured Code’s supposedly less sophisticated Things for a prolonged period of time. Being used to OmniFocus, I was almost immediately turned off by Things’ lack of… stuff. I mean, compared to OmniFocus:

OmniFocus Screenshot

… Things is positively devoid of stuff to do:

Things Screenshot

But maybe that’s just exactly what I needed? Over the last few weeks, I’ve found that I actually use Things and it’s almost transparent. There is a lot of power behind Things’ decision to use tagging instead of complex nested hierarchies like OmniFocus. The app seems to allow you to use it very simply as a To-Do list holder, or more powerfully as a more GTD-style user with nested tags for contexts. I find myself wishing there was a tad bit more structure in the way that Things offers projects, but I’ve always been able to replicate that bit of “more structure” with a good balance of Projects or Areas and nested tagging.

I’ve found that since I actually use Things, it’s much more reliable than OmniFocus for me. Not in any technical manner, but reliable in the sense that I actually use it to manage what I have to get done. If you’re a bit flustered with your current system, you might want to learn about Things and the The Morning Scrub.

I’m still a bit on the fence, but Things is steadily gaining ground. The deal-breaker may come down to OmniFocus for iPhone versus Things for iPhone (which has yet to offer sync with the desktop version).

Update: Things for iPhone has finally been updated and now syncs with the desktop version so I’ve put together a quick look at OmniFocus versus Things.


An Update to Dynamic Subdomains for Symfony 1.1

Friday, 08 August 2008

In a previous article, we saw how easy it was to enable dynamically loaded Symfony applications via subdomains. Well, the format for the controller files has changed slightly in Symfony 1.1 (most notably, the “SF_” constants have been banished) so here’s an updated version of the previous example index.php front controller:

<?php
require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');

// get the domain parts as an array
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(realpath(dirname(__FILE__).'/..').'/apps/'.$app))
{
  $configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
}
else
{
  $configuration = ProjectConfiguration::getApplicationConfiguration($app, 'prod', false);
}

sfContext::createInstance($configuration)->dispatch();

For the full details of how this file works, be sure to read the previous article.


Giving Your Body Tag Some Kick

Wednesday, 06 August 2008

A while back, my friend Dave Dash shared a Symfony CSS tip with me that I still use in almost every project I work on. It’s incredibly simple, yet powerful.

This tip (for me at least) was born from the need for a simpler way to target classes that might have different appearances depending on what section of a site is being displayed. For example, I might have several modules in my Symfony project that use a class called “container” but which have a different border or background color depending on what module or page they’re being used in. It would be helpful then if it were possible to have a mechanism for defining (or overriding) a specific CSS class attribute depending on the page or module you’re in. It would be even better if it were automatic so that there was no need to add HTML markup to a page just so we could target that attribute differently that in other pages.

Enter the body Tag

If you’re working with CSS and HTML, chances are, you use already use the body tag to set up some defaults like font-family or color that are used throughout your site. Let’s extend this a bit. The basic concept is simple: give the body tag an id attribute corresponding to each Symfony module. Then we’ll be able to do something like this:

.container { color: blue } /* site-wide declaration */
#news .container { color: red } /* "news" module declaration */

This could very easily be accomplished by printing the module name as an id attribute of the body tag in your main Symfony layout.php file:

<body id="<?php echo $sf_context->getModuleName() ?>">

Which might render something like this:

<body id="news">

With this addition every module will now have a root-level id declaration that you can use in your style-sheet to override styles on a per-module basis. We can even take this a bit further with Symfony by adding a declaration based on the action as well:

<body id="<?php echo $sf_context->getModuleName() ?>" class="<?php echo $sf_context->getModuleName() . '_' . $sf_context->getActionName() ?>">

Now we can use even more specific targeting like so:

.container { color: blue } /* site-wide declaration */
#news .container { color: red } /* "news" module declaration */
#news.news_headlines .container { color: green } /* "news/headlines" module and action declaration */

This all looks great so far… but what if you need to change this for further granularity? For example, what if you had another dynamic attribute that you were passing as part of the URL that you wanted to have specific styles for? As it stands, you’re stuck, but this is where Symfony’s slot mechanism comes to the rescue.

Here’s the custom slot helper we’ll be using:

function text_slot($slot, $default = null)
{
    if (has_slot($slot))
    {
        include_slot($slot);
    }
    else
    {
        echo $default;
    }
}

You can drop this into your own custom helper file in the lib/helper folder. This is simply a wrapper for Symfony’s built-in slot functionality to make it a bit easier to use in this situation. Once that’s done, our body tag can be changed to something like the following:

<body id="<?php text_slot('body_id',$sf_context->getModuleName())?>" class="<?php text_slot('body_class',$sf_context->getModuleName() . '_' . $sf_context->getActionName()) ?>">

This is basically the same functionality as before, with one important difference. Now, by default, we’re putting the contents of our declarations into Symfony slots body_id and body_class respectively. This means that at any time in a module we can override the default body id or class attribute to something more relevant if we need to. For example, in our “news” module and our “headlines” action, we might be using another parameter to differentiate between types of headlines. Let’s call this the “type” parameter. In the “news/headlines” template we’ll redeclare the body_class slot like so:

<?php slot('body_class') ?><?php echo $sf_context->getModuleName() . '_' . $sf_context->getActionName() . '_' . $sf_params->get('type') ?><?php end_slot() ?>

… which would render something like this if our type parameter was set to “local”:

<body id="news" class="news_headlines_local">

As you can see, you can extend this little trick into many different use-cases. It’s especially useful for navigations where you need to know what section you’re in so you can change the navigation appropriately. With this trick you can let the CSS worry about doing the heavy lifting without having to add logic to your templates. It’s powerful, light-weight and automatic.


Symfony 1.1 Stable Released

Thursday, 03 July 2008

In cased you missed it, the latest incarnation of the Symfony Framework has been released:

http://www.symfony-project.org/blog/2008/06/30/the-wait-is-over-symfony-1-1-released


Washed Out Images When Using "Save for Web" in Photoshop

Tuesday, 10 June 2008

I’ve gotten stung by this before, but since I recently purchased a new MacBook Pro the problem has reared its ugly head once more: Images shift color and get washed-out while using “Save for Web” in Photoshop CS3.

The problem is that Photoshop is an application that manages color profiles whereas the web, generally speaking, is not a color-managed environment. Safari, for example, does honor color profiles in embedded images, but it is definitely the exception and not the rule.

Much has been written about this topic elsewhere, so instead of reinventing the wheel I’ll just point you to some existing resources:

There’s a good overview of the phenomenon here: http://www.gballard.net/psd/srgbforwww.html

And some tips for setting up Photoshop here: http://www.gballard.net/psd/saveforwebshift.html

Also, although more geared towards Aperture, this Apple Support article is a good read also: http://docs.info.apple.com/article.html?artnum=302827

Personally I tend to prefer the 1.8 Gamma that ships as the default on Apple hardware, but using a 2.2 Gamma setting with a D65 target white point really does help make my images look more consistent across browsers and platforms.


PHP Sucks, But It Doesn't Matter

Wednesday, 21 May 2008

Via Coding Horror:

I’ve written both VB and PHP code, and in my opinion the comparison is grossly unfair to Visual Basic. Does PHP suck? Of course it sucks. […] If you sit down to program in PHP and have even an ounce of programming talent in your entire body, there’s no possible way to draw any other conclusion. It’s inescapable.

An interesting read. I can’t argue with the inherent problems listed in the article against PHP. It’s no wonder that frameworks like Symfony have emerged to help give structure and elegance to PHP.


Hiding Tables with Empty Body Elements With jQuery

Sunday, 20 April 2008

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();
});

Simple Image Submit Button Rollovers with jQuery

Friday, 18 April 2008

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.