Compulsivo

Make Your Friends and Family Pay for Your Product

September 22, 2008 · Leave a Comment

Sorry, Dad.  No freebies.

I created Flashlight for my dad and his fine art photography business, William Britten Photography.  He has been using Flashlight to track his sales and inventory for the past four months.  At first, he was providing valuable feedback.  But, as the site exited the beta stage and became a real product, I placed an expiration date on his rather lengthy free trial.  The day it ended he emailed me asking for a free account.  I turned him down.

The sales revenue from friends and family is inconsequential.  However, their decision whether to purchase your product, or subscribe to your service, is priceless.

Most feedback is worthless when there’s no money at stake.  Ask a bunch of people whether they like your product and would pay for it.  Many will say yes.  Now, ask them to pull out their credit card and actually buy it.  Most will change their mind

Your free-loading friends and family are just casual users of your product or service.  They are not invested at all.  However, if you ask them to pay up, and there’s real money at stake, then they are forced to really evaluate whether your offering is something worth paying for.  If their decision is to not pay, then you must take a step back and analyze why.  You need this validation.

So, tell your friends and family the free trip is over and you want some moolah.  Promise them you won’t be offended if they are not willing to pay.  Go ahead.  See what they really think.

→ Leave a CommentCategories: Business · Flashlight

TableSorter: Filter Results Based on Search Query

August 22, 2008 · 2 Comments

So, you want to use the TableSorter jQuery plugin and you want to be able to search the table and filter the results.  Attached is a companion plugin we’ve written, called tablesorterFilter, which will extend tablesorter to provide real-time filtering of rows which match a search query!

Usage


<script type="text/javascript">
    jQuery(document).ready(function() {
        $("#myTable")
        .tablesorter({debug: false, widgets: ['zebra'], sortList: [[0,0]]})
        .tablesorterFilter({filterContainer: $("#filter-box"),
                            filterClearContainer: $("#filter-clear-button"),
                            filterColumns: [0],
                            filterCaseSensitive: false});
    });

</script>
Search: <input name="filter" id="filter-box" value="" maxlength="30" size="30" type="text">
<input id="filter-clear-button" type="submit" value="Clear"/>

<table id="myTable">
  <thead>
    <tr>
      <th>Last Name</th>
      <th>First Name</th>
      <th>Email</th>
      <th>Web Site</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Smith</td>
      <td>John</td>
      <td>jsmith@gmail.com</td>
      <td>http://www.jsmith.com</td>
    </tr>
    <tr>
      <td>Doe</td>
      <td>Jason</td>
      <td>jdoe@hotmail.com</td>
      <td>http://www.jdoe.com</td>
    </tr>
  </tbody>
</table>

Configuration

tablesorterFilter takes four parameters:

  • filterContainer – The DOM id of the input box where the user will type the search string.
  • filterClearContainer – (optional) The DOM id of the button, image, or whatever which will clear the search string and reset the table to it’s original, unfiltered state.
  • filterColumns – An array of columns, starting at 0, which will be searched.
  • filterCaseSensitive – (optional) Boolean stating whether the search string is case sensitive.  The default is false.

Requirements

jQuery version 1.2.1 or higher and a slightly modified jquery.tablesorter.js version 2.0.3.

The modification to the original tablesorter plugin is the addition of a few lines which will cache all of the rows so they can be searched.  You can download the modified jquery.tablesorter.js code below.

Download

jquery.tablesorter.filter.js

jquery.tablesorter.js 2.0.3 (modified for tablesorterFilter)

→ 2 CommentsCategories: Programming
Tagged: ,

Crowdsourced Design with 99designs and Crowdspring

August 20, 2008 · 1 Comment

We recently crowdsourced the design of the Flashlight logo, as well as a postcard sized publicity flier. We ran the logo design content on 99designs and the postcard advertisement on Crowdspring, the two leading players in the crowdsourced design space.

For those of you unfamiliar with crowdsourced design, here’s how it works in a nutshell. Basically, we used 99designs and Crowdspring to run a design contest where many contributors from around the world submitted entries over a seven day period.  At the end of the contest we picked a winning entry and paid out a bounty.  Throughout the whole process we gave constant feedback to the designers to ensure the designs were moving in the desired direction.  And, it worked!  We ended up with two outstanding designs.

99designs

We ran a contest with a $175 bounty for the Flashlight logo design.  Over a seven day period we got 64 entries from 20 different designers.

99designs charges a flat $39USD to post a contest.  They do not act as a middle-man and handle the transaction between buyer and designer.  At the end of the contest, it’s up to the parties involved to exchange emails and swap the design for money via PayPal or some other means.

The site is, ironically, bland in design, but its both very usable and fast.  It appears that the site is run by a team of three out of Australia.  At the time of this writing there have been about 17,000 contests run on 99designs, yielding an assumed half-million in revenue for the small company.  Not bad for the short period they’ve been in business (less than a year we believe).  Plus, from the looks of things, they are bootstrapping their company.  Best of luck and keep up the excellent work!

Crowdspring

We posted a $150 bounty for the design of a postcard-sized publicity flier for Flashlight.  Over a week we got 36 entries from 11 different designers.

Crowdspring charges a flat 15% listing fee to run a design contest.  So, we paid them $22.50 to run the contest, and gave $150 to the winning designer.  Unlike 99designs, Crowdspring does act as the middle-man in the transaction and places the bounty in escrow.  This helps the designer feel confident they will get paid, and to ease the minds of contest holders Crowdspring offers a money-back guarantee if you don’t get at least 25 entries.  We much preferred having this buffer — it made the entire process of closing the transaction run smoother.

The site is beautifully designed; however, it is very slow.  We’ll cut them some slack since they’re new and growing pains are expected.  It appears they’ve only been around for five months or so, and they definitely don’t attract the same number of contests or designers … yet.  The founders handled all of our support tickets.  While they’re also a small team — of about six — they have received a round of funding and it shows.  Everything seems very professional.  Let’s hope there’s room for both them and 99designs.

Closing thoughts

We got a logo designed for well below what most (decent) design firms would have charged — and we got to pick from many different creative options.  We got the postcard advertisement for a steal, saving possibly hundreds of dollars.  In both cases we saved money and got better results.  Plus, the money goes directly to the designer, supporting an independent entrepreneur, which makes us feel good.

→ 1 CommentCategories: Design

Introducing Flashlight: Business Intelligence for the Artist, Craftsperson, and Hobbyist

July 29, 2008 · Leave a Comment

Flashlight (www.flashlightapp.com) offers simple and affordable sales trending and inventory management for artists, craftspeople, hobbyists, and other small business owners who need to monitor inventory on hand and track sales trends across product lines.

Quickly input your product lines and corresponding inventory values.  As sales occur, Flashlight maintains inventory amounts and alerts you to inventory shortfalls. Flashlight also tracks sales across product lines and produces a custom set of charts and tables that provide a visual explanation of sales data.  So, whether it’s clothes, art, jewelry, or something else you’re creating, Flashlight will show you which products are selling and when. Use this knowledge to make tweaks to your business and become more efficient and profitable!

Spreadsheets are a pain to write and maintain, especially if you’ve written any formulas, and other inventory management software is expensive and overly complicated.  Flashlight is a perfect replacement for these undesirable solutions.

Flashlight is intuitive and easy to learn.  Flashlight is streamlined and simple, targeted to do a clearly defined task very efficiently.

Flashlight is internet-based and always accessible, online.  You can adjust your inventory or view your sales trends from anywhere: home, retail store, or on the road.

To learn more, visit www.flashlightapp.com.

→ Leave a CommentCategories: Flashlight
Tagged:

Inline jQuery datePicker and Rails

June 26, 2008 · 6 Comments

This post describes how to use the jQuery datePicker plugin (found here) to display an inline calendar and post the selected date back to your Rails app. I’m simply going to put a hidden text_field on the page and use a bit of javascript to populate the field with the selected date.

Grab the datePicker plugin from http://plugins.jquery.com/project/datepicker, and include it in your Rails app.

Get an inline datePicker set up by following the author’s instructions at http://www.kelvinluck.com/assets/jquery/datePicker/v2/demo/inlineDatePicker.html. Quite simply, you only need to do two things.

One: Add this to your .html.erb page:

<div id="date-picker"></div>

Two: To the same .html.erb page, add this bit of jQuery to turn the the div into an inline datePicker:

<script type="text/javascript">
    $('#date-picker').datePicker({inline:true});
</script>

At this point you should see the datePicker on your page. It should look something like:
Example of datePicker

Now, let’s put a text_field in your form to keep the date. Here’s what I’ve got:

	<% form_for(@event) do |f| %>
		<fieldset>
			<label for="occurred_on">Date event occurred:</label>
			<%= f.text_field 'occurred_on' %>

			<%= f.submit "Save" %>
		</fieldset>
	<% end %>

We need to catch the user’s selected date and populate our text_field. To do this, we need to modify the bit of jQuery we used to set up the inline datePicker. We need to bind the action of selecting a date to a javascript function which will populate the text_field. Return to your .html.erb file and replace the jQuery with:

<script type="text/javascript">
  $('#date-picker').datePicker({inline:true})
  .bind(
         'dateSelected',
         function(e, selectedDate, $td)
         {
           $('#event_occurred_on').val(selectedDate.asString());
         }
         );
</script>

Finally, since you probably don’t want your users to see the text_field, just change it to a hidden_field.

→ 6 CommentsCategories: Programming
Tagged: ,

Installing Trac on CentOS 5

May 16, 2008 · Leave a Comment

I’m using Slicehost, but these instructions should work for any hosting provider running CentOS 5. I am not using Subversion (because it sucks and has been slain by Git). If you need SVN support, check out the posts by Nick or Daniel Skinner.

  1. Install python and mod_python (for Apache)
    1. yum install python mod_python
  2. Install MySQL-python
    1. Download the tarball from http://sourceforge.net/projects/mysql-python
    2. Compile the package:
      python setup.py build && python setup.py install
  3. Install Clearsilver, a templating package needed by Trac
    1. Download Clearsilver from http://www.clearsilver.net/downloads/
    2. Compile Clearsilver:
      ./configure –with-python=/path/to/python && make && make install
    3. Note: My path to python is /usr/lib/python2.4
  4. Install Trac
    1. Download Trac from http://trac.edgewall.org/wiki/TracDownload
    2. Setup Trac:
      python ./setup.py install
  5. Create a Trac project
    1. I’m going to keep all of my Trac projects in /var/www/apps/trac, and for this example, I’ll call my project MyTracProject
    2. trac-admin /var/www/apps/trac/MyTracProject initenv
    3. Give your project a name of your choosing, but accept the default for all other settings.
  6. Install TracWebAdmin
    1. Note: This is an optional step, if you would like to administer Trac from within Trac itself. Also, installation of this plugin is only necessary if you’re running Trac version 0.10 or earlier as 0.11 has integrated this plugin into the core Trac package.
    2. In a temporaty directory, grab the TracWebAdmin package:
      svn co http://svn.edgewall.com/repos/trac/sandbox/webadmin/
    3. cd webadmin
    4. python setup.py egg_info
    5. cp dist/TracWebAdmin-0.1.2dev_r5753-py2.4.egg /var/www/apps/trac/MyTracProject/plugins (Note: the actual filename may be different, depending on the build)
    6. Enable the plugin by adding the following to your trac.ini found at /var/www/apps/trac/MyTracProject/conf/trac.in
      [components]
      webadmin.* = enabled
      
  7. Define some Trac users
    1. Create a file to store your authorized users:
      touch /var/www/apps/trac/auth-file
    2. Add a user to the file:
      htpasswd -m /var/www/apps/trac/auth-file <username>
  8. Give admin permissions to the Trac user
    1. trac-admin /var/www/apps/trac/MyTracProject permission add <username> TRAC_ADMIN
  9. Configure Apache
    1. Load mod_python by editing your httpd.conf (for me this is /etc/httpd/conf/httpd.conf) and add “LoadModule python_module modules/mod_python.so”
    2. I want to access Trac via http://trac.mytrackproject.com
    3. You’ll need a CNAME record in your DNS to support the trac.mytrackproject.com subdomain.
    4. You’ll also need to create a VirtualHost section in Apache’s httpd.conf file. For example:
          <VirtualHost *:80>
      
            ServerName trac.mytrackproject.com
      
            <Location />
              SetHandler mod_python
              PythonHandler trac.web.modpython_frontend
              PythonOption TracEnv /var/www/apps/trac/mytrackproject
              PythonOption TracUriRoot /
            </Location>
            <Location "/login">
              AuthType Basic
              AuthName "trac"
              AuthUserFile /var/www/apps/trac/auth-file
              Require valid-user
            </Location>
      
          </VirtualHost>
      
  10. Restart Apache: service httpd restart
  11. Go to http://trac.mytrackproject.com and start using Trac

→ Leave a CommentCategories: Programming
Tagged: ,

How to Change or Reset your Password with RESTful_authentication

March 24, 2008 · 10 Comments

I have used the acts_as_authenticated plugin for quite some time. It works well, and there’s lots of documentation. I’ve been happy with it. Mostly. I say mostly because it has always bothered me that acts_as_authenticated generates unRESTful code, which is tainting my perfectly RESTful application.

So, I recently evaluated the restful_authentication plugin. The plugin works great, but it is lacking the same breadth of documentation. I needed to add the ability handle a lost password and to change a password. Here’s what I did:

1. Create a new Passwords controller.

The new/create actions equate to a lost password (user wants to get a new password). The edit/update actions equate to changing a password (user wants to update their password).

When a user forgets their password and requests a new one, both their login and email will be required. If this login and email pair exists, then a new, random password will be generated and emailed.

When a user desires a new password, they are required to enter their old password in addition to typing and confirming a new password. The old password is required for security sake.

class PasswordsController < ApplicationController
  before_filter :login_from_cookie
  before_filter :login_required, :except => [:create]

  # Don't write passwords as plain text to the log files
  filter_parameter_logging :old_password, :password, :password_confirmation

  # GETs should be safe
  verify :method => :post, :only => [:create], :redirect_to => { :controller => :site }
  verify :method => :put, :only => [:update], :redirect_to => { :controller => :site }

  # POST /passwords
  # Forgot password
  def create
    respond_to do |format|

      if user = User.find_by_email_and_login(params[:email], params[:login])
        @new_password = random_password
        user.password = user.password_confirmation = @new_password
        user.save_without_validation
        UserNotifier.deliver_new_password(user, @new_password)

        format.html {
          flash[:notice] = "We sent a new password to #{params[:email]}"
          redirect_to signin_path
        }
      else
        flash[:notice] =  "We can't find that account.  Try again."
        format.html { render :action => "new" }
      end
    end
  end  

  # GET /users/1/password/edit
  # Changing password
  def edit
    @user = current_user
  end

  # PUT /users/1/password
  # Changing password
  def update
    @user = current_user

    old_password = params[:old_password]

    @user.attributes = params[:user]

    respond_to do |format|
      if @user.authenticated?(old_password) && @user.save
        format.html { redirect_to user_path(@user) }
      else
        format.html { render :action => 'edit' }
      end
    end
  end

  protected 

  def random_password( len = 20 )
    chars = (("a".."z").to_a + ("1".."9").to_a )- %w(i o 0 1 l 0)
    newpass = Array.new(len, '').collect{chars[rand(chars.size)]}.join
  end

end

2. Here is the change password view (edit.html.erb):

<div class="form_container">
    <%= error_messages_for :user %>

    <% form_for(:user, :url => user_password_path(@user), :html => { :method => :put }) do |f| %>
      <fieldset>
<div class="form_row">
          <label for="password">Old password</label>
<%= password_field_tag :old_password %></div>
<div class="form_row">
          <label for="password">New password</label>
          <%= f.password_field :password %></div>
<div class="form_row">
          <label for="password_confirmation">Retype the new password</label>
          <%= f.password_field :password_confirmation %></div>
<div class="submit_row">
          <%= f.submit "Update", :class => "submit" %> or <%= link_to 'Cancel', home_path %></div>
</fieldset>
    <% end %></div>

3. Here is the new password view (new.html.erb):

<div class="form_container">
    <% form_for(:password, :url => passwords_path) do |f| %>
      <fieldset>
<div class="form_row">
          <label for="login">Username</label>
          <%= text_field_tag 'login' %></div>
<div class="form_row">
          <label for="email">Email</label>
          <%= text_field_tag 'email' %></div>
<div class="submit_row">
          <%= submit_tag 'Send Password', :class => "button-to" %></div>
</fieldset>
    <% end %></div>

4. Here’s the user notifier (emailer) code:

You’ll need a user_notifier.rb file and a view template (app/views/user_notifier/new_password.html.erb) which will just contain the text of the email you want to send.

app/models/user_notifier.rb:

class UserNotifier < ActionMailer::Base
  def new_password(user, new_password)
    setup_email(user)
    @subject    += 'Your new password'
    @body[:new_password]  = new_password
  end

  protected

  def setup_email(user)
    @recipients  = "#{user.email}"
    @from        = "Support" "<support@yoursite.com>"
    @subject     = ""
    @sent_on     = Time.now
    @body[:user] = user
  end
end

app/views/user_notifier/new_password.html.erb:

We’re sorry to hear your lost your password. But, there’s no need to worry, because we’ve created a new, temporary password for you.

Your new password is: <%=h @new_password %>

You can change this password to something more memorable once you log into your account.

5. Here’s the modified routes.rb file:

  map.resources :passwords
  map.resources :users, :has_one => [:password]

→ 10 CommentsCategories: Programming
Tagged:

Our name in print! Things are going to start happening to us now.

February 20, 2008 · Leave a Comment

Today we got a double-dosage of our first press!

First, AustinStartup.com featured us in a question and answer segment where we were given the opportunity to talk in detail about our first product, Prefinery, and our experience with bootstrapping Compulsivo.

Later in the day, TexasStartupBlog.com ran a brief bit on us as well.

We’re very happy that both of these local blogs have chosen to spotlight Compulsivo!

→ Leave a CommentCategories: Press

Prefinery Gets All Done-up for Public Launch

February 19, 2008 · Leave a Comment

Prefinery exited private beta testing last week and, to coincide with our public launch, we decided to give ourselves a makeover.

Here’s what we look like now. Oh, yeah.

Here’s what we looked like before. Bleh.



Let us know what you think of the new site …

→ Leave a CommentCategories: Prefinery
Tagged:

S3 rake tasks

January 19, 2008 · Leave a Comment

Some time ago, we started using S3 from Amazon Web Services for secure online storage, and it’s been great so far. To help take advantage of the tool, Adam Greene wrote some slick rake tasks that help you backup your code and database. With some cron jobs, it helps us sleep at night.

We’re in the process of moving to some more advanced hosting for Prefinery, and Adam’s rake tasks seemed like a good fit to help with the migration. They didn’t quite do everything we were looking for, though, so I extended them a little bit to handle restoring the database from a retrieved backup too.

Here’s the code that I added:

namespace :db do
  desc "repopulate the db from the latest S3 backup, or optionally specify a VERSION=this_archine.tar.gz"
  task :restore do
    # Fetch the backup from S3
    file_name = retrieve_file 'db', ENV['VERSION']

    # Decompress backup
    msg "decompressing backup"
    result = system("tar -zxvf #{file_name}" )
    raise("backup decompression failed. msg: #{$?}" ) unless result

    # Extract database information from database.yml
    msg "retrieving db info"
    database, user, password = retrieve_db_info

    msg "restoring db"
    cmd = "mysql -u#{user} "
    puts cmd + "... [password filtered]"
    cmd += " -p'#{password}' " unless password.nil?
    cmd += " #{database} < ./tmp/#{file_name.gsub('.tar.gz', '')}"
    result = system(cmd)
    raise("mysql restore failed.  msg: #{$?}" ) unless result
  end
end

→ Leave a CommentCategories: Programming
Tagged: , ,