eddorre

Found 2 posts tagged with 'tutorial'

Tutorial: Filtering Results with jQuery UI Slider and Rails 3 Beta 3

May 25, 2010 — 3 Comments

A few months ago, I wrote an tutorial article titled Tutorial: Filtering Results with jQuery UI Slider and Rails.

After getting Rails 3 Beta 3 up and running, I wanted to get an app running from scratch using Rails 3 Beta 3. I chose the app in the original tutorial because it was dead simple and wouldn’t take much time at all to do.

Before proceeding with this tutorial, make sure that Rails 3 Beta 3 is running. Use my tutorial. I’ll wait…

All installed and ready to go? Good. Without further ado, here we go.

Setting Up

First of all, let’s visualize what we want to achieve. In my example, I have 20 dummy stocks that I want to filter down based on a high and low price. Here is a sample screen shot:

stocks

We’re going to start from scratch from a brand new Rails project. So we’ll have to create it:

rails jquery_slider_rails3

Since we didn’t pass a -d option, the Rails 3 generator will use the SQLite database by default. That’s perfectly fine for this example.

Bundle me Some Gems

Now that Rails has created a new project for us, let’s open it up and take a look at the Gemfile.

If you’ve followed my tutorial to install Rails 3 Beta 3, this file should be familiar by now and you’ve probably already installed some gems with Bundler. If not, let’s do that now.

Make sure that you see the following line for SQLite:

gem 'sqlite3-ruby', :require => 'sqlite3'

Since we’re going to do some simple model specs, we’ll need to install RSpec and RSpec Rails. This is done by editing the Gemfile and adding the following:


# Bundle gems for certain environments:
group :test do
  gem 'rspec', '>= 2.0.0.beta.7'
  gem 'rspec-rails', '>= 2.0.0.beta.7'
end

You’ll notice that these gems belong to their own test group. This means that those gems will only be loaded in the test environment.

We’re also going to use the Faker gem to generate some fake names for our stocks. Add the following to the Gemfile (I put mine directly beneath the sqlite gem declaration).

gem 'faker'

Now that we have the necessary gems defined, we’ll go ahead and install them using Bundler.

bundle install

Generating our Model

Before we can start creating our model, we’ll need to tell Rails that we’re using RSpec. First let’s expose the generators to Rails.

Open the application.rb and uncomment the config.generators block so that it looks like this:


config.generators do |g|
  g.orm             :active_record
  g.template_engine :erb
  g.test_framework  :rspec, :fixture => true, :views => false
end

Then run the rspec installer.

rails g rspec:install

Once that’s been completed, we’re ready to move onto the next step, setting up our model.

rails g model Stock name:string price:integer

You should see something like:

      
      invoke  active_record
      create    db/migrate/20100418065519_create_stocks.rb
      create    app/models/stock.rb
      invoke    rspec
   identical      spec/models/stock_spec.rb
   identical      spec/fixtures/stocks.yml

Defining our Specs

Let’s start defining some behavior for our app. First we want to make sure that we can create a stock with a name and a price. Open the file stock_spec.rb in ~/spec/models and remove any generated code so that it looks like:


require 'spec_helper'
describe Stock do
end

Add the following lines in between the describe block:


  it "should be valid with valid attributes" do
    stock = Stock.new(:name => 'Foo', :price => 1)
    stock.should be_valid
  end

You’ll get an error if you run your spec right now because we haven’t created a test database or defined the migration. Go and run rake or rake spec if you want to see a spectacular failure in your terminal.

Since we actually want the spec to pass, let’s go and create our databases and define our migration. Creating databases is just like in previous versions of Rails.

rake db:create:all

Open the migration file located at ~/db/migrate. Most of it should already be defined but since we’re going to be filtering on price, we’ll go ahead and create an index on that column.

Your migration file should look like this:


class CreateStocks < ActiveRecord::Migration
  def self.up
    create_table :stocks do |t|
      t.string :name
      t.integer :price

      t.timestamps
    end
    
    add_index :stocks, :price
  end

  def self.down
    drop_table :stocks
  end
end

Migrating, just like creating database, is no different in Rails 3.


rake db:migrate
rake db:migrate RAILS_ENV=test

Now that we have a test database and migration run, we can run our spec using rake. You should see something like:


..

Finished in 0.0418 seconds
2 examples, 0 failures

Let’s define some more specs. We’ll always want to order the stocks by lowest price (unless we override it). Note: Normally this is where you’d start using a factory like Factory Girl or Machinist but the app is so simple it’s overkill.

Our second spec looks like this:


  it "should return stock in price ascending order by default" do
    stock_1 = Stock.create(:name => 'Foo', :price => 300)
    stock_2 = Stock.create(:name => 'Foo', :price => 400)
    stock_3 = Stock.create(:name => 'Foo', :price => 200)
    stock_4 = Stock.create(:name => 'Foo', :price => 100)
    
    Stock.all.should == [stock_4, stock_3, stock_1, stock_2]
  end

If we run our spec, it’ll fail because the default order defaults to name DESC. So we have to write some implementation code to fix our failing spec.

Open the stock.rb file located in ~/app/models. Since we want it always to order our stocks by price (lowest to highest), we can use something that was introduced in Rails 2.3; default_scope.

If you’re familiar with Rails 2, you’ll notice that the syntax of this is a bit different.


default_scope order('price ASC')

If we run rake spec again, we’ll see that we have another passing spec. Let’s move onto the next one.

We also know that we want to filter our stock prices based on low and high value dynamically. We can write a simple spec for this too. Basically what we want is "give me all of the stocks that have a price between 200 and 300 inclusive.


  it "should filter out stocks that aren't in our range" do
    stock_1 = Stock.create(:name => 'Foo', :price => 100)
    stock_2 = Stock.create(:name => 'Foo', :price => 200)
    stock_3 = Stock.create(:name => 'Foo', :price => 300)
    stock_4 = Stock.create(:name => 'Foo', :price => 400)
    Stock.filter(200,300).should == [stock_2, stock_3]
  end

Again, if we run rake spec, we’ll have a failing spec since we haven’t created the filter method. For this, we’ll be relying on scopes again.


scope :filter, lambda { |low, high| where(:price => low..high) }

If you’re familiar with Rails 2, you’ll notice that named_scope has been replaced with just scope. Also, you can see some of Arel’s new syntax.

Running rake spec gives us our next passing spec.

Creating our controller and views

Rails 3 syntax for creating controllers is pretty much unchanged from previous versions of Rails.


rails g controller stocks index

This creates our stocks controller with an index method. Open the newly created file at ~/app/controllers/stocks_controller.rb. For now, let’s just display all of the stocks and then we’ll worry about filtering them down.


  def index
    @stocks = Stock.all
  end

Open the corresponding view file by going to ~/app/views/stocks/index.html.erb.

Let’s create a simple view.


<h1>Stocks</h1>

<div id="x_slider"></div>

<div>
  <p>Showing all stocks between <span id="x_low_selected"><%= @price_range.first %></span> and <span id="x_high_selected"><%= @price_range.last %></span></p>
</div>

<ul id="x_stock_list">
  <%= render @stocks %>
</ul>

As you’ll notice, I’ve rendering out the variable @stocks in order for this to work, we have to create a stock partial. In ~/app/views/stocks we’ll create a new empty file with the file name of _stock.html.erb with the following code:


<li>
  <p><%= stock.name %></p>
  <p><%= stock.price %></p>
</li>

The render method has gotten pretty smart as of late, so we don’t have to write our own each do loop here as long as the name of the instance variable (@stocks – plural) matches the name of the partial (_stock.html.erb – singular).

Astute readers will notice that I’m referencing an instance variable called @price_range. If you try to load the app right now in the browser, you’ll get an error since we haven’t set up the @price_range variable. In the previous tutorial, I had a class method on stock that grabbed the lowest and highest prices and put them into an array. We’ll duplicate that now.

Back to RSpec

Before we define the class method, we should define a spec for it.


  it "should return the highest and lowest stock prices" do
    stock_1 = Stock.create(:name => 'Foo', :price => 100)
    stock_2 = Stock.create(:name => 'Foo', :price => 200)
    stock_3 = Stock.create(:name => 'Foo', :price => 300)
    stock_4 = Stock.create(:name => 'Foo', :price => 400)
    Stock.low_high_prices.should == [100, 400]
  end

Now we just need to create our class method. It’s a simple one:


  def self.low_high_prices
    [Stock.minimum(:price), Stock.maximum(:price)]
  end

Running rake spec now gives us another passing spec. We’re getting close to spinning up the app but we’ll want to create some dummy data first.

Loading Sample Data

We can (mis)use the rake db:seed for this purpose. Remember that faker gem that we put into our Gemfile a long time ago? Here is where we’ll use it.

Open up the seeds.rb file located at ~/db/seeds.rb and we’ll add the following:


20.times do
  Stock.create(:name => Faker::Lorem.words(1).join, :price => rand(5 * 100))
end

This just creates a stock with a random name (thanks to Faker) with a random price. One thing of note here. The words method in Faker::Lorem creates an array. We really want this to be a string but since we’re using Ruby 1.9.2 we can’t just call a .to_s on it as the just returns and array with a string in it. I found that using .join without any arguments produces the result that we want.

Let’s load up our sample data.


rake db:seed

Now we should have 20 dummy stocks waiting for us. Let’s take a look.

Booting the Application

Before we can actually see anything in the browser, we can to create a simple resourceful route for our stocks. Routing is one of the things that’s changed dramatically in Rails 3 so I suggest that you read through the commented code in the routes files (~/config/routes.rb).

Once you’ve given the routes file a once over, let’s go ahead and add our route. I Added mine right at the top:


resources :stocks

Simple, isn’t it?

In your terminal fire up a web server by running


rails server

Now we’re ready to see something in our browser. Load up http://0.0.0.0:3000/stocks and you should see the stock list.

Let’s Make it Dynamic

In order to make this dynamic, we’re going to have to download some files.

Since we’re opting to use jQuery, we’re going to use the Rails 3 jQuery specific driver. Find the rails.js in ~/public/javascripts and remove it. Download the jQuery driver to the ~/public/javascripts directory.

We’ll need to install jQuery and jQuery UI but we can kill two birds with one stone by downloading jQuery UI. The download will come in a zip file, extract the contents and copy the contents of the js directory to ~/public/javascripts.

You’ll also need to copy the CSS file located in [extracted_zip_file]/css/[theme_name] to ~/public/stylesheets. Finally copy the contents of the images directory to ~/public/images.

I’ve noticed that there is a little tweak that we have to make to the CSS file. Open it up and do a find and replace on ‘images/’ to ‘/images/’. This will prevent Rails from searching for the images in ~/stylesheets/images/…

Once all of the Javascript files have been downloaded, we need to tell Rails to use them. Open the application.html.erb file in ~/views/layouts and add the following lines inside the head tag:


  <%= stylesheet_link_tag :all, 'jquery-ui-1.8.custom' %>
  <%= javascript_include_tag 'jquery-1.4.2.min', 'jquery-ui-1.8.custom.min', 'rails' %>
  <%= csrf_meta_tag %>

The jQuery UI slider library takes care of most of the heavy lifting. Here is the final slider Javascript code with an explanation below it of all of the settings.


<script type="text/javascript">
  $(function() {
    $("#x_slider").slider( { 
      range: true,
      step: 10,
      max: <%= @price_range.last %>,
      min: <%= @price_range.first %>,
      values: [<%= @price_range.first %>, <%= @price_range.last %> ],
      stop: function(event, ui) {
        var prices = $('#x_slider').slider('option', 'values');
        $('#x_low_selected').html(prices[0]);
        $('#x_high_selected').html(prices[1]);
        $.ajax({
          type: "GET",
          data: ({ low: prices[0], high: prices[1] }),
          url: 'http://0.0.0.0:3000/stocks',
          dataType: 'script'
        });
      }
    });
  });
</script>

Before we bind the slider to our div, we’ll wrap everything in a convenience function to make sure that the DOM is loaded.

Next we bind the slider by simply writing:


$("x_slider").slider( { options here }

As you can see in the code above, the slider takes several options (see full documentation for all options):

  • Range
    • When this is set to true, you’ll get two or more points on the slider
    • Step
    • This is the value that the slider decrements or increments every time you move a point
  • Max
    • The maximum value of the slider
  • Min
    • The minimum value of the slider
  • Values
    • An array of low and high points on the slider
  • Stop
    • This is an event that is fired when a point on the slider stops moving

In the stop event there is a bunch of stuff going on. First, I set a price array to the set points on the slider so I can use them later for updating the user of the min and max values as well as passing those in as parameters to the ajax call.

This code snippet simply replaces the min and max values on the page:


$('#x_low_selected').html(prices[0]);
$('#x_high_selected').html(prices[1]);

The ajax function also has several options:

  • Type
    • This is the type of HTTP request. A simple GET request will do here
  • Data
    • These are the variables that we’ll be sending along
  • URL
    • This is the URL that we’ll be hitting – this assumes that you’ve started the web server using script/server or mongrel_rails
  • DataType
    • I originally set this to html thinking that is what I wanted, but that’s the wrong call. You’ll want to use script if you want to return fragments of HTML otherwise you’ll return the whole page in HTML form (layout and all).

Wiring up the Slider to the Controller

We have one more thing to do in order to get our application up and running. We need to tell the controller to do its job.

Open the stocks_controller.rb file in ~/app/controllers/stocks_controller.rb and create an index method if one doesn’t already exist and populate it with the following:


  def index
    unless params[:low] && params[:high]
      @stocks = Stock.all
    else
      @stocks = Stock.filter(params[:low], params[:high])
    end
    
    @price_range = Stock.low_high_prices
  end

Since the the slider uses ajax for communication with the server, we’ll create an index.js.erb file in ~/app/views/stocks.

The file contains some simple Javascript code to execute:


$("#x_stock_list").html("<%= escape_javascript(render(@stocks)) %>");

With that little bit of Javascript code, every thing should be set to start up the application. To test it, start up the web server with the rails server command. Now in your web browser, go to http://0.0.0.0:3000/stocks and see the results.

Like before, I’ve made the code available for this tutorial on Github.

Tutorial: Filtering results with jQuery UI Slider and Rails

November 05, 2009 — 22 Comments

At Planet Argon, we’re using the jQuery UI slider to filter results on one of our client projects. Since I had never implemented anything with jQuery UI slider, I decided to whip up a quick prototype just to see how it worked. It took me a little under an hour to get something put together. This post is the result of that prototype.

First of all, let’s visualize what we want to achieve. In my example, I have 20 dummy stocks that I want to filter down based on a high and low price. Here is a sample screen shot:

stocks

We’re going to start from scratch from a brand new Rails project. So we’ll have to create it:

NOTE: This tutorial uses Rails 2.3.4 but it can be easily adapted to work with recent earlier versions. The tutorial also uses jQuery 1.3.2 and jQuery UI 1.7.2 (the jQuery files can be downloaded from the Github repository.

rails jquery_slider_rails

Since we’re using jQuery instead of Prototype, we’ll want to install the jRails gem. While we’re installing gems, we’ll install a gem called Faker to create some dummy names as well as RSpec and RSpec Rails.

sudo gem install jrails
sudo gem install faker
sudo gem install rspec
sudo gem install rspec-rails

In order to use them, we’ll have to add config.gem statements to our environment.rb file.

  config.gem "faker"
  config.gem "jrails", :lib => false

Next thing we’ll have to do is go to the jQuery UI download page and we’ll download a custom build of jQuery UI. For this example, we’ll go lightweight and just select UI core and slider. I chose UI Lightness as the theme, but you can choose whatever you like; the theme won’t impact the results.

Once you download the files, place them in the following locations:

  • Images
    • /public/images/
  • jQuery and jQuery UI Javascript files
    • /public/javascripts/
  • CSS file
    • /public/stylesheets/

Now that we have all of the supporting files that we’ll need we’re ready to create our model that holds Stock information.

NOTE: Although I’m creating RSpec models and controller, I’m not writing any specs for this simple app. Perhaps that’s a post for another day.

script/generate rspec_model stock name:string price:integer

It’s probably a good idea to add an index on the price attribute since we’re going to be filtering on it.

Open up the db/migration directory and edit the only migration file to add the index. It should look like this when we’re done:

class CreateStocks < ActiveRecord::Migration
  def self.up
    create_table :stocks do |t|
      t.string :name
      t.integer :price
      t.timestamps
      
    end

      add_index :stocks, :price
  end

  def self.down
    drop_table :stocks
  end
end

Now that we have the migration, we’re ready to create our database and run said migration:

rake db:create:all
rake db:migrate

We should have a running database now (on SQLite) but now we need some data. We’ll mis(use) the seed file functionality that was added in Rails 2.3.4. You can find the seeds.rb file in the db directory.

We want 20 dummy stocks with fake names and fake prices. For fake random names, we’ll use the Faker gem. The Faker gem comes in really handy for writing specs/tests. A topic that I’ll probably cover soon. For random prices, we’ll just use Ruby’s rand method.

20.times do |x|
  Stock.create(:name => Faker::Lorem.words(1).to_s, :price => rand(5 * (100)))
end

Once we save the file, we can load up our data by running the following command:

rake db:seed

Once the sample data has been seeded we can start building our model. We know that we want to filter the data based on two points on the slider. This can easily by done by a named scope that takes two arguments.

named_scope :filter, lambda { |low, high| { :conditions => { :price => low..high } } }

We’ll also need to grab the highest and lowest prices to plug in as max and min values into the slider. For that we can create a class method that returns each price in an array.

  def self.high_low_prices
    [Stock.minimum(:price), Stock.maximum(:price)]
  end

Let’s move over to our controller. We know that we’ll need an index action but we’ll also need some sort of action to respond to slider ‘stop’ events. We can easily create a filter method/action in the controller, but as it turns out we can actually re-use the index action and remain fully RESTful.

Let’s focus on getting the correct data. We’ll be passing in the two points from the slider as parameters. If both of those exist, we’ll actually call our filter named scope. If not, we’ll just grab all of the stocks.

    unless params[:low] && params[:high]
      @stocks = Stock.all
    else
      @stocks = Stock.filter(params[:low], params[:high])
    end

We can probably grab the highest and lowest stock prices from the stocks instance variable, but let’s be explicit.

@price_range = Stock.high_low_prices

If the request is not an ajax request we’ll load the index view. If it’s an ajax request, let’s replace our stock list with the filtered list.

    respond_to do |format|
      format.html
      format.js do
        render :update do |page|
          page.replace_html 'x_stock_list', :partial => 'stocks/stock_list', :locals => { :stocks => @stocks }
        end
      end
    end
  end

We can use the replace_html method here because we’re using the jRails gem.

We’re now ready to tackle the views.

Create an index.html.erb view under /app/views/stocks/. While we’re at it we’ll create the partial that we alluded to in our controller. We’ll call this _stock_list.html.erb and it goes into the same directory as the index view.

Add the boilerplate stuff to the index.html.erb view; a DOCTYPE, html, head, and body tags. I also added an h1 tag wrapping the word Stocks.

Remember those jQuery files that we downloaded a while ago? We’ll link to them now.

  <head>
    <%= stylesheet_link_tag 'jquery-ui-1.7.2.custom.css' %>
    <%= javascript_include_tag 'jquery-1.3.2.min.js', 'jquery-ui-1.7.2.custom.min.js' %>
  </head>

Inside the body tag, we’ll create a div so that our slider can hook into it. NOTE: I use inline styles here because this is a quick and dirty example. Please keep all styles in CSS files in a real app.

I also have gotten in the habit of prepending all classes and IDs that are used by Javascript and ajax interactions with ‘x_’. This is a convention that we use at Planet Argon. Robby Russell originally blogged about this in the article Designers, Developers, and the x_ Factor

    <div id="x_slider" style="font-size:62.5%; width:350px;"></div>

I follow the slider div another div for the high and low prices.

     <div>
       <p>Showing all stocks between <span id="x_low_selected"><%= @price_range.first %></span> and <span id="x_high_selected"><%= @price_range.last %></span></p>
     </div>

The last HTML elements on the page is the stock list itself.

    <ul id="x_stock_list">
      <%= render 'stock_list', :stocks => @stocks %>
    </ul>

Before we get to the jQuery code, let’s jump over to our partial and flesh it out. The code necessary is minimal.

<% @stocks.each do |stock| %>
  <li>
      <p><%= stock.name %></p>
      <p><%= stock.price %></p>
  </li>
<% end  %>

The jQuery UI slider library takes care of most of the heavy lifting. Here is the final slider Javascript code with an explanation below it of all of the settings.

    <script type="text/javascript">
      $(function() {
        $("#x_slider").slider( { 
          range: true,
          step: 10,
          max: <%= @price_range.last %>,
          min: <%= @price_range.first %>,
          values: [<%= @price_range.first %>, <%= @price_range.last %> ],
          stop: function(event, ui) {
            var prices = $('#x_slider').slider('option', 'values');
            $('#x_low_selected').html(prices[0]);
            $('#x_high_selected').html(prices[1]);
            $.ajax({
              type: "GET",
              data: ({ low: prices[0], high: prices[1] }),
              url: 'http://0.0.0.0:3000/stocks',
              dataType: 'script'
            });
          }
        });
      });
    </script>

Before we bind the slider to our div, we’ll wrap everything in a convenience function to make sure that the DOM is loaded.

Next we bind the slider by simply writing:

$("x_slider").slider( { options here }

As you can see in the code above, the slider takes several options (see full documentation for all options):

  • Range
    • When this is set to true, you’ll get two or more points on the slider
    • Step
    • This is the value that the slider decrements or increments every time you move a point
  • Max
    • The maximum value of the slider
  • Min
    • The minimum value of the slider
  • Values
    • An array of low and high points on the slider
  • Stop
    • This is an event that is fired when a point on the slider stops moving

In the stop event there is a bunch of stuff going on. First, I set a price array to the set points on the slider so I can use them later for updating the user of the min and max values as well as passing those in as parameters to the ajax call.

This code snippet simply replaces the min and max values on the page:

$('#x_low_selected').html(prices[0]);
$('#x_high_selected').html(prices[1]);

The ajax function also has several options:

  • Type
    • This is the type of HTTP request. A simple GET request will do here
  • Data
    • These are the variables that we’ll be sending along
  • URL
    • This is the URL that we’ll be hitting – this assumes that you’ve started the web server using script/server or mongrel_rails
  • DataType
    • I originally set this to html thinking that is what I wanted, but that’s the wrong call. You’ll want to use script if you want to return fragments of HTML otherwise you’ll return the whole page in HTML form (layout and all).

That’s it. As I said before, to test it, you’ll want to start up a web server with either mongrel_rails, script/server or Passenger. In your web browser, you should be able to go to either http://0.0.0.0:3000/stocks or http://jquery_slider_rails.local/stocks if you’re using Passenger to see the results.

I’ve made all of the code available for this tutorial on GitHub.