plugin snippets

How to create an autocompleted field with jQuery, which looks and feels like autocomplete with Scriptaculous & prototype

Tagged jquery, autocomplete, plugin, scriptaculous, prototype  Languages javascript

There are a lot of autocomplete plugins for jQuery, but not one of them seems to work similarly to Scriptaculous, except for the one at bassistance.de.

To use it follow the instructions found here.

There are a couple of gotchas for ex-scriptaculous users, like that the controller should return the list of tags with a newline character as separator, compared to an HTML list with Scriptaculous.

The other gotcha is the syntax, which is a lot prettier than Scriptaculous:

<script type="text/javascript">
$("#tags-field").autocomplete("/tags/autocomplete", {
    multiple: true,
    autoFill: true,
    minChars: 3
});
</script>

Remember that /tags/autocomplete should render a list of tags delimited by linefeeds:

ruby
php
java
perl
python

Note that jQuery sends the user typed text in a request parameter named 'q', so jQuery makes the following request to your controller: '/tags/autocomplete?q=ruby'.

The list that's shown when you type something can be styled with these CSS rules:

div.ac_results {
    width: 350px;
    background: #fff;
}

div.ac_results ul {
    border:1px solid #888;
    margin:0;
    padding:0;
    width:100%;
    list-style-type:none;
}

div.ac_results ul li {
    margin:0;
    padding:3px;
}

// Hovering over list item
div.ac_results ul li.ac_over {
    background-color: #ffb;
}

The plugins documentation is quite good, but for some reason it was easier to figure out what was needed to get it working by reading the code.

How to add OpenID support to your Rails application with the open_id_authentication plugin

Tagged openid, authentication, rails, ruby, plugin, restful_authentication  Languages ruby

These instructions have been tested with Rails 2.0.2 and ruby-openid 2.0.4. The snippet is an adaptation of the instructions in Ryan Bates' screencast on how to integrate OpenID with Rails.

Installing and configuring the restful_authentication plugin

Follow these instructions: How to install and use the restful_authentication Rails plugin.

Installing the ruby-openid gem

gem install ruby-openid

Installing the open_id_authentication Rails plugin

script/plugin source http://svn.techno-weenie.net/projects/plugins/
script/plugin install open_id_authentication

Create the migration files

rake open_id_authentication:db:create

Add the following to the self.up method in 002_add_open_id_authentication_tables.rb:

add_column :users, :identity_url, :string

Configuring the routes

map.open_id_complete 'session', :controller => "sessions", :action => "create", :requirements => { :method => :get }

Protect the identity_url field

Next protect the identity_url field, by adding the following to user.rb, account.rb or your custom user model:

attr_accessible :login, :email, :password, :password_confirmation, :identity_url

Add the following to the self.down method in 002_add_open_id_authentication_tables.rb:

remove_column :users, :identity_url

Integrating Open-id with the login page

Add the following to sessions/new.html.erb:

<label for="openid_url">OpenID URL</label><br />
<%= text_field_tag "openid_url" %>

Make sure you're showing flash messages, otherwise you won't see the error messages:

<html>
  <head></head>
  <body>
    <%= [:notice, :error].collect {|type| content_tag('div', flash[type], :id => type) if flash[type] } %>

    <%= yield %>
  </body>
</html>

Modifying the sessions controller

Copy & paste the following code in app/controllers/sessions_controller.rb:

class SessionsController < ApplicationController
  # Hack to fix: No action responded to show
  def show
    create
  end

  def create
    if using_open_id?
      open_id_authentication(params[:openid_url])
    else
      password_authentication(params[:login], params[:password])
    end
  end

  def destroy
    self.current_user.forget_me if logged_in?
    cookies.delete :auth_token
    reset_session
    flash[:notice] = "You have been logged out."
    redirect_back_or_default('/')
  end

  protected

  def open_id_authentication(openid_url)
    authenticate_with_open_id(openid_url, :required => [:nickname, :email]) do |result, identity_url, registration|
      if result.successful?
        @user = User.find_or_initialize_by_identity_url(identity_url)
        if @user.new_record?
          @user.login = registration['nickname']
          @user.email = registration['email']
          @user.save(false)
        end
        self.current_user = @user
        successful_login
      else
        failed_login result.message
      end
    end
  end

  def password_authentication(login, password)
    self.current_user = User.authenticate(login, password)
    if logged_in?
      successful_login
    else
      failed_login
    end
  end

  def failed_login(message = "Authentication failed.")
    flash.now[:error] = message
    render :action => 'new'
  end

  def successful_login
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
    end
    redirect_back_or_default('/')
    flash[:notice] = "Logged in successfully"
  end
end

OpenID authentication from behind a proxy

First, set the HTTP_PROXY environment variable to the proxy URL:

export HTTP_PROXY=http://proxy.aktagon.com:8080/

Then add the following to environment.rb:

OpenID::fetcher_use_env_http_proxy

SEO optimized image URLs with the Paperclip Rails plugin

Tagged paperclip, rails, ruby, plugin, seo, url  Languages ruby

Create config/initializers/paperclip.rb:

# http://wiki.github.com/thoughtbot/paperclip/interpolations
Paperclip.interpolates :to_param do |attachment, style|
  attachment.instance.to_param
end

In the model:

has_attached_file :image, 
                    :path => ":rails_root/public/images:to_param/:style/:basename.:extension",
                    :url => "/images:to_param/:style/:basename.:extension",
                    :styles => { :large  => "250x360#",
                                 :medium => "150x230#",
                                 :small  => "110x150#" }

Instead of URLs like:

/images/products/249/large/temp.jpg

You can get a URL based on, for example, a permalink as in the example above. In my case I get URLs like this:

/images/games/nintendo-wii/large/super-mario-galaxy.jpg

Rails plugin template

Tagged plugin, rails, template  Languages ruby
script/plugin generate PluginTemplate
module PluginTemplate
  def self.included(base)
    base.class_eval do
      include InstanceMethods
      extend ClassMethods

      alias_method_chain :rescue_action_in_public, :gulag
      alias_method_chain :rescue_action_locally, :gulag
    end
  end

  module ClassMethods
  end

  module InstanceMethods
  end
end

init.rb:

ActionController.send :include, PluginTemplate

A Mozilla Jetpack extension template

Tagged jetpack, extension, firefox, plugin  Languages javascript

Jetpack extension:

jetpack.future.import('menu');
jetpack.future.import('selection');
 
jetpack.menu.context.page.on('img').add(function(target) ({
  label:   "Post to flickr.com",
  icon:    "http://xxx/icon.png",
  command: function() {
    var url = "http://flickr.com/api?";

    var selectedText = jetpack.selection.text || '';
    var sourceImage   = target.node.src;
    var sourcePage = jetpack.tabs.focused.url;

    url += encodeURI('image') + '=' + encodeURI(sourceImage);
    url += '&' + encodeURI('description') + '=' + encodeURI(selectedText.substr(0, 200));

    jetpack.tabs.open(url).focus(); 
  }
}));

Installation page:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
    <title>The Jetpack Feature</title>
    <link rel="jetpack" href="/javascripts/jetpack-extension.js" name="com extension" />
</head>
<body>
</body>
</html>

Highlighting page elements with jQuery and the jquery-color plugin

Tagged jquery, highlight, ajax, plugin  Languages javascript

This plugin is useful for making the user aware of changes to the page. It does this through an animated effect that changes the matching element's color to light yellow and then back to transparent.

You need the jquery-color plugin and the highlight plugin:

// http://github.com/jquery/jquery-color
(function(d){d.each(["backgroundColor","borderBottomColor","borderLeftColor","borderRightColor","borderTopColor","color","outlineColor"],function(f,e){d.fx.step[e]=function(g){if(!g.colorInit){g.start=c(g.elem,e);g.end=b(g.end);g.colorInit=true}g.elem.style[e]="rgb("+[Math.max(Math.min(parseInt((g.pos*(g.end[0]-g.start[0]))+g.start[0]),255),0),Math.max(Math.min(parseInt((g.pos*(g.end[1]-g.start[1]))+g.start[1]),255),0),Math.max(Math.min(parseInt((g.pos*(g.end[2]-g.start[2]))+g.start[2]),255),0)].join(",")+")"}});function b(f){var e;if(f&&f.constructor==Array&&f.length==3){return f}if(e=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(f)){return[parseInt(e[1]),parseInt(e[2]),parseInt(e[3])]}if(e=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(f)){return[parseFloat(e[1])*2.55,parseFloat(e[2])*2.55,parseFloat(e[3])*2.55]}if(e=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(f)){return[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]}if(e=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(f)){return[parseInt(e[1]+e[1],16),parseInt(e[2]+e[2],16),parseInt(e[3]+e[3],16)]}if(e=/rgba\(0, 0, 0, 0\)/.exec(f)){return a.transparent}return a[d.trim(f).toLowerCase()]}function c(g,e){var f;do{f=d.curCSS(g,e);if(f!=""&&f!="transparent"||d.nodeName(g,"body")){break}e="backgroundColor"}while(g=g.parentNode);return b(f)}var a={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0],transparent:[255,255,255]}})(jQuery);

// Highlight plugin
(function($){  
  $.fn.highlight = function(color) {
    $(this).each(function(index) {
      // Use default color if no color is specified
      if(!color) {
        color = "#faff99";
      }
      var $this = $(this);
      // We restore the original color at the end
      var original = $this.css('background-color');
      $this.animate({ backgroundColor: color }, 500)
      $this.animate({ backgroundColor: original }, 500)
    });
  }
})(jQuery);

To use the plugin use the following code:

$("table tr:first").highlight();

This will highlight the first row in the table.

You can also specify a color:

$("table tr:first").highlight('#ccc');

How to add and remove validation rules (jQuery validate plugin)

Tagged jquery, validation, plugin, remove, add  Languages javascript

This snippet shows how to change the jQuery validation plugin's validation rules dynamically:

$(document).ready(function() { 
  // Setup the default validation rules
  var validator = $('form').validate({ 
    rules: { 
      username: { 
        required: true, 
        minlength: 2
      }
    }
  };

  // Drop-down selection changes validation rules
  $('#account-type').change(function () {
    // Get the jQuery validation plugin's settings
    var settings = $('form').validate().settings;

    // Modify validation settings
    $.extend(true, settings, {
       rules: {
           // Add validation of year
           "year": {
               required: true,
               min: 2010,
               max: 2020,
               number: true
           }, 
           // Remove validation of username
           "username": {} 
       }
    });
  });
});

You can also use CSS classes to achieve the same:

$('#channel-title').addClass("{required:true,messages:{required:'required field'}}")
$('#channel-title').removeClass("{required:true,messages:{required:'required field'}}")