Wednesday, May 18, 2011

Scriptaculous Slider in Rails3 form

So there is an excellent slider. http://madrobby.github.com/scriptaculous/slider/ It is perfect to set up a select form element where values are predefined numbers or integers. First you have to download http://script.aculo.us/downloads the slider.js file and move it to the /public/javascripts directory. Then insert this row in your layout, /app/views/layouts/application.html.erb after the defaults.

<%= javascript_include_tag "slider.js" %>

Then we define CSS style for the slider as the same as at the script.aculo.us homepage. (/app/public/stylesheets/style.css)

div.slider {
 width:256px;
 margin:10px 0;
 background-color:#ccc;
 height:10px;
 position: relative;
}

div.slider div.handle {
 width:10px;
 height:15px;
 background-color:#f00;
 cursor:move;
 position: absolute;
}

Then we create a helper for it. (/app/helpers/application_helper.rb)

def slider(attributes={})
  unless attributes.nil? then
  values=attributes[:values]
  default_value=attributes[:default_value].to_s
  object=attributes[:object].to_s
  instance=attributes[:instance].to_s
  unit=attributes[:unit] || ""
  text_div_id=object + "_" + instance + "_slider_text_div"
  slider_id=object + "_" + instance + "_slider"
        
  concat(raw(
    "<div id=\"#{slider_id}\" class=\"slider\">
       <div class=\"handle\"></div>
     </div>"
  ))

  concat(raw(hidden_field(object, instance, :value => default_value.to_i)))
  concat(raw("<div id=\"#{text_div_id}\">" + default_value + " " + unit + "</div>"))
        
  js_text = <<JS3
  (function() {
    var zoom_slider = $('#{slider_id}');
    var text_div = $('#{text_div_id}');
    var hidden_item = $('#{object + "_" + instance}');
    var values=#{values.inspect};
    
    var values_length = values.length;
    var min=40;
    var max=200;

    var unit = Math.ceil((max - min) / values_length);
    var range = $R(min, max - unit);

    var pix_values = new Array();
    for(i=1;i<=values_length;i++){
      pix_values[i-1]=min + (unit * (i - 1));
    }
        
    var default_value=pix_values[#{values.index(default_value.to_i)}];

    new Control.Slider(zoom_slider.down('.handle'), zoom_slider, {
      range: range,
      sliderValue: default_value,
            increment: unit,
            values: pix_values,
      onSlide: function(value) {
                text_div.innerHTML=values[pix_values.indexOf(value)] + " #{unit}";
                hidden_item.value=values[pix_values.indexOf(value)];
      },
      onChange: function(value) { 
                text_div.innerHTML=values[pix_values.indexOf(value)] + " #{unit}";
                hidden_item.value=values[pix_values.indexOf(value)];
      }
    });
  })();
        
JS3
      concat(raw(javascript_tag(js_text)))
  end
end

Then just define your form element in your view / form:

<table><tbody><tr><td>
<% slider :object        => "config",
          :instance      => "mem_size",
          :unit          => "MB",
          :values        => [512, 1024, 1536, 2048, 2560, 3072, 4096].sort,
          :default_value => 1024 %>
</td></tr></tbody></table>

That"s all! Questions?

30 comments:

  1. Hi thanks for the tutorial - however

    I get the errors:

    app.helpers/application_helper.rb:63: can't find string "JS3" anywhere before EOF

    app/helpers/application_helper.rb:22: syntax error, unexpected $end, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END

    Is the js_text in application_help written correctly or is there a formatting error?

    ReplyDelete
  2. Hi Jalan,

    thanks, it is a heredoc string problem, I try correct it today.

    ReplyDelete
  3. ensure last JS3 must be at the beginning of the row.

    ReplyDelete
  4. Gerat! I've been looking for such a helper. Works fine!

    ReplyDelete
  5. Hello. It seems that the slider is not working in Internet Explorer 9. Do you know why?

    ReplyDelete
  6. not exactly, I didn't test. Maybe the Prototype. Try download the newest version.

    ReplyDelete
  7. Hi,
    Thanks for this tutorial. It helped me a lot. the only thing is that I just can't see the slider?!!!! it just print the default value which is 1024 !!!!
    Please help me I should fix it soon.
    All comments are highly appreciated.

    Eli.

    ReplyDelete
  8. have you included the slider.js from script.aculo.us, and control.js in header meta tag?

    ReplyDelete
  9. (in Rails 3.1 the includes could be different, jquery is the default.)

    ReplyDelete
  10. Thanks Semper for your response! I put slider.js in the /public/javascripts directory and then I added this line to /app/views/layouts/application.html.erb
    <%= javascript_include_tag :all %>

    when I open the webpage and view source code I can see that slider.js and controls.js are included!
    here is the code in the corresponding web page erb file:

    < t a b l e >
    < t b o d y >
    < t r >
    < t d >
    <% slider :object => "config", :instance => "mem_size", :values => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].sort, :default_value => 5 %>
    < / t d >
    < / t r >
    < / t b o d y >
    < / t a b l e >

    Actually it just print 5 which is default value! I have no idea what is wrong that I can just see the default value!!!

    Thanks for your help!

    Eli.

    ReplyDelete
  11. you can check javascript errors in firefox web console.

    ReplyDelete
  12. see the source in browser, prototype included also?

    ReplyDelete
  13. yes prototype is included! The problem was with style sheet. Thanks for your help and hint to check errors in firefox web console. It is working now!

    Have a nice afternoon.

    ReplyDelete
  14. Hi,
    Thanks for the great job! It works nicely. I'm just wondering is there any way that we can set a picture for the handler of the slider to looks more fancy instead of just a solid square?!

    Thanks for your help!

    ReplyDelete
  15. I didn't try, but there is a css tag: background-image:url('example.png') for the div.

    ReplyDelete
  16. Hi,
    Thanks for the useful tutorial! I'm trying to develop a webpage which has seven sliders for seven different labels. here is the code for tow label (I just test to see if it works for two label) in the view page:
    < p >
    < b > Assessments < / b >
    < / p >
    < p >
    <%= slider :object => "config", :instance => "mem_size", :values => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].sort, :default_value => 5%>
    < / p >
    < p >
    < b > CDT < / b >
    < / p >
    < p >
    <%= slider :object2 => "config", :instance => "mem_size", :values => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].sort, :default_value => 5%>
    < / p >

    Actually I want to have a title as "Assessment" and then a slider, then another title as "CDT" and then another slider. But with this code there are repetitions!!! In the web page there are two Assessment , then one CDT then two more Assessment then one more CDT. in each one of them there is also one slider. but just the first one for Assessment and the first one for CDT work correctly!!!
    Is there any suggestion why this is happening?

    I appreciate your help.

    ReplyDelete
    Replies
    1. Hi,
      that's because of object and instance.

      <%= slider :object2 => "config", :instance => "mem_size" isn't working because there isn't object2 parameter in the helper.

      <%= slider :object => "config2", :instance => "mem_size" or
      <%= slider :object => "config", :instance => "mem_size2" should work.

      Object means table name, instance means the column in sql. (Of course works with simple models, or objects also.)

      Delete
    2. (You can see the source in the browser, this helper makes hidden field for form: id="config_mem_size". If you change :object => "config2", :instance => "mem_size2"), then creates id="config2_mem_size2".)

      Delete
  17. Hi,
    Thanks for your greate tutorial! I'm using this slider for a medical app, and need to have bars in the slider to distinguesh betwen different levels. for example I want to have some thing like this:
    -------------
    | | | | |
    --------------

    istead of just a solid rectangle. any suggestions?

    Thanks for your help agian :)

    ReplyDelete
    Replies
    1. Maybe, you can create more, unmovable handlers with setDisable property:
      http://madrobby.github.com/scriptaculous/slider/

      Delete
  18. hi,
    thanks for your helpful tutorial. I'm new with ROR.
    Can you tell me please how can I get the value of slider after users select one of them?
    here is the code in my view page:

    < d i v id="config2_mem_size2_slider" class="slider" > < div class="handle" > < / d i v > < / d i v > < input id="config2_mem_size2" name="config2[mem_size2]" type="hidden" values= 'Strongly_Disagree','Disagree','Not_Sure','Agree','Strongly_Agree'] / > < div id="config2_mem_size2_slider_text_div" > "Select" < / div >

    ReplyDelete
    Replies
    1. Hi, in Javascript you can get teha values of hidden fields like that:
      $('config2_mem_size2').value

      In the controller it depends on your form. Probably:
      params[:config2][:mem_size2]
      You can check sent parameters in the log file: tail -f log/development.log

      Delete
    2. Thanks fro your response! I need to make it work by today! your help is highly appreciated.
      Actually, I want to pass the value of slider to the controller to insert into a table! and here is the code in controller:

      def add_comments
      @artistprod =Comment.new(:RviewerID => @usrld, :doc_id =>
      @did, :Comments => params[:config2][:mem_size2])
      respond_to do |format|
      format.html
      format.xml { render :xml => @artistprod }
      end
      end


      here is the code in view:

      < div id="config2_mem_size2_slider" class="slider" > < div class="handle" > < / div > < / div > < input id="config2_mem_size2" name="config2[mem_size2]" type="hidden" values=['Strongly_Disagree','Disagree','Not_Sure','Agree','Strongly_Agree'] / > < div id="config2_mem_size2_slider_text_div" > "Select" < / div >
      < script type="text/javascript" >
      //
      < / script >
      < / div >
      < br > < / br >
      < b > Your Comments: < / b >

      < br > < / br >
      <%= text_field 'comnts', 'baseLeaseFee','size'=>80 %>
      <% @did= @md_anderson.ArticleID
      @usrld= session[:user_name]
      %>
      < br > < / br >
      < br > < / br >
      <%= button_to "Submit", :method => :get, :controller => "md_andersons", :action => "add_comments" %>

      ***********************************************************
      I also can not see any parameter sending information in lof file!
      here is the part of log file:
      Started POST "/md_andersons/add_comments?method=get" for 128.206.20.171 at Sun Feb 26 10:52:10 -0600 2012
      Processing by MdAndersonsController#add_comments as HTML
      Parameters: {"method"=>"get", "authenticity_token"=>"VLDbMNuKORjSHDKSAHdSaoDZtzXxs7sDJOuKZ510LK8="}
      [1m [36mOlduser Load (0.2ms) [0m [1mSELECT `oldusers`.* FROM `oldusers` WHERE `oldusers`.`id` = 23 LIMIT 1 [0m
      Completed in 20ms

      NoMethodError (You have a nil object when you didn't expect it!
      You might have expected an instance of Array.
      The error occurred while evaluating nil.[]):
      app/controllers/md_andersons_controller.rb:173:in `add_comments'

      Rendered /usr/lib/ruby/gems/1.8/gems/actionpack-3.0.7/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.0ms)
      Rendered /usr/lib/ruby/gems/1.8/gems/actionpack-3.0.7/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (6.8ms)
      Rendered /usr/lib/ruby/gems/1.8/gems/actionpack-3.0.7/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (11.3ms)

      ****************************************************************

      but I'm getting this error:

      NoMethodError in MdAndersonsController#add_comments

      You have a nil object when you didn't expect it!
      You might have expected an instance of Array.
      The error occurred while evaluating nil.[]

      Rails.root: /home/projects/leinfo
      Application Trace | Framework Trace | Full Trace

      app/controllers/md_andersons_controller.rb:173:in `add_comments'

      Request

      Parameters:

      {"method"=>"get",
      "authenticity_token"=>"VLDbMNuKORjSHDKSAHdSaoDZtzXxs7sDJOuKZ510LK8="}

      Delete
    3. Sorry, I've just realized this message was in spams. I think your form declaration was wrong.

      Delete
  19. I was wondering: what about when I wan't a double handle slider?
    But the handles can't go past each other.
    For example if my values where: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    The defaults would be: handle1 = 1 ; handle2 = 10
    If the handle1 was moved to 4(example), then the handle2 couldn't go below 4.
    In the same way if handle2 was moved to 7(example), then the handle1 couldn't go beyond 7.

    Any guidance with this?

    I appreciate any help, thanks in advance.

    Satyr

    ReplyDelete
    Replies
    1. Hi Satyr,

      I think this was because the same of a javascript variable was using by two sliders. Try create a new helper method for second instance with different javascript variable names. I have not better idea.

      Attila

      Delete
    2. Thanks for your insight. I think I will have to get a deeper analysis of how everything works.

      Thanks again,
      Satyr.

      Delete
  20. I've got a problem in application_helper.rb with rails 4
    TypeError: zoom_slider.down is not a function

    I appreciate any help, thanks in advance.

    ReplyDelete
    Replies
    1. Hi, I think it could be a heredoc problem with
      js_text = <<JS3
      ..
      JS3

      JS3 must be at the beginning of the line.

      Delete

Note: Only a member of this blog may post a comment.