Entries tagged “spindrop”

Alphabetical sorting in Sphinx

Sphinx 0.9.9 is great at searching full text, but treating actual strings as attributes takes some work.

Read full post
A few weeks in Chrome

A number of weeks ago I got annoyed with Firefox and decided to use Chrome for a while. This reminded me of the olden days where I used Netscape for a while, and then IE6 came out, and then Phoenix came out all the while I’d keep switching to the newest shiniest thing (note: I’m not sure about the timeline of all the browsers either).

Read full post
Retrieving elements in a specific order in Django and mySQL

If you have a list of ordered ids and you want to turn them into an ordered result set you can use FIELD() in mysql:

SELECT * FROM addons
ORDER BY FIELD(id, 3, 5, 9, 1);

This is a handy trick if you use an external search engine which gives you an ordered list of ids and you want to pull out entire row sets.

We do this in addons.mozilla.org using the Django ORM like so:

The code in action.

Read full post
Palm Pré: A retraction, I really like it now

So before I went on trip to Minnesota last month, I decided maybe I would give the Palm Pré another shot. After all, my parents have no internet access, so having the Pré… if I could overcome my issues, might be a welcome distraction.

Read full post
Google Chrome Extensions Puzzle

puzzle

I went to Add-on-Con some weeks back to represent my employer, the Mozilla Corporation.

Read full post
Palm Pre: Always hot

So I borrowed a Palm Pré that we had at Mozilla to see what it was like. I was at first very excited, I remember before the Pre was released there was a lot of talk about how awesome-fantastic it was going to be. The stories of awesomeness sort of died, and I had thought nothing of it.

Read full post
AMO Search: Powered by Sphinx

Read full post
mySQL and the grand regexp retardedness with lettercasing

I wanted to find a list of Firefox addons that had smushed text in their title. E.g. FireBug or StumbleUpon. The normal porter stemming algorithm that Sphinx uses does not turn “StumbleUpon” into “stumbl upon” as it would with “Stumble Upon”. I was hoping for, and unfortunately could not find a method to do a regular expression search/replace using mysql. If I could, I could have Sphinx read “StumbleUpon” as “Stumble Upon” and all would be well (although in theory this would backfire).

Read full post
Getting started with pipe viewer

Despite working on slimming the addons.mozilla.org database through dieting and exercise - I still have to occasionally do long running database tasks. So I finally tried out pipe viewer. As someone who’s impatient this has been awesome. Here’s some quick examples:

Read full post
DjangoCon wrapup

I went to DjangoCon this past week for work. Django is one of my favorite frameworks. I dropped PHP and the symfony framework to learn python and Django and I haven’t looked back. I think for Mozilla’s webdev team it would be the framework of choice. We have 100s of sites in many frameworks, but not a lot of resuability. Django apps are built to built to be reusable. If you build correctly you don’t have to refactor, it’s already done.

Read full post
Snow Leopard for Macports and Mysql users

I use mysql and macports on OSX and both were broken when I upgraded to Snow Leopard.

Read full post
git svn rebase... forever?

While working on addons.mozilla.org I ran into an issue of git svn rebase continually asking me to merge a file, over and over.

Read full post
V is for Version Hell

Read full post
Delicious keeps you in the know

My last task at Delicious was to build along with the amazing Vik Singh was to build a new feed of bookmarks that was heavily influenced by Twitter. It was one of the most interesting and enjoyable pieces of code that I worked on at Delicious.

Read full post
Comprehensive list of international dialing codes

I get bored with mundane tasks. So I create little adventures for myself. I had to create a list of countries and country codes to use on the Firefox mobile home page. The first few lists were incomplete, so I made my own by parsing a list provided by the International Telecommunication Union. I stripped it down to simple forms of the country names and removed codes that are very rare (satellite phones).

Read full post
Question: Building a Better Search Engine

So I finally have one of those jobs where I can tell people almost every little detail about what I’m doing and I’m encouraged to talk to people on the intar-webs and solicit opinions.

Read full post
From Delicious to Mozilla

Today I said my good-byes to Delicious.com and Yahoo! and tonight I went to the Addons Meetup @ Mozilla to get a sneak peak at what I’ll be working on in less than two weeks.

I was thrilled. I had no idea how many people to expect, but the Mozilla living room was packed - and most people were there the whole time. Real developers with really cool addons giving feedback to addons.mozilla.org directly. No matter how many blog comments, forums answered, customer care emails I responded to at Delicious - nothing beats the real insight and instant feedback you get from meeting a group of users face to face.

My brain, because of Delicious is always in data mining and analysis mode so through each presentation and each question asked, my brain was churning through things that I could build to bring some level of utility to the community.

I’m also happy to be joining an organization where everything is open sourced and available for comment. So I’m hoping to post a lot more on some of the cool tricks I do at Mozilla.

Read full post
Reading urlopen and probably any file-ish things in python fast

So I’ve been churning away in my last few days in Delicious-land trying to optimize some python code.

Read full post
Python Generators

Someone had mentioned “generators” in python to me, so I decided to figure out what it was… and I figured it out. I think a simple example would help explain it:

Read full post
Fixing the DiggBar with Delicious

I’ve been tooling around with the DiggBar and I’ve been quite pleased… but I was a bit annoyed that it was hard to bookmark things in Delicious via the digg bar. I’d have to either close bar and then bookmark in delicious, or right click on the url in the DiggBar and then change the title.

Read full post
Optimizing via YSlow

Read full post
AppEngine is not a free CDN

Read full post
Resizing Image on upload in Django

I had trouble wrapping my head around Django ORM’s handling of Images.

Read full post
Database versus files for Images

This is a dead topic for sure, but one of the bad web development habits I had picked up was that storing everything in a database made things easier.

Read full post
Versioning Django Models

In symfony, versioning a model was not terribly difficult. I had my own specialized brute-force way of doing this.

Read full post
PHPs strengths: array_count_values

I always like to think of what different interpreted programming languages bring to the table.

Read full post
Python String Formatting

Python 2.6 (and Py3K) introduce a new way to format strings. Perviously you did this:

Read full post
Mimic propel's update_at and created_at in Django models

One “trick” that propel offers you is tables with fields created_at and updated_at.

Read full post
Django models: saving markdown

I love markdown. I write my blogs in markdown. For most user text areas in my web apps I support markdown and save both the markdown and the formatted text into my data store.

Read full post
Custom error messages for Django forms

For some reason, it was difficult for me to find the documentation for this. If your Django form field is required you’ll by default get an error stating that ‘This field is required.’ You can easily replace that when defining your form like so:

Read full post
sfGuardUser -> django.contrib.auth

If you find yourself moving from symfony to Django, here’s how you sf_guard_user’s user table to django.contrib.auth user table:

Read full post
Picking up loose ends: Position Pieces

I’m picking up a pet project of mine that I was working on a few months back. It’s written in Django and is openID enabled… but I didn’t used django openID and I have no clue why… I wrote my own code.

Read full post
Django Admin and Cookies

I was dusting off an old Django project and everything was working except the admin site:

Read full post
Google Apps: In search of a worthy email system

Email, in a lot of ways, is one of the most critical applications for me.

Read full post
Creating a new project in Mercurial versus SVN

One of the most annoying things about creating new SVN projects is the new project dance:

Read full post
Windows Mobile 6.1 ROM for Mogul

Read full post
Open season for headhunting Yahoos

None of my resumes floating around on the internet have my current job listed because I get enough calls and emails (mostly from Minneapolis, but some from the SF Bay Area) from recruiters attempting to help me find a job.

Read full post
Smart pricing for iphone

Going to NYC really made me want a new phone. Primarily just to have Google Maps telling me where stuff is. So I’ve been looking at prices (theoretically for my wife and myself) more closely. Despite both devices being different, I’m treating them essentially the same:

Read full post
Screen

Screen is AWESOME!

Read full post
Surgical Precision versus Brute Force

Wireless networking can be troublesome… usually I never have problems, but every now and then I’ll go to a coffeeshop or a café where I can’t just open up my laptop and immediately start surfing.

Read full post
unixtimestamp conversion

My coworker taught me this:

Read full post
Yahoo Pipes and reducing information overload

I’ve been suffering from some information overload. I subscribed to Engadget and Gizmodo because I wanted to keep up with some home based network devices like homeplug/powerline and wireless routers. But Engadget and Gizmodo are overwhelming.

Read full post
Smarter excerpts: the art of the semi-automatic CMS

I was browsing the TED site, since it’s all up in my blogospheres and ran across this:

Read full post
FriendFeed: Pure Genius

FriendFeed is pure genius.

Read full post
Building a triangle toggler in YUI

If someone knows the more common name for triangle-toggle menu’s similar to this. Let me know.

There is a widget where an element toggles the display of a secondary set of elements. The toggle shows an arrow pointing down or right depending on the visibility of the elements. I wanted to build that in YUI.

The problem is two-fold:

  1. Build a triangle that toggles from right to down.
  2. Show and hide content.

However, with some clever CSS we can do this all in a single class change on an element.

Build that triangle

There’s a number of ways to show or hide this triangle. Because it is flexible, I’m going to opt for using a background image. Also to save on HTTP requests, I’ll use a sprite-d image. This may get merged with other icons on the system.

The particular site I’m working on uses a black background with white text, therefore a white triangle toggle seems appropriate.

sprite.png

This is what I use. I leave a little space, because the background will only clip horizontally based on the size of the element.

Add the HTML/CSS

The following HTML:

and the following CSS:

Provide the two states we require for the triangle. If you are new to sprites, rather than changing the background image entirely we just shift the background image up or down appropriately to show a new background.

The Javascript

The javascript is rather simple, but we put in some magical tricks here and there.

If you’re unfamiliar with this style of Javascript, here’s what’s going on. Everything is done in the MA namespace as to not conflict with other javascript.

The toggler is fairly generic and expects a similar HTML structure for any toggle-able element. That means this code only needs to be written once, and anytime we use the toggle class, toggling in this fashion will occur.

We only run the init function. init says when the DOM is available then run the setup function. setup adds an event handler to doc4. doc4 just happens to be the id we use on our body tag.

Note that we’re listening for clicks everywhere. This means we have to define only one event handler, regardless of how many toggle-able items there are. The event handler checks to make sure we clicked on a relevant element and then applies the toggle function to the grandparent element (switching the class from off to on as appropriate).

Note that this style of event handling means you need to carefully apply your class names. Also note, that this code could be optimized a little bit more, I haven’t put this code into production, and therefore haven’t optimized it for the YUI compressor.

Try this out if you want, I’m sure it can be trimmed down overtime. If you have trouble with it, let me know.

-d

Read full post
Why I hate the Apple Store and so can you!

So if you remember, I went to the apple store over a week ago to learn that Apple gave me a goofy battery and it needed replacing. Here’s what happened:

Read full post
py vs php: stemming

I’ve been porting some PHP to python during SuperHappyDevHouse and was amazed at how little code I needed to write since python makes list manipulation a breeze.

Read full post
unix timestamp in python

I spent far too much time learning that:

Read full post
iPhone and MacBook Air

I had to wait quite some time at the Apple Store only to find out my MacBook Pro had a battery that had been recalled.

Read full post
Redo The Web

Sometimes it’s nice to read a blog the “normal” way. You go to the site and just read the entries in reverse chronological order … versus the RSS-crazy aggregated with everything else.

Read full post
Keep a second web server around for luck...

I had one of those mid-day “what’s going on with my server” heart-atacks. I have a service that emails me when reviewsby.us is down. On my old server if it went down, I could just restart the server and it’d be back up. That was big old apache, running out of memory or something.

Reviewsby.us is a medium sized site. It gets a fair amount of traffic at a steady pace. Even in this case I decided I was in need for a new server, so I looked into nginx. It’s fast and it can serve static content well and pass things to fastcgi. Joshua Schachter explains the proxy in front concept pretty well.

Back to my web developer heart attack…

Well this setup had been holding up for a better part of a month fairly well… then I saw that a lot of the pages just lagged. I restarted fastcgi and nginx (it was a fastcgi issue). Rather than try to debug something I couldn’t, I quickly installed apache2 and setup the server the tried and tested way.

This all took place in a half an hour. Not the end of the world, but not elegant either. In the future, I’ll revert to using nginx (possibly nginx+apache versus nginx+fastcgi) but I’ll keep my other configurations around when all hell breaks loose.

Read full post
Unicode or How to deal with f'd up text in Django

So I’ve been going out of my mind trying to figure out why something like:

Read full post
the magic of django: get_callable

Read full post
sfGuardUser to django.contrib.auth.models.User

Let’s pretend that your assignment was to convert a symfony app that used sfGuardAuth to a django-based system. Wouldn’t it be great if someone just gave you a bunch of SQL that you could use to convert the symfony sf_guard_user table to a django auth_user table?

Read full post
rsnapshot to the rescue

Sometimes I wonder what convinced me to buy a ReadyNAS and use rsnapshot to create incremental backups.

Read full post
Dear Bank

I can’t do simple things because they inevitably make me ranty.

Read full post
Django circular model references

I’m used to circular references in my model. Often I do a versioning of an Item with an ItemVersion. Item will link to the latest ItemVersion and ItemVersion will link to the relevant Item.

Here’s how you can define your appropriate django models:

Note in the first model, Item, we reference ItemVersion in quotes because ItemVersion is not yet defined.

Read full post
Why Django Templating is awesome and why I get smarty again

I get Smarty thanks to django… yeah, it’s weird.

Back to my original comment about templating, smarty really is trying to limit the scope of PHP in a good way. Too often I see a lot of heavy-lifting in the templates. It’s so bad it makes my MVC’d brain explode.

Django templates are very limited, based on a philosophy of keeping view logic and only view logic in the templates.

This is what smart tries to do and it’s a reasonable solution to the fact that PHP is a templating language with a kitchen sink. It’s saying, okay… well let’s treat PHP as a programming language, and keep Smarty as the template.

symfony of course says (well not really, or in any official capacity, but would you believe… frameworks talk to me), some PHP is view and some is model/controller. We’ll suggest which ones are which, but really we’re not getting in the way of making your life a living hell by sticking complicated business logic inline.

Read full post
Girl Geek Dinner

Katie and I went to Girl Geek Dinner. The best thing about being a Geek Husband is having a Geek Wife to bring you as a guest.

Read full post
django experiment: day 2 templates, and that's it?

[tags]django, symfony[/tags]

Read full post
Serving Firefox xpi extensions over ssl/https

[tags]firefox, cache, xpi, https[/tags]

Read full post
nginx and 'www' free urls

[tags]nginx, rewrite[/tags]

Read full post
ReadyNAS redux

[tags]infrant, readynas, nv+, redux, harddrive, crash, gear[/tags]

Read full post
ReadyNAS is awesome, Netgear is not so awesome

After much debate I decided to get the ReadyNAS NV+ with a single 500GB disk. It’s great so far. I even got ssh to work which means I have rsnapshot working (I can post details if necessary).

The problem is… I wouldn’t have needed rsnapshot at all if the CD they shipped me wasn’t cracked. I called Netgear (who is headquartered down the road, literally) and they said they can’t do anything and that I should call Newegg (the vendor) or call EMC the provider of the backup software (and my next door neighbor at work). Newegg could only give me a $25 credit which is fine, but as someone at my work pointed out… that’s not the same as a 5 seat Mac/Windows license for a backup software.

Of course… it’s impossible to hunt down an actual support email address for Netgear, so this might not get resolved.

FYI, Here’s some good links about readyNAS that I’ve found.

Read full post
Affordable IM/Phone-based consulting

[tags]symfony, spindrop, services, consulting[/tags]

Read full post
Affordable IM/Phone-based consulting

[tags]symfony, spindrop, services, consulting[/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
symfony and the .htaccess file

One performance boost that can be garnered from a symfony app (or any app for that matter) is disabling .htaccess. .htaccess does not need to be parsed on each visit to your app. Disabling .htaccess is trivial in your VirtualHost or another relevant part of your apache configuration place:

Read full post
doctine and getState()

[tags]doctrine, php, symfony, sfDoctrine, database,errors[/tags]

Read full post
TextMate + YUI = YUI snippets!

I do a lot of YUI grid layouts and I love the nestable grids:

<div class="yui-g$1"> 
	<div class="yui-u first">
		$2
	</div>
	<div class="yui-u">
		$3
	</div>
</div>

There’s a tab stop after yui-g in case you want to use one of the variants (yui-gb, yui-gc, etc).

I’m working on a site that uses two equal width columns… a lot… so this comes in quite handy. So long tables.

Read full post
Creative Friends

[tags]photography, design, pretty[/tags]

Read full post
Finding a co-founder

[tags]startups[/tags] A friend of mine directed me to Why to Not Not Start a Startup:

Read full post
MarsEdit

[tags]MarsEdit,software,blogs[/tags]

I recently read about the acquisition of MarsEdit by Red Sweater Software so I decided to check it out. I’m quite glad I did it does make my workflow a lot easier. What I had been doing up until today was this:

  1. I’d come up with an idea.
  2. Open up my writings project in TextMate
  3. Open a new file and start typing ideas, etc.
  4. Find the right blog to post it on (e.g. Spindrop, davedash.com, yumbo the reviewsBy.us blog or Metroblogging).
  5. Login to the blog.
  6. Cut and paste titles, categories, tags, etc.

It was easy to drop my blogging habbit ;)

Now, I can do this: 1. Come up with my idea. 2. Select which blog I want to post it in. 3. Write my idea. 4. When it’s ready to go live, just hit submit to blog.

The one slight problem I have is I use a tags feature of wordpress in addition to categories (not sure why I don’t just use categories, but that’s another story), currently there’s no easy way to deal with WordPress plugins’ custom fields that I know of. So there’s an optional 5th step of examining the post on the site and possibly adding tags.

Still, these 4 steps are easy and all done from one app. It also features a preview of your post that you can customize per blog with a custom HTML template. Which makes it easy to just drop your blog’s style sheet and see if your post looks right.

Definitely looks like a keeepr!

Read full post
Vim Undo

So apparently you can “time-travel” in vim. Commands like :earlier 10m and :later 5m will move your document to points 10 minutes ago and from their 5 minutes after.

Is it just me, or is this acceptable?

vim phd_thesis.txt

:later 5y

:)

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
Restaurants can be (tagged) spicy too!

We had a change of heart on tagging. Tag everything! Initially I felt that only menu items should be tagged. “People want to know what dishes are spicy“… true, but people also want to know what restaurants are in Uptown or are Mexican or are closed on mondays. So… we now have restaurant tags. Login and tag something!

Read full post
Menu item versions

We make a lot of mistakes, and by we, I don’t mean myself, but I mean we as humanity. That’s why we like to keep multiple versions of things in our restaurant review database. That also means menu items. Did you describe the Pesto Chicken as having pisto and not pesto? Problem solved, now any user can log in and change it. It’s like a wiki…. for restaurant menus.

Read full post
Safari Fixes

Safari interprets /* */s differently than FireFox or IE. FF and IE will ignore a unmatched /* or */, whereas Safari will ignore parts of code if there’s a lone */. Once I found that out, I was able to get the list items that are used throughout the site to render properly.

Read full post
Spindrop objectives

I had wanted to write about this later, but Darren at ProBlogger started this group writing project on “Blog Goals.” So I am jumping in quickly. This blog was created for two reasons, one, to document any technical things that I’ve learned, code samples, best practices, strategies, etc. as they pertain to web development and open source. The second is to serve as a site to record updates to any of my projects.

I’m still in my infancy for this blog, but I’ve seen a lot of little things that keep me optimistic.

The objectives I have for Spindrop are both internal and external. Internal goals are things that I can change myself. For example, the style of the site, linking to other places, posting more content, changing the way ads are presented, etc.

External goals depend on readers like you. I can do my best to make this site be relevant to a lot of people, but I can’t make people click on my site, comment, or any of that. I can still make goals for them, and that will subconsciously get me to position myself better.

They are both related. If I achieve my internal objectives, I’m better suited to getting external objectives done. If I get my external objectives done, it encourages my behavior of making the site better.

What am I willing to do

My “9-5” is a fairly demanding job as a lead web developer for a health and wellness website. On top of that, I’m getting married this month, so I’m very pressed for time when it comes to a side project like this. Luckily I’m very well organized, and do wake up early and spend time writing.

Writing

In an OmniOutliner file, I keep a detailed list of what I’ll be writing as well as other “to-do list” items for this site. I’ve already taken into account that I’m not going to be pushing anything useful for the week or two surrounding my wedding. Hopefully, I can still muster two or three articles this month. Some of those articles are on software choices, strategies for using propel and symfony and general work habbits.

By spending an hour (or more) a day on writing, I’m generally looking over things and making sure I did a halfway decent job. I prefer not to have typos, spelling errors, etc, I prefer to have a readable article, and I prefer to throw in images, when useful. Of course, I do this early in the morning, so there are mistakes. But, I’ve noticed a lot of heavy hit articles on del.icio.us and digg have typos too.

Non-content changes

I’m willing to tweak ad placement, and practice all the other ad-voodoo that is involved with blogging. I’m also willing to announce the site at appropriate moments, communicate with people via forums and other blogs. I’m willing to listen and execute on others advice. The best advice I’ve heard is to stick with it.

What can I get at the very least?

With all my efforts, at the very least, I know I’ll have something to show for. For one, I tend to do things over and over again. I’m a web developer, and the bulk of my blog is about how to do things I’ve done before. So if I have to make another map or migrate a blog, I can see how I did it before. Now, anything else this blog achieves is gravy.

External goals

Some of my hopes for the site are out of my control. I want what a lot of other blogs want: traffic, revenue and community. Additionally I want traffic to be sent to my other sites, like the reviewsby.us site so that they can share the success.

Traffic

The most important goal for me is traffic. I want this site to get a lot of traffic. For the last two weeks, I’ve averaged 112/users a day. Of course, part of that is due to a spike because of last Friday’s article on Editing CSS from Firefox which had an anomalous amount of hits due to getting on reddit, del.icio.us popular, digg.1

Traffic is important because I without it I can’t expect to have revenue or community. There’s also a sense of validation that you have something useful to say, and sites like del.icio.us make you feel that not only was it useful, it’s worth hanging on to.

Revenue

I’d like to earn enough to actually make blogging make a significant change in my lifestyle. Right now it’s a hobby. A dedicated hobby, but it’s still a hobby. I love writing, I love to see myself improve, I love what I write about. I’d like to continue to do that full time. I’m sure with some tweaks to adsense and some other forms of advertising, I can kick that up. But content, which attracts traffic, will be the biggest driver of them all. My less-than-“adsense optimized” restaurant review site generates a lot more money (seriously, we’re talking in small terms, like the cost of an iced tea), but it has a lot of content and a lot of useful ads that get generated from adsense.

Last month (May 2006) I made just shy over $2.00 from the web site. That’s not much, but it’s better than $0. I’d like to make $4 in a given month. On one hand it’s a doubling of earnings, but on another, it’s not very much money. I’m not expecting to double the earnings this month, although I might easily do that, but I am expecting to hit that $4 mark eventually. It’s an easily obtainable goal, but for me easy goals are good. I feel just as good when I hit them, as I would if I got a raise or a bonus at my “9 to 5.”

Community

I’d like for there to be some level of interaction with me. I don’t mean community in the LiveJournal sense of the word, I just want some interaction with me the poster, and my commenters. The last week or two has seen a few “real comments,” which is promising. I hope that trend continues over time. It’s an opportunity to improve myself if I get good feedback, and it’s an opportunity to further express or clarify a point. I also like to help people out.

Linking out to my other sites

Lastly, I do want this site to assist my other sites. With a decent amount of traffic, this site can be a great link in to my other sites, and bring them up to speed.

Conclusion

Overall, I’m happy where I am, and I’m happy where I intend to go. Each week brings in a new useful content which is a nice record of how I do things, and proves some degree of usefulness to others.

I hope to expand on those strengths. This body of content might be small now, but it only gets bigger. My long term goal is to be able to generate the traffic and revenue I need so I can justify dedicating more time to this project.


  1. Perhaps it's not anomalous, but if you saw my statistics you'd understand why I might think so.

Read full post