UNPKG

@livingdocs/angular-slider

Version:

Slider directive implementation for AngularJS, without jQuery dependencies.

243 lines (208 loc) 8.97 kB
# CONSTANTS MODULE_NAME = 'ui.slider' SLIDER_TAG = 'slider' # HELPER FUNCTIONS angularize = (element) -> angular.element element pixelize = (position) -> "#{position}px" hide = (element) -> element.css opacity: 0 show = (element) -> element.css opacity: 1 offset = (element, position) -> element.css left: position halfWidth = (element) -> element[0].offsetWidth / 2 offsetLeft = (element) -> element[0].offsetLeft width = (element) -> element[0].offsetWidth gap = (element1, element2) -> offsetLeft(element2) - offsetLeft(element1) - width(element1) contain = (value) -> return value if isNaN value Math.min Math.max(0, value), 100 roundStep = (value, precision, step, floor = 0) -> step ?= 1 / Math.pow(10, precision) remainder = (value - floor) % step steppedValue = if remainder > (step / 2) then value + step - remainder else value - remainder decimals = Math.pow 10, precision roundedValue = steppedValue * decimals / decimals parseFloat roundedValue.toFixed precision events = mouse: start: 'mousedown' move: 'mousemove' end: 'mouseup' touch: start: 'touchstart' move: 'touchmove' end: 'touchend' # DIRECTIVE DEFINITION sliderDirective = ($timeout) -> restrict: 'E' scope: floor: '@' ceiling: '@' values: '=?' step: '@' highlight: '@' precision: '@' buffer: '@' dragstop: '@' ngModel: '=?' ngModelLow: '=?' ngModelHigh: '=?' dragEnd: '&' valueDecorator:'&' template: ''' <div class="bar"><div class="selection"></div></div> <div class="handle low"></div><div class="handle high"></div> <div class="bubble limit low">{{ values.length ? values[floor || 0] : floor }}</div> <div class="bubble limit high">{{ values.length ? values[ceiling || values.length - 1] : ceiling }}</div> <div class="bubble value low">{{ values.length ? valueCalculator( {value: values[local.ngModelLow || local.ngModel || 0]} ) : valueCalculator( {value: local.ngModelLow || local.ngModel || 0} ) }}</div> <div class="bubble value high">{{ values.length ? valueCalculator( {value: values[local.ngModelHigh]} ) : valueCalculator( {value: local.ngModelHigh} ) }}</div>''' compile: (element, attributes) -> # Check if it is a range slider range = !attributes.ngModel? and attributes.ngModelLow? and attributes.ngModelHigh? low = if range then 'ngModelLow' else 'ngModel' high = 'ngModelHigh' # Scope values to watch for changes watchables = ['floor', 'ceiling', 'values', low] watchables.push high if range post: (scope, element, attributes) -> # just print the value if no decorator is specified scope.valueCalculator = (value) -> scope.valueDecorator(value) || value?.value # Get references to template elements [bar, minPtr, maxPtr, flrBub, ceilBub, lowBub, highBub] = (angularize(e) for e in element.children()) selection = angularize bar.children()[0] # Remove range specific elements if not a range slider unless range upper.remove() for upper in [maxPtr, highBub] selection.remove() unless attributes.highlight scope.local = {} scope.local[low] = scope[low] scope.local[high] = scope[high] bound = false ngDocument = angularize document handleHalfWidth = barWidth = minOffset = maxOffset = minValue = maxValue = valueRange = offsetRange = undefined dimensions = -> # roundStep the initial score values scope.step ?= 1 scope.floor ?= 0 scope.precision ?= 0 scope.ngModelLow = scope.ngModel unless range scope.ceiling ?= scope.values.length - 1 if scope.values?.length scope.local[low] = scope[low] scope.local[high] = scope[high] for value in watchables scope[value] = roundStep(parseFloat(scope[value]), parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)) if typeof value is 'number' # Commonly used measurements handleHalfWidth = halfWidth minPtr barWidth = width bar minOffset = 0 maxOffset = barWidth - width(minPtr) minValue = parseFloat scope.floor maxValue = parseFloat scope.ceiling valueRange = maxValue - minValue offsetRange = maxOffset - minOffset updateDOM = -> dimensions() # Translation functions percentOffset = (offset) -> contain ((offset - minOffset) / offsetRange) * 100 percentValue = (value) -> contain ((value - minValue) / valueRange) * 100 pixelsToOffset = (percent) -> pixelize percent * offsetRange / 100 setPointers = -> offset ceilBub, pixelize(barWidth - width(ceilBub)) newLowValue = percentValue scope.local[low] offset minPtr, pixelsToOffset newLowValue offset lowBub, pixelize(offsetLeft(minPtr) - (halfWidth lowBub) + handleHalfWidth) offset selection, pixelize(offsetLeft(minPtr) + handleHalfWidth) switch true when range newHighValue = percentValue scope.local[high] offset maxPtr, pixelsToOffset newHighValue offset highBub, pixelize(offsetLeft(maxPtr) - (halfWidth highBub) + handleHalfWidth) selection.css width: pixelsToOffset newHighValue - newLowValue when attributes.highlight is 'right' selection.css width: pixelsToOffset 110 - newLowValue when attributes.highlight is 'left' selection.css width: pixelsToOffset newLowValue offset selection, 0 bind = (handle, bubble, ref, events) -> currentRef = ref onEnd = -> bubble.removeClass 'active' handle.removeClass 'active' ngDocument.unbind events.move ngDocument.unbind events.end if scope.dragstop scope[high] = scope.local[high] scope[low] = scope.local[low] currentRef = ref scope.dragEnd() scope.$apply() onMove = (event) -> eventX = event.clientX or event.touches?[0].clientX or event.originalEvent?.changedTouches?[0].clientX or 0 newOffset = eventX - element[0].getBoundingClientRect().left - handleHalfWidth newOffset = Math.max(Math.min(newOffset, maxOffset), minOffset) newPercent = percentOffset newOffset newValue = minValue + (valueRange * newPercent / 100.0) if range switch currentRef when low if newValue > scope.local[high] currentRef = high minPtr.removeClass 'active' lowBub.removeClass 'active' maxPtr.addClass 'active' highBub.addClass 'active' setPointers() else if scope.buffer > 0 newValue = Math.min newValue, scope.local[high] - scope.buffer when high if newValue < scope.local[low] currentRef = low maxPtr.removeClass 'active' highBub.removeClass 'active' minPtr.addClass 'active' lowBub.addClass 'active' setPointers() else if scope.buffer > 0 newValue = Math.max newValue, parseInt(scope.local[low]) + parseInt(scope.buffer) newValue = roundStep(newValue, parseInt(scope.precision), parseFloat(scope.step), parseFloat(scope.floor)) scope.local[currentRef] = newValue unless scope.dragstop scope[currentRef] = newValue setPointers() scope.$apply() onStart = (event) -> dimensions() bubble.addClass 'active' handle.addClass 'active' setPointers() event.stopPropagation() event.preventDefault() ngDocument.bind events.move, onMove ngDocument.bind events.end, onEnd handle.bind events.start, onStart setBindings = -> for method in ['touch', 'mouse'] bind minPtr, lowBub, low, events[method] bind maxPtr, highBub, high, events[method] bound = true setBindings() unless bound setPointers() $timeout updateDOM scope.$watch w, updateDOM, true for w in watchables window.addEventListener 'resize', updateDOM qualifiedDirectiveDefinition = [ '$timeout' sliderDirective ] module = (window, angular) -> angular .module(MODULE_NAME, []) .directive(SLIDER_TAG, qualifiedDirectiveDefinition) module window, window.angular