Foundation 5 sliders in dropdowns using EmberJS

EmberJS and Foundation 5 work great together! However, I ran across an issue using sliders within dropdowns. As the content in a dropdown is added to the DOM off-screen, the sliders were setting their initial values using those off-screen coordinates. As this project is using Ember, here's how I fixed things up!

The template

Dropdowns in Foundation are easy. So are range-sliders. Here's the template you would use to embed a range-slider in a dropdown:

{{!-- templates/toolbar.hbs --}}

<li><a class="button" data-options="align:right" data-dropdown="fiberWidth" title="Fiber Width"><i class="fi-target"></i></a></li>

<div id="fiberWidth" class="f-dropdown content" data-dropdown-content>  
  <h7>Fiber Width</h7>

  <div class="range-slider" data-options="start: 2; end: 80;" {{bind-attr data-slider="width"}}>
    <span class="range-slider-handle"></span>
    <span class="range-slider-active-segment"></span>
    {{input type="hidden" value=width}}
</div> {{!-- end dropdown content --}}  

Note that both the hidden input and the data-slider attributes are bound to the width property on the controller. This allows the initial value of the slider to be set, and allows Ember to update the property when the slider is changed (more on that a little further down).

The view

To correct the slider positioning, we need to have Ember apply some event actions after inserting the view into the DOM.

// views/toolbar.js

didInsertElement: function() {  
  // making sure the slider reflows after its dropdown is made visible the first time
  var dropdown = this.$('div .f-dropdown.content');

  // make all sliders reflow when their dropdowns are first opened
  dropdown.one('opened.fndtn.dropdown', function(e) {
    $(e.target).find('.range-slider').foundation('slider', 'reflow');

  // make sure the slider stays updated when a new record loads in
  // only necessary when the dropdown opens again
  dropdown.on('opened.fndtn.dropdown', function(e) {
    var target_slider = $(e.target).find('.range-slider');
    var new_value = target_slider.attr('data-slider');

    target_slider.foundation('slider', 'set_value', new_value);

  return this._super();

Once the view is inserted into the DOM, we select all of the dropdowns that contain range sliders (in this case, all of the content dropdowns). The first time (and only the first time, thanks to the .one function) any of these dropdowns open up, they target their range-sliders and tell them to reflow. This resets their coordinate structure to the proper on-screen coordinates.

The second function makes sure that any background changes to the width property (caused by loading in a new record from the store) will properly update the position and value of the slider.

Triggering the binding in Ember

One final issue involves the binding Ember maintains to the property. By default, Ember is looking for a user initiated change to the width value in order to trigger the binding. The slider is being programmatically changed. To fix this, we need one final function attached to the slider:

// views/toolbar.js

var slider = this.$('.range-slider');

// send new value when slider is done changing
slider.on('mouseup.fndtn.slider touchend.fndtn.slider pointerup.fndtn.slider', function(e) {  

We select all of the range-sliders and tell them to trigger the change event when the mouse or touch is released. This will trigger the binding in Ember, and update the property accordingly.

While sliders in dropdowns are probably a fringe case, I've opened up an issue with the Foundation team so they're aware it happens. You can track the status of that on GitHub.

Share Comment on Twitter