diff --git a/app/views/partials/_question.html.haml b/app/views/partials/_question.html.haml index 06e0fc04..b9b0ac8d 100644 --- a/app/views/partials/_question.html.haml +++ b/app/views/partials/_question.html.haml @@ -13,7 +13,7 @@ = ff.input :question_id, :as => :quiet = ff.input :response_group, :as => :quiet, :value => rg if g && g.display_type == "repeater" = ff.input :id, :as => :quiet unless r.new_record? - = ff.input :answer_id, :as => :select, :collection => q.answers.map{|a| [a.text, a.id]}, :label => q.text, :input_html => { :disabled => disabled } + = ff.input :answer_id, :as => :select, :collection => q.answers.map{|a| [a.text, a.id]}, :include_blank => (renderer != :slider), :label => q.text, :input_html => { :disabled => disabled } - else # :default, :inline, :inline_default - if q.pick == "one" - r = response_for(@response_set, q, nil, rg) diff --git a/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js b/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js new file mode 100755 index 00000000..18704d78 --- /dev/null +++ b/lib/assets/javascripts/surveyor/jquery.selectToUISlider.js @@ -0,0 +1,240 @@ +/* + * -------------------------------------------------------------------- + * jQuery-Plugin - selectToUISlider - creates a UI slider component from a select element(s) + * by Scott Jehl, scott@filamentgroup.com + * http://www.filamentgroup.com + * reference article: http://www.filamentgroup.com/lab/update_jquery_ui_16_slider_from_a_select_element/ + * demo page: http://www.filamentgroup.com/examples/slider_v2/index.html + * + * Copyright (c) 2008 Filament Group, Inc + * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses. + * + * Usage Notes: please refer to our article above for documentation + * + * -------------------------------------------------------------------- + */ + + +jQuery.fn.selectToUISlider = function(settings){ + var selects = jQuery(this); + + //accessible slider options + var options = jQuery.extend({ + labels: 3, //number of visible labels + tooltip: true, //show tooltips, boolean + tooltipSrc: 'text',//accepts 'value' as well + labelSrc: 'value',//accepts 'value' as well , + sliderOptions: null + }, settings); + + + //handle ID attrs - selects each need IDs for handles to find them + var handleIds = (function(){ + var tempArr = []; + selects.each(function(){ + tempArr.push('handle_'+jQuery(this).attr('id')); + }); + return tempArr; + })(); + + //array of all option elements in select element (ignores optgroups) + var selectOptions = (function(){ + var opts = []; + selects.eq(0).find('option').each(function(){ + opts.push({ + value: jQuery(this).attr('value'), + text: jQuery(this).text() + }); + }); + return opts; + })(); + + //array of opt groups if present + var groups = (function(){ + if(selects.eq(0).find('optgroup').size()>0){ + var groupedData = []; + selects.eq(0).find('optgroup').each(function(i){ + groupedData[i] = {}; + groupedData[i].label = jQuery(this).attr('label'); + groupedData[i].options = []; + jQuery(this).find('option').each(function(){ + groupedData[i].options.push({text: jQuery(this).text(), value: jQuery(this).attr('value')}); + }); + }); + return groupedData; + } + else return null; + })(); + + //check if obj is array + function isArray(obj) { + return obj.constructor == Array; + } + //return tooltip text from option index + function ttText(optIndex){ + return (options.tooltipSrc == 'text') ? selectOptions[optIndex].text : selectOptions[optIndex].value; + } + + //plugin-generated slider options (can be overridden) + var sliderOptions = { + step: 1, + min: 0, + orientation: 'horizontal', + max: selectOptions.length-1, + range: selects.length > 1,//multiple select elements = true + slide: function(e, ui) {//slide function + var thisHandle = jQuery(ui.handle); + //handle feedback + var textval = ttText(ui.value); + thisHandle + .attr('aria-valuetext', textval) + .attr('aria-valuenow', ui.value) + .find('.ui-slider-tooltip .ttContent') + .text( textval ); + + //control original select menu + var currSelect = jQuery('#' + thisHandle.attr('id').split('handle_')[1]); + currSelect.find('option').eq(ui.value).attr('selected', 'selected'); + }, + values: (function(){ + var values = []; + selects.each(function(){ + values.push( jQuery(this).get(0).selectedIndex ); + }); + return values; + })() + }; + + //slider options from settings + options.sliderOptions = (settings) ? jQuery.extend(sliderOptions, settings.sliderOptions) : sliderOptions; + + //select element change event + selects.bind('change keyup click', function(){ + var thisIndex = jQuery(this).get(0).selectedIndex; + var thisHandle = jQuery('#handle_'+ jQuery(this).attr('id')); + var handleIndex = thisHandle.data('handleNum'); + thisHandle.parents('.ui-slider:eq(0)').slider("values", handleIndex, thisIndex); + }); + + + //create slider component div + var sliderComponent = jQuery('
'); + + //CREATE HANDLES + selects.each(function(i){ + var hidett = ''; + + //associate label for ARIA + var thisLabel = jQuery('label[for=' + jQuery(this).attr('id') +']'); + //labelled by aria doesn't seem to work on slider handle. Using title attr as backup + var labelText = (thisLabel.size()>0) ? 'Slider control for '+ thisLabel.text()+'' : ''; + var thisLabelId = thisLabel.attr('id') || thisLabel.attr('id', 'label_'+handleIds[i]).attr('id'); + + + if( options.tooltip == false ){hidett = ' style="display: none;"';} + jQuery(''+labelText+''+ + ''+ + ''+ + '') + .data('handleNum',i) + .appendTo(sliderComponent); + }); + + //CREATE SCALE AND TICS + + //write dl if there are optgroups + if(groups) { + var inc = 0; + var scale = sliderComponent.append('').find('.ui-slider-scale:eq(0)'); + jQuery(groups).each(function(h){ + scale.append('
'+this.label+'
');//class name becomes camelCased label + var groupOpts = this.options; + jQuery(this.options).each(function(i){ + var style = (inc == selectOptions.length-1 || inc == 0) ? 'style="display: none;"' : '' ; + var labelText = (options.labelSrc == 'text') ? groupOpts[i].text : groupOpts[i].value; + scale.append('
'+ labelText +'
'); + inc++; + }); + }); + } + //write ol + else { + var scale = sliderComponent.append('').find('.ui-slider-scale:eq(0)'); + jQuery(selectOptions).each(function(i){ + var style = (i == selectOptions.length-1 || i == 0) ? 'style="display: none;"' : '' ; + var labelText = (options.labelSrc == 'text') ? this.text : this.value; + scale.append('
  • '+ labelText +'
  • '); + }); + } + + function leftVal(i){ + return (i/(selectOptions.length-1) * 100).toFixed(2) +'%'; + + } + + + + + //show and hide labels depending on labels pref + //show the last one if there are more than 1 specified + if(options.labels > 1) sliderComponent.find('.ui-slider-scale li:last span.ui-slider-label, .ui-slider-scale dd:last span.ui-slider-label').addClass('ui-slider-label-show'); + + //set increment + var increm = Math.max(1, Math.round(selectOptions.length / options.labels)); + //show em based on inc + for(var j=0; j increm){//don't show if it's too close to the end label + sliderComponent.find('.ui-slider-scale li:eq('+ j +') span.ui-slider-label, .ui-slider-scale dd:eq('+ j +') span.ui-slider-label').addClass('ui-slider-label-show'); + } + } + + //style the dt's + sliderComponent.find('.ui-slider-scale dt').each(function(i){ + jQuery(this).css({ + 'left': ((100 /( groups.length))*i).toFixed(2) + '%' + }); + }); + + + //inject and return + sliderComponent + .insertAfter(jQuery(this).eq(this.length-1)) + .slider(options.sliderOptions) + .attr('role','application') + .find('.ui-slider-label') + .each(function(){ + jQuery(this).css('marginLeft', -jQuery(this).width()/2); + }); + + //update tooltip arrow inner color + sliderComponent.find('.ui-tooltip-pointer-down-inner').each(function(){ + var bWidth = jQuery('.ui-tooltip-pointer-down-inner').css('borderTopWidth'); + var bColor = jQuery(this).parents('.ui-slider-tooltip').css('backgroundColor') + jQuery(this).css('border-top', bWidth+' solid '+bColor); + }); + + var values = sliderComponent.slider('values'); + + if(isArray(values)){ + jQuery(values).each(function(i){ + sliderComponent.find('.ui-slider-tooltip .ttContent').eq(i).text( ttText(this) ); + }); + } + else { + sliderComponent.find('.ui-slider-tooltip .ttContent').eq(0).text( ttText(values) ); + } + + return this; +} + + diff --git a/lib/assets/javascripts/surveyor/jquery.surveyor.js b/lib/assets/javascripts/surveyor/jquery.surveyor.js index 71c19e72..fab074c9 100644 --- a/lib/assets/javascripts/surveyor/jquery.surveyor.js +++ b/lib/assets/javascripts/surveyor/jquery.surveyor.js @@ -90,7 +90,10 @@ jQuery(document).ready(function(){ } }); }); - + + // http://www.filamentgroup.com/lab/update_jquery_ui_slider_from_a_select_element_now_with_aria_support/ + $('fieldset.q_slider select').each(function(i,e){ $(e).selectToUISlider({"labelSrc": "text"}).hide() }); + // If javascript works, we don't need to show dependents from previous sections at the top of the page. jQuery("#dependents").remove(); diff --git a/lib/assets/stylesheets/surveyor.sass b/lib/assets/stylesheets/surveyor.sass index 59b3be67..dc33212e 100644 --- a/lib/assets/stylesheets/surveyor.sass +++ b/lib/assets/stylesheets/surveyor.sass @@ -112,6 +112,14 @@ body fieldset.q_dropdown, fieldset.q_inline_dropdown, fieldset.q_default_dropdown, fieldset.q_slider, fieldset.q_repeater_dropdown label :display none + fieldset.q_slider + :margin-bottom 3em + li.select + :width 50% + :font-size .8em + .ui-slider span.ui-slider-label-show + :position relative + :display inline // buttons input[type="submit"] diff --git a/lib/assets/stylesheets/surveyor/ui.slider.extras.css b/lib/assets/stylesheets/surveyor/ui.slider.extras.css new file mode 100755 index 00000000..862388b6 --- /dev/null +++ b/lib/assets/stylesheets/surveyor/ui.slider.extras.css @@ -0,0 +1,110 @@ +/*NEW SLIDER STYLES FOR SCALE, ETC*/ +/* slider widget */ +.ui-slider { + text-decoration: none !important; +} +.ui-slider .ui-slider-handle { + overflow: visible !important; +} +.ui-slider .ui-slider-tooltip { + display: none; +} +.ui-slider .screenReaderContext { + position: absolute; + width: 0; + height: 0; + overflow: hidden; + left: -999999999px; +} +.ui-slider .ui-state-active .ui-slider-tooltip, .ui-slider .ui-state-focus .ui-slider-tooltip, .ui-slider .ui-state-hover .ui-slider-tooltip { + display: block; + position: absolute; + bottom: 2.5em; + text-align: center; + padding: .3em .2em .4em; + font-size: .9em; + width: 8em; + margin-left: -3.7em; +} +.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down, .ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down-inner { + position: absolute; + display: block; + width:0; + height:0; + border-bottom-width: 0; + background: none; +} +.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down { + border-left: 7px dashed transparent; + border-right: 7px dashed transparent; + border-top-width: 8px; + bottom: -8px; + right: auto; + left: 50%; + margin-left: -7px; +} +.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down-inner { + border-left: 6px dashed transparent; + border-right: 6px dashed transparent; + border-top: 7px solid #fff; + bottom: auto; + top: -9px; + left: -6px; +} +.ui-slider a { + text-decoration: none; +} +.ui-slider ol, .ui-slider li, .ui-slider dl, .ui-slider dd, .ui-slider dt { + list-style: none; + margin: 0; + padding: 0; +} +.ui-slider ol, .ui-slider dl { + position: relative; + top: 1.3em; + width: 100%; +} +.ui-slider dt { + top: 1.5em; + position: absolute; + padding-top: .2em; + text-align: center; + border-bottom: 1px dotted #ddd; + height: .7em; + color: #999; +} +.ui-slider dt span { + background: #fff; + padding: 0 .5em; +} +.ui-slider li, .ui-slider dd { + position: absolute; + overflow: visible; + color: #666; +} +.ui-slider span.ui-slider-label { + position: absolute; +} +.ui-slider li span.ui-slider-label, .ui-slider dd span.ui-slider-label { + display: none; +} +.ui-slider li span.ui-slider-label-show, .ui-slider dd span.ui-slider-label-show { + display: block; +} +.ui-slider span.ui-slider-tic { + position: absolute; + left: 0; + height: .8em; + top: -1.3em; +} +.ui-slider li span.ui-widget-content, .ui-slider dd span.ui-widget-content { + border-right: 0; + border-left-width: 1px; + border-left-style: solid; + border-top: 0; + border-bottom: 0; +} +.ui-slider .first .ui-slider-tic, .ui-slider .last .ui-slider-tic { + display: none; +} +