<html ng-app="multiSliderDemo">
<head>
  <meta charset="UTF-8">
  <title>Multi Slider</title>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
  <link rel="stylesheet" href="multislider.css">
</head>
<body>
<div ng-controller="DemoCtrl" class="container">
  <h1>Angular Multiple Slider Directive</h1>
  <p>Pure Angular slider control with no JQuery Dependencies. This demo utilizes
    <a href="https://angular-ui.github.io/bootstrap/">UI Bootstrap</a> version 0.14.3 which also requires ngAnimate.
    These are not required for the angular-multi-slider, just for this demo.</p>
  Developer Page - <a href="http://keithfimreite.com/angular-multi-slider-directive.aspx" target="_blank">Demo Site</a>
  <article>
    <h2>Single Slider - Hover / Grab Bubble</h2>
    <fieldset class="row">
      <section class="col-sm-4">
        <pre>
          {{ slider | json}}
        </pre>
      </section>
      <section class="col-sm-6 padding-10">
        <div multi-slider
             floor="0"
             ceiling="500"
             step="10"
             precision="2"
             bubbles="false"
             ng-model="slider"></div>
      </section>
    </fieldset>
  </article>
  <hr/>
  <article>
    <h2>Adding Sliders After Creation</h2>
    <fieldset class="row">
      <section class="col-sm-4">
        <pre>
          {{ addSlider | json}}
        </pre>
      </section>
      <section class="col-sm-6 padding-10" style="padding-top: 80px;">
        <div multi-slider
             floor="0"
             ceiling="500"
             step="10"
             precision="2"
             bubbles="true"
             ng-model="addSlider"></div>
        <button class="btn btn-info" ng-click="addAnother()">Add another Slider</button>
      </section>
    </fieldset>
  </article>
  <hr/>
  <article>
    <h2>Multiple Sliders with defined Colors in a Form - Persistent Bubbles - Hidden</h2>
    <form name="sliderForm" id="sliderForm" novalidate autocomplete="off">
      <fieldset class="row">
        <section class="col-sm-4">
        <pre>
          {{ sliders | json}}
        </pre>
        </section>
        <section class="col-sm-6 padding-10">
          <multi-slider name="mySlider"
                        floor="0"
                        step="10"
                        precision="2"
                        ceiling="500"
                        bubbles="true"
                        ng-model="sliders"
                        ng-hide="hideSliders"
                        style="margin-top:60px">
          </multi-slider>
          <multi-slider-key ng-model="sliders" ng-hide="hideSliders"></multi-slider-key>
        </section>
        <section class="col-sm-2">
          <p>
            Form Dirty: {{ sliderForm.$dirty }}
            <br/>
            Form Pristine: {{ sliderForm.$pristine }}
            <br/>
            Slider Dirty: {{ sliderForm.mySlider.$dirty }}
            <br/>
            Slider Pristine: {{ sliderForm.mySlider.$pristine }}
            <br/>
            Slider Hidden: {{ hideSliders }}<br/>
            <label>Hide Slider:
              <input type="checkbox" ng-model="hideSliders">
            </label>
           <br/>

            *Slider Enabled / Hidden NOT Fully Working ->
            <button type="button" class="btn btn-lg" ng-click="fileToggleVisibility()" ng-bind="'Set File slider visible to ' + !sliders[1].visible"
                    ng-class="sliders[1].visible ? 'btn-primary' : 'btn-danger' "></button>
            <br/>
            <button type="button" class="btn btn-danger btn-lg" ng-click="fileToggleEnabled()" ng-bind="'Set File slider enabled to ' + !sliders[1].enabled"
                    ng-class=" sliders[1].enabled ? 'btn-primary' : 'btn-danger' "></button>
        </section>
      </fieldset>
    </form>
  </article>
  <hr/>
  <article>
    <h2>Multi-Slider in multiple UI Bootstrap Tabs</h2>
    <form name="tabSliderForm" id="tabSliderForm" novalidate autocomplete="off">
      <fieldset class="row">
        <uib-tabset>
          <uib-tab heading="Tab 1" active="activeTabs[0]">
            <div class="panel panel-default">
              <div class="panel-body">Tab 1 Content for angular multiple sliders<br/><br/>
                <multi-slider name="tabSlider1"
                              floor="0"
                              step="1"
                              precision="2"
                              ceiling="365"
                              bubbles="true"
                              ng-model="tabSliders"
                              ng-if="activeTabs[0]"
                              style="margin-top:80px">
                </multi-slider>
              </div>
            </div>
          </uib-tab>
          <uib-tab heading="Tab 2" active="activeTabs[1]">
            <div class="panel panel-default">
              <div class="panel-body">Tab 2 Content for angular multiple sliders<br/><br/>
                <section class="col-sm-6 padding-10">
                  <multi-slider name="tabSlider2"
                                floor="0"
                                step="1"
                                precision="2"
                                ceiling="365"
                                bubbles="true"
                                ng-model="tabSliders"
                                ng-if="activeTabs[1]"
                                style="margin-top:80px">
                  </multi-slider>
                  <multi-slider-key ng-model="tabSliders"></multi-slider-key>
                </section>
              </div>
            </div>
          </uib-tab>
        </uib-tabset>
      </fieldset>
    </form>
  </article>
  <hr/>
  <article>
    <h2>Sliders in a UI Bootstrap modal</h2>
    <button type="button" ng-click="openModal()" class="btn btn-default btn-lg">Open Modal!</button>
  </article>
  <article>
    <h2>Adjusting Floor / Ceiling end points of Slider after rendered (Maintain slider Order)</h2>
    <form name="ceilingForm" id="ceilingForm" novalidate autocomplete="off">
      <fieldset class="row">

        <section class="col-sm-4">
        <pre>
          {{ tabSliders | json}}
        </pre>
        </section>
        <section class="col-sm-6 padding-10">
          <multi-slider name="ceilingSlider"
                        floor="{{floor}}"
                        step="1"
                        precision="2"
                        ceiling="{{ceiling}}"
                        bubbles="true"
                        maintain-order="true"
                        ng-model="tabSliders"
                        style="margin-top:120px">
          </multi-slider>
          <multi-slider-key ng-model="ceilingSliders"></multi-slider-key>
          <section class="col-sm-12">
            <label class="btn btn-primary" ng-model="floor" uib-btn-radio="0">floor 0</label>
            <label class="btn btn-primary" ng-model="floor" uib-btn-radio="50">floor 50</label>
            <label class="btn btn-primary" ng-model="ceiling" uib-btn-radio="100">Ceiling 100</label>
            <label class="btn btn-primary" ng-model="ceiling" uib-btn-radio="365">Ceiling 365</label>
          </section>
        </section>
      </fieldset>
    </form>
  </article>
  <article>
    <h2>Date Filter Values and End Points of Multiple Slider</h2>
    <form name="ceilingForm" id="dateForm" novalidate autocomplete="off">
      <fieldset class="row">
        <section class="col-sm-4">
        <pre>
          {{ dateSliders | json}}
        </pre>
        </section>
        <section class="col-sm-6 padding-10">
          <multi-slider name="dateSlider"
                        floor="{{dateFloor}}"
                        step="86400000"
                        precision="2"
                        ceiling="{{dateCeiling}}"
                        bubbles="true"
                        display-filter="date : 'shortDate'"
                        maintain-order="false"
                        ng-model="dateSliders"
                        style="margin-top:200px">
          </multi-slider>
          <multi-slider-key ng-model="dateSliders" display-filter="date : 'shortDate'"></multi-slider-key>
          <section class="col-sm-12">
            <label class="btn btn-primary" ng-model="dateFloor" uib-btn-radio="getDateOffset(0)">floor 0</label>
            <label class="btn btn-primary" ng-model="dateFloor" uib-btn-radio="getDateOffset(50)">floor 50 days</label>
            <label class="btn btn-primary" ng-model="dateCeiling" uib-btn-radio="getDateOffset(100)">Ceiling 100 days</label>
            <label class="btn btn-primary" ng-model="dateCeiling" uib-btn-radio="getDateOffset(365)">Ceiling 1 year</label>
          </section>
        </section>
      </fieldset>
    </form>
  </article>

</div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular-animate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.3/ui-bootstrap-tpls.min.js"></script>
<script src="multislider.js"></script>
<script src="script.js"></script>
</body>
</html>
'use strict';

angular.module('multiSliderDemo', ['angularMultiSlider', 'ngAnimate', 'ui.bootstrap']);

angular.module('multiSliderDemo')
  .controller('DemoCtrl', function ($rootScope, $scope, $uibModal) {
    $scope.hideSliders = true;
    $scope.fileToggleVisibility = function() {
      $scope.sliders[1].visible = !$scope.sliders[1].visible;
    };

    $scope.fileToggleEnabled = function() {
      $scope.sliders[1].enabled = !$scope.sliders[1].enabled;
    };

    $scope.addSlider = [{
      value: 50,
      title: 'this is slider (1) - '
    }];

    var anotherSlider = {
      value: 300,
      title: "this is slider (2) - "
    };

    //push on slider array will work prior to first bind
    $scope.addSlider.push(anotherSlider);


    $scope.addAnother = function() {
      var updatedSlider = angular.copy($scope.addSlider);

      var another = {
        value: $scope.addSlider.length * 50,
        title: "This is slider (" + ($scope.addSlider.length + 1) + ") - "
      };

      updatedSlider.push(another);

      //pushing additional sliders requires angular copy as it will cause a render in the directive a push will not
      $scope.addSlider = angular.copy(updatedSlider);
      console.log("added another!");
    };

    //Needed for uib-tabs only
    $scope.activeTabs = [true, false];

    $scope.slider = [{value: 200}, {value: 150}];

    $scope.sliders = [
      {title: 'Weight: ', value: 100, color: 'red'},
      {title: 'File: ', value: 240, color: '#00FF00', visible: true, enabled: false},
      {title: 'Test: ', value: 250, color: 'blue', visible: false, enabled: false},
      {title: 'Folder: ', value: 10, color: '#ccc'}
    ];

    $scope.modalSliders = [
      {title: 'Weight: ', value: 1},
      {title: 'File: ', value: 1.2}
    ];

    // ceiling / floor is for demo for binding to slider with adjustable floor / ceiling
    $scope.ceiling = 365;
    $scope.floor = 0;

    $scope.tabSliders = [
      {value: 10,  title: 'Brainstorming: ', component: 'Proposal Making'},
      {value: 40,  title: 'Working groups formation: ', component: 'Proposal Making'},
      {value: 100, title: 'Proposal drafting: ', component: 'Proposal Making'},
      {value: 130, title: 'Proposal editing: ', component: 'Versioning'},
      {value: 160, title: 'Proposal selection: ', component: 'Versioning'},
      {value: 200, title: 'Discussion of proposals: ', component: 'Deliberation'},
      {value: 250, title: 'Technical assessment: ', component: 'Deliberation'},
      {value: 300, title: 'Voting on proposals: ', component: 'Voting'}
    ];

    // date conversions for filter
    $scope.getDateOffset = function(daysFromNow) {
      var today = new Date();
      return today.valueOf() + (daysFromNow * 86400000);
    };

    $scope.dateFloor = $scope.getDateOffset(0);
    $scope.dateCeiling = $scope.getDateOffset(365);

    $scope.dateSliders = angular.copy($scope.tabSliders);

    //Copy tabSlider, use value but convert to javascript date primitive
    //*Note: The primitive value is returned as the number of millisecond since midnight January 1, 1970 UTC.
    angular.forEach($scope.dateSliders,function(slider){
      slider.value = $scope.getDateOffset(slider.value);
    });

    $scope.openModal = function() {
      var modalDlg = $uibModal.open({
        templateUrl: 'sliderModal.html',
        controller: function ($scope, $uibModalInstance, sliders) {
          //Copy of the object in order to keep original values in $scope.percentages in parent controller.
          $scope.mSliders = angular.copy(sliders);

          $scope.ok = function () {
            $uibModalInstance.close($scope.mSliders);
          };

          $scope.cancel = function () {
            $uibModalInstance.dismiss('Canceled');
          };
        },
        resolve: {
          sliders: function () {
            return $scope.modalSliders;
          }
        }
      });

      modalDlg.result.then(function (updatedSliders) {
        $scope.modalSliders = updatedSliders;
      });
    };
  });
angular-multi-slider Version 0.1.5
===================

AngularJS multi slider component with multiple sliders and thumbs support. Easily bind to 'value' and 'title' json objects. 
CSS style is very simple easily customize your slider handles and bubbles. No JQuery dependency required. The only dependency is Angular.
I have added a key / legend and collision detection. The collision detection / bubble adjustment has a delay since the bubbles 
have an animation to them. Collision detection and bubble adjustment is not perfect but very good. If you have some ideas on improving
this directive feel free to let me know.

## Show Some Love
If you utilize this directive please give it a Star. It will motivate me to add features and continually maintain it. 

*[Derived from angular-rangeslider](https://github.com/supertorio/angular-rangeslider-directive) 

### Installing via Bower
```
bower install angular-multi-slider
```
	
Include both multislider.js and multislider.css, then add `angularMultiSlider` to your `angular.module` dependencies.

## Examples

* [Here is a plunker you can fork](http://plnkr.co/edit/uTrlSK4R0iEhmg3mF2Cv?p=preview)
* [Demo site](http://keithfimreite.com/angular-multi-slider-directive.aspx)

## Preview

![Angular Multiple Sliders](http://keithfimreite.com/BlogFiles/keithfimreite/angular/multislider/angular-multiple-sliders.png)

### Usage

1. Add the `angularMultiSlider` dependency to your Angular project. example:
	* `angular.module('myApp', ['angularMultiSlider'])`	
2. Create a 'multi-slider' directive in your view and give it a model, where `ng-model` is a variable on `$scope`.
```html
    <multi-slider
      floor="0"
      step="10"
      precision="2"
      ceiling="500"
      bubbles="true"
      ng-model="sliders">
    </multi-slider>
```
Controller scope for sliders:
```js
    $scope.sliders = [
      {title:'User 1: ', value:100, color: 'Red'},
      {title:'User 2: ', value:200, color: '#00FF00' },
      {title:'User 3: ', value:450, }
    ];
```

## Multi-Slider Directive Properties

* __floor__ `{number}` Minimum Value for Slider
* __ceiling__ `{number}` Maximum Value for Slider
* __step__ `{number}` Value between steps in snapping on the scale. Examples (100, 10, 1, .1}
* __precision__ `{number / integer}` The precision to which round each step is rounded to. Default: 2
* __display-filter__ `{$filter}` Optional angular filter expression.
* __ng-model__ `{object}` Bound values for sliders, requires 'value' for slider and 'title' for bubble
* __bubbles__ `{string}` true or false for showing the persistent bubbles or false for just on hover
* __maintain-order__ `{string}` true or false for maintaining the order of the sliders. To function properly you your slider array is required to be sorted by value

## NgModel Properties

* __title__ `{string}` Optional Title for the bubbles that popup during grab or persistently set
* __value__ `{number}` Required - Value for the slider handle. This value should be between floor and ceiling inclusive
* __color__ `{HEX, RGB, or HTML color}` Optional color for the handle. Basically and valid CSS color: This can be a HEX color, RGB Color, or HTML color 

## CSS Style Properties Tips

* __handle__ `.angular-multi-slider div.handle` - Override the *background-color* in CSS for all the handles or use the NgModel *color* property  
* __bar height__ `.angular-multi-slider` - Override the *height* to set the thickness of the slider line bar
* __bar color__ `.angular-multi-slider div.bar` - Override the *border-radius* and *background* to set the color of the slider line bar
* __bubble__ `.angular-multi-slider div.bubble.active` - Override the bubble *background-color*, *color*, *font-size*, etc. for the bubbles
* __bar color__ `.angular-multi-slider div.bar` - Override the *border-radius* and *background* to set the color of the slider line bar
* __limit__ `.angular-multi-slider div.limit` - Override the limits *color* and *margin-top*
* __limit floor__ `.angular-multi-slider div.limit .floor` - Override the limits *color* and *margin-top* for the floor only
* __limit ceiling__ `.angular-multi-slider div.limit .ceiling` - Override the limits *color* and *margin-top* for the ceiling only

## Multi-Slide-Key Directive

This optional directive will create a key for the slider. Similar to __multi-slider__ bind to the same 'ng-model' and use CSS to customize.

## Todo

* ~~Set Dirty / Pristine~~
* ~~Color option within json object for each slider~~
* ~~Handle overlapping tooltips by checking handle proximity~~
* ~~A key directive~~
* ~~Bind / Watch to Ceiling & Floor for adjusting endpoints~~
* ~~Add filter to directive property for possible dates / angular filter~~
* ~~Added overlap prevention for sequential sliders [__no-overlap__ branch](https://github.com/enkodellc/angular-multi-slider/tree/no-overlap)~~
* ~~ng-Hide~~
* ~~Maintain Order of sliders so they do not overlap each other~~
* ~~Adding new Sliders after initial directive render~~
* Set individual handles Visible / Enabled
* Minify src -> dist folders
* Tests

## Submitting an issue

Please be responsible, the open source community is not there to guess your problem or to do your job. When submitting an issue try as much as possible to:

1. Search in the already existing issues. if your issue has not been raised before.

2. Give a precise description including angular version, angular-multi-slider version.

3. Give a way to reproduce your issue, the best would be with a <strong>running example</strong>, you can use [plunkr](http://plnkr.co/), or [codepen](http://codepen.io/). 
**Tip:** [Here is a plunker you can fork](http://plnkr.co/edit/uTrlSK4R0iEhmg3mF2Cv?p=preview)

4. Isolate your code sample on the probable issue to avoid pollution and noise.

5. Close your issue when a solution has been found (and share it with the community)

Note that 80% of the open issues are actually not issues but "problem" due to developers laziness or lack of investigation. These "issues" are a waste of time for us and especially if we have to setup a sample to reproduce the issue which those developers could have done. Any open issue which does not fulfill this contract will be closed without investigation.

## License

Code licensed under New BSD License.
.angular-multi-slider {
    display: inline-block;
    position: relative;
    height: 5px;
    width: 100%;
    margin: 25px 5px 25px 5px;
    vertical-align: middle;
}
.angular-multi-slider div {
    white-space: nowrap;
    position: absolute;
}
.angular-multi-slider div.bar {
    width: 100%;
    height: 100%;
    border-radius: 6px;
    background: #999;
    overflow: hidden;
}
.angular-multi-slider div.handle {
    cursor: pointer;
    width: 10px;
    height: 30px;
    top: -15px;
    background-color: #13b6ff; /*can override with color in slider object*/
    border: 1px solid #000;
    z-index: 2;
    border-radius: 4px;
    -o-transition: .3s;
    -ms-transition: .3s;
    -moz-transition: .3s;
    -webkit-transition: .3s;
    -webkit-transition-property: background-color;
    transition-property: background-color;
}
.angular-multi-slider div.handle:hover,
.angular-multi-slider div.handle:focus,
.angular-multi-slider div.handle:active,
.angular-multi-slider div.handle.active {
    -webkit-filter: brightness(70%);
    filter: brightness(70%);
}
.angular-multi-slider div.handle:hover + .bubble,
.angular-multi-slider div.handle:focus + .bubble,
.angular-multi-slider div.handle.grab + .bubble,
.angular-multi-slider div.handle:hover,
.angular-multi-slider div.handle:focus,
.angular-multi-slider div.handle.grab {
    -webkit-transform: scale(1.1);
    transform: scale(1.1);
    z-index: 99;
}
.angular-multi-slider div.handle.grab + .bubble,
.angular-multi-slider div.handle.grab{
    background-color: rgba(0,0,0,1);
}
.angular-multi-slider div.bubble {
    display: none;
    cursor: default;
    top: -36px;
    padding: 1px 3px 1px 3px;
    font-size: 0.7em;
    font-family: sans-serif;
    -o-transition: .1s;
    -ms-transition: .1s;
    -moz-transition: .1s;
    -webkit-transition: .1s;
    -webkit-transition-property: top;
    transition-property: top;
    z-index: 100;
}
.angular-multi-slider-key .key span,
.angular-multi-slider div.bubble.active {
    display: inline-block;
    color: #fff;
    font-size:12px;
    font-family: 'Arial', sans-serif;
    text-align: center;
    background-color: rgba(0,0,0,0.75);
    border-radius: 8px;
    padding: 3px 8px;
}
.angular-multi-slider div.limit {
    margin-top: 12px;
    color: #000;
    font-weight: bold;
}
.angular-multi-slider-key .key span {
    border: 1px solid #000;
}
.angular-multi-slider div.handle.disabled,
.angular-multi-slider div.handle.disabled:hover + .bubble{
    -webkit-filter: brightness(100%);
    filter: brightness(100%);
    cursor: not-allowed;
    -webkit-transform: scale(1.0) !important;
    transform: scale(1.0);
    z-index: 2;
}
'use strict';

angular.module('angularMultiSlider', [])
  .directive('multiSliderKey', function($compile) {
    return {
      restrict: 'EA',
      transclude: true,
      scope: {
        displayFilter: '@',
        sliders : '=ngModel'
      },
      link: function(scope, element) {
        var sliderStr = '';
        if (scope.displayFilter === undefined) scope.displayFilter = '';
        var filterExpression = scope.displayFilter ===  '' ? '' : ' | ' + scope.displayFilter;

        angular.forEach(scope.sliders, function(slider, key){
          var colorKey = slider.color ? '<span style="background-color:' + slider.color + ';"></span>' : '';
          sliderStr += '<div class="key">' + colorKey + '{{ sliders[' + key.toString() + '].title }} <strong>{{ sliders[' + key.toString() + '].value ' + filterExpression + '}}</strong></div>';
        });

        var sliderControls = angular.element('<div class="angular-multi-slider-key">' + sliderStr + '</div>');
        element.append(sliderControls);
        $compile(sliderControls)(scope);
      }
    }
  })
  .directive('multiSlider', function($compile, $filter) {
    var events = {
      mouse: {
        start: 'mousedown',
        move: 'mousemove',
        end: 'mouseup'
      },
      touch: {
        start: 'touchstart',
        move: 'touchmove',
        end: 'touchend'
      }
    };

    function roundStep(value, precision, step, floor) {
      var remainder = (value - floor) % step;
      var steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;
      var decimals = Math.pow(10, precision);
      var roundedValue = steppedValue * decimals / decimals;
      return parseFloat(roundedValue.toFixed(precision));
    }

    function offset(element, position) {
      return element.css({
        left: position
      });
    }

    function pixelize(position) {
      return parseInt(position) + 'px';
    }

    function contain(value) {
      if (isNaN(value)) return value;
      return Math.min(Math.max(0, value), 100);
    }

    function overlaps(b1, b2, offsetTop) {
      function comparePositions(p1, p2) {
        var r1 = p1[0] < p2[0] ? p1 : p2;
        var r2 = p1[0] < p2[0] ? p2 : p1;
        return r1[1] > r2[0] || r1[0] === r2[0];
      }

      var posB1 = [[ b1.offsetLeft, b1.offsetLeft + b1.offsetWidth ], [ offsetTop, offsetTop -  b1.scrollTop + b1.offsetHeight ]],
        posB2 = [[ b2.offsetLeft, b2.offsetLeft + b2.offsetWidth ], [ b2.offsetTop, b2.offsetTop -  b2.scrollTop + b2.offsetHeight ]];

      return comparePositions( posB1[0], posB2[0] ) && comparePositions( posB1[1], posB2[1] );
    }

    return {
      restrict: 'EA',
      require: '?ngModel',
      scope: {
        floor: '@',
        ceiling: '@',
        step: '@',
        precision: '@',
        bubbles: '@',
        displayFilter: '@',
        maintainOrder: '@',
        sliders: '=ngModel',
        ngHide: '=?'
      },
      template : '',

      link: function(scope, element, attrs, ngModel) {
        if (!ngModel) return; // do nothing if no ng-model

        //base copy to see if sliders returned to original
        var original = [];

        ngModel.$render = function() {
          if (original.length > 0 && (original.length != scope.sliders.length)) {
            angular.element(element).html('');
            buildSliderStr();
            bindingsSet = true;
            updateDOM();
          }

          original = angular.copy(scope.sliders);
        };

        element.addClass('angular-multi-slider');

        // DOM Components
        if (scope.displayFilter === undefined) scope.displayFilter = '';
        var filterExpression = scope.displayFilter ===  '' ? '' : ' | ' + scope.displayFilter;

        var sliderStr = '';
        var bar, ngDocument, ceilBubble,
          bubbles = [],
          handles = [];

        var buildSliderStr = function() {
          sliderStr = '<div class="bar"></div><div class="limit floor">{{ floor ' + filterExpression + ' }}</div>' +
            '<div class="limit ceiling">{{ ceiling ' + filterExpression + '}}</div>';

          angular.forEach(scope.sliders, function(slider, key){
            sliderStr += '<div class="handle"></div><div class="bubble">{{ sliders[' + key.toString() + '].title }}{{ sliders[' + key.toString() + '].value ' + filterExpression + ' }}</div>';
          });

          var sliderControls = angular.element(sliderStr);
          element.append(sliderControls);
          $compile(sliderControls)(scope);

          var children = element.children();
          bar          = angular.element(children[0]);
          ngDocument   = angular.element(document);
          ceilBubble   = angular.element(children[2]);
          bubbles = [];
          handles = [];

          angular.forEach(scope.sliders, function(slider, key) {
            handles.push(angular.element(children[(key * 2) + 3]));
            bubbles.push(angular.element(children[(key * 2) + 4]));
          });
        };

        buildSliderStr();

        // Control Dimensions Used for Calculations
        var handleHalfWidth = 0,
          barWidth = 0,
          minOffset = 0,
          maxOffset = 0,
          minValue = 0,
          maxValue = 0,
          valueRange = 0,
          offsetRange = 0,
          bubbleTop = undefined,
          bubbleHeight = undefined,
          handleTop = undefined,
          handleHeight = undefined;

        if (scope.step === undefined) scope.step = 10;
        if (scope.floor === undefined) scope.floor = 0;
        if (scope.ceiling === undefined) scope.ceiling = 500;
        if (scope.precision === undefined) scope.precision = 2;
        if (scope.bubbles === undefined) scope.bubbles = false;
        if (scope.maintainOrder === undefined) scope.maintainOrder = false;

        var bindingsSet = false;

        var updateCalculations = function() {
          scope.floor = roundStep(parseFloat(scope.floor), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor));
          scope.ceiling = roundStep(parseFloat(scope.ceiling), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor));

          angular.forEach(scope.sliders, function(slider) {
            slider.value = roundStep(parseFloat(slider.value), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor));
          });

          handleHalfWidth = handles[0][0].offsetWidth / 2;
          barWidth = bar[0].offsetWidth;
          minOffset = 0;
          maxOffset = barWidth - handles[0][0].offsetWidth;
          minValue = parseFloat(scope.floor);
          maxValue = parseFloat(scope.ceiling);
          valueRange = maxValue - minValue;
          offsetRange = maxOffset - minOffset;
        };

        var updateDOM = function () {
          if(angular.isDefined(attrs.ngHide) && scope.ngHide == true) {
            return;
          }

          updateCalculations();

          var percentOffset = function (offset) {
            return contain(((offset - minOffset) / offsetRange) * 100);
          };

          var percentValue = function (value) {
            return contain(((value - minValue) / valueRange) * 100);
          };

          var pixelsToOffset = function (percent) {
            return pixelize(percent * offsetRange / 100);
          };

          var setHandles = function () {
            offset(ceilBubble, pixelize(barWidth - ceilBubble[0].offsetWidth));
            angular.forEach(scope.sliders, function(slider,key){
              if (slider.color) {
                handles[key].css({'background-color': slider.color});
              }

              if (slider.value >= minValue && slider.value <= maxValue) {
                offset(handles[key], pixelsToOffset(percentValue(slider.value)));
                offset(bubbles[key], pixelize(handles[key][0].offsetLeft - (bubbles[key][0].offsetWidth / 2) + handleHalfWidth));
                handles[key].css({'display': 'block'});
                if ('' + scope.bubbles === 'true') {
                  bubbles[key].css({'display': 'block'});
                }
              } else {
                handles[key].css({'display': 'none'});
                bubbles[key].css({'display': 'none'});
              }

              if (slider.hasOwnProperty("visible") && slider.visible === false) {
                handles[key].css({'display': 'none'});
                bubbles[key].css({'display': 'none'});
              }

              if (slider.hasOwnProperty("enabled") && slider.enabled === false) {
                handles[key].addClass('disabled');
                bubbles[key].addClass('disabled');
              } else {
                handles[key].removeClass('disabled');
                bubbles[key].removeClass('disabled');
              }
            });
          };

          var resetBubbles = function() {
            if (scope.sliders.length > 1) {
              //timeout must be longer than css animation for proper bubble collision detection
              for (var i = 0; i < scope.sliders.length; i++) {
                (function (index) {
                  setTimeout(function () {
                    overlapCheck(index);
                  }, i * 150);
                })(i);
              }
            }
          };

          var overlapCheck = function(currentRef) {
            var safeAtLevel = function(cRef, level) {
              for (var x = 0; x < scope.sliders.length; x++) {
                if (x != cRef && overlaps(bubbles[cRef][0], bubbles[x][0], (bubbleTop * level))) {
                  return safeAtLevel(cRef, level + 1);
                }
              }
              return level;
            };

            if (scope.sliders.length > 1 && scope.bubbles === "true") {
              var safeLevel = safeAtLevel(currentRef, 1) - 1;
              handles[currentRef].css({top: pixelize((-1 * (safeLevel * bubbleHeight)) + handleTop), height: pixelize(handleHeight + (bubbleHeight * safeLevel)), 'z-index':  99-safeLevel});
              bubbles[currentRef].css({top: pixelize(bubbleTop - (bubbleHeight * safeLevel))});
            }
          };

          var bind = function (handle, bubble, currentRef, events) {
            var onEnd = function () {
              handle.removeClass('grab');
              bubble.removeClass('grab');
              if (!(''+scope.bubbles === 'true')) {
                bubble.removeClass('active');
              }

              ngDocument.unbind(events.move);
              ngDocument.unbind(events.end);

              if (angular.equals(scope.sliders, original)) {
                ngModel.$setPristine();
              }

              //Move possible elevated bubbles back down if one below it moved.
              resetBubbles();
              scope.$apply();
            };

            var onMove = function (event) {
              // Suss out which event type we are capturing and get the x value
              var eventX = 0;
              if (event.clientX !== undefined) {
                eventX = event.clientX;
              }
              else if ( event.touches !== undefined && event.touches.length) {
                eventX = event.touches[0].clientX;
              }
              else if ( event.originalEvent !== undefined &&
                event.originalEvent.changedTouches !== undefined &&
                event.originalEvent.changedTouches.length) {
                eventX = event.originalEvent.changedTouches[0].clientX;
              }

              var newOffset = Math.max(Math.min((eventX - element[0].getBoundingClientRect().left - handleHalfWidth), maxOffset), minOffset),
                newPercent = percentOffset(newOffset),
                newValue = minValue + (valueRange * newPercent / 100.0);

              if (scope.maintainOrder === 'true') {
                if ((currentRef === 0) && (newValue >= scope.sliders[currentRef + 1].value)) {
                  newValue = scope.sliders[currentRef + 1].value;
                } else if ((currentRef + 1 === scope.sliders.length) && (newValue <= scope.sliders[currentRef-1].value)) {
                  newValue = scope.sliders[currentRef - 1].value;
                } else if ((currentRef > 0) && (currentRef + 1 < scope.sliders.length)) {
                  if (newValue <= scope.sliders[currentRef - 1].value){
                    newValue = scope.sliders[currentRef - 1].value;
                  }
                  if (newValue >= scope.sliders[currentRef + 1].value) {
                    newValue = scope.sliders[currentRef + 1].value;
                  }
                }
              }

              newValue = roundStep(newValue, parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor));
              scope.sliders[currentRef].value = newValue;

              setHandles();
              overlapCheck(currentRef);

              ngModel.$setDirty();
              scope.$apply();
            };

            var onStart = function (event) {
              if (scope.sliders[currentRef].hasOwnProperty("enabled") && scope.sliders[currentRef].enabled === false) {
                bubble.addClass('disabled');
                handle.addClass('disabled');
                return;
              }
              updateCalculations();
              bubble.addClass('active grab');
              handle.addClass('active grab');
              setHandles();
              event.stopPropagation();
              event.preventDefault();
              ngDocument.bind(events.move, onMove);
              return ngDocument.bind(events.end, onEnd);
            };

            handle.bind(events.start, onStart);
          };

          var setBindings = function () {
            var method, i;
            var inputTypes = ['touch', 'mouse'];
            for (i = 0; i < inputTypes.length; i++) {
              method = inputTypes[i];
              angular.forEach(scope.sliders, function(slider, key){
                bind(handles[key], bubbles[key], key, events[method]);
                if (scope.sliders[key].hasOwnProperty("enabled") && scope.sliders[key].enabled === false) {
                  handles[key].addClass('disabled');
                  bubbles[key].addClass('disabled');
                }
              });
            }

            bindingsSet = true;
          };

          if (!bindingsSet) {
            setBindings();

            // Timeout needed because bubbles offsetWidth is incorrect during initial rendering of html elements
            setTimeout( function() {
              if ('' + scope.bubbles === 'true') {
                angular.forEach(bubbles, function (bubble) {
                  bubble.addClass('active');
                });
              }
              updateCalculations();
              setHandles();

              //Get Default sizes of bubbles and handles, assuming each are equal, calculated from css
              handleTop = handleTop === undefined ? handles[0][0].offsetTop : handleTop;
              handleHeight = handleHeight === undefined ? handles[0][0].offsetHeight : handleHeight;
              bubbleTop = bubbleTop === undefined ? bubbles[0][0].offsetTop : bubbleTop;
              bubbleHeight = bubbleHeight === undefined ? bubbles[0][0].offsetHeight + 7 : bubbleHeight ; //add 7px bottom margin to the bubble offset for handle

              resetBubbles();
            }, 10);
          }
        };

        // Watch Models based on mode
        scope.$watch('sliders', function () {
          bindingsSet = false;
          updateDOM();
        });

        scope.$watch('ceiling', function () {
          bindingsSet = false;
          updateDOM();
        });

        scope.$watch('floor', function () {
          bindingsSet = false;
          updateDOM();
        });

        // Watch if ng-Hide is utilized
        if (angular.isDefined(attrs.ngHide)) {
          scope.$watch('ngHide', function () {
            bindingsSet = false;
            updateDOM();
          });
        }
        // Update on Window resize
        window.addEventListener('resize', updateDOM);
      }
    }
  });
<div class="modal-body">
  <div class="container-fluid">
    <section class="row">
      <article class="col-md-12 col-lg-12">
        <h2>Sliders in a UI Bootstrap Modal</h2>
        <multi-slider name="myModalSlider" floor="0" step=".1" precision="2" ceiling="5" bubbles="true" ng-model="mSliders" style="margin-top:60px">
        </multi-slider>  
      </article>
    </section>
    <div class="row">
      <div class="col-lg-12">
        <button type="button" ng-click="ok()" class="btn btn-primary">Save</button>
        <button type="button" ng-click="cancel()" class="btn btn-default">Cancel</button>
      </div>
    </div>
  </div>
</div>