Created
February 12, 2016 01:29
-
-
Save paveltyavin/b9895fbc58fbf1c5e750 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports = class Base | |
edge: (which) -> | |
if which == 'start' | |
return 'left' | |
else if which == 'end' | |
return 'right' | |
throw new TypeError('What kind of an edge is ' + which) | |
edgeProp: (edge, prop) -> | |
o = @$el[prop]() | |
o[@edge(edge)] | |
startProp: (prop) -> | |
@edgeProp 'start', prop | |
endProp: (prop) -> | |
@edgeProp 'end', prop |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
has = Object::hasOwnProperty | |
module.exports = (prop, event) -> | |
if has.call(event, prop) | |
return event[prop] | |
else if event.originalEvent and event.originalEvent.touches | |
return event.originalEvent.touches[0][prop] | |
else | |
return 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ = require('jquery') | |
Range = require('./range') | |
requestAnimationFrame = require('./raf') | |
class Phantom extends Range | |
constructor: (options)-> | |
options = $.extend({ | |
readonly: true | |
label: '+' | |
}, options) | |
super options | |
@$el.addClass 'bbslider-phantom' | |
@$el.on 'mousedown.bbslider touchstart.bbslider', @mousedown | |
mousedown: (ev) => | |
if ev.which == 1 | |
# left mouse button | |
range_val = @val() | |
newRange = @options.perant.addRange(range_val) | |
@$el.remove() | |
@options.perant.$el.trigger 'addrange', [ | |
newRange.val() | |
newRange | |
] | |
requestAnimationFrame -> | |
newRange.$el.find('.bbslider-handle:first-child').trigger ev.type | |
removePhantom: -> | |
null | |
module.exports = Phantom |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# thanks to http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating | |
lastTime = 0 | |
vendors = [ | |
'webkit' | |
'moz' | |
] | |
requestAnimationFrame = window.requestAnimationFrame | |
x = 0 | |
while x < vendors.length and !window.requestAnimationFrame | |
requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'] | |
++x | |
if !requestAnimationFrame | |
requestAnimationFrame = (callback, element) -> | |
currTime = (new Date).getTime() | |
timeToCall = Math.max(0, 16 - (currTime - lastTime)) | |
id = window.setTimeout((-> | |
callback currTime + timeToCall | |
), timeToCall) | |
lastTime = currTime + timeToCall | |
id | |
module.exports = requestAnimationFrame |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ = require('jquery') | |
Base = require('./base') | |
getEventProperty = require('./eventprop') | |
class Range extends Base | |
constructor: (options) -> | |
@id = options.id || Math.random().toString(36).substring(7) | |
@$el = $('<div class="bbslider-range"><span class="bbslider-barlabel">') | |
@options = options | |
@perant = options.perant | |
if @options.rangeClass | |
@$el.addClass @options.rangeClass | |
if !@readonly() | |
@$el.prepend('<div class="bbslider-handle">').append '<div class="bbslider-handle">' | |
@$el.on 'mouseenter.bbslider touchstart.bbslider', @removePhantom | |
@$el.on 'mousedown.bbslider touchstart.bbslider', @mousedown | |
@$el.on 'click', @click | |
else | |
@$el.addClass 'bbslider-readonly' | |
if typeof @options.label == 'function' | |
@$el.on 'changing', (ev, range) => | |
@writeLabel @options.label range.map(@perant.normalise), @ | |
else | |
@writeLabel @options.label | |
@range = [] | |
@hasChanged = false | |
if @options.value | |
@val @options.value | |
writeLabel: (text) => | |
@$el.find('.bbslider-barlabel')[if @options.htmlLabel then 'html' else 'text'] text | |
removePhantom: => | |
@perant.removePhantom() | |
readonly: => | |
if typeof @options.readonly == 'function' | |
return @options.readonly.call(@perant, @) | |
@options.readonly | |
val: (range, valOpts) => | |
if typeof range == 'undefined' | |
return @range | |
snap = (val) => | |
Math.round(val / @options.snap) * @options.snap | |
valOpts = $.extend({}, { | |
dontApplyDelta: false | |
trigger: true | |
}, valOpts or {}) | |
next = @perant.nextRange(@$el) | |
prev = @perant.prevRange(@$el) | |
delta = range[1] - (range[0]) | |
if @options.snap | |
range = range.map(snap) | |
delta = snap(delta) | |
if next and next.val()[0] <= range[1] and prev and prev.val()[1] >= range[0] | |
range[1] = next.val()[0] | |
range[0] = prev.val()[1] | |
if next and next.val()[0] < range[1] | |
if [email protected] or next.val()[1] >= range[0] | |
range[1] = next.val()[0] | |
if !valOpts.dontApplyDelta | |
range[0] = range[1] - delta | |
else | |
@perant.repositionRange @, range | |
if prev and prev.val()[1] > range[0] | |
if [email protected] or prev.val()[0] <= range[1] | |
range[0] = prev.val()[1] | |
if !valOpts.dontApplyDelta | |
range[1] = range[0] + delta | |
else | |
@perant.repositionRange @, range | |
if range[1] >= 1 | |
range[1] = 1 | |
if !valOpts.dontApplyDelta | |
range[0] = 1 - delta | |
if range[0] <= 0 | |
range[0] = 0 | |
if !valOpts.dontApplyDelta | |
range[1] = delta | |
if @perant.options.bound | |
bound = @perant.options.bound(@) | |
if bound | |
if bound.upper and range[1] > @perant.abnormalise(bound.upper) | |
range[1] = @perant.abnormalise(bound.upper) | |
if !valOpts.dontApplyDelta | |
range[0] = range[1] - delta | |
if bound.lower and range[0] < @perant.abnormalise(bound.lower) | |
range[0] = @perant.abnormalise(bound.lower) | |
if !valOpts.dontApplyDelta | |
range[1] = range[0] + delta | |
if @range[0] == range[0] and @range[1] == range[1] | |
return @$el | |
@range = range | |
if valOpts.trigger | |
@$el.triggerHandler 'changing', [ | |
range | |
@$el | |
] | |
@hasChanged = true | |
start = 100 * range[0] + '%' | |
size = 100 * (range[1] - (range[0])) + '%' | |
drawCss = | |
left: start | |
minWidth: size | |
if @drawing | |
return @$el | |
requestAnimationFrame => | |
@drawing = false | |
@$el.css drawCss | |
@drawing = true | |
@ | |
click: (ev) => | |
ev.preventDefault() | |
mousedown: (ev) => | |
ev.stopPropagation() | |
ev.preventDefault() | |
@hasChanged = false | |
if ev.which > 1 | |
return | |
if $(ev.target).is('.bbslider-handle:first-child') | |
$('body').addClass 'bbslider-resizing' | |
$(document).on 'mousemove.bbslider touchmove.bbslider', @resizeStart(ev) | |
else if $(ev.target).is('.bbslider-handle:last-child') | |
$('body').addClass 'bbslider-resizing' | |
$(document).on 'mousemove.bbslider touchmove.bbslider', @resizeEnd(ev) | |
else | |
$('body').addClass 'bbslider-dragging' | |
$(document).on 'mousemove.bbslider touchmove.bbslider', @drag(ev) | |
$(document).one 'mouseup.bbslider touchend.bbslider', (ev) => | |
ev.stopPropagation() | |
ev.preventDefault() | |
if @hasChanged and !@swapping | |
@$el.trigger 'change', [ | |
@range | |
@$el | |
] | |
@swapping = false | |
$(document).off 'mouseup.bbslider mousemove.bbslider touchend.bbslider touchmove.bbslider' | |
$('body').removeClass 'bbslider-resizing bbslider-dragging' | |
drag: (origEv) => | |
beginStart = @startProp('offset') | |
mousePos = getEventProperty('clientX', origEv) | |
mouseOffset = if mousePos then mousePos - beginStart else 0 | |
beginSize = @$el.width() | |
perant = @options.perant | |
perantStart = perant.startProp('offset') | |
perantSize = perant.$el.width() | |
return (ev) => | |
ev.stopPropagation() | |
ev.preventDefault() | |
mousePos = getEventProperty('clientX', ev) | |
if mousePos | |
start = mousePos - perantStart - mouseOffset | |
if start >= 0 and start <= perantSize - beginSize | |
rangeOffset = start / perantSize - (@range[0]) | |
@val [ | |
start / perantSize | |
@range[1] + rangeOffset | |
] | |
else | |
mouseOffset = mousePos - @startProp('offset') | |
resizeEnd: (origEv) => | |
beginStart = @startProp('offset') | |
beginPosStart = @startProp('position') | |
perant = @options.perant | |
perantSize = perant.$el.width() | |
minSize = @options.minSize * perantSize | |
return (ev) => | |
opposite = if ev.type == 'touchmove' then 'touchend' else 'mouseup' | |
subsequent = if ev.type == 'touchmove' then 'touchstart' else 'mousedown' | |
ev.stopPropagation() | |
ev.preventDefault() | |
mousePos = getEventProperty('clientX', ev) | |
size = mousePos - beginStart | |
if mousePos | |
if size > perantSize - beginPosStart | |
size = perantSize - beginPosStart | |
if size >= minSize | |
@val [ | |
@range[0] | |
@range[0] + size / perantSize | |
], dontApplyDelta: true | |
else if size <= 10 | |
@swapping = true | |
$(document).trigger opposite + '.bbslider' | |
@$el.find('.bbslider-handle:first-child').trigger subsequent + '.bbslider' | |
resizeStart: (origEv) => | |
beginStart = @startProp('offset') | |
beginPosStart = @startProp('position') | |
mousePos = getEventProperty('clientX', origEv) | |
mouseOffset = if mousePos then mousePos - beginStart else 0 | |
beginSize = @$el.width() | |
perant = @options.perant | |
perantStart = perant.startProp('offset') | |
perantSize = perant.$el.width() | |
minSize = @options.minSize * perantSize | |
return (ev) => | |
opposite = if ev.type == 'touchmove' then 'touchend' else 'mouseup' | |
subsequent = if ev.type == 'touchmove' then 'touchstart' else 'mousedown' | |
ev.stopPropagation() | |
ev.preventDefault() | |
mousePos = getEventProperty('clientX', ev) | |
start = mousePos - perantStart - mouseOffset | |
size = beginPosStart + beginSize - start | |
if mousePos | |
if start < 0 | |
start = 0 | |
size = beginPosStart + beginSize | |
if size >= minSize | |
@val [ | |
start / perantSize | |
@range[1] | |
], dontApplyDelta: true | |
else if size <= 10 | |
@swapping = true | |
$(document).trigger opposite + '.bbslider' | |
@$el.find('.bbslider-handle:last-child').trigger subsequent + '.bbslider' | |
module.exports = Range |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Range = require('./range') | |
Phantom = require('./phantom') | |
getEventProperty = require('./eventprop') | |
Base = require('./base') | |
$ = require('jquery') | |
class RangeBar extends Base | |
canAddRange: true | |
defaultOptions: | |
min: 0 | |
max: 100 | |
valueFormat: (a) -> | |
a | |
valueParse: (a) -> | |
a | |
maxRanges: Infinity | |
readonly: false | |
htmlLabel: false | |
allowSwap: true | |
constructor: (options) -> | |
options = options or {} | |
@$el = $('<div class="bbslider-rangebar">') | |
@options = $.extend({}, @defaultOptions, options) | |
@options.min = @options.valueParse(@options.min) | |
@options.max = @options.valueParse(@options.max) | |
if @options.barClass | |
@$el.addClass @options.barClass | |
@ranges = [] | |
@$el.on 'mousemove.bbslider touchmove.bbslider', @mousemove | |
@$el.on 'mouseleave.bbslider touchleave.bbslider', @removePhantom | |
if options.values | |
@setVal options.values | |
normaliseRaw: (value) => | |
@options.min + value * (@options.max - (@options.min)) | |
normalise: (value) => | |
@options.valueFormat @normaliseRaw(value) | |
abnormaliseRaw: (value) => | |
(value - (@options.min)) / (@options.max - (@options.min)) | |
abnormalise: (value) => | |
@abnormaliseRaw @options.valueParse(value) | |
findGap: (range) => | |
newIndex = 0 | |
@ranges.forEach ($r, i) -> | |
if $r.val()[0] < range[0] and $r.val()[1] < range[1] | |
newIndex = i + 1 | |
newIndex | |
insertRangeIndex: (range, index, avoidList) => | |
if !avoidList | |
@ranges.splice index, 0, range | |
if @ranges[index - 1] | |
@ranges[index - 1].$el.after range.$el | |
else | |
@$el.prepend range.$el | |
addRange: (range, options) => | |
if options is undefined | |
options = {} | |
$range = new Range $.extend options, | |
perant: @ | |
snap: if @options.snap then @abnormaliseRaw(@options.snap + @options.min) else null | |
label: @options.label | |
rangeClass: @options.rangeClass | |
minSize: if @options.minSize then @abnormaliseRaw(@options.minSize + @options.min) else null | |
readonly: @options.readonly | |
htmlLabel: @options.htmlLabel | |
@insertRangeIndex $range, @findGap(range) | |
$range.val range | |
$range.$el.on('changing', (ev, nrange, changed) => | |
ev.stopPropagation() | |
@$el.trigger 'changing', [ | |
@val() | |
changed | |
] | |
).on 'change', (ev, nrange, changed) => | |
ev.stopPropagation() | |
@$el.trigger 'change', [ | |
@val() | |
changed | |
] | |
$range | |
prevRange: (range) => | |
idx = range.index() | |
if idx >= 0 | |
return @ranges[idx - 1] | |
nextRange: (range) => | |
idx = range.index() | |
if idx >= 0 | |
return @ranges[if range instanceof Phantom then idx else idx + 1] | |
setVal: (ranges) => | |
if @ranges.length > ranges.length | |
i = ranges.length - 1 | |
l = @ranges.length - 1 | |
while i < l | |
@removeRange l | |
--l | |
@ranges.length = ranges.length | |
ranges.forEach (range, i) => | |
if @ranges[i] | |
@ranges[i].val range.map(@abnormalise) | |
else | |
@addRange range.map(@abnormalise) | |
@ | |
val: (ranges) => | |
return @ranges.map (range) => | |
range.val().map @normalise | |
removePhantom: => | |
if @phantom | |
@phantom.$el.remove() | |
@phantom = null | |
removeRange: (i, noTrigger, preserveEvents) => | |
if i instanceof Range | |
i = @ranges.indexOf(i) | |
if preserveEvents | |
method = 'detach' | |
else | |
method = 'remove' | |
range = @ranges.splice(i, 1)[0] | |
range.$el[method]() | |
if !noTrigger | |
@$el.trigger 'change', [@val()] | |
repositionRange: (range, val) => | |
@removeRange range, true, true | |
@insertRangeIndex range, @findGap(val) | |
calcGap: (index) => | |
start = if @ranges[index - 1] then @ranges[index - 1].val()[1] else 0 | |
end = if @ranges[index] then @ranges[index].val()[0] else 1 | |
@normaliseRaw(end) - @normaliseRaw(start) | |
readonly: => | |
if typeof @options.readonly == 'function' | |
return @options.readonly.call(@) | |
@options.readonly | |
mouseMoveAddRange: (ev, val, range) => | |
range.$el.one 'mouseup', => | |
@$el.trigger 'change', [ | |
@val() | |
range | |
] | |
@canAddRange = true | |
mousemove: (ev) => | |
w = if @options.minSize then @abnormaliseRaw(@options.minSize + @options.min) else 0.05 | |
pageStart = getEventProperty('pageX', ev) | |
val = (pageStart - @startProp('offset')) / @$el.width() - (w / 2) | |
direct = ev.target == ev.currentTarget | |
phantom = $(ev.target).is('.bbslider-phantom') | |
if (direct or phantom) and @ranges.length < @options.maxRanges and !$('body').is('.bbslider-dragging, .bbslider-resizing') and !@readonly() | |
if !@phantom | |
@phantom = new Phantom | |
perant: @ | |
snap: if @options.snap then @abnormaliseRaw(@options.snap + @options.min) else null | |
label: '+' | |
minSize: if @options.minSize then @abnormaliseRaw(@options.minSize + @options.min) else null | |
rangeClass: @options.rangeClass | |
idx = @findGap([ | |
val | |
val + w | |
]) | |
if @canAddRange | |
@$el.one 'addrange', @mouseMoveAddRange | |
@canAddRange = false | |
if [email protected] or @calcGap(idx) >= @options.minSize | |
@insertRangeIndex @phantom, idx, true | |
@phantom.val [ | |
val | |
val + w | |
], trigger: false | |
module.exports = RangeBar |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment