var app = angular.module('plunker', ['rzModule']);

app.controller('sliderController', function($scope) {

  $scope.init = function() {
    $scope.componentDetails = $scope.formatSliderMetadata($scope.validValues, $scope.translate,$scope.incrSpeed,$scope.decrSpeed)
  };

  $scope.formatSliderMetadata = function(validValues, translate,incrFun,decrFun) {
    var codeArray = [];
    var valueArray = [];
    for (var i = 0; i < validValues.length; i++) {
      codeArray[i] = validValues[i].code;
      valueArray[i] = validValues[i].decode;
    }

    // formatted slider attruibutes
    var formattedSliderValidValues = 
    {
        floor: codeArray[0],
        ceil: codeArray[codeArray.length-1],        
        value: 0,
        translate : translate,
        incrSpeed : incrFun,
        decrSpeed  : decrFun,
        codeArray : codeArray,
        stepArray : codeArray,
        valueArray : valueArray,
        selection : true,
        displayScale : true,
        typedValue: codeArray[0]
    };

    return formattedSliderValidValues;
  };
  
  $scope.$on('slideEnded', function () {
      console.log("slideEnded Event Fired : " +$scope.componentDetails.stepArray[$scope.componentDetails.value]);
  });
  
  /**
	 * function executed whenever the bandwidthCIR value is increased through associated text-field
	 * It takes slider to next available step 
	 */
	$scope.incrSpeed=function(){
 		for(var i=0; i< $scope.componentDetails.stepArray.length; i++){
 			if(parseInt($scope.componentDetails.stepArray[i]) > parseInt($scope.componentDetails.typedValue)){
 				$scope.componentDetails.typedValue = $scope.componentDetails.stepArray[i];
 				$scope.componentDetails.value = i;
 				break;
 			}
 		}
 	};
 	
	/**
	 * function executed whenever the bandwidthCIR value is decreased through associated text-field
	 * It takes slider to previous available step 
	 */
 	$scope.decrSpeed=function(){
 		for(var i=$scope.componentDetails.stepArray.length-1; i>=0; i--){
 			if(parseInt($scope.componentDetails.stepArray[i]) < parseInt($scope.componentDetails.typedValue)){ 			
 				$scope.componentDetails.typedValue = $scope.componentDetails.stepArray[i];
 				$scope.componentDetails.value = i;
 				break;
 			} 			
 		}
 	};

  /**
   * function executed whenever the bandwidthCIR slider value is changed
   */
  $scope.translate = function(value) {
    $scope.componentDetails.typedValue = $scope.componentDetails.stepArray[value];
    return $scope.componentDetails.valueArray[value];
  };

  $scope.validValues = [{
      "code": "2",
      "decode": "2 Mbps"
    }, {
      "code": "4",
      "decode": "4 Mbps"
    }, {
      "code": "5",
      "decode": "5 Mbps"
    }, {
      "code": "8",
      "decode": "8 Mbps"
    }, {
      "code": "10",
      "decode": "10 Mbps"
    }, {
      "code": "20",
      "decode": "20 Mbps"
    }, {
      "code": "50",
      "decode": "50 Mbps"
    }, {
      "code": "100",
      "decode": "100 Mbps"
    }, {
      "code": "150",
      "decode": "150 Mbps"
    }, {
      "code": "250",
      "decode": "250 Mbps"
    }, {
      "code": "500",
      "decode": "500 Mbps"
    }, {
      "code": "600",
      "decode": "600 Mbps"
    }, {
      "code": "1000",
      "decode": "1 Gbps"
  }];
});
<!DOCTYPE html>
<html ng-app="plunker">

<head>
  <meta charset="utf-8" />
  <title>AngularJS Plunker</title>
  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
  <link rel="stylesheet" href="style.css" />
  <script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.20/angular.js" data-semver="1.2.20"></script>
  <script src="rzslider.js"></script>
  <script src="app.js"></script>
</head>

<body>
  <div class="general-config" ng-controller="sliderController" ng-init="init()">
    <rzslider class="rzslider-evc-size"
				id="componentDetails.id" rz-slider-floor="componentDetails.floor"
				rz-slider-ceil="componentDetails.ceil"
				rz-slider-model="componentDetails.value"
				rz-slider-step-array="componentDetails.stepArray"
				rz-slider-translate="componentDetails.translate"
				rz-slider-selection="componentDetails.selection"
				rz-slider-display-scale="componentDetails.displayScale"> </rzslider>		
			<span class="sliderinputbox"> 
				<input num-only
					ng-keypress="onSliderEnter($event)" readonly="readonly"
					class="slidervaluebox disablepointer1" id="inputTrueBandwidth" min="0"
					max="{{componentDetails.stepArray[componentDetails.ceil]}}"
					ng-blur="validateSliderRange();" type="text"
					ng-model="componentDetails.typedValue"/> 
				<span class="slidervalueincrdecr">
					<span ng-click="componentDetails.incrSpeed()"
					ng-class="(componentDetails.typedValue!=componentDetails.stepArray[componentDetails.ceil]) ? 'sliderTextIncrementer' : 'sliderTextIncrementerDisabled'"></span>
					<span ng-click="componentDetails.decrSpeed()"
					ng-class="(componentDetails.typedValue!=componentDetails.stepArray[componentDetails.floor]) ? 'sliderTextDecrementer' : 'sliderTextDecrementerDisabled'"></span>
				</span>
			</span>
  </div>
</body>
</html>
/* Put your css in here */
/**
 * Angular JS slider directive
 *
 * (c) Rafal Zajac <rzajac@gmail.com>
 * http://github.com/rzajac/angularjs-slider
 *
 * Licensed under the MIT license
 */

rzslider {
  position: relative;
  display: inline-block;
  width: 80%;
  height: 2px;
  margin: 30px 0 15px 0;
  vertical-align: middle;
}

rzslider span {
  position: absolute;
  display: inline-block;
  white-space: nowrap;
}

rzslider span.base {
  width: 100%;
  height: 100%;
  padding: 0;
}

rzslider span.bar {
  z-index: 1;
  width: 100%;
  height: 100%;
  border: 1px solid #D7D7D7!important;
  border-radius: 4px;
  background: #D7D7D7!important;
}
.wrapper {
    width: 97%;
}
rzslider span.bar.selection {
  z-index: 1;
  width: 0;
  background: #1575A9!important;
  border: 1px solid #1575A9!important;
}

rzslider span.pointer {
  background: #0579B4;
  top: -10px;
  z-index: 2;
  width: 22px;
  height: 22px;
  cursor: pointer;
  -webkit-border-radius: 16px;
  -moz-border-radius: 16px;
  border-radius: 16px;
}
/*
rzslider span.pointer:after {
  position: absolute;
  top: 12px;
  left: 12px;
  padding: 5px;
  background: #71818e;
  -webkit-border-radius: 4px;
  -moz-border-radius: 4px;
  border-radius: 4px;
  content: '';
  background: #0579B4;
}*/

rzslider span.pointer:hover:after {
  background: #0579B4;
}



rzslider span.bubble {
  top: 18px;
  padding: 1px 3px 1px 3px;
  color: #808080;
  cursor: default;
  font-size: 12px;
}

rzslider span.bubble.selection {
  top: 15px;
}

rzslider span.bubble.limit {
  /*color: #808080;*/

}
.DisableSlider .EnableSliderToggle{ 
	background:#BBBBBB!important; 
	border: 1px solid #BBBBBB!important;	
}
.EnableSlider .EnableSliderToggle { 
	background:#1575A9; 
	border: 1px solid #1575A9;	
}
.DisableSlider .selection  {
   	background:#BBBBBB!important; 
	border: 1px solid #BBBBBB!important;
} 
.DisableSlider .Disabletooltip { 
	display:none;
}
.EnableSlider .Disabletooltip{ 
	display:block; 
}


.DisableSlider .DisableCOS { 
	display:none;
}
.EnableSlider .DisableCOS { 
	display:block; 
}

.disablepointer2 { pointer-events:none;}

.disablepointer3 { border-bottom: 8px solid grey; pointer-events:none; }

.disablepointer4 {border-top: 8px solid grey; }

.scaleWrap ul {
  overflow: hidden;
	float: left;
	margin-left: -31px;
	margin-top: 12px;
	width: 97.5%;
	list-style: none;
}
/* rz-slider styling */

.rzslide-addport { 
	width:100px;
	float:right;
	margin-top:20px;
	margin-right: 5px; 
}

.inputslide-port { 
	float:right;
	width:60px;
	padding:5px 24px 5px 0px;
	border:1px solid grey;
	border-radius:5px;
	text-align:center;
}

.slidearrow { 
	position: absolute;
	right: 0;
	height: 23px;
	border-left: 1px solid grey;
	width: 17%;
	top: 2px;
	padding: 2px 3px;
}

.rzslide-addevc { 
	width:100px;
	float:right;
	margin-top:20px;
	margin-right: 5px; 
}

.inputslide-evc { 
	float:right;
	width:60px;
	padding:5px 24px 5px 0px;
	border:1px solid grey;
	border-radius:5px;
	text-align:center;
}

.slidearrow-evc { 
	position: absolute;
	right: 0;
	height: 23px;
	border-left: 1px solid grey;
	width: 17%;top: 2px;
	padding: 2px 3px;
}

.rzslider-evc-size {
	width: 80%;
}

.scaleWrap ul li{
	float:left;
}

.scaleWrap ul li:last-child{
	width: 1% !important;
}

.sliderinputbox {
	width:100px;
	float:right;
	margin-top:20px;
	margin-right: 5px;
}

.slidervaluebox {
	float:right;
	width:60px;
	padding:5px 24px 5px 0px;
	border:1px solid grey;
	border-radius:5px;
	text-align:center;
	position:relative;
}

.slidervalueincrdecr {
	position: absolute;
right: 13px;
height: 23px;
border-left: 1px solid grey;
width: 2%;
top: 28px;
padding: 2px 3px;
}

.sliderTextIncrementer{
	width: 0;
	height: 0;
	border-left: 7px solid transparent;
	border-right: 7px solid transparent;
	border-bottom: 8px solid #0579B4;
	border-radius: 5px;
	position: absolute;
	top: 6px;
	right: 4px;
}

.sliderTextIncrementerDisabled{
	width: 0;
	height: 0;
	border-left: 7px solid transparent;
	border-right: 7px solid transparent;
	border-bottom: 8px solid grey;
	border-radius: 5px;
	position: absolute;
	top: 6px;
	right: 4px;
}

.sliderTextDecrementer{
	width: 0;
	height: 0;
	border-left: 7px solid transparent;
	border-right: 7px solid transparent;
	border-top: 8px solid #0579B4;
	border-radius: 5px;
	position: absolute;
	top: 16px;
	right: 4px;
}

.sliderTextDecrementerDisabled{
	width: 0;
	height: 0;
	border-left: 7px solid transparent;
	border-right: 7px solid transparent;
	border-top: 8px solid grey;
	border-radius: 5px;
	position: absolute;
	top: 16px;
	right: 4px;
}

/**
 * Angular JS slider directive
 *
 * (c) Rafal Zajac <rzajac@gmail.com>
 * http://github.com/rzajac/angularjs-slider
 *
 * Version: v0.1.3
 *
 * Licensed under the MIT license
 */

/* global angular: false */

angular.module('rzModule', [])

.value('throttle',
  /**
   * throttle
   *
   * Taken from underscore project
   *
   * @param {Function} func
   * @param {number} wait
   * @param {ThrottleOptions} options
   * @returns {Function}
   */
function throttle(func, wait, options) {
  var getTime = (Date.now || function() {
    return new Date().getTime();
  });
  var context, args, result;
  var timeout = null;
  var previous = 0;
  options || (options = {});
  var later = function() {
    previous = options.leading === false ? 0 : getTime();
    timeout = null;
    result = func.apply(context, args);
    context = args = null;
  };
  return function() {
    var now = getTime();
    if (!previous && options.leading === false) previous = now;
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    if (remaining <= 0) {
      clearTimeout(timeout);
      timeout = null;
      previous = now;
      result = func.apply(context, args);
      context = args = null;
    } else if (!timeout && options.trailing !== false) {
      timeout = setTimeout(later, remaining);
    }
    return result;
  }
})

.factory('Slider', ['$timeout', '$document','$compile', 'throttle', function($timeout, $document,$compile, throttle)
{
  /**
   * Slider
   *
   * @param {ngScope} scope            The AngularJS scope
   * @param {Element} sliderElem The slider directive element wrapped in jqLite
   * @param {*} attributes       The slider directive attributes
   * @constructor
   */
  var Slider = function(scope, sliderElem, attributes)
  {
    /**
     * The slider's scope
     *
     * @type {ngScope}
     */
    this.scope = scope;

    /**
     * The slider attributes
     *
     * @type {*}
     */
    this.attributes = attributes;

    /**
     * Slider element wrapped in jqLite
     *
     * @type {jqLite}
     */
    this.sliderElem = sliderElem;

    /**
     * Slider type
     *
     * @type {string}
     */
    this.range = attributes.rzSliderHigh !== undefined && attributes.rzSliderModel !== undefined;

    /**
     * Half of the width of the slider handles
     *
     * @type {number}
     */
    this.handleHalfWidth = 0;

    /**
     * Maximum left the slider handle can have
     *
     * @type {number}
     */
    this.maxLeft = 0;

    /**
     * Precision
     *
     * @type {number}
     */
    this.precision = 0;

    /**
     * Step
     *
     * @type {number}
     */
    this.step = 0;
    
    /**
     * Step Array
     *
     * @type {[]}
     */
    this.stepArray = [];   

    /**
     * The name of the handle we are currently tracking
     *
     * @type {string}
     */
    this.tracking = '';

    /**
     * Minimum value (floor) of the model
     *
     * @type {number}
     */
    this.minValue = 0;

    /**
     * Maximum value (ceiling) of the model
     *
     * @type {number}
     */
    this.maxValue = 0;

    /**
     * The delta between min and max value
     *
     * @type {number}
     */
    this.valueRange = 0;

    /**
     * Set to true if init method already executed
     *
     * @type {boolean}
     */
    this.initRun = false;

    /**
     * for custom tooltip, slider fix
     *
     * @type {boolean}
     */
    this.hasPopOver = attributes.rzHasPopover !== undefined;
    
    /**
     * Text to be displayed inside popover
     *
     * @type {boolean}
     */
    this.sliderPopoverText = (attributes.popoverText !== undefined)?attributes.popoverText:"Unable to retrive message";
    
    /**
     * Slider Selection colored value  
     */
    this.isSelection = false;
    
    /**
     * Slider scale display flag
     */
    this.displayScale = false;
    
    /**
     * Custom translate function
     *
     * @type {function}
     */
    this.customTrFn = null;

    // Slider DOM elements wrapped in jqLite
    this.fullBar = null; // The whole slider bar
    this.selBar = null;  // Highlight between two handles
    this.singleSel = null; // Custom added for single selection
    this.minH = null;  // Left slider handle
    this.maxH = null;  // Right slider handle
    this.flrLab = null;  // Floor label
    this.ceilLab = null; // Ceiling label
    this.minLab =  null; // Label above the low value
    this.maxLab = null; // Label above the high value
    this.cmbLab = null;  // Combined label
    this.dispScale = null; //Custom added for display Scale

    // Initialize slider
    this.init();
  };

  // Add instance methods
  Slider.prototype = {

    /**
     * Initialize slider
     *
     * @returns {undefined}
     */
    init: function()
    {
      var self = this;

      if(this.scope.rzSliderTranslate)
      {
        this.customTrFn = this.scope.rzSliderTranslate();
      }
      
      this.isSelection = this.scope.rzSliderSelection === undefined ? true : this.scope.rzSliderSelection;     
      this.displayScale = this.scope.rzSliderDisplayScale === undefined ? false : this.scope.rzSliderDisplayScale;     
      
      this.initElemHandles();
      this.calcViewDimensions();
     
      this.precision = this.scope.rzSliderPrecision === undefined ? 0 : +this.scope.rzSliderPrecision;
      this.step = this.scope.rzSliderStep === undefined ? 1 : +this.scope.rzSliderStep;      
      
      if(this.scope.rzSliderStepArray){
    	  this.stepArray = this.scope.rzSliderStepArray;
    	  this.step = 1;    	 
    	  this.scope.rzSliderFloor = 0;
    	  this.scope.rzSliderCeil = this.stepArray.length-1;
      }
      
      this.setMinAndMax();

      $timeout(function()
      {
        self.updateCeilLab();
        self.updateFloorLab();
        self.initHandles();
        self.bindEvents();
      });

      // Recalculate slider view dimensions
      this.scope.$on('reCalcViewDimensions', angular.bind(this, this.calcViewDimensions));

      // Recalculate stuff if view port dimensions have changed
      angular.element(window).on('resize', angular.bind(this, this.calcViewDimensions));

      this.initRun = true;

      // Watch for changes to the model

      var thrLow = throttle(function()
      {
        self.setMinAndMax();
        self.updateLowHandle(self.valueToOffset(self.scope.rzSliderModel));

        if(self.range)
        {
          self.updateSelectionBar();
          self.updateCmbLabel();
        }
        
        if(self.isSelection)
        {
          self.updateSingleSelectionBar();
        }

      }, 350, { leading: false });

      var thrHigh = throttle(function()
      {
        self.setMinAndMax();
        self.updateHighHandle(self.valueToOffset(self.scope.rzSliderHigh));
        self.updateSelectionBar();
        self.updateCmbLabel();
      }, 350, { leading: false });

      this.scope.$on('rzSliderForceRender', function()
      {
        self.resetLabelsValue();
        thrLow();
        thrHigh();
        self.resetSlider();
      });

      // Watchers

      this.scope.$watch('rzSliderModel', function(newValue, oldValue)
      {
        if(newValue === oldValue) return;
        thrLow();
      });

      this.scope.$watch('rzSliderHigh', function(newValue, oldValue)
      {
        if(newValue === oldValue) return;
        thrHigh();
      });

      this.scope.$watch('rzSliderFloor', function(newValue, oldValue)
      {
        if(newValue === oldValue) return;
        self.resetSlider();
      });

      this.scope.$watch('rzSliderCeil', function(newValue, oldValue)
      {
        if(newValue === oldValue) return;
        self.resetSlider();
      });
    },

    /**
     * Resets slider
     *
     * @returns {undefined}
     */
    resetSlider: function()
    {
      this.setMinAndMax();
      this.calcViewDimensions();
      this.updateCeilLab();
      this.updateFloorLab();
    },

    /**
     * Reset label values
     *
     * @return {undefined}
     */
    resetLabelsValue: function()
    {
      this.minLab.rzsv = undefined;
      this.maxLab.rzsv = undefined;
    },

    /**
     * Initialize slider handles positions and labels
     *
     * Run only once during initialization and every time view port changes size
     *
     * @returns {undefined}
     */
    initHandles: function()
    {
      this.updateLowHandle(this.valueToOffset(this.scope.rzSliderModel));

      if(this.range)
      {
        this.updateHighHandle(this.valueToOffset(this.scope.rzSliderHigh));
        this.updateSelectionBar();
        this.updateCmbLabel();
      }
      if(this.isSelection)
      {
        this.updateSingleSelectionBar();
      }
    },

    /**
     * Translate value to human readable format
     *
     * @param {number|string} value
     * @param {jqLite} label
     * @param {bool?} useCustomTr
     * @returns {undefined}
     */
    translateFn: function(value, label, useCustomTr)
    {
      useCustomTr = useCustomTr === undefined ? true : useCustomTr;     

      var valStr = this.customTrFn && useCustomTr ? '' + this.customTrFn(value) : '' + value,
        getWidth = false;

      if(label.rzsv === undefined || label.rzsv.length != valStr.length)
      {
        getWidth = true;
        label.rzsv = valStr;
      }

      label.text(valStr);

      // Update width only when length of the label have changed
      if(getWidth) { this.getWidth(label); }
    },

    /**
     * Set maximum and minimum values for the slider
     *
     * @returns {undefined}
     */
    setMinAndMax: function()
    {
      if(this.scope.rzSliderFloor)
      {
        this.minValue = +this.scope.rzSliderFloor;
      }
      else
      {
        this.minValue = this.scope.rzSliderFloor = 0;
      }

      if(this.scope.rzSliderCeil)
      {
        this.maxValue = +this.scope.rzSliderCeil;
      }
      else
      {
        this.scope.rzSliderCeil = this.maxValue = this.range ? this.scope.rzSliderHigh : this.scope.rzSliderModel;
      }

      this.valueRange = this.maxValue - this.minValue;
    },

    /**
     * Set the slider children to variables for easy access
     *
     * Run only once during initialization
     *
     * @returns {undefined}
     */
    initElemHandles: function()
    {
      angular.forEach(this.sliderElem.children(), function(elem, index)
      {
        var _elem = angular.element(elem);

        switch(index)
        {
          case 0: this.fullBar = _elem; break;
          case 1: this.selBar = _elem; break;
          case 2: this.singleSel = _elem; break;
          case 3: this.minH = _elem; break;
          case 4: this.maxH = _elem; break;
          case 5: this.flrLab = _elem; break;
          case 6: this.ceilLab = _elem; break;
          case 7: this.minLab = _elem; break;
          case 8: this.maxLab = _elem; break;
          case 9: this.cmbLab = _elem; break;
          case 10: this.dispScale = _elem; break;
        }

      }, this);

      // Initialize offsets
      this.fullBar.rzsl = 0;
      this.selBar.rzsl = 0;
      this.singleSel.rzsl = 0;
      this.minH.rzsl = 0;
      this.maxH.rzsl = 0;
      this.flrLab.rzsl = 0;
      this.ceilLab.rzsl = 0;
      this.minLab.rzsl = 0;
      this.maxLab.rzsl = 0;
      this.cmbLab.rzsl = 0;
      this.dispScale.rzsl = 0;

      // Remove stuff not needed in single slider
      if( ! this.range)
      {
        this.cmbLab.remove();
        this.maxLab.remove();
        this.maxH.remove();
        this.selBar.remove();
      }
      
      if( ! this.isSelection)
      {
        this.singleSel.remove();
      }
      
      if( ! this.displayScale)
      {
        this.dispScale.remove();
      }
    },

    /**
     * Calculate dimensions that are dependent on view port size
     *
     * Run once during initialization and every time view port changes size.
     *
     * @returns {undefined}
     */
    calcViewDimensions: function ()
    {
      var handleWidth = this.getWidth(this.minH);

      this.handleHalfWidth = handleWidth / 2;
      this.barWidth = this.getWidth(this.fullBar);

      this.maxLeft = this.barWidth - handleWidth;

      this.getWidth(this.sliderElem);
      this.sliderElem.rzsl = this.sliderElem[0].getBoundingClientRect().left;

      if(this.initRun)
      {
        this.updateCeilLab();
        this.initHandles();
      }
    },

    /**
     * Update position of the ceiling label
     *
     * @returns {undefined}
     */
    updateCeilLab: function()
    {
      this.translateFn(this.scope.rzSliderCeil, this.ceilLab);
      this.setLeft(this.ceilLab, this.barWidth - this.ceilLab.rzsw);
      this.getWidth(this.ceilLab);
    },

    /**
     * Update position of the floor label
     *
     * @returns {undefined}
     */
    updateFloorLab: function()
    {
      this.translateFn(this.scope.rzSliderFloor, this.flrLab);
      this.getWidth(this.flrLab);
    },

    /**
     * Update slider handles and label positions
     *
     * @param {string} which
     * @param {number} newOffset
     */
    updateHandles: function(which, newOffset)
    {
      if(which === 'rzSliderModel')
      {
        this.updateLowHandle(newOffset);
        if(this.range)
        {
          this.updateSelectionBar();
          this.updateCmbLabel();
        }
        
        if(this.isSelection)
        {
          this.updateSingleSelectionBar();
        }
        return;
      }

      if(which === 'rzSliderHigh')
      {
        this.updateHighHandle(newOffset);
        if(this.range)
        {
          this.updateSelectionBar();
          this.updateCmbLabel();
        }
        
        if(this.isSelection)
        {
          this.updateSingleSelectionBar();
        }
        return;
      }

      // Update both
      this.updateLowHandle(newOffset);
      this.updateHighHandle(newOffset);
      this.updateSelectionBar();
      this.updateSingleSelectionBar();
      this.updateCmbLabel();
    },

    /**
     * Update low slider handle position and label
     *
     * @param {number} newOffset
     * @returns {undefined}
     */
    updateLowHandle: function(newOffset)
    {
      this.setLeft(this.minH, newOffset);
      this.translateFn(this.scope.rzSliderModel, this.minLab);
      this.setLeft(this.minLab, newOffset - this.minLab.rzsw / 2 + this.handleHalfWidth);

      this.shFloorCeil();
    },

    /**
     * Update high slider handle position and label
     *
     * @param {number} newOffset
     * @returns {undefined}
     */
    updateHighHandle: function(newOffset)
    {
      this.setLeft(this.maxH, newOffset);
      this.translateFn(this.scope.rzSliderHigh, this.maxLab);
      this.setLeft(this.maxLab, newOffset - this.maxLab.rzsw / 2 + this.handleHalfWidth);

      this.shFloorCeil();
    },

    /**
     * Show / hide floor / ceiling label
     *
     * @returns {undefined}
     */
    shFloorCeil: function()
    {
      var flHidden = false, clHidden = false;

      if(this.minLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + 5)
      {
        flHidden = true;
        this.hideEl(this.flrLab);
      }
      else
      {
        flHidden = false;
        this.showEl(this.flrLab);
      }

      if(this.minLab.rzsl + this.minLab.rzsw >= this.ceilLab.rzsl - this.handleHalfWidth - 10)
      {
        clHidden = true;
        this.hideEl(this.ceilLab);
      }
      else
      {
        clHidden = false;
        this.showEl(this.ceilLab);
      }

      if(this.range)
      {
        if(this.maxLab.rzsl + this.maxLab.rzsw >= this.ceilLab.rzsl - 10)
        {
          this.hideEl(this.ceilLab);
        }
        else if( ! clHidden)
        {
          this.showEl(this.ceilLab);
        }

        // Hide or show floor label
        if(this.maxLab.rzsl <= this.flrLab.rzsl + this.flrLab.rzsw + this.handleHalfWidth)
        {
          this.hideEl(this.flrLab);
        }
        else if( ! flHidden)
        {
          this.showEl(this.flrLab);
        }
      }
    },

    /**
     * Update slider selection bar, combined label and range label
     *
     * @returns {undefined}
     */
    updateSelectionBar: function()
    {
      this.setWidth(this.selBar, this.maxH.rzsl - this.minH.rzsl);
      this.setLeft(this.selBar, this.minH.rzsl + this.handleHalfWidth);
    },
    
    /**
     * Update slider selection bar for single handle 
     *
     * @returns {undefined}
     */
    updateSingleSelectionBar: function()
    {
      this.setWidth(this.singleSel, this.minH.rzsl);
    },

    /**
     * Update combined label position and value
     *
     * @returns {undefined}
     */
    updateCmbLabel: function()
    {
      var lowTr, highTr;

      if(this.minLab.rzsl + this.minLab.rzsw + 10 >= this.maxLab.rzsl)
      {
        if(this.customTrFn)
        {
          lowTr = this.customTrFn(this.scope.rzSliderModel);
          highTr = this.customTrFn(this.scope.rzSliderHigh);
        }
        else
        {
          lowTr = this.scope.rzSliderModel;
          highTr = this.scope.rzSliderHigh;
        }

        this.translateFn(lowTr + ' - ' + highTr, this.cmbLab, false);
        this.setLeft(this.cmbLab, this.selBar.rzsl + this.selBar.rzsw / 2 - this.cmbLab.rzsw / 2);
        this.hideEl(this.minLab);
        this.hideEl(this.maxLab);
        this.showEl(this.cmbLab);
      }
      else
      {
        this.showEl(this.maxLab);
        this.showEl(this.minLab);
        this.hideEl(this.cmbLab);
      }
    },

    /**
     * Round value to step and precision
     *
     * @param {number} value
     * @returns {number}
     */
    roundStep: function(value)
    {    	
    	var step = this.step,
        remainder = (value - this.minValue) % step,
        steppedValue = remainder > (step / 2) ? value + step - remainder : value - remainder;

        return +(steppedValue).toFixed(this.precision);
    },

    /**
     * Hide element
     *
     * @param element
     * @returns {jqLite} The jqLite wrapped DOM element
     */
   hideEl: function (element)
    {
      return element.css({opacity: 1});
    },

    /**
     * Show element
     *
     * @param element The jqLite wrapped DOM element
     * @returns {jqLite} The jqLite
     */
    showEl: function (element)
    {
      return element.css({opacity: 1});
    },

    /**
     * Set element left offset
     *
     * @param {jqLite} elem The jqLite wrapped DOM element
     * @param {number} left
     * @returns {number}
     */
    setLeft: function (elem, left)
    {
      elem.rzsl = left;
      elem.css({left: left + 'px'});
      return left;
    },

    /**
     * Get element width
     *
     * @param {jqLite} elem The jqLite wrapped DOM element
     * @returns {number}
     */
    getWidth: function(elem)
    {
      var val = elem[0].getBoundingClientRect();
      elem.rzsw = val.right - val.left;
      return elem.rzsw;
    },

    /**
     * Set element width
     *
     * @param {jqLite} elem  The jqLite wrapped DOM element
     * @param {number} width
     * @returns {*}
     */
    setWidth: function(elem, width)
    {
      elem.rzsw = width;
      elem.css({width: width + 'px'});
      return width;
    },

    /**
     * Translate value to pixel offset
     *
     * @param {number} val
     * @returns {number}
     */
    valueToOffset: function(val)
    {
      return (val - this.minValue) * this.maxLeft / this.valueRange;
    },

    /**
     * Translate offset to model value
     *
     * @param {number} offset
     * @returns {number}
     */
    offsetToValue: function(offset)
    {
      return (offset / this.maxLeft) * this.valueRange + this.minValue;
    },

    // Events

    /**
     * Bind mouse and touch events to slider handles
     *
     * @returns {undefined}
     */
    bindEvents: function()
    {
      this.minH.on('mousedown', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
      if(this.range) { this.maxH.on('mousedown', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')) }

      this.minH.on('touchstart', angular.bind(this, this.onStart, this.minH, 'rzSliderModel'));
      if(this.range) { this.maxH.on('touchstart', angular.bind(this, this.onStart, this.maxH, 'rzSliderHigh')) }
      
      if(this.hasPopOver) {
    	  angular.element(".pointer:first").attr({"popover":"","item":"slider","trigger":"hover"}).attr("slider-popover-text","{{" + this.sliderPopoverText + "}}");
    	  $compile(angular.element(".pointer:first"))(this.scope.$parent);
      }
    },

    /**
     * onStart event handler
     *
     * @param {Object} pointer The jqLite wrapped DOM element
     * @param {string} ref     One of the refLow, refHigh values
     * @param {Event}  event   The event
     * @returns {undefined}
     */
    onStart: function (pointer, ref, event)
    {
      event.stopPropagation();
      event.preventDefault();

      if(this.tracking !== '') { return }

      // We have to do this in case the HTML where the sliders are on
      // have been animated into view.
      this.calcViewDimensions();
      this.tracking = ref;

      pointer.addClass('active');

      if(this.hasPopOver === false) // This means slider is not disabled 
      {
	      if(event.touches || (typeof(event.originalEvent) != 'undefined' && event.originalEvent.touches))
	      {
	        $document.on('touchmove', angular.bind(this, this.onMove, pointer));
	        $document.on('touchend', angular.bind(this, this.onEnd));
	      }
	      else
	      {
	        $document.on('mousemove', angular.bind(this, this.onMove, pointer));
	        $document.on('mouseup', angular.bind(this, this.onEnd));
	      }
      }
    },

    /**
     * onMove event handler
     *
     * @param {jqLite} pointer
     * @param {Event}  event The event
     * @returns {undefined}
     */
    onMove: function (pointer, event)
    {
      var eventX = event.clientX || (typeof(event.originalEvent) != 'undefined' ? event.originalEvent.touches[0].clientX : event.touches[0].clientX),
        sliderLO = this.sliderElem.rzsl,
        newOffset = eventX - sliderLO - this.handleHalfWidth,
        newValue;

      if(newOffset <= 0)
      {
        if(pointer.rzsl !== 0)
        {
          this.scope[this.tracking] = this.minValue;
          this.updateHandles(this.tracking, 0);
          this.scope.$apply();
        }

        return;
      }

      if(newOffset >= this.maxLeft)
      {
        if(pointer.rzsl !== this.maxLeft)
        {
          this.scope[this.tracking] = this.maxValue;
          this.updateHandles(this.tracking, this.maxLeft);
          this.scope.$apply();
        }

        return;
      }

      newValue = this.offsetToValue(newOffset);
      
      newValue = this.roundStep(newValue);
      
      if (this.range)
      {
        if (this.tracking === 'rzSliderModel' && newValue >= this.scope.rzSliderHigh)
        {
          this.scope[this.tracking] = this.scope.rzSliderHigh;
          this.updateHandles(this.tracking, this.maxH.rzsl);
          this.tracking = 'rzSliderHigh';
          this.minH.removeClass('active');
          this.maxH.addClass('active');
        }
        else if(this.tracking === 'rzSliderHigh' && newValue <= this.scope.rzSliderModel)
        {
          this.scope[this.tracking] = this.scope.rzSliderModel;
          this.updateHandles(this.tracking, this.minH.rzsl);
          this.tracking = 'rzSliderModel';
          this.maxH.removeClass('active');
          this.minH.addClass('active');
        }
      }

      if(this.scope[this.tracking] !== newValue)
      {
        this.scope[this.tracking] = newValue;
        this.updateHandles(this.tracking, this.valueToOffset(newValue));
        this.scope.$apply();
      }
    },

    /**
     * onEnd event handler
     *
     * @param {Event} event    The event
     * @returns {undefined}
     */
    onEnd: function(event)
    {
      this.minH.removeClass('active');
      this.maxH.removeClass('active');

      if(event.touches || (typeof(event.originalEvent) != 'undefined' && event.originalEvent.touches))
      {
        $document.unbind('touchmove');
        $document.unbind('touchend');
      }
      else
      {
        $document.unbind('mousemove');
        $document.unbind('mouseup');
      }
     	this.scope.$emit('slideEnded');
    
          this.tracking = '';
        }
      };

  return Slider;
}])

.directive('rzslider', ['Slider', function(Slider)
{
  return {
    restrict: 'EA',
    scope: {
      rzSliderFloor: '=?',
      rzSliderCeil: '=?',
      rzSliderStep: '@',
      rzSliderStepArray: '=',
      rzSliderPrecision: '@',
      rzSliderModel: '=?',
      rzSliderHigh: '=?',
      rzSliderTranslate: '&',
      rzSliderSelection: '=',
      rzSliderDisplayScale: '='
    },
    template:   '<span class="bar EnableSliderToggle"></span>' + // 0 The slider bar
                '<span class="bar selection"></span>' + // 1 Highlight between two handles
                '<span class="bar selection"></span>' + // 2 Highlight for single bar
                '<span class="pointer  EnableSliderToggle"></span>' + // 3 Left slider handle
                '<span class="pointer EnableSliderToggle"></span>' + // 4 Right slider handle
                '<span class="bubble limit"></span>' + // 5 Floor label
                '<span class="bubble limit"></span>' + // 6 Ceiling label
                '<span class="bubble Disabletooltip bubble_slider_tooltip"></span>' + // 7 Label above left slider handle
                '<span class="bubble Disabletooltip bubble_slider_tooltip"></span>' + // 8 Label above right slider handle
                '<span class="bubble Disabletooltip bubble_slider_tooltip"></span>' + // 9 Range label when the slider handles are close ex. 15 - 17
                '<div class="scaleWrap"><ul><li ng-repeat="step in rzSliderStepArray" ng-attr-style="width:{{99 / (rzSliderStepArray.length-1)}}%"><b>|</b></li></ul></div>',

    link: function(scope, elem, attr)
    {
      return new Slider(scope, elem, attr);
    }
  };
}]);

// IDE assist

/**
 * @name ngScope
 *
 * @property {number} rzSliderModel
 * @property {number} rzSliderHigh
 * @property {number} rzSliderCeil
 */

/**
 * @name jqLite
 *
 * @property {number|undefined} rzsl
 * @property {number|undefined} rzsw
 * @property {string|undefined} rzsv
 * @property {Function} css
 * @property {Function} text
 */

/**
 * @name Event
 * @property {Array} touches
 */

/**
 * @name ThrottleOptions
 *
 * @property {bool} leading
 * @property {bool} trailing
 */