Pages

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