code tunes

Web applications, software engineering, Ruby on Rails, Cake PHP, JavaScript, etc.

SwfUpload and Flash 10


SwfUpload is a graceful combination of flash and javascript that provides a mechanism for uploading files which is far more user-friendly than standard <input type=”file” />. It allows to upload many files at once and displays nice-looking progress bar.

Not everything works fine in every environment, though. Some problems are caused by the differences between some versions of flash player or even by bugs in flash players. Many of them can be solved, I wrote a post about it some time ago. But now when the Flash 10 came out, we have new obstacles to overcome.

Flash 10 is more restrictive about security issues that it’s predecessors. Now you cannot trigger opening file-select window from outside of the flash and it affects SwfUpload which used to do it from Javascript.

Thankfully, developers reacted quickly and are now working on new version (2.2) of the library. Alpha version is already out there and it deals with the mentioned problem. Since it is only alpha I recommend upgrading to stable version as soon as it comes out.

Here are inctructions how to reconfigure your app to work with SwfUpload 2.2.

First, download the most recent version of the library from SVN repository:

svn co http://swfupload.googlecode.com/svn/swfupload/trunk/core swfupload

You’ll only need two files from this: swfupload/swfupload.js and swfupload/Flash/swfupload.swf. These are the new version of files you should already have in your application. Replace old ones with these.

The new SwfUpload creates “Select files” button on it’s own, you only need to provide a placeholder for it.

<div id="swfuploadButtonPlaceHolder"></div>

The last thing is to update some Javascript.

swfu = new SWFUpload({
...
button_placeholder_id: "swfuploadButtonPlaceHolder",
button_image_url: "../images/swfupload/button.jpg",
button_width: "216",
button_height: "25"
...});

“button_image_url” accepts a path relative to the swf file. The image it points to, should look somewhat like this:

One image containing three states of a button (normal, on mouse over, on mouse click). You can have text instead of image if you want:

swfu = new SWFUpload({
...
button_text: '<span class="theFont">Upload</span>',
button_text_style: ".theFont { font-size: 16; }"
...});

That’s it. You can see working example I based on here.

If you find it useful, please share.

4 comments

Written by Michał Szajbe

October 21st, 2008 at 6:23 pm

Tagged with ,

NamedScopeBehavior upgrade

I have just added a small functionality to my NamedScopeBehavior.

Now you can use named scopes from multiple models at once in a single find query. Assuming that given models are associated through belongsTo or hasOne association.

Quick example:

// model definitions
class User extends AppModel {
  var $actsAs = array(
    'NamedScope' => array(
      'activated' => array('User.activated' => 1)
    )
  );
  var $belongsTo = array(
    'Group'
  );
}
 
class Group extends AppModel {
  var $actsAs = array(
    'NamedScope' => array(
      'admins' => array('Group.name' => 'admins')
    )
  );
}
 
// in controller
$this->User->find('all', array('scope' => array('User.activated', 'Group.admins')));

New version can be found in repository.

2 comments

Written by Michał Szajbe

September 15th, 2008 at 12:39 am

Named scope in CakePHP

Some time ago I wrote how to improve Cake controllers’ callback system basing on a solution from Ruby on Rails. Today I am going to port another cool feature of Rails to CakePHP - named scope.

Check this Rails example first if you don’t know what the named scope is:

# model definition
class User < ActiveRecord::Base
  # only activated users
  named_scope :activated, :conditions => "activated_at is not null" 
 
  # only currently online users
  named_scope :online, :conditions => "date_add(last_activity, interval 5 minute) < now()"
end

This is very simple example. In fact, named scope functionality can be used for much more than this. However my Cake version I present below is limited to defining conditions.

So we defined some scopes and gave them names. Now we can apply them to searching.

# let's find all activated users
users = User.activated.find(:all) 
 
# let's find all activated users who are currently online and has more than 10 points
users = User.activated.online.find(:all, :conditions => "points > 10")

I bet you can already see advantages of such notation. Often used conditions (for example: often you want to limit search results to activated users only) are placed in named_scope definition, the others (probably more situation-specific ones) go to usual :conditions option. Improved readability, less writing, fewer chances to make mistakes.

Let me present you NamedScopeBehavior which will give you similat functionality in CakePHP. What it does is converting scope definitions to conditions parameter of find() method and that is done transparently in beforeFind callback method.

Download named_scope.php file from repository and place it it app/models/behaviors folder of your Cake app. Then attach this behavior to a model and define named scopes.

class User extends AppModel {
  var $actsAs = array(
    'NamedScope' => array(
      'activated' => array('User.activated in not null'),
      'online' => array('date_add(User.last_activity, interval 5 minute) > now()')
    )
  );
}

Then you can use it.

// in your controller
$this->User->find('all', array('scope' => 'activated'));
$this->User->find('all',
  array('conditions' => 'points > 10', 'scope' => array('activated', 'online')))

Pagination also works fine with scopes.

// in your controller
$paginate = array(
  'User' => array(
    'order' => 'created ASC',
    'limit' => 20,
    'scope' => array('online', 'activated')
  )
);

Happy developing!

5 comments

Written by Michał Szajbe

September 5th, 2008 at 2:35 pm

Convenient controller’s callback methods with CakePHP

Controller’s callback methods (beforeFilter, beforeRender and afterFilter) are very useful, they save a lot of time, makes code more readable and DRY. You probably use them a lot. They are great. Unless they’re too big.

The process is simple.

  • You want some code executed before every action in your controller, so you put the code in beforeFilter callback.
  • Then you want some code executed only before certain actions, so you add the if-then-else block to your before filter that deciced whether to execute the code or not.
  • Some code needs to be executed only if the user is logged in, so another if-then-else block is created.
  • More conditions creates more mess in your callback…

Could it be changed? Well, look at following snippet which comes from Ruby on Rails.

class UsersController < ApplicationController
  before_filter :do_something, :do_always
  before_filter :do_something_else, :only => [:show, :new]
  before_filter :do_something_different, :except => :edit
 
  # method definitions commented out
end

What will happen:

  • do_something and do_always methods will be called during every request
  • do_something_else method will be called only if show or new actions are requested
  • do_something_different method will be called during every request except those to edit action

Clean and simple. Every piece of code that needs to be called in before_filter callback (under whatever conditions) is placed in it’s own method. The controller decides what methods to invoke and when.

I prefer this approach to Cake’s built-in one, so I decided to port it.

You need to put this code into your AppController.

class AppController extends Controller {
 
  function _callbacks($callbacks) {
    $defaults = array(
      'methods' => array(),
      'only' => array(),
      'except' => array(),
      'if' => array(),
      'unless' => array()
    );
    $ifs = $unlesses = $methods = array();
    foreach ($callbacks as $array) {
      $array = am($defaults, $array);
      foreach ($array as $key => $value) {
        if (!is_array($value)) {
          $array[$key] = array($value);
        }
      }
      $ok = true;
      foreach ($array['if'] as $if) {
        if (!array_key_exists($if, $ifs)) {
          $ifs[$if] = $this->dispatchMethod("_$if");
        }
        $ok = $ok && $ifs[$if];
        if (!$ok) {
          break;
        }
      }
      foreach ($array['unless'] as $unless) {
        if (!array_key_exists($unless, $unlesses)) {
          $unlesses[$unless] = $this->dispatchMethod("_$unless");
        }
        $ok = $ok && !$unlesses[$unless];
        if (!$ok) {
          break;
        }
      }
      if ($ok) {
        if (!empty($array['only'])) {
          if (!in_array($this->action, $array['only'])) {
            $ok = false;
          }
        } elseif (!empty($array['except'])) {
          if (in_array($this->action, $array['except'])) {
            $ok = false;
          }
        }
      }
      if ($ok) {
        $methods = am($methods, $array['methods']);
      }
    }
    foreach (array_unique($methods) as $method) {
      $this->dispatchMethod("_$method");
    }
  }
 
  function __mergeVars() {
    $pluginName = Inflector::camelize($this->plugin);
    $pluginController = $pluginName . 'AppController';
 
    if (is_subclass_of($this, 'AppController') || is_subclass_of($this, $pluginController)) {
      $appVars = get_class_vars('AppController');
      $uses = $appVars['uses'];
      $merge = array('beforeFilter', 'afterFilter', 'beforeRender');
      $plugin = null;
 
      if (!empty($this->plugin)) {
        $plugin = $pluginName . '.';
        if (!is_subclass_of($this, $pluginController)) {
          $pluginController = null;
        }
      } else {
        $pluginController = null;
      }
 
      if ($pluginController) {
        $pluginVars = get_class_vars($pluginController);
      }
 
      foreach ($merge as $var) {
        $appVar = (isset($appVars[$var]) && !empty($appVars[$var]) && is_array($appVars[$var])) ? $appVars[$var] : array();
        $pluginVar = (isset($pluginVars[$var]) && !empty($pluginVars[$var]) && is_array($pluginVars[$var])) ? $pluginVars[$var] : array();
        $thisVar = (isset($this->{$var}) && !empty($this->{$var}) && is_array($this->{$var})) ? $this->{$var} : array();
        $this->{$var} = am($appVar, $pluginVar, $thisVar);
      }
    }
 
    parent::__mergeVars();
  }
 
  function beforeFilter() {  
    if (!empty($this->beforeFilter)) {
      $this->_callbacks($this->beforeFilter);
    }
  }
 
  function afterFilter() {
    if (!empty($this->afterFilter)) {
      $this->_callbacks($this->afterFilter);
    }
  }
 
  function beforeRender() {
    if (!empty($this->beforeRender)) {
      $this->_callbacks($this->beforeRender);
    }
  }
}

Pretty long but worth it. Now you can define in controllers (or AppController, or PluginAppController):

  var $beforeFilter = array(
    array(
      'methods' => array('do_something', 'do_always')
    ),
    array(
      'methods' => array('do_something_else'),
      'only' => array('show', 'new')
    ),
    array(
      'methods' => array('do_something_different'),
      'except' => array('edit')
    )
  );
 
  function _do_something() { }
  function _do_always() { }
  function _do_something_else() { }
  function _do_something_different { }

This is equivalent to Rails definition above. Note the underscores before function names. They help distinguish those methods from usual controller actions.

You can do even more:

  var $beforeFilter = array(
    array(
      'methods' => array('do_something'),
      'if' => array('is_admin')
    ),
    array(
      'methods' => array('do_something_else'),
      'unless' => array('is_logged_in')
    )
  );
 
  function _logged_in() { }
  function _is_admin() { }

Now do_something method will be called if _is_admin returns true, and do_something_else if _is_logged_in returns false. It’s self-explanatory I think.

Of course you can use all options (only, except, if, unless) together or mix them.

The definitions are processed in order they appear in $beforeFilter (or $beforeRender, or $afterFilter) array.

The definitions from your controller are merged with those defined in PluginAppController and AppController (thanks to redefined __mergeVars() method which is called during controller construction). Firstly AppController’s definitions are processed, then PluginAppController’s, and your controller’s at last.

Enjoy and comment please.

6 comments

Written by Michał Szajbe

August 21st, 2008 at 12:50 am

Custom thumbnail generation with Paperclip

Paperclip is a great plugin for Ruby on Rails which eases the pain of image upload and resize process. The usage is very simple, results are what you want in most cases. Not in all cases though.

Your role is only to define the sizes of thumbnails that will be generated from original image. Thumnailing in is fact done by calling ImageMagick’s convert command with -scale argument, for example convert -scale ‘640×480>’.

You can alter how exactly the image is scaled by appending modifiers to desired thumbnail size, like ‘>’ in ‘640×480>, however it won’t give you unlimited power over thumbnailing process.

Consider following example. I have a small image (120×120).

Scaling it to smaller size gives me the result I want. But when the result image needs to be bigger than original, the original image is enlarged in a way that you’ll see single pixels.

What I want to achieve is the enlargment done not by scaling the original image, but by adding a border around it.

This cannot be achieved by adding any of modifiers to desired thumbnail size, so why not to pass additional arguments to convert command? Yes, that would be a solution.

And here’s my solution - PaperclipExtended. It’s a plugin that modifies original Paperclip, so that now it accepts additional (optional) parameter :commands when defining the thumbnails sizes.

class User < ActiveRecord::Base
  has_attached_file :avatar, :styles => { :medium => "300x300>", :small => "100x100>" },
    :commands => { :medium => "-background white -gravity center -extent 300x300 +repage" }
end

During thumbnail generation Paperclip will now append given commands to convert command. Convert command for medium style will be now:
convert -scale ‘300×300>’ -background white -gravity center -extent 300×300 +repage
Convert command for small style will remain unchanged.

Passing such commands will help me with the enlargment I mentioned above. For what else could be done check the documentation of ImageMagick’s command line options.

PaperclipExtended is not a replacement for Paperclip, just an extension. It works with version 2.1.2 of Paperclip plugin (the current one and the only one I tested). Compatibility with future version is not guaranteed.

I put a plugin on GitHub.
http://github.com/netguru/paperclip-extended

How to install:

script/plugin install git://github.com/netguru/paperclip-extended.git

Configuration is the same as of original Paperclip plugin, but you can now add an optional :commands parameter.

Hope it’ll be helpful!

If you have any questions, post them in comments to this post.

1 comment

Written by Michał Szajbe

July 30th, 2008 at 12:34 pm

“Send this page to friend” with polymorphic controller in Rails

Last week I wrote about Tableless models in Ruby on Rails, giving an example of Recommendation model which could be used to validate users’ recommendations. Today I will extend that example so that it will be more complete.

What the mentioned solution lacks is a controller handling recommendation actions. Let’s first make Article and Photo models recoommendable ones by defining their associations:

  has_many :recommendations, :as => :recommendable

The models are ready, so let’s create a controller.

class RecommendationsController < ApplicationController
  def new
    if !params[:photo_id].nil?
      @recommedable = Photo.find params[:photo_id]
    elsif !params[:article_id].nil?
      @recommedable = Article.find params[:article_id]
    end
 
    raise ActiveRecord::RecordNotFound if @recommendable.nil?
  end
 
  def create
    @recommendable = Recommendable.new :params[:recommendation]
 
    if @recommendable.valid?
      # send it via e-mail
      flash[:notice] = 'Your recommendations has been processed'
      redirect_to @recommendation.recommendable
    else
      render :action => :new
    end
  end
end

We’re almost there, but there’s more Rails can do for us here. Let’s define some new routes.

# config/routes.rb
  map.resources :recommendations
 
  map.resources :articles do |a|
    a.resources :recommendations
  end
 
  map.resources :photos do |p|
    p.resources :recommendations
  end

Now we’re able to use those pretty helpers in our views that will generate nice-looking URLs (like: article/1/recommendation/new).

<%= link_to 'recommend this article', new_article_recommendation_path(@article) %>

The link above will take us to the ‘new’ action of RecommendationsController. The last thing we need to do is to render the form, so that user can comment his recommendation.

<% form_for @recommendable.recommendations.build do |f| %>
  <%= f.hidden_field :recommendable_type, :value => @recommendable.class %>
  <%= f.hidden_field :recommendable_id, :value => @recommendable.id %>
  <%= f.text_field :email %>
  <%= f.text_area :body %>
  <%= f.submit 'Do it!' %>
<% end %>

There are many more cases of use polymorphic controllers (with normal or tableless models) than sending recommendations. Commenting, ranking, abuse-reporting, tagging… The rule is that if you have a model that is associated to other models via polymorphic association, you should consider using polymorpic controller to manage it. Not only Rails will do much work for you behind the scenes, but the structure of your application will stay clear both inside (reusable code) and outside (nice links).

1 comment

Written by Michał Szajbe

July 26th, 2008 at 4:06 pm