Pages

Thursday, December 3, 2009

BFTP Enters Test Phase

I'm happy to report that BFTP has entered the test phase. It is already stable and usable, but a few bugs still need working out. The initial feature set is also more robust than I had intended, making the imminent release of version 1 that much more exciting!

Tuesday, December 1, 2009

Moving the Cursor to the End of a TextField in REALBasic

You will now learn in three seconds what took me 15 minutes to discover.

1. Double-click on the TextField.

2. Select GotFocus.

3. Add this to the method:

me.selStart = me.text.len() + 1

Monday, November 30, 2009

Forcing a 404 Error with Zend Framework

There are many things I like about Zend Framework. And there are times when using it is like searching for a black cat in a dark room. Seemingly simple tasks end up frittering away time looking through the documentation and source code.

Inducing a 404 response is one of these things. I didn't want to simply redirect the page, but rather, I wanted the ErrorController to handle the error just like usual.

In your action controller:

// Some "not found" condition occurs.
throw new Zend_Controller_Action_Exception('Page not found.',404);

And in views/scripts/error/error.phtml:

<? if (404 == $this->exception->getCode()): ?>
// Put your 404 message here.
<? endif ?>

Thursday, November 19, 2009

Been There: The Long and Winding Road

We started our geographic note taking iPhone application, Been There, back in April. Principal development was done by July. Then the process of test, report, fix, test lasted through September. At which point we started the process of preparing for submission. It's been one thing after another, but Lord Willing, I will submit the app to Apple's approval process tomorrow.

I could do it tonight, but I don't think I'm up to it.

Thursday, November 5, 2009

When Flexigrid Columns Don't Line Up

If find it really aggravating to fix a problem only to encounter it again -- after I've long forgotten how I fixed it. This happened to me when I was creating a new PHP wrapper class around the Flexigrid plugin for jQuery.

And the reason was ... the stylesheet had:

table { width: 100%; }

Remove this or add a class or inline style to your table to allow Flexigrid to do its thing.

Of course, I don't actually remember if this is the problem I encountered last time, but I'm posting this for the benefit of all -- including myself.


Monday, November 2, 2009

Update: Bogeymin is Ready for Testing

Although there are a few plugins left to create, I'll be starting the test phase of Bogeymin this week. I am also lining up beta testers for some initial installs of the software. This is a private test and the participants have already been lined up. But if you'd like to know when the product is ready for release, sign up here.

Thursday, October 29, 2009

BFTP Update

I reviewed the initial build of BFTP today -- sorry, still no screenshots -- and am pleased with our progress so far. One feature that was cool to see in action was the directory masking. Essentially this allows the service provider to create human-friendly aliases for a directory on the server. So, for example, a directory at galleries/wedding would appear as "Wedding Gallery" to the end user.

We are behind on my original schedule, but should have a beta version soon. The management website is another story, though. Guess I better get to work.

Friday, October 23, 2009

What's Wrong with Guru.com?

Most software developers, firms or entrepreneurs will eventually look for sales leads on sites like guru.com where there are jobs-a-plenty -- as well as competitors. This post focuses on my experiences with guru.com as well as other job sites.

First, here is a typical job posting:

I have bought and registered my domain. I already have a webhost. Now i need Someone to program/create my online sporting goods store for me. Im going to need a very appealing site. I will be needing an automatic payment processor that can be set up to take orders from me then automatically purchase the orders from my dropshipper(Wholesaler). There are several other things i will need that we can discuss with your bid. Sorry but my budget for this is below $250 but im not needing an extreme amount of stuff. I need it to go from an Empty site to completely finished and ready to operate.

I'm not sure why people use such strange capitalization as "i need Someone"? At any rate, the punctuation is often incorrect or non-existent and the grammar is often bad as well. Additionally, you typically only get a sentence or two and requirements are rarely included.

In general, most project owners are:

  1. Not qualified to assess whether a given contractor can do the job or not.
  2. Unable to sum up their project in TV guide style, much less commit to creating a good set of requirements.

This is not uncommon of all clients everywhere, BUT the big difference is when you deal with clients over the phone or face to face, you have a better chance of getting to the root of what needs to be done. The impersonal nature of the site makes it difficult to gather info as well as differentiate yourself from everyone else.

Project owners aren't required to submit a structured request for proposal and their assumptions on cost versus result are usually way off. Furthermore, the typical client usually has no concept of value/benefit marketing, they have no USP for their business or product, and they never heard of a S.W.A.T analysis. This means that you have to produce something that has no measurable goals using only vague requirements.

Sites like guru.com are still the land of dreams and big ideas. You know, that happy place where a good idea gets lots of free programming, graphic design and advertising.

Expectations are often as high as the budgets are low. Nothing about the project seems all that difficult to the client and therefore shouldn't cost too much. Almost everyone on sites like guru.com is shopping based on price alone.

The expectation of minimal cost for maximum result produces a glut of projects on which you will never want to bid; the number of "good" jobs is extremely limited.

To sum up, employers on these sites are quite possibly the worst possible prospects for an independent developer because:

  1. It is difficult to differentiate yourself from the competition.
  2. Prospects often dream big, but have skewed expectations for features and cost.
  3. Most prospects are shopping based on price.
  4. "Good" jobs are few and far between.
Is it impossible to win good jobs? No.
If you are a developer, I would say that it is not impossible to overcome these deficits, but I wouldn't hold my breath waiting for the jobs to poor in -- especially if you're a free-lancer. You'll want to develop a strategy that's right for your skills and business and be willing to experiment for a long time with any of these jobs sites.

If you are a prospective employer, I would encourage you to purchase Outsourcing Web Projects: 6 Steps to a Smarter Business from sitepoint.com -- it's an excellent book that will help you be a more attractive prospect, which can get you better bids from better developers.

Thursday, October 22, 2009

Bogeymin is Almost Alpha

Almost. After my previous exercise in optimism, a number of other projects took precedence over Bogeymin. Even so, I spent last Saturday with blinders on and managed to finish all of the core commands.

I'm hoping to start testing this weekend as well as working on some of the built-in plugins. Speaking of which, the plugins system I've devised is pretty neat -- if I say so myself.

  • It allows the registration of arbitrary callbacks both before and after the command runs. So for example, when installhost is issued, various plugins can express their interest in responding to this activity and execute using the same command line switches that were given to installhost.
  • Plugins may be built in (written here) or user defined (written there). As noted, they have access to the original command line arguments. They can also use Bogeymin's built-in logging, user, and exit tools.
  • Plugins can be disabled or enabled in the main configuration file.

The __init__.py file looks something like this:

# Dependencies.
import os
from bogeymin import bogeymin

# Define the commands we want to which we wish to respond.
_implements = [ 
    'installhost',
    'movehost',
    'renamehost',
    'uninstallhost',
]

# All plugins must define an implements function.
def implements(command):
    """Determine whether the plugin implements the given command. Returns 
    True if implemented, False if not.
    """
    if command in _implements: return True
    return False

# Run after the main command has finished.
def post(Command):
    """Run the plugin *after* execution of the command.

    Command - The command object from Bogeymin.

    Returns an exit code.
    """
    domain_name = Command.getOption('d')

    ServerCfg = bogeymin.ServerConfiguration()
    if not ServerCfg.has('virtual_host','path_shared_ssl'):
        return 1
    path = '%s/%s' %(path_shared_ssl,Command.domain_name)

    if not os.path.exists(path):
        return 1

    if 'installhost' == command:
        return os.system('installsharedssl -d %s' %domain_name)
    elif 'movehost' == command:
        os.system('uninstallsharedssl -d %s' %domain_name)
        return os.system('installsharedssl -d %s' %domain_name)
    elif 'renamehost' == command:
        os.system('uninstallsharedssl -d %s' %domain_name)
        new_domain_name = options[options.index('-n') + 1]
        return os.system('installsharedssl -d %s' %new_domain_name)
    elif 'uninstallhost' == command:
        return os.system('uninstallsharedssl -d %s' %domain_name)

Monday, October 19, 2009

WebStar - Try it Again for the First Time

I've been working on this project called Web Star off and on for more years than I care to mention. It's basically a web directory and social network for those interested in film and television.

Last year, I finished it (once and for all, I thought) but I didn't promote it -- at all. So, I reviewed the project and decided that

  1. The project as a whole could be and should be a lot simpler.
  2. I still like the project and would like to try again -- and promote it this time.

I've replaced the existing site with a sign-up page and have scheduled the job for completion this year.


Friday, October 16, 2009

Using jQuery's Flexigrid Plugin with Zend

There is no ready grid component in Zend Framework. Most of the forums recommend implementation using Dojo's grid tool, but since I'm new to both Zend Framework and Dojo, this didn't seem like all that great of an idea.

However, I have made frequent use of Flexigrid in the past and after making a half-hearted attempt at using Dojo, I decided that I might as well use Flexigrid -- at least I'm already familiar with how it works.

Now, none of this was obvious at first. Sure, I understand all of the principals of Zend Framework's MVC implementation, but figuring out where to put stuff is a real challenge.

This demonstration is meant to show how to get started. You'll want to make this more programmatic later, but hopefully it gets you to a working implementation.

AJAX Action

First, I would recommend creating the action handler for the AJAX call. You can test this to make sure it's the output expected by Flexigrid.

public function ajaxAction()
{
    $Table = new Model_User_Table();
    $total_rows = $Table->count(); # this is a custom method
    
    // Request parameters received via GET from flexigrid.
    $sort_column = $this->_getParam('sortname','name'); # this will default to undefined
    $sort_order = $this->_getParam('sortorder','asc'); # this will default to undefined
    $page = $this->_getParam('page',1);
    $limit = $this->_getParam('rp',10);
    $offset = (($page - 1) * $limit);
    $search_column = $this->_getParam('qtype');
    $search_for = $this->_getParam('query');
    
    // Build a query to get the results.
    $Select = $Table->select()->order("$sort_column $sort_order")->limit($limit,$offset);
    if (!empty($search_column) && !empty($search_for))
    {
        $Select->where($search_column.' LIKE ?','%'.$search_for.'%');
    }

    // Get the result rows using Zend's paginator.
    $Pager = Zend_Paginator::factory($Select);
    $Pager->setCurrentPageNumber($page);
    $Pager->setItemCountPerPage($limit);
    $rows = $Pager->getIterator();

    // Send headers.
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT" );
    header("Last-Modified: " . gmdate( "D, d M Y H:i:s" ) . "GMT" );
    header("Cache-Control: no-cache, must-revalidate" );
    header("Pragma: no-cache" );
    header("Content-type: text/xml");

    // Prep the XML.
    $xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?";
    $xml .= ">\n";
    $xml .= "<rows>";
    $xml .= "<page>$page</page>";
    $xml .= "<total>$total_rows</total<";
    foreach ($rows as $row)
    {   
        $xml .= sprintf('<row id="%s">',$row['id']);
        $xml .= sprintf('<cell<<![CDATA[%s]]<</cell<',$row['id']);
        $xml .= sprintf('<cell<<![CDATA[%s]]<</cell<',$row['name']);
        $xml .= sprintf('<cell<<![CDATA[%s]]<</cell<',$row['email']);
    }

    // Disable the default layout and output the XML.
    $this->getHelper('layout')->disableLayout();
    $this->getHelper('ViewRenderer')->setNoRender();
    $this->getResponse()->setBody($xml);
}

View Script

Next we'll look at the view script. In my app, the default action displays users in a grid, so in this case the view script was located at view/scripts/user/index.phtml and all this is where I put my javascript and table HTML (such as it is). The CSS and JavaScript should probably be abstracted into a Grid class along with the specification of rows and tables.

<style type="text/css">
<!--
.flexigrid div.fbutton .add { background: url(/eve/icons/small/plus.png) no-repeat center left; }
.flexigrid div.fbutton .delete { background: url(/eve/icons/small/subtract.png) no-repeat center left; }
.flexigrid div.fbutton .edit { background: url(/eve/icons/small/pencil.png) no-repeat center left; }
-->
</style>
<script type="text/javascript">
$(document).ready(function(){
    function add_record() { window.location='/user/add'; }
    
    function delete_record(command,grid)
    {
        var record_count = $('.trSelected',grid).length;
        if (0 == record_count)
        {
            alert('Please select a record first by clicking on the appropriate row.');
            return;
         }
         $('.trSelected',grid).each(function(){
             var id = this.id.substr(3);
             window.location = '/user/delete/id/'+id;
         });
     } // delete_record

     function edit_record(command,grid)
     {
         var record_count = $('.trSelected',grid).length;
         if (0 == record_count)
         {
             alert('Select a record first by clicking on the appropriate row.');
             return;
         }
         $('.trSelected',grid).each(function(){
             var id = this.id.substr(3);
             window.location = '/user/edit/id/'+id;
         });
     } // edit_record

    $('#users').flexigrid({
        url: '/user/ajax',
        dataType: "xml",
        usepager: true,
        useRp: true,
        rp: 10,
        sortname: 'name',
        sortorder: 'asc',
        buttons: [
            {name: 'Add', bclass: 'add', onpress: add_record},
            {name: 'Edit', bclass: 'edit', onpress: edit_record},
            {name: 'Delete', bclass: 'delete', onpress: delete_record}
        ],
        colModel: [
            {display: 'ID', name: 'id', width: 30, sortable: true, align: 'left'},
            {display: 'Name', name: 'name', width: 120, sortable: true, align: 'left'},
            {display: 'Email', name: 'email', width: 150, sortable: true, align: 'left'}
        ]
     });
});
</script>
<table id="users"></table>

Main Action

Last, change the indexAction() method of the controller.

$this->view->headScript()->appendFile('/scripts/jquery.js');
$this->view->headScript()->appendFile('/scripts/flexigrid/flexigrid.pack.js');
$this->view->appendStylesheet('/scripts/flexigrid/css/flexigrid/flexigrid.css');

Thursday, October 15, 2009

Been There Update

It's been a while since I reported on our new iPhone app for documenting your favorite places -- Been There -- so here's a quick update:

  • The app is still undergoing testing. I've found bugs with each release and I want to make sure we release it to the public as bug-free as possible.
  • We have finally been approved for an Apple Developer account. The process is sort of straight forward, but issues with identity, typoes, and mistakes on my part has made this quite painful.
  • We are now entering the final test phase with the hope of submitting the app to Apple some time this month (October).

Tuesday, October 6, 2009

BFTP Update

A quick update on the brandable FTP application:

  • Initial user interface is done -- sorry, no screenshot yet.
  • FTP connection is done. That's good.
  • XML for meta data and the XML parser is done.

I've posted a preliminary site where you can sign up to be notified when BFTP is released.

Bogeymin Nearing Completion

They say that programmers are optimists, so when I say "nearing completion", it probably means it's about 51% -- hey, that's nearer to completion than to just starting.

I'd like to say I'm wrapping it up on Bogeymin, but I'm not. I am, however, able to see the end of the project from here. Coding in python is enjoyable and speedy and I expect to finish up the website management commands this week.

As a guy that deals daily with the problems that Bogeymin solves, I am very excited about the progress and have already started using the new tools.

There are lots of cool features. Below is a screenshot of the self-documentation tools in action:

Bogeymin Self Documentation

Tuesday, September 15, 2009

Do You Have Too Many Products?

Circumstances beyond my control led me to the Oracle website. It's been a number of years since I used an Oracle database and I wanted to see what they have in the way of rapid application development tools.

Now, why is it that big companies like Oracle, Microsoft, HP, and IBM have websites that are completely impossible to navigate? I think I would have an easier time finding my way out of the Australian outback than finding my way to a product on any of these sites.

How is it that these companies -- industry giants -- have abysmally difficult websites? The budget for these sites probably rival's the GDP of a small country, and for what? Usability? Ha! Accessibility? Doubt it. They spend this money and they arrive at a website that is so difficult to navigate, that one wonders how such a monstrosity could have been contrived.

There is so much fluff and market-speak on these sites that it is truly challenging to get an idea of what they are selling. Does Oracle sell a database server? Does Microsoft have some sort of collaboration tool? Do HP and IBM still sell servers? The answers -- if they exist -- are buried deep in a big ball of mud.

Could it be they have too many products? I mean, look at Microsoft and Oracle. Does every product they offer have their name in front of it because their products aren't unique? And although I think Microsoft's site is very much improved from recent versions, check out this list of "Windows" products:

  • Windows 7
  • Windows Vista
  • Windows XP
  • Windows Mobile
  • Windows Automotive
  • Windows Azure
  • Windows Embedded

"Windows Automotive"? Really? "Azure"? And the list of servers:

  • Windows Server
  • Windows Essential Business Server
  • Windows Small Business Server
  • Windows Home Server

Apparently, Windows servers aren't essential to small businesses or in the home? You get my point: Too many products.

Back to oracle.com and the impossibility of understanding what products they really offer. A database? Sure. But what's all that other stuff. I need to hire a consultant to tell me what all these products are for.

After a while I was able to find the developer's version of the Oracle database as well as their free IDE known as JDeveloper. I got these things set up and poked around a bit, building a simple JSP page and generally fiddling with the software.

As an experienced systems administrator and programmer, I can see there is a lot of power there, but I am totally overwhelmed by the massive amount of products offered by Oracle. Could I use them? Probably, but I don't have time to figure out which ones I need.

Call me impatient, but I want something that obviously fits my needs. As a builder of things, I understand the desire to create more products and I reckon most of these companies are (or should be) creating products to obtain a competitive advantage. But more products as a differentiator? That I doubt.

Is anyone doing it right? I tried to think of a "big" company with a lot of products. The Apple website is pretty easy to use. But I think Google has the right idea; lay it all out in a simple way; icons, title/link, and a (very) brief introduction.

It is easy for me to sweep aside years of product development and branding, making simple something that is inherently complicated -- and it's not like any of these companies would listen to the likes of me. But this made me examine my own products and websites -- do I have too many products? Do you?

Monday, September 14, 2009

Brandable FTP Client

We've started on a new project called -- for lack of a better name at the moment -- BFTP. It's a brandable FTP client for ISPs and Web developers that will greatly reduce the hassle of providing FTP to customers.

Features include:

  • Brandable for each ISP or developer.
  • Packages customer FTP info for quick and easy access to a website.
  • Web-based control panel for managing customer access.

Thursday, September 10, 2009

Remove Your Site from the Wayback Machine

Recently I had a request from a client to remove a site from the Wayback Machine. If you've never heard of it, I suggest you check it out (it is a service of archive.org that captures website content for posterity).

But why would you want to remove your site? Well, because it's your content and your copyright. Or because you don't want a potential employer seeing an old version of your blog. Or because the design of your site in 1997 is just plain embarrassing.

Until this time, I didn't know if or how site could be removed from the Wayback Machine, but I did some digging and found some pretty clear instructions on removing a site from archive.org

It involves:

  1. Creating or editing the site's robots.txt file.
  2. Submitting a request to crawl the site.

Pretty simple, eh, Mr. Peabody?

Friday, August 28, 2009

Bogeymin

I'm proud to announce the commencement of a new project called "Bogeymin" -- a command suite tool for managing Web servers and related services such as DNS and email.

Features include:

  • Linux-based.
  • Total control from the command line and complex automation for consistent maintenance and administration.
  • Utilizes existing security technologies to safely permit access to level 1 support, level 2 technicians, and full administrators.
  • Unique website organization system.

The project is scheduled for completion in October. Visit the official site to for the latest info and sign up to be notified when it's released.


The Shoe is On the Other Foot

I am a pretty good guesser and empathizer. I also have a good imagination. This helps me build Web sites and applications that are useful, thoughtful, and helpful to clients. Most of the time, anyway. It also helps me understand the client's perspective and win more jobs.

But after deciding to outsource the Objective-C portion of our iPhone application, I am now "the client". And the experience has been helpful in understanding even more of the client's perspective.

Timing and Silence

My chief complaint -- if it can be called that -- is getting the job done on time (which we have not) and hearing from the developer on progress (which was good at first but lately has been sporadic). Most of us (developers) are well aware that communication can be an issue, but this experience has really driven home to me the necessity of prompt communication.

  • Say something, anything. Never let a message go unanswered, eve if it's just to say "message received". We often feel pressured to have news or progress before responding, but this takes time. Instead, let the customer know you're still there.
  • Be up-front about delays. Let the customer know as soon as possible if there's going to be a delay.
  • Be open about the current schedule for incoming change requests.

Silence leads to frustration -- don't frustrate your clients.

As a result, I have a new goal for communication with my own clients.

Tuesday, August 25, 2009

My First Experience with "Offshore" Outsourcing

I have scads of experience both building and managing software projects. I've also worked with a lot of people; full-time employees as well as contractors. My first experience with outsourcing a project went to India where I could leverage the exchange rates to purchase more hours for my dollars. I put together a fairly extensive project plan from which everyone could work.

After having review the source code and tested the application, I know the developer is competent. However, there are several things I will do differently next time.

1. My application needed a Web service. I was providing this service, but I wanted to let the developer have some leeway here -- the developer would know best as to what would be needed, right? This was bad. I should have defined the Web service before hand -- method names, parameter names, design by contract, etc.

2. Design. I am no designer. The developer did the design work. The graphic design didn't turn out bad but it wasn't what I wanted. The problem is, I didn't come up with a design concept until the work was well underway. I couldn't criticize a design that was otherwise okay because I was too unimaginative to come up with something on my own in the first place.

3. Layout, labels, and process flow were all left to the developer. Again, this didn't turn out that bad, but it could have been much better in terms of UI. I should have had my own screenshots in advance with flow-charts indicating how the user experience should be implemented.

There were of course times when the language barrier was a problem, but it was never a significant problem, especially via email. Planning -- even more than I had done -- would have saved a lot of time and produced a better product.

Tuesday, August 18, 2009

It's a Dirty Hack, But Someone Has to Do It

I had finished a fairly complicated application -- to the client's specification -- that featured a complex system for selecting advertisers for various types of announcements. Although the money was paid, the project lay dormant for many months until the client started selling the ad spots.

Unfortunately, the person selling the spots had been left in the dark on how the system was built and sold the ads in a way that was completely contrary to the current tools -- control panel, automation, and front-end were now incompatible.

I looked at every way possible to make it work within the capabilities of the application but there was no reconciling the way the product was sold and the way the product was built.

To make matters worse, the deadline for the "new" way of doing things was two weeks and I had already had a full plate. I sensed stupid hours in my future, but promised the client I would come up with something.

The more I got in to the code, the more I realized that it was worse than I anticipated. I tried several approaches, but everything was backward in terms of implementation and data that was previously needed in one place was now needed somewhere else -- some where the scope was completely wrong.

I really only had a weekend to work out a solution and the "real" way of doing it would probably take a week's worth of work.

Sometimes you just have to hack something in place. In this case, I bypassed all of the dynamic stuff and just hacked in the raw data for the specific advertiser. Yes, lots of really complicated and fascinating code was essentially discarded for a plain text band-aide that will have to be fixed some day. It wasn't pretty but it was simpler, and more importantly it got the job done quicker.

Sometimes the greatest difficulty in this situation is getting over the fact that you are hacking otherwise excellent code into pieces.

This brings me to yet another truth in programming:

It's a Dirty Hack, But Someone Has to Do It.

You are the best candidate to hack on your own code and when the rubber hits the road, the reality is you just have to do it.

But what can you do to minimize the damage?

  1. Leave comments for yourself (or the next guy) wherever you need to change the code.
  2. Use @hack or some similar marker so you can easily find the changes and comments.
  3. Go back and fix it on a rainy day.

Thursday, August 13, 2009

Lemonade Web Site Hosting

I've been managing servers off and on since 1998 and have been working with Web-related servers (DNS, FTP, HTTP, POP, SMTP) since about 2001. And starting around 2004 for, I took over the management of a small hosting company with about 1500 to 2000 websites.

It has always been frustrating to me when customers balk at a decent price for hosting. Everyone tends to gravitate toward the cheapest of any product, but having used various hosting services as well as operated my own, I understand better than most that real hosting costs real money.

What is "real" hosting? And why does it cost money?

  • Hosting occurs on servers that cost money.
  • Servers are located in a building that costs money.
  • Connecting the servers to the Internet costs money.
  • The skilled employee(s) needed to maintain this infrastructure costs money.

In the old days of the Web, that is, 1995, I remember "business class" hosting was sold for $50 to $150 per month. But some companies today are selling hosting for as little $2.95 a month. And what about free hosting? How do they sell it so cheaply or even give it away?

  • Hosting may be a secondary business.
  • Advertising is inserted into the hosted sites.
  • They offer little or no support to hosting customers.

Real hosting companies needs to make a profit. With the exception of a very few, these low-bid hosting companies are like childhood lemonade stands -- here today, gone tomorrow. They are in the business to make a quick profit and it doesn't matter if they aren't around tomorrow -- to them at least. It is easy to find horror stories of cheap hosting gone bad.

Real website hosting comes from people offering friendly and responsive support on a stable and well maintained infrastructure. This is NOT what you pay for when you purchase hosting for $2.95 a month.

Oh, and if you're looking for "real" hosting, check out Connecting Point. And if you're a photographer, I would recommend Marathon Press. They aren't paying me to say good things about them, but I work for and with both of these companies from time to time and you'll find them to be real indeed.

Thursday, July 23, 2009

Our First iPhone Application -- Been There -- is Almost Done

Been There, which is something like a geographic discussion board allowing users to share their favorite locations with comments and rankings, is almost done.

The whole process has been a ... learning process. I was managing the project's outsourcing team as well as my own labor on the Web service that drives the app. There are many things I would have done differently, which will probably constitute a separate post, but all in all, I've been happy with the progress.

Now we'll see if anyone will buy the app!

Thursday, July 2, 2009

The Log File is Your Friend

So, I was working with the flexigrid plugin for jquery, which is pretty cool but not very well documented, and I encountered this strange problem where the grid was failing to display the output.

Now, with most of the grid tools you

  1. Create only the most basic HTML on your page.
  2. Write some javascript to make the AJAX call, also included on your page.
  3. Create a PHP (or whatever) script to generate the output which is typically JSON or XML.

In this case, I choose to generate XML for use by the flexigrid plugin. But my grid wasn't working. It would just sit there and spin, saying "processing".

  • The jquery javascript was working with no errors (confirmed this in the browser's debugger).
  • The PHP script was generating the correct XML (confirmed by manually entering the script address in the browser).

But it wasn't working when the PHP script was called from the javascript on the page.

The solution? When the PHP script was being called from the javascript on the page, the flexigrid plugin was sending sortname and sortorder values that were "undefined" in javascript. So the query against the database was something like "SELECT * FROM sometable ORDER BY undefined". This of course was generating SQL errors and the PHP script was failing to output the XML. But only when called from the page. It was fine when called manually (and flexigrid was bypassed). A simple check for undefined values in the PHP script and all is well.

This brings me to one of the great canons of programming for the Web (and perhaps other types of programming). The Log File is Your Friend. I discovered this problem by tailing the PHP error log. Otherwise, the failure is very puzzling because everything appears to be working, just not together. When using AJAX or creating Web services, things going on unseen may only be exposed by consulting the log(s).

Wednesday, April 22, 2009

YAML to the Rescue

I needed to create and import product data into an SQL database, but the information I had been given was scattered through various email messages from the client. There was no structure, no CSV file, and no time -- as usual.

It was shaping up to be a real bother to create a CSV file from the information I had and CSV wasn't exactly the friendliest format to look at with poor human eyes.

So I created a YAML file with an entry representing each product. Since the project was mostly written in PHP, I used spyc to parse the file and convert it to SQL insert statements. YAML was much easier to look at than CSV and allowed me to quickly organize the product data without mistakes (such as putting a piece of data in the wrong column).

Writing the parse was easy because each product was already generated as an object, so one could say something like:

foreach ($Yaml as $Product) { print "INSERT INTO product VALUES ('$Product->title', ...); }

I've used YAML for meta data and other applications, but this quick and dirty use saved a lot of time and frustration.

Using Image Magick in a Crunch

So you just got a bunch of images from a client and you need them converted tonight before you can go to sleep. You really don't want to prep each image manually. Whatever shall you do?

Think Image Magick. It's a potent package of image processing goodness with an antiquated name, but I promise you'll get over the name.

This situation happened to me last night. I had a bunch of TIFF files and no time to process them in to Web-ready JPGs. So I created the following shell script:

# Create web versions of product labels and 
# copy them into the appropriate directory.
list=`ls --color=never *.tiff`;
for image in $list
do
    product_id="`echo $image | awk -F "." '{print $1}'`";
    echo "convert $image -resize \"50%\" label.jpg;";
    convert $image -resize "50%" label.jpg;
    mv label.jpg /path/to/products/$product_id/;
done

The convert command is part of the Image Magick package. All of the images were named in the form of product_id.tiff, so that helped. Also, there many other options you could investigate with the convert command, so some experimentation might be required. However, since you've just scripted it, the experimentation becomes very easy.

I observe that even a trivial number of images might be worth scripting in this manner; this script took just a few minutes to write (even looking at the man page for convert). Plus, I knew I would be getting more images later. Plus, plus, I later had to resize the images which was easy using the script.

This Code Goes Up to 11

Some people, when confronted with a problem, think “I know, I'll use regular expressions.”

Now they have two problems.

-- Generally attributed to Jamie Zawinski, an early Netscape engineer.

I was working with HTML strings yesterday, trying to create a teaser from a a block of HTML with predictable structure. My first thought was to use regex and parse the first paragraph and I happily set about doing the work. I mean, I can do regex, that means I'm cool, right? A bit later I realized that there was a much easier way:

$tokens = split('\<\/p\>');
$teaser = strip_tags($tokens[0)];

I've been coding for a while and I am mindful of simplicity in engineering, but this just goes to show that we often use more complicated tools than necessary, just because they are there.

So when you're coding on ten and you need that extra umph, consider turning it down to five instead of up to eleven.

Friday, April 17, 2009

Checking PHP Syntax from VIM

I use vim/gvim for my text editor and I found a handy way to quickly check PHP syntax using a couple of keystrokes from within the editor. Add this to your vimrc file:

" Check php syntax on the current file with CTRL+L
autocmd FileType php noremap <C-L> :!/usr/bin/php -l %<CR>

As an added bonus, you can also use a key mapping to run the execute the PHP file as well, though I find this less useful on a daily basis.

" Run php cli on the current file with CTRL+M
autocmd FileType php noremap <C-M> :w!<CR>:!/usr/bin/php %<CR>

You can change the key used to trigger the command, but you have to be careful not to interfere with pre-existing key combinations.

Shorten Your Ifs

Here’s a PHP example for shortening your conditionals and parameter checking. (There is probably a design pattern for this, but I haven’t found a name for it yet. Submit a comment if you know the name.)

This is a typical PHP example.

/* A useful function.

@param $arg1 First parameter.
@param $arg2 An optional array.

@return Returns a useful string.

*/
public function doSomethingUseful($arg1,$arg2=array())
{
    if (isset($arg2))
    {
        foreach ($arg2 as $v) { /* ... */ }
    }
    else
    {
        return '';
    }
}

Here’s a way to shorten and simplify.

/* A useful function.

@param $arg1 First parameter.
@param $arg2 An optional array.

@return Returns a useful string.

*/
public function doSomethingUseful($arg1,$arg2=NULL)
{
    if (is_array($arg2)) { return ''; }
    foreach ($arg2 as $v)
    {
        /* ... */
    }
}

Friday, April 3, 2009

When MySQL Saved the Day

Although I must grudgingly admit that MySQL has improved over the years, I still think it is a toy database. Rants (both valid and unreasonable) on the vagaries of MySQL abound, so I won't duplicate any of those comments here. Instead, I will relate a recent experience on when MySQL "saved the day".

Once upon a time there was an orders database storing order data and ordered items in MySQL without InnoDb. Someone deleted a really large order and all seemed lost. The order itself could be easily recreated but the list of ordered items was extensive.

But MySQL's lack of referential integrity meant that the ordered items were still present in a separate table. A quick query or two and all of the ordered items were available again.

Message to Elancers

I recently placed a project up for bid on elance.com. As guy involved in sales, marketing, and programming, it's hard for me to give up control of a project. But I needed someone else's time and skill and was willing to trade some money for both.

When the bidding had run its course I selected a winning bidder and since I am involved in the same business as the Elance bidders, and I am not the typical project owner on elance.com, I thought I would send those that didn't win a note on how to do better at sales. I am sharing it here in case it has any useful nuggets on sales and bidding in the world of software and websites.

All,

Thank you for your participation. This project is now closed and I have selected a winning bidder. As you may know, I am a software developer running a family-owned shop. My focus is Web development. I do sales, marketing, and most of the development work. It is important that I do all of these things well if I want to pay my bills and take care of my family.

It is important for you as well, and I wanted to take a moment to offer some unsolicited advice for those of you that did not win the job, so that you might be able to win the next job you bid.

1. I have lots of experience in bidding on sites like elance. The typical employer is unorganized, unprepared, and unaware of the complexity and cost of software development. They are also not qualified to know whether you are a good programmer or not. I am NOT the typical employer. But for most prospects (myself included) it simply boils down to this: Which provider presents the least amount of risk to my business? As both an employer and a contractor, I believe I gave you better and more complete information than you are likely to receive from most employers. Yet

- Many of you submitted a standard "proposal" and had obviously not read any of the information I had provided.

- Some of you said you would follow up after reviewing the information and that was the last contact I saw.

- Some of you bid the maximum bid. Keep in mind that when employers select a budget, it is a range of numbers. Bidding the maximum may put you out of the running immediately.

2. Like most employers, I had a limited amount of time to review your bids. So, I was only interested in those providers that:

- contacted me early,

- had obviously read the information I provided,

- asked meaningful questions,

- offered real advice.

3. Lastly, I view sales as a means of helping people rather than making money. "Sales" for me is matching the right solution for the client. It is the Biblical principal of "doing unto others as you would have them do unto you." Clients feel vulnerable in many ways. Show them that you are trying to help and not just trying to make money. I know that bidding on a site like elance is hectic and hard to manage. It's easy to submit some boiler plate text, some examples of your work, and then move on to the next bid. My advice is the "three B's":

- Be helpful. Ask questions and offer advice.

- Be available. For client questions, advice, etc., but take care not to leave everything up to the client. Following up is important.

- Become a partner. Show that you are interested in mutual benefit and relationship.

Been There Developer Selected

I have selected a winning bidder for the location aware application I'm calling Been There. It was a tough decision as there were lots of good bids and developers. It was also interesting to be the customer for once!

Monday, March 16, 2009

About "Us"

Gollumn

Who's "Us", Precious?

I have been in the technology business for a long time and I have seen a lot of "businesses" come and go. The unique and wonderful thing about the early days of the Web, and to some extent it still applies, is that you could just "hang out a shingle" (that is, a Web site), and be as competitive as the next guy. In theory at least.

The idea that the Web "leveled the playing field" was sort of a valid one, though a guy selling computers from his garage was decidedly not on the same playing field as Dell, HP, or Gateway at the time. I say sort of because the playing field was not actually leveled, it's just that everyone had the perception of business equality on the Web. And what was this perception based upon?

Ignorance.

Someone could create a Web site with the appearance of a full-blown clothing store. Of course, a brick and mortar store is assumed to exist, but this is not necessarily so. To this day, Web site visitors have no way of knowing at a glance if a random site belongs to an establish company or is just one guy in a basement.

This ignorance has engendered a verbal fiction, especially with one-man development shops that I find really annoying. About "Us".

To "compete" and "level the playing field", small development shops (and other business types) try to look big, and in doing so, have created some sort of business schizophrenia.

I Yam what I Yam

I am a firm believer in being who you are. It is disingenuous to represent yourself as something that you're not, whether it's in business or in personal life. I recently updated my Web site and though my wife is a big part of "our" business, and I do work with a number of sub-contractors, I decided to continue with the "I" instead of the "web".

Additionally, there advantages to "being small". I have worked in companies much larger than mine and the ability to determine direction and in some cases make simple decisions is greatly hindered by the size of the company.

Anyone interested in more on the topic should great Chapter 3 of Getting Real, starting with "Less Mass". (Getting Real has been required reading for my staff for a couple of years now.)

Monday, March 2, 2009

Have a Blank PHP Page?

So you're coding a PHP page, you switch to your browser and hit refresh, and you get -- nothin'. No error message, blank page, and depending upon your error settings, you may not see anything in the logs. You have display_errors turned on, so what's going on?
If you're using PHP 5.x, it may be a combination of two  things:
  1. You have white space somewhere.
  2. Output buffering is set to 4096 by default.
What's happening is that some sort of error has occurred in your code (or you have whitespace in an include before a header call) but the error output is being buffered by the output_buffering setting new to php.ini in 5.x and the error is being hidden.
If you have logging turned on, you would still see there error there, but this can otherwise be misleading.

Thursday, February 26, 2009

Vim and Spell Check

I often need to check my spelling when marking up websites or coding for website output. Thankfully, my text editor of choice has a nifty spell check feature. The key is

:set spell

You can add this to your vimrc or just type it in command mode. And I guess if you wanted to be really fancy, you could enable spell-check per file type, but I went the vimrc route. Also, when using gvim, you can enable or disable spell check from the Tools > Spelling Menu. This menu also gives some hints for using the spell checker.

After enabling spell check, you'll notice blue and red underlines under certain words; blue is apparently for grammar syntax (such as failing to capitalize a word) and red is for spelling errors.

  • ]s and [s goes to next or previous error respectively.
  • When the cursor is over a word, use z= for suggestions.
  • :spellrepall repeats a correction.

As usual, there are lots more features to explore. Check out vim help or go to the vimdoc site:

http://vimdoc.sourceforge.net/htmldoc/spell.html

Saturday, January 31, 2009

Been There

After much deliberation, my wife and I have decided to pursue the creation of a location aware iPhone application called "Been There". It will be something like a geographic discussion board where users can take photos, add notes and share locations with contacts from the address book.

As I am extremely busy with other projects and not yet well-versed in Objective-C, we will be outsourcing the job. Although I have been on guru.com as a freelancer for some time, I have also decided to try out elance.com to solicit the bids.