Entries tagged “programming”

tunneling and mysql

I have a mysql server for reviewsby.us that requires tunneling, and after establishing a tunnel like so:

Read full post
Why code rewrites are often coupled with redesigns

I’ve done some rewrites of code, and they usually are coupled with redesigns.

Read full post
postgresql and python in osx

I want to start dabbling with postgreSQL on OS X. After several SVN checkouts, binary packages, etc, I’ve realize the easiest path to success is just installing from fink unstable.

Read full post
Optimization heuristics

I decided to play version control private-eye today when my coworker mentioned that we make a system call to check the time several times per request on a few specific pages.

Read full post
How to code... or do anything.

Imagine the most ideal way for something to work, assume that it’s going to work, and then make it work like that.

It’s amazing how much bad coupled code exists for no good reason.

Read full post
How to code... or do anything.

Imagine the most ideal way for something to work, assume that it’s going to work, and then make it work like that.

It’s amazing how much bad coupled code exists for no good reason.

Read full post
reusability

[tags]django, plugins, apps, projects, symfony[/tags]

Read full post
Templating

[tags]smarty, symfony, php, python, django[/tags]

Read full post
YUI Autocomplete the easy way

[tags]yui, autocomplete, javascript, jquery, symfony[/tags]

Read full post
python: relative paths

So I started yesterday with Django, and I decided I didn’t want to futz with creating another mysql database that I’d need to manage, etc. Instead I’ll just use sqlite.

I wanted to keep my sqlite database within my project regardless of where I might move my project later. So I did this:

I confused a lot of people on IRC, but it’s really quite easy:

  • __file__ is the filename of the current script, very similar to PHP’s __FILE__
  • os.path.abspath calculates the absolute path, hence the absolute path of the current file
  • os.path.join does all the nasty business of joining paths together and figuring out what type of slashes are needed, etc.
  • ‘data/db.sqlite’ is a string

So really all we were doing is creating a relative path, but setting it absolutely.

Read full post
GeoDjango

[tags]geodjango, django, gis[/tags]

Read full post
django experiment: Day 1 and Model Inheritence

[tags]django, inheritance[/tags]

Read full post
python: it begins

[tags]php, python, symfony, frameworks, programming[/tags]

Read full post
python: it begins

[tags]php, python, symfony, frameworks, programming[/tags]

Read full post
Python, Named arguments: Pure Genius

I decided I want to learn python, if only to learn Django and to “get” what all the python hub-bub is about.

Python’s named arguments in function calls is pure genius. Let me explain.

In PHP, and many other languages you can define a function as such:

function foo($a = 2, $b = 2)
{
	return pow($a,$b);
}

If you follow, foo() will give you 4. foo(3) is 9 and foo(99,0) is 1. In python we can do the same thing, but it’ll pay to use some better variable names:

def foo(base=2, exponent=2):
	return base**exponent

Similarly foo() will give you 4. foo(3) is 9 and foo(99,0) is 1. But what if we forgot what the order was? Did base come first or was it exponent? We can do this:

foo(exponent=99, base=2)

Since base and exponent both have default values, we can even omit base and let it use the default:

foo(exponent=10)

This means rather than passing an $options array to my functions and checking whether an option was set or not, I can just specify which options I want in my function call. Or instead of remembering the order of the arguments, I can use whatever order suits me. Or instead of calling a function like bar(null, null, null, 2) I can just skip those first three arguments all together.

A side effect of this, is now there’s a real use, even for simple functions, to give your variables easy to remember names.

Read full post
Python, Named arguments: Pure Genius

I decided I want to learn python, if only to learn Django and to “get” what all the python hub-bub is about.

Python’s named arguments in function calls is pure genius. Let me explain.

In PHP, and many other languages you can define a function as such:

function foo($a = 2, $b = 2)
{
	return pow($a,$b);
}

If you follow, foo() will give you 4. foo(3) is 9 and foo(99,0) is 1. In python we can do the same thing, but it’ll pay to use some better variable names:

def foo(base=2, exponent=2):
	return base**exponent

Similarly foo() will give you 4. foo(3) is 9 and foo(99,0) is 1. But what if we forgot what the order was? Did base come first or was it exponent? We can do this:

foo(exponent=99, base=2)

Since base and exponent both have default values, we can even omit base and let it use the default:

foo(exponent=10)

This means rather than passing an $options array to my functions and checking whether an option was set or not, I can just specify which options I want in my function call. Or instead of remembering the order of the arguments, I can use whatever order suits me. Or instead of calling a function like bar(null, null, null, 2) I can just skip those first three arguments all together.

A side effect of this, is now there’s a real use, even for simple functions, to give your variables easy to remember names.

Read full post
Better than ORM Object Persistence

After talking to people about the benefits and disadvantages of various ORMs… and reading up a little on non RDMBSs like Amazon SimpleDB I came to the realization that ORM is really a hack to get RDBMSs to work as a storage for objects.

I’m being liberal with the term hack. It really does work for a lot of situations, but it’s not very elegant. The workflow is more or less this:

  • You create a database.
  • You create some objects
  • You define tables to store attributes of objects
  • You establish a mapping

There’s a lot that goes into database definition. It would be nice to breakout from this line of thinking and do things a bit differently:

  • Create database
  • Save named (e.g. Person, Place, Log, Restaurant, Rating, Review) serialized objects to the database.

Let the database learn from the saving of the object how to define the type. In fact, it should be flexible and let us have mixmatched types, etc.

Let the database index everything, and keep the indexes up to date, but prioritize it based on usage. In other words if we usually query a database of Persons in order by a field called lastname, then make sure that index on lastname is always at hand. We should be able to query this data back out of storage based on how we stored it.

We should also be able to reach other objects in the database in a similar manner.

The key here is letting the database layer do the heavy-thinking about what to do with serialized data, versus having some middle layer take care of it.

so I might just be naive about data and databases. But if this idea is worthwhile and some database people can validate me, I’d be willing to work on it.

Read full post
jQuery shortcut functions and jQuery plugins

[tags]js, javascript, readability, yui, jquery, shortcuts[/tags]

Read full post
Symfony Camp: Ajax and Zend, what would you like to know?

[tags]symfonyCamp, symfony, netherlands, ajax, zend search lucene, zsl, jquery[/tags]

I’ve been asked to speak at SymfonyCamp (symfony['camp']) next month (you should all go if you can) and I thought I’d present as well as I could on Ajax and the Zend Framework Bridge (including Zend Search Lucene).

If you’re attending the camp and/or would like to hear about these topics please let me know any specific questions you might have about “symfony and Ajax” and “symfony and Zend” and I’ll try to address them in my presentations.

If you are unable to go fear not, I’ll try to post my notes on this site.

Read full post
Using sfDoctrine to match allowed email domains

[tags]php, propel, doctrine, validators, symfony, optiopt, startup[/tags]

Read full post
Boosting terms in Zend Search Lucene

[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl[/tags]

Read full post
Finding things using Zend Search Lucene in symfony

[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl[/tags]

Read full post
Equal height columns with jQuery

[tags]css, jQuery, layout, javascript, equal, columns, equal columns[/tags]

I’ve seen a few examples of how to equalize column heights using javascript, and none of them seem appealing:

  • jquery.equalizecols.js
    • This required a few other libraries, and I wanted more flexibility (e.g. where the column should grow in order to equalize)
  • Project 7
    • The Project 7 approach was the most interesting, but the code seemed a bit messy and not so open source friendly (even thought it might have been). It would let you specify which element was to grow inside a column.
  • Nifty Corners
    • I had trouble with the syntax, but I liked how it just created a new element out of thin air…

So I wrote my own:

$("#col1, #col2").equalizeCols();

will equalize the columns as expected

$("#col1, #col2").equalizeCols("p,p");

will equalize the columns and add the extra space after the p tag in #col1 or #col2 (whichever is shorter).

Here’s our function:

This requires jQuery of course, and it hasn’t been tested much.

Read full post
Debugging yaml configuration with the symfony web debugger

[tags]symfony, yaml, configuration, web debug, debug[/tags] [symfony]: http://symfony-project.com/

Read full post
The Lucene Search Index and symfony

[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl, index[/tags]

Read full post
Tips for symfony and Subversion

[tags]symfony, subversion[/tags]

There’s some tricks you can do to running a symfony project with subversion:

Ignoring files in cache/ and log/

The first thing you can do (and this is well documented in the askeet tutorial) is ignore files in cache/ and log/. These files are specific to each instance of your app and don’t contain anything that needs to be in version control.

Run the following:

cd $SF_ROOT
rm -rf log/* cache/*
svn propedit svn:ignore log
svn propedit svn:ignore cache

svn propedit will bring up a text editor, in both instances you want to save the following:

*

Ignore other auto-generated files

Eric Sink wrote an excellent tutorial on source control. In his chapter on repositories he recommends checking in only hand edited source code. If a property file generates another file, check in the property file, not the auto-generated result. This not only keeps your repository clean, it prevents a lot of unnecessary check-ins.

If you use propel for your ORM layer there are a few files you can ignore using svn propedit svn:ignore {dirname}.

In $SF_ROOT/config we can ignore:

*schema-transformed.xml

These are xml files that propel generates from schema.xml (or schema.yml).

In $SF_ROOT/data/sql we can ignore:

lib.model.schema.sql
plugins.*.sql
sqldb.map

These are created from schema.xml (or schema.yml) as well.

The real savings will come with your model. The propel model creates customizable php classes in lib/model which inherit from auto-generated files in lib/om there are also auto-generated map files in `lib/map’

We can run from $SF_ROOT:

svn propedit svn:ignore lib/model/om
svn propedit svn:ignore lib/model/map

and enter

*

for both properties.

If you’ve mistakenly checked in some of these files you will need to remove them from your repository via svn delete.

Linking the symfony Library

I prefer to embed the symfony library into each of my symfony apps rather than relying on a shared PEAR library. This lets me run multiple versions of symfony without much fuss. With subversion we can use the svn:externals property to directly link our app with the symfony subversion repository.

At first this sounds like danger, but externals can be linked to specific revisions. However, the symfony team tags their repository with version numbers. To get this to work we need to do 3 things. (UPDATE: See Fabien’s comment about using the lib/vendor directory)

  1. Modify config/config.php to look for symfony internally. Just open it up and change it so it says this:
  2. Run svn propedit svn:externals lib from $SF_ROOT and enter:

     symfony http://svn.symfony-project.com/tags/RELEASE_1_0_2/lib/ or whatever version of symfony you want to link to, at the time of this post, `RELEASE_1_0_2` is fairly fresh.
    
  3. Run svn propedit svn:externals data from $SF_ROOT and enter:

     symfony http://svn.symfony-project.com/tags/RELEASE_1_0_2/data/ or whatever version of symfony you want to link to, at the time of this post.
    

Now when you do svn update you’ll have the symfony library all linked up. Furthermore this keeps all the developers on the same version of symfony.

Also you may want to start running symfony using ./symfony versus symfony. The former looks at your configuration settings to determine which symfony command to use, the latter is generally linked to your system wide command (which is generally the PEAR installed command).

Linking to symfony Plugins

I have my hands in a number of symfony plugins because I work on a lot of projects which tend to share a lot of similar functionality. Many of the plugins are in early stages of development, so I find it helpful to have them linked from svn as well. This way I can get the benefits of any new functionality and if the occasion should arise, I can contribute any useful changes I make.

To link to the plugins you run svn propedit svn:externals plugins and enter one plugin per line in the following format:

{plugin_name} -r{REVISION} {URL}

For one of my projects I use:

sfPrototypeTooltipPlugin http://svn.symfony-project.com/plugins/sfPrototypeTooltipPlugin
sfGuardPlugin http://svn.symfony-project.com/plugins/sfGuardPlugin
sfZendPlugin http://svn.symfony-project.com/plugins/sfZendPlugin

I’ve omitted the revision, because I live dangerously and want to use the latest $HEAD.

Read full post
sfZendPlugin

[tags]Zend, Zend Search Lucene, Search, Lucene, php, symfony, zsl, plugins[/tags]

Read full post
Caching REST with sfFunctionCache

[tags]geocoding, caching, REST, symfony, Cache_Lite, php, cache, sfFunctionCache[/tags]

For reviewsby.us we do a lot of geocoding. To facilitate we use Yahoo! Geocoding API. This helps us normalize data, obtain latitude and longitude, interpret location specific searches.

These REST queries happen a lot and will continue to happen, but this data that Yahoo! provides is fairly static. We’re basically querying a database of sorts. So it makes sense that we should cache this data.

We’ll demonstrate how to cache these queries using symfony’s sfFunctionCache class.

I wrote a wrapper (I’ll release it as a plugin if requested) for the Geocoding API, the bulk of the work (the REST call) occurs in a function called doQueryGIS:

The call to this function is always wrapped with queryGIS:

This wrapper creates a sfFunctionCache objet and calls the function and caches it for subsequent queries.

What this means is once Yahoo! teaches reviewsby.us that India is located at (25.42°, 77.830002°) and that the precision is ‘country’ we remember it in the future.

These features will be incorporated into future versions of reviewsby.us.

Read full post
PHP double versus single quotes

[tags]best practices, php[/tags]

Read full post
How do you find good programmers?

[tags]programmers, hiring[/tags]

Read full post
Parsing a list of Key:Value pairs

[tags]best practices,php,openID[/tags] [PHP]: http://php.net/ [openID]: http://openid.net/ [reviewsby.us]: http://reviewsby.us/ [symfony]: http://www.symfony-project.com/

Read full post
Make Adium show your Facebook status

By using a simple RSS feed, you can have Adium report your Facebook status.

I am a late-adopter to social networks. I participated in LiveJournal years after many of my friends started using it. I finally got into Facebook months after it was “openned up.” I like this strategy because I can immediately find my friends on the network and add them and amass 100s in a few short days.

Facebook is nice for its simple and clean interface. Even the features are fairly simple. I like the status feature… for no good reason. It’s a simple AJAX status update that updates your status on the site. What I don’t like to do is repeat myself. I wanted Adium to repeat what Facebook said.

My first inkling was to use the Facebook API, but that seemed less appealing since it would involve authentication. I almost gave up and then discovered that in Facebook you can go to “My Profile” and under “Status” go to “See All” and there’s an RSS link to your status messages. Awesome. All I needed to do is grab the first one.

I decided this looked like a task for Simple XML which can take an XML string and turn it into a PHP object.

The first step of course was to fetch the RSS feed into a string. file_get_contents made the most sense, but Facebook does a browser check even for getting RSS feeds. I’m not fond of browser sniffing, but its easy to work around if you use cURL.

Excellent… this does exactly what I want - but it’s slow. Like any good script, we should use caching if it makes sense. Obviously we don’t want to overload the servers at Facebook (or for that matter any place that serves up RSS feeds) so we implement PEAR’s Cache_Lite package. We’ll tell it to cache results for 15 minutes. The code changes are marked with //+++ at the end of each new line:

That’s it on the PHP side. On the Adium side you’ll need to use the exec AdiumXtra. It’ll allow you to set your status to something like:

/exec {/usr/local/php5/bin/php /usr/local/bin/facebook.php}

Enjoy.

Read full post
Make Adium show your Facebook status

By using a simple RSS feed, you can have Adium report your Facebook status.

I am a late-adopter to social networks. I participated in LiveJournal years after many of my friends started using it. I finally got into Facebook months after it was “openned up.” I like this strategy because I can immediately find my friends on the network and add them and amass 100s in a few short days.

Facebook is nice for its simple and clean interface. Even the features are fairly simple. I like the status feature… for no good reason. It’s a simple AJAX status update that updates your status on the site. What I don’t like to do is repeat myself. I wanted Adium to repeat what Facebook said.

My first inkling was to use the Facebook API, but that seemed less appealing since it would involve authentication. I almost gave up and then discovered that in Facebook you can go to “My Profile” and under “Status” go to “See All” and there’s an RSS link to your status messages. Awesome. All I needed to do is grab the first one.

I decided this looked like a task for Simple XML which can take an XML string and turn it into a PHP object.

The first step of course was to fetch the RSS feed into a string. file_get_contents made the most sense, but Facebook does a browser check even for getting RSS feeds. I’m not fond of browser sniffing, but its easy to work around if you use cURL.

Excellent… this does exactly what I want - but it’s slow. Like any good script, we should use caching if it makes sense. Obviously we don’t want to overload the servers at Facebook (or for that matter any place that serves up RSS feeds) so we implement PEAR’s Cache_Lite package. We’ll tell it to cache results for 15 minutes. The code changes are marked with //+++ at the end of each new line:

That’s it on the PHP side. On the Adium side you’ll need to use the exec AdiumXtra. It’ll allow you to set your status to something like:

/exec {/usr/local/php5/bin/php /usr/local/bin/facebook.php}

Enjoy.

Read full post
Not taking frameworks for granted

One of my clients approached me with a relatively easy project. She gave me a log file of PHP errors and I was supposed to fix her scripts. I fixed about 100+ different errors in a few hours. It was fairly straightforward.

Throughout the site I could understand the previous developer and the choices he or she made for better or worse. It did look like a struggle however.

Ultimately it felt that there were some fairly simple things that each script needed to do, but each task was a challenge. Validating forms, storing data across pages, decorating the site, interacting with the database. Everything seemed very kludged together.

I realized that this is exactly how I used to write code, sure… my way of course was better and more logical, yada, yada, yada… but ultimately I was there before.

My client noted that these scripts were made from another contract programmer, and then a light-bulb went on… frameworks (whether it be symfony, ROR, Django, CakePHP, etc) help iron out and standardize these tasks.

Since I know symfony best, I’ll cover what I think could have helped in this last project. I’m sure other major frameworks have their equivalents.

  • Form validation: symfony lets you define form validation in a very simple manner. The validation logic is also separate from the rest of the code.
  • Storing Data: Without a framework, you generally have to rely on the $_SESSION array in PHP. While very useful and easy to use, storing parameters and attributes to a user object is done a lot more cleanly.
  • decorating the site: My biggest problem was with each page I had,, I used to have to call headers, sidebars, etc, etc. symfony’s layout system was a boon. I had a common layout for all pages (maybe a few alternates) and hooks inside them if they needed to be adjusted. Then the various actions had their own seperate templates that were injected into the common layout. It made adding new pages easy, since I didn’t need to remember header() and footer() functions for each and every page.
  • Interacting with the database: I’ve covered before the benefits of ORM.

Not only do the bulk of these problems disappear with a framework, a lot of the difficulties of switching developers melt away. If you tell me, a developer, that I’m walking into a project made with a framework, I can learn about the framework and be able to understand its ins and outs.

If you just tell me it’s written in PHP, chances are I’m going to want to do things my own way. It’s hard to understand the logic that another programmer was using so we fall back to standards whether they are your own or borrowed.

When we use a framework, we can find some mutually agreed upon standards and usually people who specialize in that framework and are willing to help. So my advice: stick to frameworks. The coding style will be no worse than the whims of a programmer, but at best it’ll be something that anyone can pick up. The general case is that even a bad coder can only do so much damage within a framework. The bullet points I covered above will cut down on development time tremendously.

Read full post
How to make Prototype Window Class as easy as Lightbox Gone Wild

I like the way that Lightbox Gone Wild will automatically pickup any links with the class=lbOn, but I wanted to use (at some point) Prototype Window Class instead.

Luckily PWC is built on Prototype which means we’ve already loaded a helpful library.

In order to take all class=lbOn objects and run them through PWC we just write a simple loop and iterate.

So here’s the low-down:

  • Download PWC
  • Copy window.js
  • Use the included prototype & script.aculo.us if you don’t already include it in your page
  • copy any themes you wish to use.

In your page add this bit of JavaScript:

So, this code simply looks for all the anchor tags with class=lbOn and then creates a new mylb instance for each anchor. The end.

Read full post
Cropping Images using DHTML (Prototype) and symfony

Note: Like many of my tutorials, you don’t need symfony, just PHP. However, I develop in symfony and take advantage of the MVC-support that it offers.

Years ago when I was working on a photo gallery for davedash.com I got the art of making tumbnails down fairly well. It was automated and didn’t allow for specifying how the thumbnail should be made. With dozens of photos (which was a lot back then), when would I find that kind of time.

Flashback to today, for my company… we want users with avatars… but nothing too large. Maybe a nice 80x80 picture. Well the coolest UI I’ve seen was Apple’s Address Book which let you use this slider mechanism to crop a fixed sized image from a larger image.

Here’s a demo.

Overview

The front-end GUI is based on code from digg which is based on the look and feel (as near as I can tell) from Apple.

The GUI provides a clever visual way of telling the server how to chop the image. The gist is this, sliding the image around and zooming in and out change a few form values that get passed to another script which uses this data to produce the image.

Frontend: What would you like to crop?

In this tutorial, we’re going to be cropping an 80x80 avatar from an uploaded image. The front-end requires the correct mix of Javascript, CSS, HTML and images. The Javascript sets up the initial placements of the image and the controls. The CSS presents some necessary styling. The images makeup some of the controls. The HTML glues everything together.

HTML

Let’s work on our HTML first. Since I used symfony, I created a crop action for a userpics module. So in our cropSuccess.php template:

<div id="ava">
	<?php echo form_tag("userpics/crop") ?>
		<div id="ava_img">
			<div id="ava_overlay"></div>
			<div id="ava_drager"></div>
			<img src="<?php echo $image ?>" id="avatar" />
		</div>
		<div id="ava_slider"><div id="ava_handle"></div></div>
		<input type="hidden" id="ava_width" name="width" value="80" />
		<input type="hidden" id="ava_x" name="x" value="100" />
		<input type="hidden" id="ava_y" name="y" value="100" />
		<input type="hidden" id="ava_image" name="file" value="<?php echo $image ?>" />
		</div>
		<input type="submit" name="submit" id="ava_submit" value="Crop" style="width: auto; font-size: 105%; font-weight: bold; margin: 1em 0;" />
	</form>
</div>

Right now a lot of this doesn’t quite make sense. If you attempt to render it, you will just see only the image. As we add the corresponding CSS and images it will make some more sense.

CSS and corresponding images

We’ll go through each style individually and explain what purpose it serves in terms of the GUI.

#ava is our container.

#ava {
	border: 1px solid gray;
	 width: 200px;
}

#ava_img is the area that contains our image. Our window for editing this image 200x200 pixels. If we drag out image out of bounds we just want the overflowing image to be clipped. We also want our position to be relative so any child elements can be positioned absolutely with respect to #ava_img.

#ava_img {
	width: 200px;
	height: 200px;
	overflow: hidden;
	position: relative;
}
overlay

#ava_overlay is a window we use to see what exactly will be our avatar. If it’s in the small 80x80 window in the center of the image, then it’s part of the avatar. If it’s in the fuzzy region, then it’s getting cropped out. This overlay of course needs to be positioned absolutely.

#ava_overlay {
	width: 200px;
	height: 200px;
	position: absolute;
	top: 0px;
	left: 0px;
	background: url('/images/overlay.png');
	z-index: 50;
}

#ava_drager is probably the least intuitive element (Heck, I’m not even sure if I’ve even got it right). In our demo you’re not actually dragging the image, because you can drag anywhere within the #ava_img container and move the image around. You’re using dragging an invisible handle. It’s a 400x400 pixel square that can be dragged all over the container and thusly move the image as needed.

#ava_drager {
	width: 400px;
	height: 400px;
	position: absolute;
	z-index: 100;
	color: #fff;
	cursor: move;
}

#avatar is our image, and since it will be moving all around the window, it requires absolute positioning.

#avatar {
	position: absolute;
}
overlay
overlay

#ava_slider and #ava_handle are our slider components. They should be self-explanatory.

#ava_slider {
	width: 200px;
	height: 27px;
	background: #eee;
	position: relative;
	border-top: 1px solid gray;	
	background: url('/images/slider_back.png');
}
#ava_handle {
	width: 19px;
	height: 20px;
	background: blue;
	position: absolute;
	background: url('/images/handle.png');
}
Internet Explorer

PNG do not work so well in Internet Explorer, but there is a small trick, adding these components into a style sheet that only IE can read will make things work:

#ava_overlay {
  background: none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/ui/cropper/overlay.png', sizingMethod='crop');
}

#ava_handle {
  background: none;
  filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/ui/cropper/handle.png', sizingMethod='crop');
}

The Javascript

The Javascript is actually not as complicated as you’d expect thanks to the wonder of prototype. This framework provides so much so easily. You’ll need to include prototype.js and dom-drag.js.

So let’s take a look.

<script type="text/javascript" language="javascript" charset="utf-8">
// <![CDATA[
function setupAva() {
	if ($("avatar")) {
		var handle = $("ava_handle");
		var avatar = $("avatar");
		var drager = $("ava_drager");
		var slider = $("ava_slider");
		var ava_width = $("ava_width");
		var ava_x = $("ava_x");
		var ava_y = $("ava_y");
		// four numbers are minx, maxx, miny, maxy
		Drag.init(handle, null, 0, 134, 0, 0);
		Drag.init(drager, avatar, -100, 350, -100, 350);
		var start_w = avatar.width;
		var start_h = avatar.height;
		var ratio = (start_h / start_w);
		var new_h;
		var new_w;
		if (ratio > 1) {
			new_w = 80;
			new_h = (80*start_h)/start_w;
		} else {
			new_h = 80;
			new_w = (80*start_w)/start_h;
		}
		// these need to be set after we init
		avatar.style.top = '100px';
		avatar.style.left = '100px';
		avatar.style.width = new_w + 'px';
		avatar.style.height = new_h + 'px';
		avatar.style.margin = '-' + (new_h / 2) + 'px 0 0 -' + (new_w / 2) + 'px';
		handle.style.margin = '3px 0 0 20px';
		avatar.onDrag = function(x, y) {
			ava_x.value = x;
			ava_y.value = y;
		}
		handle.onDrag = function(x, y) {
			var n_width = (new_w + (x * 2));
			var n_height = (new_h + ((x * 2) * ratio));			
			avatar.style.width = n_width + 'px';
			avatar.style.height = n_height+ 'px';
			ava_width.value = n_width;	
			avatar.style.margin = '-' + (n_height / 2) + 'px 0 0 -' + (n_width / 2) + 'px';
		}
	}
}
Event.observe(window,'load',setupAva, false);
// ]]>
</script>

If this isn’t exactly crystal clear, I can explain. If you’re new to prototype, $() is the same as doucment.getElementByID() (at least for our purposes).

We need to initialize two draggable elements, one is our slider for zooming and the other is our avatar itself. We initialize the draggers using Drag.init(). We specify what to drag, if another element should be used as a handle and then the range of motion in xy coordinates. In the second call we use that #dragger to move around the image in this manner.

Drag.init(handle, null, 0, 134, 0, 0);
Drag.init(drager, avatar, -100, 350, -100, 350);

We want to initialize the the size and placement of the avatar. We do that using maths. First we want it in our 80x80 pixel box. So it should be roughly 80x80. I’ve set the math up so that the smallest side is 80 pixels (there’s reasons for doing this the other way around).

	if (ratio > 1) {
		new_w = 80;
		new_h = (80*start_h)/start_w;
	} else {
		new_h = 80;
		new_w = (80*start_w)/start_h;
	}

We then place the avatar element. We initialize it to be in the center of the screen (top: 100px;left:100px) and then nudge the image using margins.

	avatar.style.top = '100px';
	avatar.style.left = '100px';
	avatar.style.width = new_w + 'px';
	avatar.style.height = new_h + 'px';
	avatar.style.margin = '-' + (new_h / 2) + 'px 0 0 -' + (new_w / 2) + 'px';

We also use margins to place the handle.

	handle.style.margin = '3px 0 0 20px';

#ava_x and #ava_y tell us where the center of the avatar is. So when the avatar is moved we need to set these again:

	avatar.onDrag = function(x, y) {
		ava_x.value = x;
		ava_y.value = y;
	}

That was easy. Slighly more complicated is the zoomer function. We are basically adjusting the width and the height proportionately based on roughly where the slider is. Note that we’re still using that ratio variable that we calculated earlier. We basically take the new x-coordinate of the handle and allow our image to get just slightly larger than the #ava_image container.

	handle.onDrag = function(x, y) {
		var n_width = (new_w + (x * 2));
		var n_height = (new_h + ((x * 2) * ratio));			
		avatar.style.width = n_width + 'px';
		avatar.style.height = n_height+ 'px';
		ava_width.value = n_width;	
		avatar.style.margin = '-' + (n_height / 2) + 'px 0 0 -' + (n_width / 2) + 'px';
	}

We want to load initialize the slider right away when the page loads: Event.observe(window,'load',setupAva, false);

Not terribly hard or complicated. Once these elements are all in place you have a working functioning slider. It returns the x and y coordinates of the center of the image with respect to our 200x200 pixel #ava_image. It also tells us the new width of our image. We feed this information into a new script and out should pop a new image which matches exactly what we see in our GUI.

Processing the crop

Initially I was frustrated with the data that was being sent. I knew the center of the image in relation to this 200x200 pixel canvas and its width… but what could I do with that. Well I could just recreate what I saw in the GUI. I needed to create a 200x200 pixel image first, place my original avatar resized (and resampled) at the precise coordinates and then cut out the center most 80x80 pixels to become the final avatar image.

If you note in our template above for cropSuccess.php we submit our form back to the crop action. Let’s look at the action:

public function executeCrop()
{
	if ($this->getRequestParameter('file')&&$this->getRequestParameter('width')) {			// we are saving our cropped image
		// Load the original avatar into a GD image so we can manipulate it with GD
		$o_filename = $this->getRequestParameter('file');  // we'll use this to find the file on our system
		$o_filename = sfConfig::get('sf_root_dir').'/web' . $o_filename;
		$o_im = @imagecreatetruecolor(80, 80) or die("Cannot Initialize new GD image stream");
		$o_imagetype = exif_imagetype($o_filename); // is this gif/jpeg/png
	
		// appropriately create the GD image
		switch ($o_imagetype) {
			case 1: // gif
				$o_im = imagecreatefromgif($o_filename);
				break;
			case 2: // jpeg
				$o_im = imagecreatefromjpeg($o_filename);	
				break;
			case 3: // png
				$o_im = imagecreatefrompng($o_filename);
				break;
		}
		
		// Let's create our canvas
		$im = @imagecreatetruecolor(200, 200) or die("Cannot Initialize new GD image stream");
		imagecolortransparent ( $im, 127 ); // set the transparency color to 127
		imagefilledrectangle( $im, 0, 0, 200, 200, 127 ); // fill the canvas with a transparent rectangle
	
		// let's get the new dimension for our image
	
		$new_width = $this->getRequestParameter('width');
		$o_width = imageSX($o_im);
		$o_height = imageSY($o_im);
		
		$new_height = $o_height/$o_width * $new_width;
	
		// we place the image at the xy coordinate and then shift it so that the image is now centered at the xy coordinate
		$x = $this->getRequestParameter('x') - $new_width/2;
		$y = $this->getRequestParameter('y') - $new_height/2;
		
		// copy the original image resized and resampled onto the canvas
		imagecopyresampled($im,$o_im,$x,$y,0,0,$new_width,$new_height,$o_width,$o_height); 
		imagedestroy($o_im);
		
		// $final will be our final image, we will chop $im and take out the 80x80 center
		$final = @imagecreatetruecolor(80, 80) or die("Cannot Initialize new GD image stream");
		imagecolortransparent ( $final, 127 ); // maintain transparency
	
		//copy the center of our original image and store it here
		imagecopyresampled ( $final, $im, 0, 0, 60, 60, 80, 80, 80, 80 );
		imagedestroy($im);
	
		//save our new user pic
		$p = new Userpic();
		$p->setUser($this->getUser()->getUser());
		$p->setGD2($final);
		$p->save();
		imagedestroy($final);
		$this->userpic = $p;
		return "Finished";
	}


	$this->getResponse()->addJavascript("dom-drag");
	$this->getResponse()->addJavascript('/sf/js/prototype/prototype');
	$this->getResponse()->addJavascript('/sf/js/prototype/effects');
	$this->image = '/images/userpics/originals/' . $this->getRequestParameter('file');
}

It’s doing exactly what the paragraph above explains when the image dimensions are given. The code is well commented so it should be easy enough to follow.

GD image functions in PHP are fairly robust and can help you do a lot of tricks with image data. Note the code to save the image, we’ll cover it in detail soon.

The Model

$p = new Userpic();
$p->setUser($this->getUser()->getUser());
$p->setGD2($final);
$p->save();

First some clarification the second line. myUser::getUser() gets the User object associated with the currently logged in user. The third line, however, is where the magic happens. Before we look at it, let’s have a quick look at our model:

userpic:
 _attributes: { phpName: Userpic }
 id:
 user_id:
 image: blob
 thumb: blob
 created_at:
 updated_at:

We have an image attribute and a thumb property to our Userpic object. This is where we store PNG versions of each icon and their 16x16 thumbnails respectively. We do this in Userpic::setGD2():

public function setGD2($gd2_image)
{
	//convert to PNG
	ob_start();
	imagepng($gd2_image);
	$png = ob_get_clean();
	//save 16x16
	$gd2_tn = @imagecreatetruecolor(16, 16) or die("Cannot Initialize new GD image stream");
	imagealphablending( $gd2_tn, true );
	imagecolortransparent ( $gd2_tn, 127 );
	
	imagecopyresampled ( $gd2_tn, $gd2_image, 0, 0, 0, 0, 16, 16, 80, 80 );
	ob_start();
	imagepng($gd2_tn);
	$tn = ob_get_clean();
	
	$this->setImage($png);
	$this->setThumb($tn);
}

We capture the output of the full size PNG, then we scale it again and capture the output of the thumbnail and set them.

Conclusion

When it comes to web apps, having a relatively simple GUI for people to resize images can go a long way in terms of adoption rate of avatars and custom user pictures by non technical users.

Enjoy, and if you found this useful (or better implemented it) let me know.

Read full post
Using Zend Search Lucene in a symfony app

[tags]zend, search, lucene, zend search lucene, zsl, symfony,php[/tags]

Read full post
Digg-style AJAX comment editing in PHP/symfony

Digg“-style anything can be pretty slick. The AJAX-interactions on that site make it very fun to use. It’s styles have been copied everywhere, and are definitely worth copying. The latest feature that had caught my eye was the ability to edit your comments for a set time after posting them. Of course, it wasn’t just the ability to edit comments, it was AJAX too and it has a timer.

This is totally something I could use on a restaurant review site. So I started on this project. It’s pretty straight forward. For all of your posted comments you check if the owner of them is viewing them within 3 minutes of posting the commen. 3 minutes is usually enough time to notice you made a typo, but if you disagree I’ll leave it to you to figure out how to adjust the code.

For example, I make a comment, realize I spelled something wrong and then I can click on my comment to edit it. Of course using AJAX means this all happens without having to reload the web page. Therefore the edits are seemingly quick. So let’s add it to any web site.

In Place Forms

First and foremost, the ability to edit a comment means you have a form that you can use to edit and submit your changes. But rather than deal with creating a boring unAJAXy form, we’ll enlist the help of script.aculo.us.

First, each comment is rendered using the following HTML and PHP:

<div class="review_block" id="comment_<?php echo $comment->getId() ?>">  
	<p class="author"><?php echo link_to_user($comment->getUser()) ?> - <?php echo $comment->getCreatedAt('%d %B %Y') ?></p>
	<div class="review_text" id="review_text_<?php echo $comment->getId()?>"><?php echo $comment->getHtmlNote() ?></div>
</div>

Note that this div and it’s child div have unique ids that we can refer back to (comment_n and review_text_n where n is the id of the comment). We can use this to interact with the DOM via JavaScript. What we do is for each comment, we check if it is owned by the current visitor and if it’s within our prescribed 3 minute window. We can do that with some simple PHP:

<?php if ($comment->getUser() && $comment->getUserId() == $sf_user->getId() && time() < 181 + $comment->getCreatedAt(null) ): ?>
	<script type="text/javascript">
	//<![CDATA[
		makeEditable('<?php echo $comment->getId() ?>', "<?php echo url_for($module . '/save?id=' . $comment->getId()) ?>", "<?php echo url_for('restaurantnote/show?id=' . $comment->getId() . '&mode=raw') ?>", <?php echo 181-(time() - $comment->getCreatedAt(null)) ?>);
	//]]></script>
<?php endif ?>	

As you can see we run the makeEditable() function for each applicable comment. As you can guess, makeEditable() makes a comment editable. For parameters it takes the comment’s id (so it can refer to it in the DOM and other background scripts). It also takes as an argument the “save” URL as well as a URL from which it can load the raw comment. The last argument is for the timer.

Here is our function:

var editor;
var pe;
makeEditable = function(id, url, textUrl, time) {
	var div = $("review_text_" + id);
	
	pe = new PeriodicalExecuter(function() { updateTime(id); }, 1);
	
	Element.addClassName($('comment_' + id), 'editable');
	new Insertion.Bottom(div, '<div class="edit_control" id="edit_control_'+id+'">Edit Comment (<span id="time_'+id+'">'+time+' seconds</span>)</div>');
	
	editor = new Ajax.InPlaceEditor(div, url, { externalControl: 'edit_control_'+id, rows:6, okText: 'Save', cancelText: 'Cancel', 
	loadTextURL: textUrl, onComplete: function() { makeUneditable(id) } });
}

It does a couple things. It runs a PeriodicalExecuter to run the updateTime function which updates our countdown timer. It adds a CSS class to our comment div. It adds a control button to edit a comment. Lastly it uses the script.aculo.us Ajax.InPlaceEditor to do most of the magic. The hard part is done.

Periodic Execution Timer

So the updateTime function is reasonably simple. It finds the time written out in the DOM and decrements it by 1 second each second. Once it hits zero seconds it disables itself and the ability to edit the block. Let’s take a look:

updateTime = function(id) {
  var div = $("time_"+id);
  if (div) {
    var time =  parseInt(div.innerHTML) - 1;
    div.innerHTML = time;
  }
  if (time < 1) {
    pe.stop();
    var editLink = $('edit_control_'+id);
    if (Element.visible(editLink)) {
      makeUneditable(id);
      editLink.parentNode.removeChild(editLink);
    }
  }
}

Call backs

We’ll need a few call backs for the editor to work properly. Since many content pieces are converted from something else to HTML and not directly written in HTML we’ll need a callback that will load our text. We’ll also need a callback which will save our text (and then display it).

Load Text

The first call back we can see is referenced in the makeEditable() function. In our example it’s:

url_for('restaurantnote/show?id=' . $comment->getId() . '&mode=raw');

Which is a symfony route to the restaurantnote module and the show action with an argument mode=raw. Let’s take a look at this action:

public function executeShow ()
{
	$this->restaurant_note = RestaurantNotePeer::retrieveByPk($this->getRequestParameter('id'));
	$this->forward404Unless($this->restaurant_note instanceof RestaurantNote);
}

All this does is load the text (in our case the [markdown] formatting) into a template.

Save Text

The save text url in our example is:

url_for('restaurantnote/save?id=' . $comment->getId());

Using the Ajax.InPlaceEditor the value of the text-area is saved to the value POST variable. We consume it in our action like so:

public function executeSave() 
{
	$note = RestaurantNotePeer::retrieveByPk($this->getRequestParameter('id'));
	$this->forward404Unless($note instanceof RestaurantNote);
	if ($note->getUserId() == $this->getUser()->getId()) {
		$note->setNote($this->getRequestParameter('value'));
		$note->save();
	}
	$this->note = $note;
}

The note is also sent to a template that renders it, so when the save takes place, the edit form will be replaced with the new text.

Conclusion

As you can see with some script.aculo.us and symfony, it’s fairly easy to mimic “Digg-style” in-place comment editing. You can test out a real example by visiting reviewsby.us.

Read full post
Syncing with symfony and clearing your cache in one shot

If you’re using symfony’s sync command to synchronize files across environments (e.g. moving your development files to a staging server), it helps usually to clear the cache of the receiving server.

The following line will help:

symfony sync production go ; ssh user@production "cd /var/www; symfony cc"   

Assuming you have SSH keys defined and that you change user@production to your username and server host as well as /var/www switched to your website path. Also the symfony command needs to work on your “production” server (or whatever environment).

There may be a cleaner way to take care of this by changing the symfony command, but this works sufficiently well. One of the more frustrating elements of web development is synchronizing multiple sites: usually a development site, a staging site and a production site. SVN helps with keeping your code versioned, but usually you don’t want to check out a copy of your web site onto your live server.

Usually we use SFTP or rsync. The former has lots of problems, because a lot of manual work is usually involved to make sure you don’t over-write important files.

rsync, however, is a champ and symfony takes advantage of this. The key files you’ll have to deal with are $PROJECT_HOME/config/properties.ini and $PROJECT_HOME/config/rsync_exclude.txt.

Your properties.ini should look roughly like:

[symfony]
  name=reviewsby.us
[staging]
  host=staging.reviewsby.us
  port=22
  user=root
  dir=/var/www/staging/
[staging2]
  host=staging2.reviewsby.us
  port=22
  user=root
  dir=/var/www/staging

Each heading other than “[symfony]” is a different environment. In our example, we have two staging environments. The values under each heading should be self-explanatory. We can now run the following commands:

symfony sync staging
symfony sync staging go
symfony sync staging2
symfony sync staging2 go

The commands that lack go are “dry-runs” which just show you what files will be transfered. The other commands will run rsync and transfer all the files not specified in the exclude file, rsync_exclude.txt.

See askeet or the symfony documentation for more details.

Read full post
Dynamic Linking to Syndication Feeds with symfony

Adding a statically-linked syndication feed, a feed that is the same no matter where on the site you are, is a cinch with symfony, but what about dynamically linked syndication feeds? Let’s say we’re building the latest and greatest Web 2.0 app, there’s going to be hundreds of RSS feeds, not just the most recent items. We’ll want the latest comments to a post, the favorite things of a website member and it all has to be feed enabled. Sure, we can slap a link to the RSS feed and call it a day, but let’s go a step further and stick it in the <head/> area as well. That way when someone clicks on the RSS icon in their browser, or adds a web page to Bloglines those extra feeds can be found.

Expanding your head

A typical layout.php for a symfony app will have a <head/> section like this:

<head>
	<?php echo include_http_metas() ?>
	<?php echo include_metas() ?>
	<?php echo include_title() ?>
	<?php echo auto_discovery_link_tag('rss', 'feed/latest')?> 	
	<?php echo auto_discovery_link_tag('rss', '@feed_latest_georss', 
	array('title' => 'Latest Restaurants\' Locations (GeoRSS)' ))?> 	
	<?php echo include_feeds() ?><!-- this is the custom feed includer -->
	<link rel="shortcut icon" href="/favicon.ico" />
</head>

Since this is in the reviewsby.us layout.php, the latest feed and the latest GeoRSS feed (which we developed in this article) will show up on every page. So for example, if you use FireFox, you can subscribe to either link when you click on the orange feed icon (feed) in the URL bar no matter where you are in the web-application.

To expand this to allow for multiple feeds, we need to include <?php echo include_feeds() ?> (before or after the auto_discovery_link_tag calls makes the most sense).

Making the Feed Helper

Let’s created a FeedHelper.php to put the include_feeds() function (don’t forget to add use_helper('Feed') to your layout.php).

The function looks like this:

function include_feeds()
{
	$type = 'rss';
	$already_seen = array();
	foreach (sfContext::getInstance()->getRequest()->getAttributeHolder()->getAll('helper/asset/auto/feed') as $files)
	{
		if (!is_array($files))
		{
			$files = array($files);
		}
		foreach ($files as $file)
		{
			if (isset($already_seen[$file])) continue;
			$already_seen[$file] = 1;
			echo tag('link', array('rel' => 'alternate', 'type' => 'application/'.$type.'+xml', 'title' => ucfirst($type), 'href' => url_for($file, true)));
		}
	}
}

The function is doing what the deprecated include_javascripts and include_stylesheets functions did, just with syndication feeds. Also note, I stuck to just using RSS feeds. This function can no doubt be extended to Atom or other feed types, but for my purposes it was unnecessary1.

Dynamically setting the feeds

In the reviewsby.us site, the menu items are tagged. There’s tags for chicken, indian and bread for example. Each of them are to have an associated GeoRSS feed as described in a previous tutorial. I built our tagging system similar to Askeet. So in our tag module I created a function in the corresponding actions.class.php:

public function addFeed($feed)
{
	$this->getRequest()->setAttribute($feed, $feed, 'helper/asset/auto/feed');
}

This sets the attribute that include_feeds() pulls from. Here $feed is simply the route to our feed. So in our executeShow() I just make a call to $this->addFeed('@feed_tag_georss?tag=' . $tag). We’re done.

We can now go to any of our tagged pages. Let’s try chicken and see that we can subscribe to a GeoRSS feed of restaurants serving dishes tagged as chicken.

Slight problem. The title attribute of the generated link tags are always Rss. That can be mildly unusable.

Throwing feed titles into the mix

Let’s change our addFeed() to allow for a second parameter, a title and have it store both the route and the title in the request attribute:

public function addFeed($feed, $title = null)
{
	$feedArray = array('url' => $feed, 'title' => $title);
	$this->getRequest()->setAttribute($feed, $feedArray, 'helper/asset/auto/feed');
}

We’ll also need to adapt the include_feeds to appropriately accommodate associative arrays:

function include_feeds()
{
	$type = 'rss';
	$already_seen = array();
	foreach (sfContext::getInstance()->getRequest()->getAttributeHolder()->getAll('helper/asset/auto/feed') as $feeds)
	{
		if (!is_array($feeds) || is_associative($feeds))
		{
			$feeds = array($feeds);
		}

		foreach ($feeds as $feed)
		{
			if (is_array($feed)) {
				$file = $feed['url'];
				$title = empty($feed['title']) ? $type : $feed['title'];
			} else {
				$file = $feed;
				$title = $type;
			}

			if (isset($already_seen[$file])) continue;

			$already_seen[$file] = 1;
			echo tag('link', array('rel' => 'alternate', 'type' => 'application/'.$type.'+xml', 'title' => $title, 'href' => url_for($file, true)));
		}
	}
}

Note, there’s a function is_associative(). It’s a custom function that we can place in another helper:

function is_associative($array)
{
  if (!is_array($array) || empty($array)) return false;
  $keys = array_keys($array);
  return array_keys($keys) !== $keys;
}

It’s a clever way of determining if a function is an associative array or not.

Conclusion

It looks like our GeoRSS feeds are on all our tag pages. Now we can take our favorite items labeled as Indian food and easily add the URL to a service like Bloglines and have it keep us up to date on new Indian dishes. This was simple, especially when much of the work was taken care of by the framework.


  1. Most clients support RSS so unless there is a compelling need to use Atom or another format, then keeping it down to one choice is always your best bet.

Read full post
ReviewsBy.Us bugfixes

A lot of bugs have popped up recently.

Logins

The logins weren’t redirecting people to the correct place. Unfortunately the login system still needs a lot of work. I am probably going to rewrite it completely. It doesn’t consistantly remember where you are coming from or where you intend to go after logging in. I’ll be jotting down a clean system to log people in propperly.

Tags

Tags work a bit better. They follow the flickr style of tagging, which is each word is a tag, unless surrounded in double quotes. Previously they weren’t producing a lot of empty tags.

Latitude and Longitude even more precise.

A small error in the database definitions resulted in lat/longitudes of restaurants that were greater than 100 (or rather abs(x) > 100 where x is latitude or longitude were being truncated to 100 or -100. This was easily fixed so things should look just right on the maps.

Read full post
Quicksilver + TextMate = craZy delicious development environment

UPDATE:Cmd-T allows you to search for the files in your currently opened TextMate project. I learned this shortly after writing this post, but forgot to mention it. Thanks again to Tyson Tune for pointing that out.

There’s a number of tools for the OS X that help me with my productivity (and unfortunately have no equivalents on other platforms). Quicksilver, a launcher, and TextMate, a text editor work wonders and together work fairly well.

Quicksilver is a the GUI equivalent to the command line. You can launch applications or files or perform any number of operations on those files or applications. With its powerful collection of plugins you can have it do much more, for example you can take a music file and play it in iTunes within the iTunes party shuffle. Or take an image file and have it submit to flickr with a few simple keystrokes. Initially, I couldn’t get an idea of the application, other than a lot of people loved it. Now, I’m barely using it to its potential and I love it. Using a computer without it is quite a drag.

TextMate is similarly feature rich and elegant. Just using a small number of its features makes it worth its cost. All my symfony projects are written using TextMate as are my articles for this web site. It’s strength for me is its automation. Together Quicksilver and Textmate make a winning combination.

Projects in TextMate

I like the concept of “projects” in TextMate (its common to a lot of text editors). You can drag files or folders into TextMate and group them as you see fit.

Many of my projects are written using symfony, so I’ll try to keep the entire project folder in my TextMate project. Additionally I’ll keep the symfony libraries if not the entire PHP libraries referenced in the project as well. Now I have access to all of my files with relative ease. I generally create a file group in TextMate of frequently accessed files to bypass the pain going through hierarchies of folders.

If I have a TextMate project open, anytime I open a file that belongs in that project, it will open in that project window. That means if I use the mate command line utility, Finder or even Quicksilver, it’ll still open in the project window. This is useful.1

Quicksilver Catalogue

Catalogues in Quicksilver

When you open up Quicksilver and type in some letters, it searches some catalogues by default (e.g. Applications, Documents, Desktop, etc) in an attempt to figure out what the “subject” of your action to be. These catalogues are fully customizable, so it’s trivial to add the directories of your project and your libraries into Quicksilver.

New Workflow

Quicksilver finding reviewsby.us

Now that you’ve setup your project in TextMate and added the same directories to Quicksilver, you’ll have a much improved workflow. If you save your TextMate project in your Documents directory, you need only open Quicksilver (I use Command+Space as a shortcut) and type a few letters of the project (for example, I named my project file reviewsby.us for my restaurant review site).

When that’s open, I can now open any file of that project in anyway I feel necessary. Let’s say I need to open a library file, like sfFeed.class.php. I need only type in a few letters and it opens inside my project.

This process now saves me a ton of time in digging through hierarchies of folders upon folders. It’s many times quicker than Spotlight. Give it a try, there’s thousands of uses for it, this is just one way I use it.


  1. While debugging errors in a project, if PHP tells you a certain file gave you a certain error, you can highlight the filename in Safari (or any other Cocoa browser), hit Command Escape (or whatever custome keystroke you setup for sending selection to Quicksilver), hit Enter and have it show up in TextMate.

Read full post
Google Analytics

Google Analytics Screenshot

I’ve also jumped on the Google Analytics bandwagon and added a lot of the sites in “family.” If you’re familiar with Urchin, it has a similar feel to it. It almost feels a bit lighter feature-wise. There’s a few issues I will take with it:

  • When they list links to your site (e.g. /login) they don’t hyperlink it.
  • I’d like to drill down to raw data. I like to make my own correlations. Heck, I want to drill down to see if I should filter someone from the list.

Other than that, I like the potential that this will offer me. This combined with Google Sitemaps will provide powerful analysis of the site. As it stands, Katie and I should be eating at the Cheesecake Factory and non-chain restaurants as we seem to do well in the ranks and I need to write more about maps.

Read full post
AJAX star rater for symfony

Francois from the symfony project beat me to the punch. I was going to post a detailed how-to on adding a star-rater to your web site (similar to the one’s I created for reviewsby.us), but for most of you this should do the trick. Unless people request it sooner, I’ll hold off on publishing the details on my star-rater for a while. It only offers a few minor differences (IMO advantages) to this snippet.

Read full post
Migrating from Drupal (4.7) to Wordpress

I helped Katie setup her new blog this weekend and decided that WordPress offers much of what I want out of this blog for a lot less effort than drupal1. I decided it might be worth my time to now while this blog is in it’s infancy to try converting from drupal to WordPress.

The way I start most of my projects is with a plan:

I’m really confident that this will be easy. I don’t even have to worry about comments or anything, since this blog is pretty new, but I can demonstrate how to take care of the.

Discovery

This post details a migration path from drupal to wordpress. Some considerations had to be made since I’m using drupal 4.7.

Implementation

Copy content

I followed most of the instructions, with some alterations from vrypan.net.

I installed WordPress and in mysql ran the following commands:

use wordpress;
delete from wp_categories;
delete from wp_posts;     
delete from wp_post2cat;
delete from wp_comments

I run my drupal site in the same database server, so the data copying was a snap. If you aren’t so fortunate, just copy the relevant drupal tables temporarily your wordpress database.

First we get the drupal categories into WordPress:

USE wordpress;

INSERT INTO 
	wp_categories (cat_ID, cat_name, category_nicename, category_description, category_parent)
SELECT term_data.tid, name, name, description, parent 
FROM drupal.term_data, drupal.term_hierarchy 
WHERE term_data.tid=term_hierarchy.tid;

Again with the posts:

INSERT INTO 
	wp_posts (id, post_date, post_content, post_title, 
	post_excerpt, post_name, post_modified)
SELECT DISTINCT
	n.nid, FROM_UNIXTIME(created), body, n.title, 
	teaser, 
	REPLACE(REPLACE(REPLACE(REPLACE(LOWER(n.title),' ', '_'),'.', '_'),',', '_'),'+', '_'),
	FROM_UNIXTIME(changed) 
FROM drupal.node n, drupal.node_revisions r
WHERE n.vid = r.vid
	AND type='story' OR type='page' ;

And the relation between posts and categories:

INSERT INTO wp_post2cat (post_id,category_id) SELECT nid,tid FROM drupal.term_node ;

And finally comments:

INSERT INTO 
	wp_comments 
	(comment_post_ID, comment_date, comment_content, comment_parent)
SELECT 
	nid, FROM_UNIXTIME(timestamp), 
	concat('',subject, '<br />', comment), thread 
FROM drupal.comments ;

I ended up moving the one static page I had into WordPress’s “pages” section manually.

Since my pages are written in Markdown, I enabled the Markdown for WordPress plugin.

URLs

Now for the real test. I needed to go through each page on my site and see if I could get to it using the same URLs. Since I had only 14 posts, I did this somewhat manually. I used drupal’s built in admin to do this from most popular to least popular. Most URLs worked fine. There were a small number that didn’t for various reasons, I used custom mod_rewrite rules to handle them.

Adjusting Templates

My drupal template was fairly clean and simple. So I adjusted the CSS for the default theme in WordPress until I got what I liked. Very minimal changes had to be made to the actual “HTML.”

Make the switch

Well, time to make the switch. In the WordPress administration, I just had to tell it that it’s now going to be located at spindrop.us. Then I moved my WordPress installation to the spindrop.us web root. It was a snap. Let me know if you have any troubles.


  1. Taxonomy, legible URLs and trackback support all seemed quite difficult to master in Drupal. In Wordpress they appeared to be available standard or with a minor change in the administration panels.
  2. This is a prime reason why URLs should be clean and make sense to the end user, not the programmer of the publishing software.

Read full post
Easy Yahoo! Maps and GeoRSS with symfony

[rbu]: http://reviewsby.us/ [ymap]: http://developer.yahoo.com/maps/index.html [gmap]: http://www.google.com/apis/maps/ [ygeo]: http://developer.yahoo.com/maps/rest/V1/geocode.html [GeoRSS]: http://developer.yahoo.com/maps/georss/index.html [symfony]: http://www.symfony-project.com/ [GeoRSS][GeoRSS] is an extension of RSS that incorporates geographic data (i.e. latitude/longitude coordinates). This is useful for plotting any data that might need to be placed on a map. While building out the [reviewsby.us][rbu] map, I decided to use the [Yahoo! Maps API][ymap] versus the [Google Maps API][gmap] because I wanted to gain some familiarity with another API. It was worth trying [Yahoo!'s API][ymap]. First of all, [reviewsby.us][rbu] has addresses for restaurants and Yahoo! provides a simple [Geocoding REST][ygeo] service. This made it easy for me to convert street addresses to latitude and longitude pairs (even though this wasn't required as we'll soon see).[1] The real selling point of [Yahoo!][ymap] was the [GeoRSS] functionality. I can extend an RSS feed (which [symfony] generates quite easily) to add latitude or longitude points (or even the street address), direct my [Yahoo! map][ymap] to the feed and voila, all the locations in that feed are now on the map, and when I click on them, the RSS item is displayed. That cut down on a lot of development time.

Read full post